[
  {
    "path": ".dockerignore",
    "content": ".git\ntest\nDockerfile*\n.gitignore\n.dockerignore\n.travis.yml\n*.md\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "# This controls who gets notified for review and allows branches to be protected.\n# Protected branches can only be merged into after being approved by a codeowner.\n\n* @freeCodeCamp/devdocs\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributing to DevDocs\n\nWant to contribute? Great. Please review the following guidelines carefully and search for existing issues before opening a new one.\n\n**Table of Contents:**\n\n1. [Reporting bugs](#reporting-bugs)\n2. [Requesting new features](#requesting-new-features)\n3. [Requesting new documentations](#requesting-new-documentations)\n4. [Contributing code and features](#contributing-code-and-features)\n5. [Contributing new documentations](#contributing-new-documentations)\n6. [Updating existing documentations](#updating-existing-documentations)\n7. [Coding conventions](#coding-conventions)\n8. [Questions?](#questions)\n\n## Reporting bugs\n\n1. Update to the most recent main release; the bug may already be fixed.\n2. Search for existing issues; it's possible someone has already encountered this bug.\n3. Try to isolate the problem and include steps to reproduce it.\n4. Share as much information as possible (e.g. browser/OS environment, log output, stack trace, screenshots, etc.).\n\n## Requesting new features\n\n1. Search for similar feature requests; someone may have already requested it.\n2. Make sure your feature fits DevDocs's [vision](../README.md#vision).\n3. Provide a clear and detailed explanation of the feature and why it's important to add it.\n\n## Requesting new documentations\n\nPlease don't open issues to request new documentations.\nUse the [Trello board](https://trello.com/b/6BmTulfx/devdocs-documentation) where everyone can vote.\n\n## Contributing code and features\n\n1. Search for existing issues; someone may already be working on a similar feature.\n2. Before embarking on any significant pull request, please open an issue describing the changes you intend to make. Otherwise you risk spending a lot of time working on something we may not want to merge. This also tells other contributors that you're working on the feature.\n3. Follow the [coding conventions](#coding-conventions).\n4. If you're modifying the Ruby code, include tests and ensure they pass.\n5. Try to keep your pull request small and simple.\n6. When it makes sense, squash your commits into a single commit.\n7. Describe all your changes in the commit message and/or pull request.\n\n## Contributing new documentations\n\nSee the [`docs` folder](https://github.com/freeCodeCamp/devdocs/tree/main/docs) to learn how to add new documentations.\n\n**Important:** the documentation's license must permit alteration, redistribution and commercial use, and the documented software must be released under an open source license. Feel free to get in touch if you're not sure if a documentation meets those requirements.\n\nIn addition to the [guidelines for contributing code](#contributing-code-and-features), the following guidelines apply to pull requests that add a new documentation:\n\n* Your documentation must come with an official icon, in both 1x and 2x resolutions (16x16 and 32x32 pixels). This is important because icons are the only thing differentiating search results in the UI.\n* DevDocs favors quality over quantity. Your documentation should only include documents that most developers may want to read semi-regularly. By reducing the number of entries, we make it easier to find other, more relevant entries.\n* Remove as much content and HTML markup as possible, particularly content not associated with any entry (e.g. introduction, changelog, etc.).\n* Names must be as short as possible and unique across the documentation.\n* The number of types (categories) should ideally be less than 100.\n\n## Updating existing documentations\n\nIf the latest [documentation versions report](https://github.com/freeCodeCamp/devdocs/issues?utf8=%E2%9C%93&q=Documentation+versions+report+is%3Aissue+author%3Adevdocs-bot+sort%3Acreated-desc) wrongly shows a documentation to be up-to-date, please open an issue or a PR to fix it.\n\n**Important:** PR's that update documentation versions that do not contain the checklist shown to you in section B of the PR template may be closed without review.\n\nFollow the following steps to update documentations to their latest version:\n\n1. Make version/release changes in the scraper file.\n2. Check if the license is still correct. Update `options[:attribution]` if needed.\n3. If the documentation has a custom icon, ensure the icons in <code>public/icons/*your_scraper_name*/</code> are up-to-date. If you pull the updated icon from a place different than the one specified in the `SOURCE` file, make sure to replace the old link with the new one.\n4. If `self.links` is defined, check if the urls are still correct.\n5. If the scraper inherits from `FileScraper` rather than `URLScraper`, follow the instructions for that scraper in [`file-scrapers.md`](../docs/file-scrapers.md) to obtain the source material for the scraper.\n6. Generate the docs using `thor docs:generate <doc@version>`.\n7. Make sure `thor docs:generate` doesn't show errors and that the documentation still works well. Verify locally that everything works and that the categorization of entries is still good. Often, updates will require code changes in the scraper or its filters to tweak some new markup in the source website or to categorize new entries.\n8. Repeat steps 5 and 6 for all versions that you updated.\n9. Create a PR and make sure to fill the checklist in section B of the PR template (remove the other sections).\n\n## Coding conventions\n\n* two spaces; no tabs\n* no trailing whitespace; blank lines should have no spaces; new line at end-of-file\n* use the same coding style as the rest of the codebase\n\nThese conventions are formalized in [our `.editorconfig` file](../.editorconfig).\nCheck out [EditorConfig.org](https://editorconfig.org/) to learn how to make your tools adhere to it.\n\n## Questions?\n\nIf you have any questions, please feel free to ask them on the contributor chat room on [Discord](https://discord.gg/PRyKn3Vbay).\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve DevDocs\ntitle: ''\nlabels: 'bug'\nassignees: ''\n---\n\n<!--\nIf possible fill each section\n-->\n\n# Bug report\n\n<!--\nVerify this steps before writing a new issue:\n\n - Search for existing issues; it's possible someone has already encountered this bug.\n-->\n\n## OS information\n\n<!--\nWhat operating system and browser version are you using?\n-->\n\n## Steps to reproduce\n\n<!--\nWrite the steps to reproduce this bug or write a description about when and how you\nencountered it\n-->\n\n## More resources\n\n<!--\nAdd images, GIFs, screenshot, console output or any other resource that might help to understand this bug\n-->\n\n## Possible fix\n\n<!--\nIf you have an idea how to fix this you can write here\n-->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Question\n    about: \"Ask questions and have discussions on Discord\"\n    url: \"https://discord.gg/PRyKn3Vbay\"\n  - name: New Documentation\n    about: \"Request a new documentation on Trello\"\n    url: \"https://trello.com/b/6BmTulfx/devdocs-documentation\"\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/documentation_bug.md",
    "content": "---\nname: Documentation bug\nabout: Report a problem with a specific documentation\ntitle: ''\nlabels: 'docs/improvement'\nassignees: ''\n---\n\n<!--\nIf possible fill each section\n-->\n\n# Documentation style bug\n\n<!--\n  - Search for existing issues\n    https://github.com/freeCodeCamp/devdocs/labels/docs%2Fimprovement\n-->\n\n## Summary\n<!--\nAdd a description about how the documentation should be\n-->\n\n## Actual style\n<!--\nAdd images or urls of the miss-formatted DevDocs documentation\n-->\n\n## Expected style\n<!--\nAdd images showing the expected style or urls of the source page\n-->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest a new feature\ntitle: ''\nlabels: 'feature'\nassignees: ''\n---\n\n<!--\nIf possible fill each section\n-->\n\n# Feature request\n\n<!--\nVerify this steps before suggesting a new feature:\n\n  - Check if anyone has suggested this feature before\n    https://github.com/freeCodeCamp/devdocs/labels/feature\n\n  - Make sure your feature fits DevDocs' vision\n    https://github.com/freeCodeCamp/devdocs/blob/main/README.md#vision\n-->\n\n## Summary\n\n<!--\nWrite a description of this feature and write why it should be added to DevDocs\n-->\n\n## Examples\n\n<!--\nIf you have seen this feature before you can add images, URLs, GIFs and any other\nresource that might help to understand how this feature works\n-->\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!-- Remove the sections that don't apply to your PR. -->\n\n<!-- Replace the `[ ]` with a `[x]` in checklists once you’ve completed each step. -->\n<!-- Please create a draft PR when you haven't completed all steps yet upon creation of the PR. -->\n\n<!-- SECTION A - Adding a new scraper -->\n<!-- See https://github.com/freeCodeCamp/devdocs/blob/main/.github/CONTRIBUTING.md#contributing-new-documentations -->\n\nIf you’re adding a new scraper, please ensure that you have:\n\n- [ ] Tested the scraper on a local copy of DevDocs\n- [ ] Ensured that the docs are styled similarly to other docs on DevDocs\n<!-- If the docs don’t have an icon, delete the next four items: -->\n- [ ] Added these files to the <code>public/icons/*your_scraper_name*/</code> directory:\n  - [ ] `16.png`: a 16×16 pixel icon for the doc\n  - [ ] `16@2x.png`: a 32×32 pixel icon for the doc\n  - [ ] `SOURCE`: A text file containing the URL to the page the image can be found on or the URL of the original image itself\n\n<!-- SECTION B - Updating an existing documentation to its latest version -->\n<!-- See https://github.com/freeCodeCamp/devdocs/blob/main/.github/CONTRIBUTING.md#updating-existing-documentations -->\n\nIf you're updating existing documentation to its latest version, please ensure that you have:\n\n- [ ] Updated the versions and releases in the scraper file\n- [ ] Ensured the license is up-to-date\n- [ ] Ensured the icons and the `SOURCE` file in <code>public/icons/*your_scraper_name*/</code> are up-to-date if the documentation has a custom icon\n- [ ] Ensured `self.links` contains up-to-date urls if `self.links` is defined\n- [ ] Tested the changes locally to ensure:\n  - The scraper still works without errors\n  - The scraped documentation still looks consistent with the rest of DevDocs\n  - The categorization of entries is still good\n"
  },
  {
    "path": ".github/no-response.yml",
    "content": "daysUntilClose: 30\nresponseRequiredLabel: needs-info\ncloseComment: >\n  This issue has been automatically closed because there has been no response\n  to our request for more information from the original author. With only the\n  information that’s currently in the issue, we don’t have enough information\n  to take action. Please comment if you have or find the answer we need so we\n  can investigate further.\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Deploy\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  deploy:\n    name: Deploy to Heroku\n    runs-on: ubuntu-24.04\n    if: github.repository == 'freeCodeCamp/devdocs'\n    steps:\n    - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0\n    - name: Set up Ruby\n      uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0\n      with:\n        bundler-cache: true # runs 'bundle install' and caches installed gems automatically\n    - name: Run tests\n      run: bundle exec rake\n    - name: Install Heroku CLI\n      run: |\n        curl https://cli-assets.heroku.com/install.sh | sh\n    - name: Deploy to Heroku\n      uses: akhileshns/heroku-deploy@e3eb99d45a8e2ec5dca08735e089607befa4bf28 # v3.14.15\n      with:\n        heroku_api_key: ${{secrets.HEROKU_API_KEY}}\n        heroku_app_name: \"devdocs\"\n        heroku_email: \"team@freecodecamp.com\"\n        dontuseforce: true # --force should never be necessary\n        dontautocreate: true # The app exists, it should not be created\n"
  },
  {
    "path": ".github/workflows/docker-build.yml",
    "content": "name: Build and Push Docker Images\non:\n  schedule:\n    - cron: '0 0 1 * *'  # Run monthly on the 1st\n  workflow_dispatch:  # Allow manual triggers\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: ${{ github.repository }}\njobs:\n  build-and-push:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      packages: write\n    strategy:\n      matrix:\n        variant:\n          - name: regular\n            file: Dockerfile\n            suffix: ''\n          - name: alpine\n            file: Dockerfile-alpine\n            suffix: '-alpine'\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4\n        with:\n          persist-credentials: false\n\n      - name: Log in to the Container registry\n        uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Extract metadata for Docker\n        id: meta\n        uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5\n        with:\n          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\n          tags: |\n            type=raw,value=latest${{ matrix.variant.suffix }}\n            type=raw,value={{date 'YYYYMMDD'}}${{ matrix.variant.suffix }}\n\n      - name: Build and push image\n        uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5\n        with:\n          context: .\n          file: ./${{ matrix.variant.file }}\n          push: true\n          tags: ${{ steps.meta.outputs.tags }}\n          labels: ${{ steps.meta.outputs.labels }}\n"
  },
  {
    "path": ".github/workflows/schedule-doc-report.yml",
    "content": "name: Generate documentation versions report\non:\n  schedule:\n    - cron: '17 4 1 * *'\n  workflow_dispatch:\n\njobs:\n  report:\n    runs-on: ubuntu-24.04\n    if: github.repository == 'freeCodeCamp/devdocs'\n    steps:\n    - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0\n    - name: Set up Ruby\n      uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0\n      with:\n        bundler-cache: true # runs 'bundle install' and caches installed gems automatically\n    - name: Generate report\n      run: bundle exec thor updates:check --github-token ${{ secrets.DEVDOCS_BOT_PAT }} --upload\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Ruby tests\n\non:\n  pull_request:\n    branches:\n      - main\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1\n    - name: Set up Ruby\n      uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0\n      with:\n        bundler-cache: true # runs 'bundle install' and caches installed gems automatically\n    - name: Run tests\n      run: bundle exec rake\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n.bundle\nlog\ntmp\npublic/assets\npublic/fonts\npublic/docs/**/*\ndocs/**/*\n!docs/*.md\n/vendor\n*.tar\n*.tar.bz2\n*.tar.gz\n*.zip\nassets/stylesheets/components/_environment.scss\nassets/stylesheets/global/_icons.scss\n"
  },
  {
    "path": ".image_optim.yml",
    "content": "verbose: false\nskip_missing_workers: true\nallow_lossy: true\nthreads: 1\nadvpng: false\ngifsicle:\n  interlace: false\n  level: 3\n  careful: true\njhead: false\njpegoptim:\n  strip: all\n  max_quality: 100\njpegrecompress: false\njpegtran: false\noptipng: false\npngcrush: false\npngout: false\npngquant:\n  quality: !ruby/range 80..99\n  speed: 3\nsvgo: false\n"
  },
  {
    "path": ".ruby-version",
    "content": "3.4.8\n"
  },
  {
    "path": ".slugignore",
    "content": "test\n"
  },
  {
    "path": ".tool-versions",
    "content": "ruby 3.4.8\n"
  },
  {
    "path": "COPYRIGHT",
    "content": "Copyright 2013-2026 Thibaut Courouble and other contributors\n\n  This Source Code Form is subject to the terms of the Mozilla Public\n  License, v. 2.0. If a copy of the MPL was not distributed with this\n  file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\nPlease do not use the name DevDocs to endorse or promote products\nderived from this software without the maintainers' permission, except\nas may be necessary to comply with the notice/attribution requirements.\n\nWe also wish that any documentation file generated using this software\nbe attributed to DevDocs. Let's be fair to all contributors by giving\ncredit where credit's due. Thanks.\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM ruby:3.4.7\nENV LANG=C.UTF-8\nENV ENABLE_SERVICE_WORKER=true\n\nWORKDIR /devdocs\n\nRUN apt-get update && \\\n    apt-get -y install git nodejs libcurl4 && \\\n    gem install bundler && \\\n    rm -rf /var/lib/apt/lists/*\n\nCOPY Gemfile Gemfile.lock Rakefile /devdocs/\n\nRUN bundle config set path.system true && \\\n    bundle install && \\\n    rm -rf ~/.gem /root/.bundle/cache /usr/local/bundle/cache\n\nCOPY . /devdocs\n\nRUN thor docs:download --all && \\\n    thor assets:compile && \\\n    rm -rf /tmp\n\nEXPOSE 9292\nCMD rackup -o 0.0.0.0\n"
  },
  {
    "path": "Dockerfile-alpine",
    "content": "FROM ruby:3.4.7-alpine\n\nENV LANG=C.UTF-8\nENV ENABLE_SERVICE_WORKER=true\n\nWORKDIR /devdocs\n\nCOPY . /devdocs\n\nRUN apk --update add nodejs build-base libstdc++ gzip git zlib-dev libcurl && \\\n    gem install bundler && \\\n    bundle config set path.system true && \\\n    bundle config set without 'test' && \\\n    bundle install && \\\n    thor docs:download --all && \\\n    thor assets:compile && \\\n    apk del gzip build-base git zlib-dev && \\\n    rm -rf /var/cache/apk/* /tmp ~/.gem /root/.bundle/cache \\\n    /usr/local/bundle/cache /usr/lib/node_modules\n\nEXPOSE 9292\nCMD rackup -o 0.0.0.0\n"
  },
  {
    "path": "Gemfile",
    "content": "source 'https://rubygems.org'\nruby '3.4.8'\n\ngem 'activesupport', require: false\ngem 'html-pipeline'\ngem 'nokogiri'\ngem 'pry-byebug'\ngem 'rake'\ngem 'terminal-table'\ngem 'thor'\ngem 'typhoeus'\ngem 'yajl-ruby', require: false\n\ngroup :app do\n  gem 'browser'\n  gem 'chunky_png'\n  gem 'erubi'\n  gem 'dartsass-sprockets'\n  gem 'image_optim_pack', platforms: :ruby\n  gem 'image_optim'\n  gem 'rack-ssl-enforcer'\n  gem 'rack'\n  gem 'rss'\n  gem 'sinatra-contrib'\n  gem 'sinatra'\n  gem 'sprockets-helpers'\n  gem 'sprockets'\n  gem 'thin'\nend\n\ngroup :production do\n  gem 'newrelic_rpm'\n  gem \"terser\"\nend\n\ngroup :development do\n  gem 'better_errors'\nend\n\ngroup :docs do\n  gem 'progress_bar', require: false\n  gem 'redcarpet'\n  gem 'tty-pager', require: false\n  gem 'unix_utils', require: false\nend\n\ngroup :test do\n  gem 'minitest'\n  gem 'rack-test', require: false\n  gem 'rr', require: false\nend\n\nif ENV['SELENIUM'] == '1'\n  gem 'capybara'\n  gem 'selenium-webdriver'\nend\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 http://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": "Procfile",
    "content": "web: bundle exec rackup config.ru -p $PORT\n"
  },
  {
    "path": "README.md",
    "content": "# [DevDocs](https://devdocs.io) — API Documentation Browser\n\nDevDocs combines multiple developer documentations in a clean and organized web UI with instant search, offline support, mobile version, dark theme, keyboard shortcuts, and more.\n\nDevDocs was created by [Thibaut Courouble](https://thibaut.me) and is operated by [freeCodeCamp](https://www.freecodecamp.org).\n\n## We are currently searching for maintainers\n\nPlease reach out to the community on [Discord](https://discord.gg/PRyKn3Vbay) if you would like to join the team!\n\nKeep track of development news:\n\n* Join the `#contributors` chat room on [Discord](https://discord.gg/PRyKn3Vbay)\n* Watch the repository on [GitHub](https://github.com/freeCodeCamp/devdocs/subscription)\n* Follow [@DevDocs](https://twitter.com/DevDocs) on Twitter\n\n**Table of Contents:** [Quick Start](#quick-start) · [Vision](#vision) · [App](#app) · [Scraper](#scraper) · [Commands](#available-commands) · [Contributing](#contributing) · [Documentation](#documentation) · [Related Projects](#related-projects) · [License](#copyright--license) · [Questions?](#questions)\n\n## Quick Start\n\nUnless you wish to contribute to the project, we recommend using the hosted version at [devdocs.io](https://devdocs.io). It's up-to-date and works offline out-of-the-box.\n\n### Using Docker (Recommended)\n\nThe easiest way to run DevDocs locally is using Docker:\n\n```sh\ndocker run --name devdocs -d -p 9292:9292 ghcr.io/freecodecamp/devdocs:latest\n```\n\nThis will start DevDocs at [localhost:9292](http://localhost:9292). We provide both regular and Alpine-based images:\n- `ghcr.io/freecodecamp/devdocs:latest` - Standard image\n- `ghcr.io/freecodecamp/devdocs:latest-alpine` - Alpine-based (smaller size)\n\nImages are automatically built and updated monthly with the latest documentation.\n\nAlternatively, you can build the image yourself:\n\n```sh\ngit clone https://github.com/freeCodeCamp/devdocs.git && cd devdocs\ndocker build -t devdocs .\ndocker run --name devdocs -d -p 9292:9292 devdocs\n```\n\n### Manual Installation\n\nDevDocs is made of two pieces: a Ruby scraper that generates the documentation and metadata, and a JavaScript app powered by a small Sinatra app.\n\nDevDocs requires Ruby 3.4.1 (defined in [`Gemfile`](./Gemfile)), libcurl, and a JavaScript runtime supported by [ExecJS](https://github.com/rails/execjs#readme) (included in OS X and Windows; [Node.js](https://nodejs.org/en/) on Linux). On Arch Linux run `pacman -S ruby ruby-bundler ruby-erb ruby-irb`.\n\nOnce you have these installed, run the following commands:\n\n```sh\ngit clone https://github.com/freeCodeCamp/devdocs.git && cd devdocs\ngem install bundler\nbundle install\nbundle exec thor docs:download --default\nbundle exec rackup\n```\n\nFinally, point your browser at [localhost:9292](http://localhost:9292) (the first request will take a few seconds to compile the assets). You're all set.\n\nThe `thor docs:download` command is used to download pre-generated documentations from DevDocs's servers (e.g. `thor docs:download html css`). You can see the list of available documentations and versions by running `thor docs:list`. To update all downloaded documentations, run `thor docs:download --installed`. To download and install all documentation this project has available, run `thor docs:download --all`.\n\n**Note:** there is currently no update mechanism other than `git pull origin main` to update the code and `thor docs:download --installed` to download the latest version of the docs. To stay informed about new releases, be sure to [watch](https://github.com/freeCodeCamp/devdocs/subscription) this repository.\n\n## Vision\n\nDevDocs aims to make reading and searching reference documentation fast, easy and enjoyable.\n\nThe app's main goals are to:\n\n* Keep load times as short as possible\n* Improve the quality, speed, and order of search results\n* Maximize the use of caching and other performance optimizations\n* Maintain a clean and readable user interface\n* Be fully functional offline\n* Support full keyboard navigation\n* Reduce “context switch” by using a consistent typography and design across all documentations\n* Reduce clutter by focusing on a specific category of content (API/reference) and indexing only the minimum useful to most developers.\n\n**Note:** DevDocs is neither a programming guide nor a search engine. All our content is pulled from third-party sources and the project doesn't intend to compete with full-text search engines. Its backbone is metadata; each piece of content is identified by a unique, \"obvious\" and short string. Tutorials, guides and other content that don't meet this requirement are outside the scope of the project.\n\n## App\n\nThe web app is all client-side JavaScript, powered by a small [Sinatra](http://www.sinatrarb.com)/[Sprockets](https://github.com/rails/sprockets) application. It relies on files generated by the [scraper](#scraper).\n\nMany of the code's design decisions were driven by the fact that the app uses XHR to load content directly into the main frame. This includes stripping the original documents of most of their HTML markup (e.g. scripts and stylesheets) to avoid polluting the main frame, and prefixing all CSS class names with an underscore to prevent conflicts.\n\nAnother driving factor is performance and the fact that everything happens in the browser. A service worker (which comes with its own set of constraints) and `localStorage` are used to speed up the boot time, while memory consumption is kept in check by allowing the user to pick his/her own set of documentations. The search algorithm is kept simple because it needs to be fast even searching through 100,000 strings.\n\nDevDocs being a developer tool, the browser requirements are high:\n\n* Recent versions of Firefox, Chrome, or Opera\n* Safari 11.1+\n* Edge 17+\n* iOS 11.3+\n\nThis allows the code to take advantage of the latest DOM and HTML5 APIs and make developing DevDocs a lot more fun!\n\n## Scraper\n\nThe scraper is responsible for generating the documentation and index files (metadata) used by the [app](#app). It's written in Ruby under the `Docs` module.\n\nThere are currently two kinds of scrapers: `UrlScraper` which downloads files via HTTP and `FileScraper` which reads them from the local filesystem. They both make copies of HTML documents, recursively following links that match a set of rules and applying all sorts of modifications along the way, in addition to building an index of the files and their metadata. Documents are parsed using [Nokogiri](http://nokogiri.org).\n\nModifications made to each document include:\n\n* removing content such as the document structure (`<html>`, `<head>`, etc.), comments, empty nodes, etc.\n* fixing links (e.g. to remove duplicates)\n* replacing all external (not scraped) URLs with their fully qualified counterpart\n* replacing all internal (scraped) URLs with their unqualified and relative counterpart\n* adding content, such as a title and link to the original document\n* ensuring correct syntax highlighting using [Prism](http://prismjs.com/)\n\nThese modifications are applied via a set of filters using the [HTML::Pipeline](https://github.com/jch/html-pipeline) library. Each scraper includes filters specific to itself, one of which is tasked with figuring out the pages' metadata.\n\nThe end result is a set of normalized HTML partials and two JSON files (index + offline data). Because the index files are loaded separately by the [app](#app) following the user's preferences, the scraper also creates a JSON manifest file containing information about the documentations currently available on the system (such as their name, version, update date, etc.).\n\nMore information about [scrapers](./docs/scraper-reference.md) and [filters](./docs/filter-reference.md) is available in the `docs` folder.\n\n## Available Commands\n\nThe command-line interface uses [Thor](http://whatisthor.com). To see all commands and options, run `thor list` from the project's root.\n\n```sh\n# Server\nrackup              # Start the server (ctrl+c to stop)\nrackup --help       # List server options\n\n# Docs\nthor docs:list      # List available documentations\nthor docs:download  # Download one or more documentations\nthor docs:manifest  # Create the manifest file used by the app\nthor docs:generate  # Generate/scrape a documentation\nthor docs:page      # Generate/scrape a documentation page\nthor docs:package   # Package a documentation for use with docs:download\nthor docs:clean     # Delete documentation packages\n\n# Console\nthor console        # Start a REPL\nthor console:docs   # Start a REPL in the \"Docs\" module\n\n# Tests can be run quickly from within the console using the \"test\" command.\n# Run \"help test\" for usage instructions.\nthor test:all       # Run all tests\nthor test:docs      # Run \"Docs\" tests\nthor test:app       # Run \"App\" tests\n\n# Assets\nthor assets:compile # Compile assets (not required in development mode)\nthor assets:clean   # Clean old assets\n```\n\nIf multiple versions of Ruby are installed on your system, commands must be run through `bundle exec`.\n\n## Contributing\n\nContributions are welcome. Please read the [contributing guidelines](./.github/CONTRIBUTING.md).\n\n## Documentation\n\n* [Adding documentations to DevDocs](./docs/adding-docs.md)\n* [Scraper Reference](./docs/scraper-reference.md)\n* [Filter Reference](./docs/filter-reference.md)\n* [Maintainers’ Guide](./docs/maintainers.md)\n\n## DevDocs Quick Usage Cheatsheet\n\nBelow are some helpful shortcuts and usage tips that are not immediately obvious to new users:\n\n- Press <kbd>/</kbd> or <kbd>Ctrl + K</kbd> to instantly focus the search bar.\n- Press <kbd>?</kbd> to open DevDocs’ built-in help overlay.\n- Press <kbd>↑</kbd> or <kbd>↓</kbd> to navigate search results without touching the mouse.\n- Press <kbd>Enter</kbd> to open the highlighted search result.\n- Press <kbd>Backspace</kbd> to go back to the previously viewed page.\n- Press <kbd>Shift + S</kbd> to toggle the sidebar visibility.\n- Press <kbd>A</kbd> to open the list of all installed documentation sets.\n- Press <kbd>Esc</kbd> to close popups, overlays, and search.\n- Use the **⚡ Offline Mode toggle** to download docs for offline use.\n- You can pin specific documentation sets to the sidebar for quicker access.\n\nThese shortcuts make DevDocs faster to navigate and more efficient for daily use.\n\n\n## Related Projects\n\nMade something cool? Feel free to open a PR to add a new row to this table! You might want to discover new projects via https://github.com/topics/devdocs.\n\n<!-- table is sorted by description -->\n\n| Project                                                                                     | Description                          | Last commit                                                                                                          | Stars                                                                                                  |\n| ------------------------------------------------------------------------------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |\n| [yannickglt/alfred-devdocs](https://github.com/yannickglt/alfred-devdocs)                   | Alfred workflow                      | ![Latest GitHub commit](https://img.shields.io/github/last-commit/yannickglt/alfred-devdocs?logo=github&label)       | ![GitHub stars](https://img.shields.io/github/stars/yannickglt/alfred-devdocs?logo=github&label)       |\n| [Merith-TK/devdocs_webapp_kotlin](https://github.com/Merith-TK/devdocs_webapp_kotlin)       | Android application                  | ![Latest GitHub commit](https://img.shields.io/github/last-commit/Merith-TK/devdocs_webapp_kotlin?logo=github&label) | ![GitHub stars](https://img.shields.io/github/stars/Merith-TK/devdocs_webapp_kotlin?logo=github&label) |\n| [gruehle/dev-docs-viewer](https://github.com/gruehle/dev-docs-viewer)                       | Brackets extension                   | ![Latest GitHub commit](https://img.shields.io/github/last-commit/gruehle/dev-docs-viewer?logo=github&label)         | ![GitHub stars](https://img.shields.io/github/stars/gruehle/dev-docs-viewer?logo=github&label)         |\n| [egoist/devdocs-desktop](https://github.com/egoist/devdocs-desktop)                         | Electron application                 | ![Latest GitHub commit](https://img.shields.io/github/last-commit/egoist/devdocs-desktop?logo=github&label)          | ![GitHub stars](https://img.shields.io/github/stars/egoist/devdocs-desktop?logo=github&label)          |\n| [skeeto/devdocs-lookup](https://github.com/skeeto/devdocs-lookup)                           | Emacs function                       | ![Latest GitHub commit](https://img.shields.io/github/last-commit/skeeto/devdocs-lookup?logo=github&label)           | ![GitHub stars](https://img.shields.io/github/stars/skeeto/devdocs-lookup?logo=github&label)           |\n| [astoff/devdocs.el](https://github.com/astoff/devdocs.el)                                   | Emacs viewer                         | ![Latest GitHub commit](https://img.shields.io/github/last-commit/astoff/devdocs.el?logo=github&label)               | ![GitHub stars](https://img.shields.io/github/stars/astoff/devdocs.el?logo=github&label)               |\n| [naquad/devdocs-shell](https://github.com/naquad/devdocs-shell)                             | GTK shell with Vim integration       | ![Latest GitHub commit](https://img.shields.io/github/last-commit/naquad/devdocs-shell?logo=github&label)            | ![GitHub stars](https://img.shields.io/github/stars/naquad/devdocs-shell?logo=github&label)            |\n| [hardpixel/devdocs-desktop](https://github.com/hardpixel/devdocs-desktop)                   | GTK application                      | ![Latest GitHub commit](https://img.shields.io/github/last-commit/hardpixel/devdocs-desktop?logo=github&label)       | ![GitHub stars](https://img.shields.io/github/stars/hardpixel/devdocs-desktop?logo=github&label)       |\n| [qwfy/doc-browser](https://github.com/qwfy/doc-browser)                                     | Linux application                    | ![Latest GitHub commit](https://img.shields.io/github/last-commit/qwfy/doc-browser?logo=github&label)                | ![GitHub stars](https://img.shields.io/github/stars/qwfy/doc-browser?logo=github&label)                |\n| [dteoh/devdocs-macos](https://github.com/dteoh/devdocs-macos)                               | macOS application                    | ![Latest GitHub commit](https://img.shields.io/github/last-commit/dteoh/devdocs-macos?logo=github&label)             | ![GitHub stars](https://img.shields.io/github/stars/dteoh/devdocs-macos?logo=github&label)             |\n| [Sublime Text plugin](https://sublime.wbond.net/packages/DevDocs)                           | Sublime Text plugin                  | ![Latest GitHub commit](https://img.shields.io/github/last-commit/vitorbritto/sublime-devdocs?logo=github&label)     | ![GitHub stars](https://img.shields.io/github/stars/vitorbritto/sublime-devdocs?logo=github&label)     |\n| [mohamed3nan/DevDocs-Tab](https://github.com/mohamed3nan/DevDocs-Tab)                       | VS Code extension (view as tab)      | ![Latest GitHub commit](https://img.shields.io/github/last-commit/mohamed3nan/DevDocs-Tab?logo=github&label)         | ![GitHub stars](https://img.shields.io/github/stars/mohamed3nan/DevDocs-Tab?logo=github&label)         |\n| [deibit/vscode-devdocs](https://marketplace.visualstudio.com/items?itemName=deibit.devdocs) | VS Code extension (open the browser) | ![Latest GitHub commit](https://img.shields.io/github/last-commit/deibit/vscode-devdocs?logo=github&label)           | ![GitHub stars](https://img.shields.io/github/stars/deibit/vscode-devdocs?logo=github&label)           |\n| [mdh34/quickDocs](https://github.com/mdh34/quickDocs)                                       | Vala/Python based viewer             | ![Latest GitHub commit](https://img.shields.io/github/last-commit/mdh34/quickDocs?logo=github&label)                 | ![GitHub stars](https://img.shields.io/github/stars/mdh34/quickDocs?logo=github&label)                 |\n| [girishji/devdocs.vim](https://github.com/girishji/devdocs.vim)                               | Vim plugin & TUI (browse inside Vim)                           | ![Latest GitHub commit](https://img.shields.io/github/last-commit/girishji/devdocs.vim?logo=github&label)             | ![GitHub stars](https://img.shields.io/github/stars/girishji/devdocs.vim?logo=github&label)             |\n| [romainl/vim-devdocs](https://github.com/romainl/vim-devdocs)                               | Vim plugin                           | ![Latest GitHub commit](https://img.shields.io/github/last-commit/romainl/vim-devdocs?logo=github&label)             | ![GitHub stars](https://img.shields.io/github/stars/romainl/vim-devdocs?logo=github&label)             |\n| [waiting-for-dev/vim-www](https://github.com/waiting-for-dev/vim-www)                       | Vim plugin                           | ![Latest GitHub commit](https://img.shields.io/github/last-commit/waiting-for-dev/vim-www?logo=github&label)         | ![GitHub stars](https://img.shields.io/github/stars/waiting-for-dev/vim-www?logo=github&label)         |\n| [emmanueltouzery/apidocs.nvim](https://github.com/emmanueltouzery/apidocs.nvim)             | Neovim plugin                        | ![Latest GitHub commit](https://img.shields.io/github/last-commit/emmanueltouzery/apidocs.nvim?logo=github&label)    | ![GitHub stars](https://img.shields.io/github/stars/emmanueltouzery/apidocs.nvim?logo=github&label)    |\n| [toiletbril/dedoc](https://github.com/toiletbril/dedoc)                                     | Terminal based viewer                | ![Latest GitHub commit](https://img.shields.io/github/last-commit/toiletbril/dedoc?logo=github&label)                | ![GitHub stars](https://img.shields.io/github/stars/toiletbril/dedoc?logo=github&label)                |\n| [Raycast Devdocs](https://www.raycast.com/djpowers/devdocs)                                 | Raycast extension                    | Unavailable                 | Unavailable                |\n| [chrisgrieser/alfred-docs-searches](https://github.com/chrisgrieser/alfred-docs-searches)   | Alfred workflow                      | ![Latest GitHub commit](https://img.shields.io/github/last-commit/chrisgrieser/alfred-docs-searches?logo=github&label) | ![GitHub stars](https://img.shields.io/github/stars/chrisgrieser/alfred-docs-searches?logo=github&label) |\n\n## Copyright / License\n\nCopyright 2013–2026 Thibaut Courouble and [other contributors](https://github.com/freeCodeCamp/devdocs/graphs/contributors)\n\nThis software is licensed under the terms of the Mozilla Public License v2.0. See the [COPYRIGHT](./COPYRIGHT) and [LICENSE](./LICENSE) files.\n\nPlease do not use the name DevDocs to endorse or promote products derived from this software without the maintainers' permission, except as may be necessary to comply with the notice/attribution requirements.\n\nWe also wish that any documentation file generated using this software be attributed to DevDocs. Let's be fair to all contributors by giving credit where credit's due. Thanks!\n\n## Questions?\n\nIf you have any questions, please feel free to ask them on the `#contributors` chat room on [Discord](https://discord.gg/PRyKn3Vbay).\n"
  },
  {
    "path": "Rakefile",
    "content": "#!/usr/bin/env rake\n\nrequire 'bundler/setup'\nrequire 'thor'\n\nBundler.require :default\n$LOAD_PATH.unshift 'lib'\n\ntask :default do\n  $LOAD_PATH.unshift 'test'\n  Dir['test/**/*_test.rb'].map(&File.method(:expand_path)).each(&method(:require))\nend\n\nnamespace :assets do\n  desc 'Compile all assets'\n  task :precompile do\n    load 'tasks/docs.thor'\n    DocsCLI.new.prepare_deploy\n\n    load 'tasks/assets.thor'\n    AssetsCLI.new.compile\n  end\nend\n"
  },
  {
    "path": "Thorfile",
    "content": "$LOAD_PATH.unshift 'lib'\n"
  },
  {
    "path": "assets/images/.gitignore",
    "content": "sprites/**/*\n"
  },
  {
    "path": "assets/javascripts/app/app.js",
    "content": "class App extends Events {\n  _$ = $;\n  _$$ = $$;\n  _page = page;\n  collections = {};\n  models = {};\n  templates = {};\n  views = {};\n\n  init() {\n    try {\n      this.initErrorTracking();\n    } catch (error) {}\n    if (!this.browserCheck()) {\n      return;\n    }\n\n    this.el = $(\"._app\");\n    this.localStorage = new LocalStorageStore();\n    if (app.ServiceWorker.isEnabled()) {\n      this.serviceWorker = new app.ServiceWorker();\n    }\n    this.settings = new app.Settings();\n    this.db = new app.DB();\n\n    this.settings.initLayout();\n\n    this.docs = new app.collections.Docs();\n    this.disabledDocs = new app.collections.Docs();\n    this.entries = new app.collections.Entries();\n\n    this.router = new app.Router();\n    this.shortcuts = new app.Shortcuts();\n    this.document = new app.views.Document();\n    if (this.isMobile()) {\n      this.mobile = new app.views.Mobile();\n    }\n\n    if (document.body.hasAttribute(\"data-doc\")) {\n      this.DOC = JSON.parse(document.body.getAttribute(\"data-doc\"));\n      this.bootOne();\n    } else if (this.DOCS) {\n      this.bootAll();\n    } else {\n      this.onBootError();\n    }\n  }\n\n  browserCheck() {\n    if (this.isSupportedBrowser()) {\n      return true;\n    }\n    document.body.innerHTML = app.templates.unsupportedBrowser;\n    this.hideLoadingScreen();\n    return false;\n  }\n\n  initErrorTracking() {\n    // Show a warning message and don't track errors when the app is loaded\n    // from a domain other than our own, because things are likely to break.\n    // (e.g. cross-domain requests)\n    if (this.isInvalidLocation()) {\n      new app.views.Notif(\"InvalidLocation\");\n    } else {\n      if (this.config.sentry_dsn) {\n        Raven.config(this.config.sentry_dsn, {\n          release: this.config.release,\n          whitelistUrls: [/devdocs/],\n          includePaths: [/devdocs/],\n          ignoreErrors: [/NPObject/, /NS_ERROR/, /^null$/, /EvalError/],\n          tags: {\n            mode: this.isSingleDoc() ? \"single\" : \"full\",\n            iframe: (window.top !== window).toString(),\n            electron: (!!window.process?.versions?.electron).toString(),\n          },\n          shouldSendCallback: () => {\n            try {\n              if (this.isInjectionError()) {\n                this.onInjectionError();\n                return false;\n              }\n              if (this.isAndroidWebview()) {\n                return false;\n              }\n            } catch (error) {}\n            return true;\n          },\n          dataCallback(data) {\n            try {\n              data.user ||= {};\n              Object.assign(data.user, app.settings.dump());\n              if (data.user.docs) {\n                data.user.docs = data.user.docs.split(\"/\");\n              }\n              if (app.lastIDBTransaction) {\n                data.user.lastIDBTransaction = app.lastIDBTransaction;\n              }\n              data.tags.scriptCount = document.scripts.length;\n            } catch (error) {}\n            return data;\n          },\n        }).install();\n      }\n      this.previousErrorHandler = onerror;\n      window.onerror = this.onWindowError.bind(this);\n      CookiesStore.onBlocked = this.onCookieBlocked;\n    }\n  }\n\n  bootOne() {\n    this.doc = new app.models.Doc(this.DOC);\n    this.docs.reset([this.doc]);\n    this.doc.load(this.start.bind(this), this.onBootError.bind(this), {\n      readCache: true,\n    });\n    new app.views.Notice(\"singleDoc\", this.doc);\n    delete this.DOC;\n  }\n\n  bootAll() {\n    const docs = this.settings.getDocs();\n    for (var doc of this.DOCS) {\n      (docs.includes(doc.slug) ? this.docs : this.disabledDocs).add(doc);\n    }\n    this.migrateDocs();\n    this.docs.load(this.start.bind(this), this.onBootError.bind(this), {\n      readCache: true,\n      writeCache: true,\n    });\n    delete this.DOCS;\n  }\n\n  start() {\n    let doc;\n    for (doc of this.docs.all()) {\n      this.entries.add(doc.toEntry());\n    }\n    for (doc of this.disabledDocs.all()) {\n      this.entries.add(doc.toEntry());\n    }\n    for (doc of this.docs.all()) {\n      this.initDoc(doc);\n    }\n    this.trigger(\"ready\");\n    this.router.start();\n    this.hideLoadingScreen();\n    setTimeout(() => {\n      if (!this.doc) {\n        this.welcomeBack();\n      }\n      return this.removeEvent(\"ready bootError\");\n    }, 50);\n  }\n\n  initDoc(doc) {\n    for (var type of doc.types.all()) {\n      doc.entries.add(type.toEntry());\n    }\n    this.entries.add(doc.entries.all());\n  }\n\n  migrateDocs() {\n    let needsSaving;\n    for (var slug of this.settings.getDocs()) {\n      if (!this.docs.findBy(\"slug\", slug)) {\n        var doc;\n\n        needsSaving = true;\n        if (slug === \"webpack~2\") {\n          doc = this.disabledDocs.findBy(\"slug\", \"webpack\");\n        }\n        if (slug === \"angular~4_typescript\") {\n          doc = this.disabledDocs.findBy(\"slug\", \"angular\");\n        }\n        if (slug === \"angular~2_typescript\") {\n          doc = this.disabledDocs.findBy(\"slug\", \"angular~2\");\n        }\n        if (!doc) {\n          doc = this.disabledDocs.findBy(\"slug_without_version\", slug);\n        }\n        if (doc) {\n          this.disabledDocs.remove(doc);\n          this.docs.add(doc);\n        }\n      }\n    }\n\n    if (needsSaving) {\n      this.saveDocs();\n    }\n  }\n\n  enableDoc(doc, _onSuccess, onError) {\n    if (this.docs.contains(doc)) {\n      return;\n    }\n\n    const onSuccess = () => {\n      if (this.docs.contains(doc)) {\n        return;\n      }\n      this.disabledDocs.remove(doc);\n      this.docs.add(doc);\n      this.docs.sort();\n      this.initDoc(doc);\n      this.saveDocs();\n      if (app.settings.get(\"autoInstall\")) {\n        doc.install(_onSuccess, onError);\n      } else {\n        _onSuccess();\n      }\n    };\n\n    doc.load(onSuccess, onError, { writeCache: true });\n  }\n\n  saveDocs() {\n    this.settings.setDocs(this.docs.all().map((doc) => doc.slug));\n    this.db.migrate();\n    return this.serviceWorker != null\n      ? this.serviceWorker.updateInBackground()\n      : undefined;\n  }\n\n  welcomeBack() {\n    let visitCount = this.settings.get(\"count\");\n    this.settings.set(\"count\", ++visitCount);\n    if (visitCount === 5) {\n      new app.views.Notif(\"Share\", { autoHide: null });\n    }\n    new app.views.News();\n    new app.views.Updates();\n    return (this.updateChecker = new app.UpdateChecker());\n  }\n\n  reboot() {\n    if (location.pathname !== \"/\" && location.pathname !== \"/settings\") {\n      window.location = `/#${location.pathname}`;\n    } else {\n      window.location = \"/\";\n    }\n  }\n\n  reload() {\n    this.docs.clearCache();\n    this.disabledDocs.clearCache();\n    if (this.serviceWorker) {\n      this.serviceWorker.reload();\n    } else {\n      this.reboot();\n    }\n  }\n\n  reset() {\n    this.localStorage.reset();\n    this.settings.reset();\n    if (this.db != null) {\n      this.db.reset();\n    }\n    if (this.serviceWorker != null) {\n      this.serviceWorker.update();\n    }\n    window.location = \"/\";\n  }\n\n  showTip(tip) {\n    if (this.isSingleDoc()) {\n      return;\n    }\n    const tips = this.settings.getTips();\n    if (!tips.includes(tip)) {\n      tips.push(tip);\n      this.settings.setTips(tips);\n      new app.views.Tip(tip);\n    }\n  }\n\n  hideLoadingScreen() {\n    if ($.overlayScrollbarsEnabled()) {\n      document.body.classList.add(\"_overlay-scrollbars\");\n    }\n    document.documentElement.classList.remove(\"_booting\");\n  }\n\n  indexHost() {\n    // Can't load the index files from the host/CDN when service worker is\n    // enabled because it doesn't support caching URLs that use CORS.\n    return this.config[\n      this.serviceWorker && this.settings.hasDocs()\n        ? \"index_path\"\n        : \"docs_origin\"\n    ];\n  }\n\n  onBootError(...args) {\n    this.trigger(\"bootError\");\n    this.hideLoadingScreen();\n  }\n\n  onQuotaExceeded() {\n    if (this.quotaExceeded) {\n      return;\n    }\n    this.quotaExceeded = true;\n    new app.views.Notif(\"QuotaExceeded\", { autoHide: null });\n  }\n\n  onCookieBlocked(key, value, actual) {\n    if (this.cookieBlocked) {\n      return;\n    }\n    this.cookieBlocked = true;\n    new app.views.Notif(\"CookieBlocked\", { autoHide: null });\n    Raven.captureMessage(`CookieBlocked/${key}`, {\n      level: \"warning\",\n      extra: { value, actual },\n    });\n  }\n\n  onWindowError(...args) {\n    if (this.cookieBlocked) {\n      return;\n    }\n    if (this.isInjectionError(...args)) {\n      this.onInjectionError();\n    } else if (this.isAppError(...args)) {\n      if (typeof this.previousErrorHandler === \"function\") {\n        this.previousErrorHandler(...args);\n      }\n      this.hideLoadingScreen();\n      if (!this.errorNotif) {\n        this.errorNotif = new app.views.Notif(\"Error\");\n      }\n      this.errorNotif.show();\n    }\n  }\n\n  onInjectionError() {\n    if (!this.injectionError) {\n      this.injectionError = true;\n      alert(`\\\nJavaScript code has been injected in the page which prevents DevDocs from running correctly.\nPlease check your browser extensions/addons. `);\n      Raven.captureMessage(\"injection error\", { level: \"info\" });\n    }\n  }\n\n  isInjectionError() {\n    // Some browser extensions expect the entire web to use jQuery.\n    // I gave up trying to fight back.\n    return (\n      window.$ !== app._$ ||\n      window.$$ !== app._$$ ||\n      window.page !== app._page ||\n      typeof $.empty !== \"function\" ||\n      typeof page.show !== \"function\"\n    );\n  }\n\n  isAppError(error, file) {\n    // Ignore errors from external scripts.\n    return file && file.includes(\"devdocs\") && file.endsWith(\".js\");\n  }\n\n  isSupportedBrowser() {\n    try {\n      const features = {\n        bind: !!Function.prototype.bind,\n        pushState: !!history.pushState,\n        matchMedia: !!window.matchMedia,\n        insertAdjacentHTML: !!document.body.insertAdjacentHTML,\n        defaultPrevented:\n          document.createEvent(\"CustomEvent\").defaultPrevented === false,\n        cssVariables: !!CSS.supports?.(\"(--t: 0)\"),\n      };\n\n      for (var key in features) {\n        var value = features[key];\n        if (!value) {\n          Raven.captureMessage(`unsupported/${key}`, { level: \"info\" });\n          return false;\n        }\n      }\n\n      return true;\n    } catch (error) {\n      Raven.captureMessage(\"unsupported/exception\", {\n        level: \"info\",\n        extra: { error },\n      });\n      return false;\n    }\n  }\n\n  isSingleDoc() {\n    return document.body.hasAttribute(\"data-doc\");\n  }\n\n  isMobile() {\n    return this._isMobile != null\n      ? this._isMobile\n      : (this._isMobile = app.views.Mobile.detect());\n  }\n\n  isAndroidWebview() {\n    return this._isAndroidWebview != null\n      ? this._isAndroidWebview\n      : (this._isAndroidWebview = app.views.Mobile.detectAndroidWebview());\n  }\n\n  isInvalidLocation() {\n    return (\n      this.config.env === \"production\" &&\n      !location.host.startsWith(app.config.production_host)\n    );\n  }\n}\n\nthis.app = new App();\n"
  },
  {
    "path": "assets/javascripts/app/config.js.erb",
    "content": "app.config = {\n  db_filename: 'db.json',\n  default_docs: <%= App.default_docs.to_json %>,\n  docs_aliases: <%= App.docs_aliases.to_json %>,\n  docs_origin: '<%= App.docs_origin %>',\n  env: '<%= App.environment %>',\n  history_cache_size: 10,\n  index_filename: 'index.json',\n  index_path: '/<%= App.docs_prefix %>',\n  max_results: 50,\n  production_host: 'devdocs.io',\n  search_param: 'q',\n  sentry_dsn: '<%= App.sentry_dsn %>',\n  version: <%= Time.now.to_i %>,\n  release: <%= Time.now.utc.httpdate.to_json %>,\n  mathml_stylesheet: '/mathml.css',\n  favicon_spritesheet: '<%= image_path('sprites/docs.png') %>',\n  service_worker_path: '/service-worker.js',\n  service_worker_enabled: <%= App.environment == :production || ENV['ENABLE_SERVICE_WORKER'] == 'true' %>,\n}\n"
  },
  {
    "path": "assets/javascripts/app/db.js",
    "content": "app.DB = class DB {\n  static NAME = \"docs\";\n  static VERSION = 15;\n\n  constructor() {\n    this.versionMultipler = $.isIE() ? 1e5 : 1e9;\n    this.useIndexedDB = this.useIndexedDB();\n    this.callbacks = [];\n  }\n\n  db(fn) {\n    if (!this.useIndexedDB) {\n      return fn();\n    }\n    if (fn) {\n      this.callbacks.push(fn);\n    }\n    if (this.open) {\n      return;\n    }\n\n    try {\n      this.open = true;\n      const req = indexedDB.open(\n        DB.NAME,\n        DB.VERSION * this.versionMultipler + this.userVersion(),\n      );\n      req.onsuccess = (event) => this.onOpenSuccess(event);\n      req.onerror = (event) => this.onOpenError(event);\n      req.onupgradeneeded = (event) => this.onUpgradeNeeded(event);\n    } catch (error) {\n      this.fail(\"exception\", error);\n    }\n  }\n\n  onOpenSuccess(event) {\n    let error;\n    const db = event.target.result;\n\n    if (db.objectStoreNames.length === 0) {\n      try {\n        db.close();\n      } catch (error1) {}\n      this.open = false;\n      this.fail(\"empty\");\n    } else if ((error = this.buggyIDB(db))) {\n      try {\n        db.close();\n      } catch (error2) {}\n      this.open = false;\n      this.fail(\"buggy\", error);\n    } else {\n      this.runCallbacks(db);\n      this.open = false;\n      db.close();\n    }\n  }\n\n  onOpenError(event) {\n    event.preventDefault();\n    this.open = false;\n    const { error } = event.target;\n\n    switch (error.name) {\n      case \"QuotaExceededError\":\n        this.onQuotaExceededError();\n        break;\n      case \"VersionError\":\n        this.onVersionError();\n        break;\n      case \"InvalidStateError\":\n        this.fail(\"private_mode\");\n        break;\n      default:\n        this.fail(\"cant_open\", error);\n    }\n  }\n\n  fail(reason, error) {\n    this.cachedDocs = null;\n    this.useIndexedDB = false;\n    if (!this.reason) {\n      this.reason = reason;\n    }\n    if (!this.error) {\n      this.error = error;\n    }\n    if (error) {\n      if (typeof console.error === \"function\") {\n        console.error(\"IDB error\", error);\n      }\n    }\n    this.runCallbacks();\n    if (error && reason === \"cant_open\") {\n      Raven.captureMessage(`${error.name}: ${error.message}`, {\n        level: \"warning\",\n        fingerprint: [error.name],\n      });\n    }\n  }\n\n  onQuotaExceededError() {\n    this.reset();\n    this.db();\n    app.onQuotaExceeded();\n    Raven.captureMessage(\"QuotaExceededError\", { level: \"warning\" });\n  }\n\n  onVersionError() {\n    const req = indexedDB.open(DB.NAME);\n    req.onsuccess = (event) => {\n      return this.handleVersionMismatch(event.target.result.version);\n    };\n    req.onerror = function (event) {\n      event.preventDefault();\n      return this.fail(\"cant_open\", error);\n    };\n  }\n\n  handleVersionMismatch(actualVersion) {\n    if (Math.floor(actualVersion / this.versionMultipler) !== DB.VERSION) {\n      this.fail(\"version\");\n    } else {\n      this.setUserVersion(actualVersion - DB.VERSION * this.versionMultipler);\n      this.db();\n    }\n  }\n\n  buggyIDB(db) {\n    if (this.checkedBuggyIDB) {\n      return;\n    }\n    this.checkedBuggyIDB = true;\n    try {\n      this.idbTransaction(db, {\n        stores: $.makeArray(db.objectStoreNames).slice(0, 2),\n        mode: \"readwrite\",\n      }).abort(); // https://bugs.webkit.org/show_bug.cgi?id=136937\n      return;\n    } catch (error) {\n      return error;\n    }\n  }\n\n  runCallbacks(db) {\n    let fn;\n    while ((fn = this.callbacks.shift())) {\n      fn(db);\n    }\n  }\n\n  onUpgradeNeeded(event) {\n    const db = event.target.result;\n    if (!db) {\n      return;\n    }\n\n    const objectStoreNames = $.makeArray(db.objectStoreNames);\n\n    if (!$.arrayDelete(objectStoreNames, \"docs\")) {\n      try {\n        db.createObjectStore(\"docs\");\n      } catch (error) {}\n    }\n\n    for (var doc of app.docs.all()) {\n      if (!$.arrayDelete(objectStoreNames, doc.slug)) {\n        try {\n          db.createObjectStore(doc.slug);\n        } catch (error1) {}\n      }\n    }\n\n    for (var name of objectStoreNames) {\n      try {\n        db.deleteObjectStore(name);\n      } catch (error2) {}\n    }\n  }\n\n  store(doc, data, onSuccess, onError, _retry) {\n    if (_retry == null) {\n      _retry = true;\n    }\n    this.db((db) => {\n      if (!db) {\n        onError();\n        return;\n      }\n\n      const txn = this.idbTransaction(db, {\n        stores: [\"docs\", doc.slug],\n        mode: \"readwrite\",\n        ignoreError: false,\n      });\n      txn.oncomplete = () => {\n        if (this.cachedDocs != null) {\n          this.cachedDocs[doc.slug] = doc.mtime;\n        }\n        onSuccess();\n      };\n      txn.onerror = (event) => {\n        event.preventDefault();\n        if (txn.error?.name === \"NotFoundError\" && _retry) {\n          this.migrate();\n          setTimeout(() => {\n            return this.store(doc, data, onSuccess, onError, false);\n          }, 0);\n        } else {\n          onError(event);\n        }\n      };\n\n      let store = txn.objectStore(doc.slug);\n      store.clear();\n      for (var path in data) {\n        var content = data[path];\n        store.add(content, path);\n      }\n\n      store = txn.objectStore(\"docs\");\n      store.put(doc.mtime, doc.slug);\n    });\n  }\n\n  unstore(doc, onSuccess, onError, _retry) {\n    if (_retry == null) {\n      _retry = true;\n    }\n    this.db((db) => {\n      if (!db) {\n        onError();\n        return;\n      }\n\n      const txn = this.idbTransaction(db, {\n        stores: [\"docs\", doc.slug],\n        mode: \"readwrite\",\n        ignoreError: false,\n      });\n      txn.oncomplete = () => {\n        if (this.cachedDocs != null) {\n          delete this.cachedDocs[doc.slug];\n        }\n        onSuccess();\n      };\n      txn.onerror = function (event) {\n        event.preventDefault();\n        if (txn.error?.name === \"NotFoundError\" && _retry) {\n          this.migrate();\n          setTimeout(() => {\n            return this.unstore(doc, onSuccess, onError, false);\n          }, 0);\n        } else {\n          onError(event);\n        }\n      };\n\n      let store = txn.objectStore(\"docs\");\n      store.delete(doc.slug);\n\n      store = txn.objectStore(doc.slug);\n      store.clear();\n    });\n  }\n\n  version(doc, fn) {\n    const version = this.cachedVersion(doc);\n    if (version != null) {\n      fn(version);\n      return;\n    }\n\n    this.db((db) => {\n      if (!db) {\n        fn(false);\n        return;\n      }\n\n      const txn = this.idbTransaction(db, {\n        stores: [\"docs\"],\n        mode: \"readonly\",\n      });\n      const store = txn.objectStore(\"docs\");\n\n      const req = store.get(doc.slug);\n      req.onsuccess = function () {\n        fn(req.result);\n      };\n      req.onerror = function (event) {\n        event.preventDefault();\n        fn(false);\n      };\n    });\n  }\n\n  cachedVersion(doc) {\n    if (!this.cachedDocs) {\n      return;\n    }\n    return this.cachedDocs[doc.slug] || false;\n  }\n\n  versions(docs, fn) {\n    const versions = this.cachedVersions(docs);\n    if (versions) {\n      fn(versions);\n      return;\n    }\n\n    return this.db((db) => {\n      if (!db) {\n        fn(false);\n        return;\n      }\n\n      const txn = this.idbTransaction(db, {\n        stores: [\"docs\"],\n        mode: \"readonly\",\n      });\n      txn.oncomplete = function () {\n        fn(result);\n      };\n      const store = txn.objectStore(\"docs\");\n      var result = {};\n\n      docs.forEach((doc) => {\n        const req = store.get(doc.slug);\n        req.onsuccess = function () {\n          result[doc.slug] = req.result;\n        };\n        req.onerror = function (event) {\n          event.preventDefault();\n          result[doc.slug] = false;\n        };\n      });\n    });\n  }\n\n  cachedVersions(docs) {\n    if (!this.cachedDocs) {\n      return;\n    }\n    const result = {};\n    for (var doc of docs) {\n      result[doc.slug] = this.cachedVersion(doc);\n    }\n    return result;\n  }\n\n  load(entry, onSuccess, onError) {\n    if (this.shouldLoadWithIDB(entry)) {\n      return this.loadWithIDB(entry, onSuccess, () =>\n        this.loadWithXHR(entry, onSuccess, onError)\n      );\n    } else {\n      return this.loadWithXHR(entry, onSuccess, onError);\n    }\n  }\n\n  loadWithXHR(entry, onSuccess, onError) {\n    return ajax({\n      url: entry.fileUrl(),\n      dataType: \"html\",\n      success: onSuccess,\n      error: onError,\n    });\n  }\n\n  loadWithIDB(entry, onSuccess, onError) {\n    return this.db((db) => {\n      if (!db) {\n        onError();\n        return;\n      }\n\n      if (!db.objectStoreNames.contains(entry.doc.slug)) {\n        onError();\n        this.loadDocsCache(db);\n        return;\n      }\n\n      const txn = this.idbTransaction(db, {\n        stores: [entry.doc.slug],\n        mode: \"readonly\",\n      });\n      const store = txn.objectStore(entry.doc.slug);\n\n      const req = store.get(entry.dbPath());\n      req.onsuccess = function () {\n        if (req.result) {\n          onSuccess(req.result);\n        } else {\n          onError();\n        }\n      };\n      req.onerror = function (event) {\n        event.preventDefault();\n        onError();\n      };\n      this.loadDocsCache(db);\n    });\n  }\n\n  loadDocsCache(db) {\n    if (this.cachedDocs) {\n      return;\n    }\n    this.cachedDocs = {};\n\n    const txn = this.idbTransaction(db, {\n      stores: [\"docs\"],\n      mode: \"readonly\",\n    });\n    txn.oncomplete = () => {\n      setTimeout(() => this.checkForCorruptedDocs(), 50);\n    };\n\n    const req = txn.objectStore(\"docs\").openCursor();\n    req.onsuccess = (event) => {\n      const cursor = event.target.result;\n      if (!cursor) {\n        return;\n      }\n      this.cachedDocs[cursor.key] = cursor.value;\n      cursor.continue();\n    };\n    req.onerror = function (event) {\n      event.preventDefault();\n    };\n  }\n\n  checkForCorruptedDocs() {\n    this.db((db) => {\n      let slug;\n      this.corruptedDocs = [];\n      const docs = (() => {\n        const result = [];\n        for (var key in this.cachedDocs) {\n          var value = this.cachedDocs[key];\n          if (value) {\n            result.push(key);\n          }\n        }\n        return result;\n      })();\n      if (docs.length === 0) {\n        return;\n      }\n\n      for (slug of docs) {\n        if (!app.docs.findBy(\"slug\", slug)) {\n          this.corruptedDocs.push(slug);\n        }\n      }\n\n      for (slug of this.corruptedDocs) {\n        $.arrayDelete(docs, slug);\n      }\n\n      if (docs.length === 0) {\n        setTimeout(() => this.deleteCorruptedDocs(), 0);\n        return;\n      }\n\n      const txn = this.idbTransaction(db, {\n        stores: docs,\n        mode: \"readonly\",\n        ignoreError: false,\n      });\n      txn.oncomplete = () => {\n        if (this.corruptedDocs.length > 0) {\n          setTimeout(() => this.deleteCorruptedDocs(), 0);\n        }\n      };\n\n      for (var doc of docs) {\n        txn.objectStore(doc).get(\"index\").onsuccess = (event) => {\n          if (!event.target.result) {\n            this.corruptedDocs.push(event.target.source.name);\n          }\n        };\n      }\n    });\n  }\n\n  deleteCorruptedDocs() {\n    this.db((db) => {\n      let doc;\n      const txn = this.idbTransaction(db, {\n        stores: [\"docs\"],\n        mode: \"readwrite\",\n        ignoreError: false,\n      });\n      const store = txn.objectStore(\"docs\");\n      while ((doc = this.corruptedDocs.pop())) {\n        this.cachedDocs[doc] = false;\n        store.delete(doc);\n      }\n    });\n    Raven.captureMessage(\"corruptedDocs\", {\n      level: \"info\",\n      extra: { docs: this.corruptedDocs.join(\",\") },\n    });\n  }\n\n  shouldLoadWithIDB(entry) {\n    return (\n      this.useIndexedDB && (!this.cachedDocs || this.cachedDocs[entry.doc.slug])\n    );\n  }\n\n  idbTransaction(db, options) {\n    app.lastIDBTransaction = [options.stores, options.mode];\n    const txn = db.transaction(options.stores, options.mode);\n    if (options.ignoreError !== false) {\n      txn.onerror = function (event) {\n        event.preventDefault();\n      };\n    }\n    if (options.ignoreAbort !== false) {\n      txn.onabort = function (event) {\n        event.preventDefault();\n      };\n    }\n    return txn;\n  }\n\n  reset() {\n    try {\n      indexedDB?.deleteDatabase(DB.NAME);\n    } catch (error) {}\n  }\n\n  useIndexedDB() {\n    try {\n      if (!app.isSingleDoc() && window.indexedDB) {\n        return true;\n      } else {\n        this.reason = \"not_supported\";\n        return false;\n      }\n    } catch (error) {\n      return false;\n    }\n  }\n\n  migrate() {\n    app.settings.set(\"schema\", this.userVersion() + 1);\n  }\n\n  setUserVersion(version) {\n    app.settings.set(\"schema\", version);\n  }\n\n  userVersion() {\n    return app.settings.get(\"schema\");\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/app/router.js",
    "content": "app.Router = class Router extends Events {\n  static routes = [\n    [\"*\", \"before\"],\n    [\"/\", \"root\"],\n    [\"/settings\", \"settings\"],\n    [\"/offline\", \"offline\"],\n    [\"/about\", \"about\"],\n    [\"/news\", \"news\"],\n    [\"/help\", \"help\"],\n    [\"/:doc-:type/\", \"type\"],\n    [\"/:doc/\", \"doc\"],\n    [\"/:doc/:path(*)\", \"entry\"],\n    [\"*\", \"notFound\"],\n  ];\n\n  constructor() {\n    super();\n    for (var [path, method] of this.constructor.routes) {\n      page(path, this[method].bind(this));\n    }\n    this.setInitialPath();\n  }\n\n  start() {\n    page.start();\n  }\n\n  show(path) {\n    page.show(path);\n  }\n\n  triggerRoute(name) {\n    this.trigger(name, this.context);\n    this.trigger(\"after\", name, this.context);\n  }\n\n  before(context, next) {\n    const previousContext = this.context;\n    this.context = context;\n    this.trigger(\"before\", context);\n\n    const res = next();\n    if (res) {\n      this.context = previousContext;\n      return res;\n    } else {\n      return;\n    }\n  }\n\n  doc(context, next) {\n    let doc;\n    if (\n      (doc =\n        app.docs.findBySlug(context.params.doc) ||\n        app.disabledDocs.findBySlug(context.params.doc))\n    ) {\n      context.doc = doc;\n      context.entry = doc.toEntry();\n      this.triggerRoute(\"entry\");\n      return;\n    } else {\n      return next();\n    }\n  }\n\n  type(context, next) {\n    const doc = app.docs.findBySlug(context.params.doc);\n    const type = doc?.types?.findBy(\"slug\", context.params.type);\n\n    if (type) {\n      context.doc = doc;\n      context.type = type;\n      this.triggerRoute(\"type\");\n      return;\n    } else {\n      return next();\n    }\n  }\n\n  entry(context, next) {\n    const doc = app.docs.findBySlug(context.params.doc);\n    if (!doc) {\n      return next();\n    }\n    let { path } = context.params;\n    const { hash } = context;\n\n    let entry = doc.findEntryByPathAndHash(path, hash);\n    if (entry) {\n      context.doc = doc;\n      context.entry = entry;\n      this.triggerRoute(\"entry\");\n      return;\n    } else if (path.slice(-6) === \"/index\") {\n      path = path.substr(0, path.length - 6);\n      entry = doc.findEntryByPathAndHash(path, hash);\n      if (entry) {\n        return entry.fullPath();\n      }\n    } else {\n      path = `${path}/index`;\n      entry = doc.findEntryByPathAndHash(path, hash);\n      if (entry) {\n        return entry.fullPath();\n      }\n    }\n\n    return next();\n  }\n\n  root() {\n    if (app.isSingleDoc()) {\n      return \"/\";\n    }\n    this.triggerRoute(\"root\");\n  }\n\n  settings(context) {\n    if (app.isSingleDoc()) {\n      return `/#/${context.path}`;\n    }\n    this.triggerRoute(\"settings\");\n  }\n\n  offline(context) {\n    if (app.isSingleDoc()) {\n      return `/#/${context.path}`;\n    }\n    this.triggerRoute(\"offline\");\n  }\n\n  about(context) {\n    if (app.isSingleDoc()) {\n      return `/#/${context.path}`;\n    }\n    context.page = \"about\";\n    this.triggerRoute(\"page\");\n  }\n\n  news(context) {\n    if (app.isSingleDoc()) {\n      return `/#/${context.path}`;\n    }\n    context.page = \"news\";\n    this.triggerRoute(\"page\");\n  }\n\n  help(context) {\n    if (app.isSingleDoc()) {\n      return `/#/${context.path}`;\n    }\n    context.page = \"help\";\n    this.triggerRoute(\"page\");\n  }\n\n  notFound(context) {\n    this.triggerRoute(\"notFound\");\n  }\n\n  isIndex() {\n    return (\n      this.context?.path === \"/\" ||\n      (app.isSingleDoc() && this.context?.entry?.isIndex())\n    );\n  }\n\n  isSettings() {\n    return this.context?.path === \"/settings\";\n  }\n\n  setInitialPath() {\n    // Remove superfluous forward slashes at the beginning of the path\n    let path = location.pathname.replace(/^\\/{2,}/g, \"/\");\n    if (path !== location.pathname) {\n      page.replace(path + location.search + location.hash, null, true);\n    }\n\n    if (location.pathname === \"/\") {\n      if ((path = this.getInitialPathFromHash())) {\n        page.replace(path + location.search, null, true);\n      } else if ((path = this.getInitialPathFromCookie())) {\n        page.replace(path + location.search + location.hash, null, true);\n      }\n    }\n  }\n\n  getInitialPathFromHash() {\n    try {\n      return new RegExp(\"#/(.+)\").exec(decodeURIComponent(location.hash))?.[1];\n    } catch (error) {}\n  }\n\n  getInitialPathFromCookie() {\n    const path = Cookies.get(\"initial_path\");\n    if (path) {\n      Cookies.expire(\"initial_path\");\n      return path;\n    }\n  }\n\n  replaceHash(hash) {\n    page.replace(\n      location.pathname + location.search + (hash || \"\"),\n      null,\n      true\n    );\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/app/searcher.js",
    "content": "//\n// Match functions\n//\n\nlet fuzzyRegexp,\n  i,\n  index,\n  lastIndex,\n  match,\n  matcher,\n  matchIndex,\n  matchLength,\n  queryLength,\n  score,\n  separators,\n  value,\n  valueLength;\nconst SEPARATOR = \".\";\n\nlet query =\n  (queryLength =\n  value =\n  valueLength =\n  matcher = // current match function\n  fuzzyRegexp = // query fuzzy regexp\n  index = // position of the query in the string being matched\n  lastIndex = // last position of the query in the string being matched\n  match = // regexp match data\n  matchIndex =\n  matchLength =\n  score = // score for the current match\n  separators = // counter\n  i =\n    null); // cursor\n\nfunction exactMatch() {\n  index = value.indexOf(query);\n  if (!(index >= 0)) {\n    return;\n  }\n\n  lastIndex = value.lastIndexOf(query);\n\n  if (index !== lastIndex) {\n    return Math.max(\n      scoreExactMatch(),\n      ((index = lastIndex) && scoreExactMatch()) || 0,\n    );\n  } else {\n    return scoreExactMatch();\n  }\n}\n\nfunction scoreExactMatch() {\n  // Remove one point for each unmatched character.\n  score = 100 - (valueLength - queryLength);\n\n  if (index > 0) {\n    // If the character preceding the query is a dot, assign the same score\n    // as if the query was found at the beginning of the string, minus one.\n    if (value.charAt(index - 1) === SEPARATOR) {\n      score += index - 1;\n      // Don't match a single-character query unless it's found at the beginning\n      // of the string or is preceded by a dot.\n    } else if (queryLength === 1) {\n      return;\n      // (1) Remove one point for each unmatched character up to the nearest\n      //     preceding dot or the beginning of the string.\n      // (2) Remove one point for each unmatched character following the query.\n    } else {\n      i = index - 2;\n      while (i >= 0 && value.charAt(i) !== SEPARATOR) {\n        i--;\n      }\n      score -=\n        index -\n        i + // (1)\n        (valueLength - queryLength - index); // (2)\n    }\n\n    // Remove one point for each dot preceding the query, except for the one\n    // immediately before the query.\n    separators = 0;\n    i = index - 2;\n    while (i >= 0) {\n      if (value.charAt(i) === SEPARATOR) {\n        separators++;\n      }\n      i--;\n    }\n    score -= separators;\n  }\n\n  // Remove five points for each dot following the query.\n  separators = 0;\n  i = valueLength - queryLength - index - 1;\n  while (i >= 0) {\n    if (value.charAt(index + queryLength + i) === SEPARATOR) {\n      separators++;\n    }\n    i--;\n  }\n  score -= separators * 5;\n\n  return Math.max(1, score);\n}\n\nfunction fuzzyMatch() {\n  if (valueLength <= queryLength || value.includes(query)) {\n    return;\n  }\n  if (!(match = fuzzyRegexp.exec(value))) {\n    return;\n  }\n  matchIndex = match.index;\n  matchLength = match[0].length;\n  score = scoreFuzzyMatch();\n  if (\n    (match = fuzzyRegexp.exec(\n      value.slice((i = value.lastIndexOf(SEPARATOR) + 1)),\n    ))\n  ) {\n    matchIndex = i + match.index;\n    matchLength = match[0].length;\n    return Math.max(score, scoreFuzzyMatch());\n  } else {\n    return score;\n  }\n}\n\nfunction scoreFuzzyMatch() {\n  // When the match is at the beginning of the string or preceded by a dot.\n  if (matchIndex === 0 || value.charAt(matchIndex - 1) === SEPARATOR) {\n    return Math.max(66, 100 - matchLength);\n    // When the match is at the end of the string.\n  } else if (matchIndex + matchLength === valueLength) {\n    return Math.max(33, 67 - matchLength);\n    // When the match is in the middle of the string.\n  } else {\n    return Math.max(1, 34 - matchLength);\n  }\n}\n\n//\n// Searchers\n//\n\napp.Searcher = class Searcher extends Events {\n  static CHUNK_SIZE = 20000;\n\n  static DEFAULTS = {\n    max_results: app.config.max_results,\n    fuzzy_min_length: 3,\n  };\n\n  static SEPARATORS_REGEXP =\n    /#|::|:-|->|\\$(?=\\w)|\\-(?=\\w)|\\:(?=\\w)|\\ [\\/\\-&]\\ |:\\ |\\ /g;\n  static EOS_SEPARATORS_REGEXP = /(\\w)[\\-:]$/;\n  static INFO_PARANTHESES_REGEXP = /\\ \\(\\w+?\\)$/;\n  static EMPTY_PARANTHESES_REGEXP = /\\(\\)/;\n  static EVENT_REGEXP = /\\ event$/;\n  static DOT_REGEXP = /\\.+/g;\n  static WHITESPACE_REGEXP = /\\s/g;\n\n  static EMPTY_STRING = \"\";\n  static ELLIPSIS = \"...\";\n  static STRING = \"string\";\n\n  static normalizeString(string) {\n    return string\n      .toLowerCase()\n      .replace(Searcher.ELLIPSIS, Searcher.EMPTY_STRING)\n      .replace(Searcher.EVENT_REGEXP, Searcher.EMPTY_STRING)\n      .replace(Searcher.INFO_PARANTHESES_REGEXP, Searcher.EMPTY_STRING)\n      .replace(Searcher.SEPARATORS_REGEXP, SEPARATOR)\n      .replace(Searcher.DOT_REGEXP, SEPARATOR)\n      .replace(Searcher.EMPTY_PARANTHESES_REGEXP, Searcher.EMPTY_STRING)\n      .replace(Searcher.WHITESPACE_REGEXP, Searcher.EMPTY_STRING);\n  }\n\n  static normalizeQuery(string) {\n    string = this.normalizeString(string);\n    return string.replace(Searcher.EOS_SEPARATORS_REGEXP, \"$1.\");\n  }\n\n  constructor(options) {\n    super();\n    this.options = { ...Searcher.DEFAULTS, ...(options || {}) };\n  }\n\n  find(data, attr, q) {\n    this.kill();\n\n    this.data = data;\n    this.attr = attr;\n    this.query = q;\n    this.setup();\n\n    if (this.isValid()) {\n      this.match();\n    } else {\n      this.end();\n    }\n  }\n\n  setup() {\n    query = this.query = this.constructor.normalizeQuery(this.query);\n    queryLength = query.length;\n    this.dataLength = this.data.length;\n    this.matchers = [exactMatch];\n    this.totalResults = 0;\n    this.setupFuzzy();\n  }\n\n  setupFuzzy() {\n    if (queryLength >= this.options.fuzzy_min_length) {\n      fuzzyRegexp = this.queryToFuzzyRegexp(query);\n      this.matchers.push(fuzzyMatch);\n    } else {\n      fuzzyRegexp = null;\n    }\n  }\n\n  isValid() {\n    return queryLength > 0 && query !== SEPARATOR;\n  }\n\n  end() {\n    if (!this.totalResults) {\n      this.triggerResults([]);\n    }\n    this.trigger(\"end\");\n    this.free();\n  }\n\n  kill() {\n    if (this.timeout) {\n      clearTimeout(this.timeout);\n      this.free();\n    }\n  }\n\n  free() {\n    this.data = null;\n    this.attr = null;\n    this.dataLength = null;\n    this.matchers = null;\n    this.matcher = null;\n    this.query = null;\n    this.totalResults = null;\n    this.scoreMap = null;\n    this.cursor = null;\n    this.timeout = null;\n  }\n\n  match() {\n    if (!this.foundEnough() && (this.matcher = this.matchers.shift())) {\n      this.setupMatcher();\n      this.matchChunks();\n    } else {\n      this.end();\n    }\n  }\n\n  setupMatcher() {\n    this.cursor = 0;\n    this.scoreMap = new Array(101);\n  }\n\n  matchChunks() {\n    this.matchChunk();\n\n    if (this.cursor === this.dataLength || this.scoredEnough()) {\n      this.delay(() => this.match());\n      this.sendResults();\n    } else {\n      this.delay(() => this.matchChunks());\n    }\n  }\n\n  matchChunk() {\n    ({ matcher } = this);\n    for (let j = 0, end = this.chunkSize(); j < end; j++) {\n      value = this.data[this.cursor][this.attr];\n      if (value.split) {\n        // string\n        valueLength = value.length;\n        if ((score = matcher())) {\n          this.addResult(this.data[this.cursor], score);\n        }\n      } else {\n        // array\n        score = 0;\n        for (value of Array.from(this.data[this.cursor][this.attr])) {\n          valueLength = value.length;\n          score = Math.max(score, matcher() || 0);\n        }\n        if (score > 0) {\n          this.addResult(this.data[this.cursor], score);\n        }\n      }\n      this.cursor++;\n    }\n  }\n\n  chunkSize() {\n    if (this.cursor + Searcher.CHUNK_SIZE > this.dataLength) {\n      return this.dataLength % Searcher.CHUNK_SIZE;\n    } else {\n      return Searcher.CHUNK_SIZE;\n    }\n  }\n\n  scoredEnough() {\n    return this.scoreMap[100]?.length >= this.options.max_results;\n  }\n\n  foundEnough() {\n    return this.totalResults >= this.options.max_results;\n  }\n\n  addResult(object, score) {\n    let name;\n    (\n      this.scoreMap[(name = Math.round(score))] || (this.scoreMap[name] = [])\n    ).push(object);\n    this.totalResults++;\n  }\n\n  getResults() {\n    const results = [];\n    for (let j = this.scoreMap.length - 1; j >= 0; j--) {\n      var objects = this.scoreMap[j];\n      if (objects) {\n        results.push(...objects);\n      }\n    }\n    return results.slice(0, this.options.max_results);\n  }\n\n  sendResults() {\n    const results = this.getResults();\n    if (results.length) {\n      this.triggerResults(results);\n    }\n  }\n\n  triggerResults(results) {\n    this.trigger(\"results\", results);\n  }\n\n  delay(fn) {\n    return (this.timeout = setTimeout(fn, 1));\n  }\n\n  queryToFuzzyRegexp(string) {\n    const chars = string.split(\"\");\n    for (i = 0; i < chars.length; i++) {\n      var char = chars[i];\n      chars[i] = $.escapeRegexp(char);\n    }\n    return new RegExp(chars.join(\".*?\")); // abc -> /a.*?b.*?c.*?/\n  }\n};\n\napp.SynchronousSearcher = class SynchronousSearcher extends app.Searcher {\n  match() {\n    if (this.matcher) {\n      if (!this.allResults) {\n        this.allResults = [];\n      }\n      this.allResults.push(...this.getResults());\n    }\n    return super.match(...arguments);\n  }\n\n  free() {\n    this.allResults = null;\n    return super.free(...arguments);\n  }\n\n  end() {\n    this.sendResults(true);\n    return super.end(...arguments);\n  }\n\n  sendResults(end) {\n    if (end && this.allResults?.length) {\n      return this.triggerResults(this.allResults);\n    }\n  }\n\n  delay(fn) {\n    return fn();\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/app/serviceworker.js",
    "content": "app.ServiceWorker = class ServiceWorker extends Events {\n  static isEnabled() {\n    return !!navigator.serviceWorker && app.config.service_worker_enabled;\n  }\n\n  constructor() {\n    super();\n    this.onStateChange = this.onStateChange.bind(this);\n    this.registration = null;\n    this.notifyUpdate = true;\n\n    navigator.serviceWorker\n      .register(app.config.service_worker_path, { scope: \"/\" })\n      .then(\n        (registration) => this.updateRegistration(registration),\n        (error) => console.error(\"Could not register service worker:\", error),\n      );\n  }\n\n  update() {\n    if (!this.registration) {\n      return;\n    }\n    this.notifyUpdate = true;\n    return this.registration.update().catch(() => {});\n  }\n\n  updateInBackground() {\n    if (!this.registration) {\n      return;\n    }\n    this.notifyUpdate = false;\n    return this.registration.update().catch(() => {});\n  }\n\n  reload() {\n    return this.updateInBackground().then(() => app.reboot());\n  }\n\n  updateRegistration(registration) {\n    this.registration = registration;\n    $.on(this.registration, \"updatefound\", () => this.onUpdateFound());\n  }\n\n  onUpdateFound() {\n    if (this.installingRegistration) {\n      $.off(this.installingRegistration, \"statechange\", this.onStateChange);\n    }\n    this.installingRegistration = this.registration.installing;\n    $.on(this.installingRegistration, \"statechange\", this.onStateChange);\n  }\n\n  onStateChange() {\n    if (\n      this.installingRegistration &&\n      this.installingRegistration.state === \"installed\" &&\n      navigator.serviceWorker.controller\n    ) {\n      this.installingRegistration = null;\n      this.onUpdateReady();\n    }\n  }\n\n  onUpdateReady() {\n    if (this.notifyUpdate) {\n      this.trigger(\"updateready\");\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/app/settings.js",
    "content": "app.Settings = class Settings {\n  static PREFERENCE_KEYS = [\n    \"hideDisabled\",\n    \"hideIntro\",\n    \"manualUpdate\",\n    \"fastScroll\",\n    \"arrowScroll\",\n    \"analyticsConsent\",\n    \"docs\",\n    \"dark\", // legacy\n    \"theme\",\n    \"layout\",\n    \"size\",\n    \"tips\",\n    \"noAutofocus\",\n    \"autoInstall\",\n    \"spaceScroll\",\n    \"spaceTimeout\",\n    \"noDocSpecificIcon\",\n  ];\n\n  static INTERNAL_KEYS = [\"count\", \"schema\", \"version\", \"news\"];\n\n  static LAYOUTS = [\n    \"_max-width\",\n    \"_sidebar-hidden\",\n    \"_native-scrollbars\",\n    \"_text-justify-hyphenate\",\n  ];\n\n  static defaults = {\n    count: 0,\n    hideDisabled: false,\n    hideIntro: false,\n    news: 0,\n    manualUpdate: false,\n    schema: 1,\n    analyticsConsent: false,\n    theme: \"auto\",\n    spaceScroll: 1,\n    spaceTimeout: 0.5,\n    noDocSpecificIcon: false,\n  };\n\n  constructor() {\n    this.store = new CookiesStore();\n    this.cache = {};\n    this.autoSupported =\n      window.matchMedia(\"(prefers-color-scheme)\").media !== \"not all\";\n    if (this.autoSupported) {\n      this.darkModeQuery = window.matchMedia(\"(prefers-color-scheme: dark)\");\n      this.darkModeQuery.addListener(() => this.setTheme(this.get(\"theme\")));\n    }\n  }\n\n  get(key) {\n    let left;\n    if (this.cache.hasOwnProperty(key)) {\n      return this.cache[key];\n    }\n    this.cache[key] =\n      (left = this.store.get(key)) != null\n        ? left\n        : this.constructor.defaults[key];\n    if (key === \"theme\" && this.cache[key] === \"auto\" && !this.darkModeQuery) {\n      return (this.cache[key] = \"default\");\n    } else {\n      return this.cache[key];\n    }\n  }\n\n  set(key, value) {\n    this.store.set(key, value);\n    delete this.cache[key];\n    if (key === \"theme\") {\n      this.setTheme(value);\n    }\n  }\n\n  del(key) {\n    this.store.del(key);\n    delete this.cache[key];\n  }\n\n  hasDocs() {\n    try {\n      return !!this.store.get(\"docs\");\n    } catch (error) {}\n  }\n\n  getDocs() {\n    return this.store.get(\"docs\")?.split(\"/\") || app.config.default_docs;\n  }\n\n  setDocs(docs) {\n    this.set(\"docs\", docs.join(\"/\"));\n  }\n\n  getTips() {\n    return this.store.get(\"tips\")?.split(\"/\") || [];\n  }\n\n  setTips(tips) {\n    this.set(\"tips\", tips.join(\"/\"));\n  }\n\n  setLayout(name, enable) {\n    this.toggleLayout(name, enable);\n\n    const layout = (this.store.get(\"layout\") || \"\").split(\" \");\n    $.arrayDelete(layout, \"\");\n\n    if (enable) {\n      if (!layout.includes(name)) {\n        layout.push(name);\n      }\n    } else {\n      $.arrayDelete(layout, name);\n    }\n\n    if (layout.length > 0) {\n      this.set(\"layout\", layout.join(\" \"));\n    } else {\n      this.del(\"layout\");\n    }\n  }\n\n  hasLayout(name) {\n    const layout = (this.store.get(\"layout\") || \"\").split(\" \");\n    return layout.includes(name);\n  }\n\n  setSize(value) {\n    this.set(\"size\", value);\n  }\n\n  dump() {\n    return this.store.dump();\n  }\n\n  export() {\n    const data = this.dump();\n    for (var key of Settings.INTERNAL_KEYS) {\n      delete data[key];\n    }\n    return data;\n  }\n\n  import(data) {\n    let key, value;\n    const object = this.export();\n    for (key in object) {\n      value = object[key];\n      if (!data.hasOwnProperty(key)) {\n        this.del(key);\n      }\n    }\n    for (key in data) {\n      value = data[key];\n      if (Settings.PREFERENCE_KEYS.includes(key)) {\n        this.set(key, value);\n      }\n    }\n  }\n\n  reset() {\n    this.store.reset();\n    this.cache = {};\n  }\n\n  initLayout() {\n    if (this.get(\"dark\") === 1) {\n      this.set(\"theme\", \"dark\");\n      this.del(\"dark\");\n    }\n    this.setTheme(this.get(\"theme\"));\n    for (var layout of app.Settings.LAYOUTS) {\n      this.toggleLayout(layout, this.hasLayout(layout));\n    }\n    this.initSidebarWidth();\n  }\n\n  setTheme(theme) {\n    if (theme === \"auto\") {\n      theme = this.darkModeQuery.matches ? \"dark\" : \"default\";\n    }\n    const { classList } = document.documentElement;\n    classList.remove(\"_theme-default\", \"_theme-dark\");\n    classList.add(\"_theme-\" + theme);\n    this.updateColorMeta();\n  }\n\n  updateColorMeta() {\n    const color = getComputedStyle(document.documentElement)\n      .getPropertyValue(\"--headerBackground\")\n      .trim();\n    $(\"meta[name=theme-color]\").setAttribute(\"content\", color);\n  }\n\n  toggleLayout(layout, enable) {\n    const { classList } = document.body;\n    // sidebar is always shown for settings; its state is updated in app.views.Settings\n    if (layout !== \"_sidebar-hidden\" || !app.router?.isSettings) {\n      classList.toggle(layout, enable);\n    }\n    classList.toggle(\"_overlay-scrollbars\", $.overlayScrollbarsEnabled());\n  }\n\n  initSidebarWidth() {\n    const size = this.get(\"size\");\n    if (size) {\n      document.documentElement.style.setProperty(\"--sidebarWidth\", size + \"px\");\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/app/shortcuts.js",
    "content": "app.Shortcuts = class Shortcuts extends Events {\n  constructor() {\n    super();\n    this.onKeydown = this.onKeydown.bind(this);\n    this.onKeypress = this.onKeypress.bind(this);\n    this.isMac = $.isMac();\n    this.start();\n  }\n\n  start() {\n    $.on(document, \"keydown\", this.onKeydown);\n    $.on(document, \"keypress\", this.onKeypress);\n  }\n\n  stop() {\n    $.off(document, \"keydown\", this.onKeydown);\n    $.off(document, \"keypress\", this.onKeypress);\n  }\n\n  swapArrowKeysBehavior() {\n    return app.settings.get(\"arrowScroll\");\n  }\n\n  spaceScroll() {\n    return app.settings.get(\"spaceScroll\");\n  }\n\n  showTip() {\n    app.showTip(\"KeyNav\");\n    return (this.showTip = null);\n  }\n\n  spaceTimeout() {\n    return app.settings.get(\"spaceTimeout\");\n  }\n\n  onKeydown(event) {\n    if (this.buggyEvent(event)) {\n      return;\n    }\n    const result = (() => {\n      if (event.ctrlKey || event.metaKey) {\n        if (!event.altKey && !event.shiftKey) {\n          return this.handleKeydownSuperEvent(event);\n        }\n      } else if (event.shiftKey) {\n        if (!event.altKey) {\n          return this.handleKeydownShiftEvent(event);\n        }\n      } else if (event.altKey) {\n        return this.handleKeydownAltEvent(event);\n      } else {\n        return this.handleKeydownEvent(event);\n      }\n    })();\n\n    if (result === false) {\n      event.preventDefault();\n    }\n  }\n\n  onKeypress(event) {\n    if (\n      this.buggyEvent(event) ||\n      (event.charCode === 63 && document.activeElement.tagName === \"INPUT\")\n    ) {\n      return;\n    }\n    if (!event.ctrlKey && !event.metaKey) {\n      const result = this.handleKeypressEvent(event);\n      if (result === false) {\n        event.preventDefault();\n      }\n    }\n  }\n\n  handleKeydownEvent(event, _force) {\n    if (\n      !_force &&\n      [37, 38, 39, 40].includes(event.which) &&\n      this.swapArrowKeysBehavior()\n    ) {\n      return this.handleKeydownAltEvent(event, true);\n    }\n\n    if (\n      !event.target.form &&\n      ((48 <= event.which && event.which <= 57) ||\n        (65 <= event.which && event.which <= 90))\n    ) {\n      this.trigger(\"typing\");\n      return;\n    }\n\n    switch (event.which) {\n      case 8:\n        if (!event.target.form) {\n          return this.trigger(\"typing\");\n        }\n        break;\n      case 13:\n        return this.trigger(\"enter\");\n      case 27:\n        this.trigger(\"escape\");\n        return false;\n      case 32:\n        if (\n          event.target.type === \"search\" &&\n          this.spaceScroll() &&\n          (!this.lastKeypress ||\n            this.lastKeypress < Date.now() - this.spaceTimeout() * 1000)\n        ) {\n          this.trigger(\"pageDown\");\n          return false;\n        }\n        break;\n      case 33:\n        return this.trigger(\"pageUp\");\n      case 34:\n        return this.trigger(\"pageDown\");\n      case 35:\n        if (!event.target.form) {\n          return this.trigger(\"pageBottom\");\n        }\n        break;\n      case 36:\n        if (!event.target.form) {\n          return this.trigger(\"pageTop\");\n        }\n        break;\n      case 37:\n        if (!event.target.value) {\n          return this.trigger(\"left\");\n        }\n        break;\n      case 38:\n        this.trigger(\"up\");\n        if (typeof this.showTip === \"function\") {\n          this.showTip();\n        }\n        return false;\n      case 39:\n        if (!event.target.value) {\n          return this.trigger(\"right\");\n        }\n        break;\n      case 40:\n        this.trigger(\"down\");\n        if (typeof this.showTip === \"function\") {\n          this.showTip();\n        }\n        return false;\n      case 191:\n        if (!event.target.form) {\n          this.trigger(\"typing\");\n          return false;\n        }\n        break;\n    }\n  }\n\n  handleKeydownSuperEvent(event) {\n    switch (event.which) {\n      case 13:\n        return this.trigger(\"superEnter\");\n      case 37:\n        if (this.isMac) {\n          this.trigger(\"superLeft\");\n          return false;\n        }\n        break;\n      case 38:\n        this.trigger(\"pageTop\");\n        return false;\n      case 39:\n        if (this.isMac) {\n          this.trigger(\"superRight\");\n          return false;\n        }\n        break;\n      case 40:\n        this.trigger(\"pageBottom\");\n        return false;\n      case 188:\n        this.trigger(\"preferences\");\n        return false;\n    }\n  }\n\n  handleKeydownShiftEvent(event, _force) {\n    if (\n      !_force &&\n      [37, 38, 39, 40].includes(event.which) &&\n      this.swapArrowKeysBehavior()\n    ) {\n      return this.handleKeydownEvent(event, true);\n    }\n\n    if (!event.target.form && 65 <= event.which && event.which <= 90) {\n      this.trigger(\"typing\");\n      return;\n    }\n\n    switch (event.which) {\n      case 32:\n        this.trigger(\"pageUp\");\n        return false;\n      case 38:\n        if (!getSelection()?.toString()) {\n          this.trigger(\"altUp\");\n          return false;\n        }\n        break;\n      case 40:\n        if (!getSelection()?.toString()) {\n          this.trigger(\"altDown\");\n          return false;\n        }\n        break;\n    }\n  }\n\n  handleKeydownAltEvent(event, _force) {\n    if (\n      !_force &&\n      [37, 38, 39, 40].includes(event.which) &&\n      this.swapArrowKeysBehavior()\n    ) {\n      return this.handleKeydownEvent(event, true);\n    }\n\n    switch (event.which) {\n      case 9:\n        return this.trigger(\"altRight\", event);\n      case 37:\n        if (!this.isMac) {\n          this.trigger(\"superLeft\");\n          return false;\n        }\n        break;\n      case 38:\n        this.trigger(\"altUp\");\n        return false;\n      case 39:\n        if (!this.isMac) {\n          this.trigger(\"superRight\");\n          return false;\n        }\n        break;\n      case 40:\n        this.trigger(\"altDown\");\n        return false;\n      case 67:\n        this.trigger(\"altC\");\n        return false;\n      case 68:\n        this.trigger(\"altD\");\n        return false;\n      case 70:\n        return this.trigger(\"altF\", event);\n      case 71:\n        this.trigger(\"altG\");\n        return false;\n      case 79:\n        this.trigger(\"altO\");\n        return false;\n      case 82:\n        this.trigger(\"altR\");\n        return false;\n      case 83:\n        this.trigger(\"altS\");\n        return false;\n    }\n  }\n\n  handleKeypressEvent(event) {\n    if (event.which === 63 && !event.target.value) {\n      this.trigger(\"help\");\n      return false;\n    } else {\n      return (this.lastKeypress = Date.now());\n    }\n  }\n\n  buggyEvent(event) {\n    try {\n      event.target;\n      event.ctrlKey;\n      event.which;\n      return false;\n    } catch (error) {\n      return true;\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/app/update_checker.js",
    "content": "app.UpdateChecker = class UpdateChecker {\n  constructor() {\n    this.lastCheck = Date.now();\n\n    $.on(window, \"focus\", () => this.onFocus());\n    if (app.serviceWorker) {\n      app.serviceWorker.on(\"updateready\", () => this.onUpdateReady());\n    }\n\n    setTimeout(() => this.checkDocs(), 0);\n  }\n\n  check() {\n    if (app.serviceWorker) {\n      app.serviceWorker.update();\n    } else {\n      ajax({\n        url: $('script[src*=\"application\"]').getAttribute(\"src\"),\n        dataType: \"application/javascript\",\n        error: (_, xhr) => {\n          if (xhr.status === 404) {\n            return this.onUpdateReady();\n          }\n        },\n      });\n    }\n  }\n\n  onUpdateReady() {\n    new app.views.Notif(\"UpdateReady\", { autoHide: null });\n  }\n\n  checkDocs() {\n    if (!app.settings.get(\"manualUpdate\")) {\n      app.docs.updateInBackground();\n    } else {\n      app.docs.checkForUpdates((i) => {\n        if (i > 0) {\n          return this.onDocsUpdateReady();\n        }\n      });\n    }\n  }\n\n  onDocsUpdateReady() {\n    new app.views.Notif(\"UpdateDocs\", { autoHide: null });\n  }\n\n  onFocus() {\n    if (Date.now() - this.lastCheck > 21600e3) {\n      this.lastCheck = Date.now();\n      this.check();\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/application.js",
    "content": "//= require_tree ./vendor\n\n//= require lib/license\n//= require_tree ./lib\n\n//= require app/app\n//= require app/config\n//= require_tree ./app\n\n//= require collections/collection\n//= require_tree ./collections\n\n//= require models/model\n//= require_tree ./models\n\n//= require views/view\n//= require_tree ./views\n\n//= require_tree ./templates\n\n//= link_tree ../images/sprites\n\n//= require tracking\n\nvar init = function () {\n  document.removeEventListener(\"DOMContentLoaded\", init, false);\n\n  if (document.body) {\n    return app.init();\n  } else {\n    return setTimeout(init, 42);\n  }\n};\n\ndocument.addEventListener(\"DOMContentLoaded\", init, false);\n"
  },
  {
    "path": "assets/javascripts/collections/collection.js",
    "content": "app.Collection = class Collection {\n  constructor(objects) {\n    if (objects == null) {\n      objects = [];\n    }\n    this.reset(objects);\n  }\n\n  model() {\n    return app.models[this.constructor.model];\n  }\n\n  reset(objects) {\n    if (objects == null) {\n      objects = [];\n    }\n    this.models = [];\n    for (var object of objects) {\n      this.add(object);\n    }\n  }\n\n  add(object) {\n    if (object instanceof app.Model) {\n      this.models.push(object);\n    } else if (object instanceof Array) {\n      for (var obj of object) {\n        this.add(obj);\n      }\n    } else if (object instanceof app.Collection) {\n      this.models.push(...(object.all() || []));\n    } else {\n      this.models.push(new (this.model())(object));\n    }\n  }\n\n  remove(model) {\n    this.models.splice(this.models.indexOf(model), 1);\n  }\n\n  size() {\n    return this.models.length;\n  }\n\n  isEmpty() {\n    return this.models.length === 0;\n  }\n\n  each(fn) {\n    for (var model of this.models) {\n      fn(model);\n    }\n  }\n\n  all() {\n    return this.models;\n  }\n\n  contains(model) {\n    return this.models.includes(model);\n  }\n\n  findBy(attr, value) {\n    return this.models.find((model) => model[attr] === value);\n  }\n\n  findAllBy(attr, value) {\n    return this.models.filter((model) => model[attr] === value);\n  }\n\n  countAllBy(attr, value) {\n    let i = 0;\n    for (var model of this.models) {\n      if (model[attr] === value) {\n        i += 1;\n      }\n    }\n    return i;\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/collections/docs.js",
    "content": "app.collections.Docs = class Docs extends app.Collection {\n  static model = \"Doc\";\n  static NORMALIZE_VERSION_RGX = /\\.(\\d)$/;\n  static NORMALIZE_VERSION_SUB = \".0$1\";\n\n  // Load models concurrently.\n  // It's not pretty but I didn't want to import a promise library only for this.\n  static CONCURRENCY = 3;\n\n  findBySlug(slug) {\n    return (\n      this.findBy(\"slug\", slug) || this.findBy(\"slug_without_version\", slug)\n    );\n  }\n  sort() {\n    return this.models.sort((a, b) => {\n      if (a.name === b.name) {\n        if (\n          !a.version ||\n          a.version.replace(\n            Docs.NORMALIZE_VERSION_RGX,\n            Docs.NORMALIZE_VERSION_SUB,\n          ) >\n            b.version.replace(\n              Docs.NORMALIZE_VERSION_RGX,\n              Docs.NORMALIZE_VERSION_SUB,\n            )\n        ) {\n          return -1;\n        } else {\n          return 1;\n        }\n      } else if (a.name.toLowerCase() > b.name.toLowerCase()) {\n        return 1;\n      } else {\n        return -1;\n      }\n    });\n  }\n  load(onComplete, onError, options) {\n    let i = 0;\n\n    var next = () => {\n      if (i < this.models.length) {\n        this.models[i].load(next, fail, options);\n      } else if (i === this.models.length + Docs.CONCURRENCY - 1) {\n        onComplete();\n      }\n      i++;\n    };\n\n    var fail = function (...args) {\n      if (onError) {\n        onError(args);\n        onError = null;\n      }\n      next();\n    };\n\n    for (let j = 0, end = Docs.CONCURRENCY; j < end; j++) {\n      next();\n    }\n  }\n\n  clearCache() {\n    for (var doc of this.models) {\n      doc.clearCache();\n    }\n  }\n\n  uninstall(callback) {\n    let i = 0;\n    var next = () => {\n      if (i < this.models.length) {\n        this.models[i++].uninstall(next, next);\n      } else {\n        callback();\n      }\n    };\n    next();\n  }\n\n  getInstallStatuses(callback) {\n    app.db.versions(this.models, (statuses) => {\n      if (statuses) {\n        for (var key in statuses) {\n          var value = statuses[key];\n          statuses[key] = { installed: !!value, mtime: value };\n        }\n      }\n      callback(statuses);\n    });\n  }\n\n  checkForUpdates(callback) {\n    this.getInstallStatuses((statuses) => {\n      let i = 0;\n      if (statuses) {\n        for (var slug in statuses) {\n          var status = statuses[slug];\n          if (this.findBy(\"slug\", slug).isOutdated(status)) {\n            i += 1;\n          }\n        }\n      }\n      callback(i);\n    });\n  }\n\n  updateInBackground() {\n    this.getInstallStatuses((statuses) => {\n      if (!statuses) {\n        return;\n      }\n      for (var slug in statuses) {\n        var status = statuses[slug];\n        var doc = this.findBy(\"slug\", slug);\n        if (doc.isOutdated(status)) {\n          doc.install($.noop, $.noop);\n        }\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/collections/entries.js",
    "content": "app.collections.Entries = class Entries extends app.Collection {\n  static model = \"Entry\";\n};\n"
  },
  {
    "path": "assets/javascripts/collections/types.js",
    "content": "app.collections.Types = class Types extends app.Collection {\n  static model = \"Type\";\n  static GUIDES_RGX =\n    /(^|\\()(guides?|tutorials?|reference|book|getting\\ started|manual|examples)($|[\\):])/i;\n  static APPENDIX_RGX = /appendix/i;\n\n  groups() {\n    const result = [];\n    for (var type of this.models) {\n      const name = this._groupFor(type);\n      result[name] ||= [];\n      result[name].push(type);\n    }\n    return result.filter((e) => e.length > 0);\n  }\n\n  _groupFor(type) {\n    if (Types.GUIDES_RGX.test(type.name)) {\n      return 0;\n    } else if (Types.APPENDIX_RGX.test(type.name)) {\n      return 2;\n    } else {\n      return 1;\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/debug.js",
    "content": "//\n// App\n//\n\nconst _init = app.init;\napp.init = function () {\n  console.time(\"Init\");\n  _init.call(app);\n  console.timeEnd(\"Init\");\n  return console.time(\"Load\");\n};\n\nconst _start = app.start;\napp.start = function () {\n  console.timeEnd(\"Load\");\n  console.time(\"Start\");\n  _start.call(app, ...arguments);\n  return console.timeEnd(\"Start\");\n};\n\n//\n// Searcher\n//\n\napp.Searcher = class TimingSearcher extends app.Searcher {\n  setup() {\n    console.groupCollapsed(`Search: ${this.query}`);\n    console.time(\"Total\");\n    return super.setup();\n  }\n\n  match() {\n    if (this.matcher) {\n      console.timeEnd(this.matcher.name);\n    }\n    return super.match();\n  }\n\n  setupMatcher() {\n    console.time(this.matcher.name);\n    return super.setupMatcher();\n  }\n\n  end() {\n    console.log(`Results: ${this.totalResults}`);\n    console.timeEnd(\"Total\");\n    console.groupEnd();\n    return super.end();\n  }\n\n  kill() {\n    if (this.timeout) {\n      if (this.matcher) {\n        console.timeEnd(this.matcher.name);\n      }\n      console.groupEnd();\n      console.timeEnd(\"Total\");\n      console.warn(\"Killed\");\n    }\n    return super.kill();\n  }\n};\n\n//\n// View tree\n//\n\nthis.viewTree = function (view, level, visited) {\n  if (view == null) {\n    view = app.document;\n  }\n  if (level == null) {\n    level = 0;\n  }\n  if (visited == null) {\n    visited = [];\n  }\n  if (visited.includes(view)) {\n    return;\n  }\n  visited.push(view);\n\n  console.log(\n    `%c ${Array(level + 1).join(\"  \")}${\n      view.constructor.name\n    }: ${!!view.activated}`,\n    \"color:\" + ((view.activated && \"green\") || \"red\"),\n  );\n\n  for (var key of Object.keys(view || {})) {\n    var value = view[key];\n    if (key !== \"view\" && value) {\n      if (typeof value === \"object\" && value.setupElement) {\n        this.viewTree(value, level + 1, visited);\n      } else if (value.constructor.toString().match(/Object\\(\\)/)) {\n        for (var k of Object.keys(value || {})) {\n          var v = value[k];\n          if (v && typeof v === \"object\" && v.setupElement) {\n            this.viewTree(v, level + 1, visited);\n          }\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/docs.js.erb",
    "content": "//= depend_on docs.json\napp.DOCS = <%= File.read App.docs_manifest_path %>;\n"
  },
  {
    "path": "assets/javascripts/lib/ajax.js",
    "content": "const MIME_TYPES = {\n  json: \"application/json\",\n  html: \"text/html\",\n};\n\nfunction ajax(options) {\n  applyDefaults(options);\n  serializeData(options);\n\n  const xhr = new XMLHttpRequest();\n  xhr.open(options.type, options.url, options.async);\n\n  applyCallbacks(xhr, options);\n  applyHeaders(xhr, options);\n\n  xhr.send(options.data);\n\n  if (options.async) {\n    return { abort: abort.bind(undefined, xhr) };\n  } else {\n    return parseResponse(xhr, options);\n  }\n\n  function applyDefaults(options) {\n    for (var key in ajax.defaults) {\n      if (options[key] == null) {\n        options[key] = ajax.defaults[key];\n      }\n    }\n  }\n\n  function serializeData(options) {\n    if (!options.data) {\n      return;\n    }\n\n    if (options.type === \"GET\") {\n      options.url += \"?\" + serializeParams(options.data);\n      options.data = null;\n    } else {\n      options.data = serializeParams(options.data);\n    }\n  }\n\n  function serializeParams(params) {\n    return Object.entries(params)\n      .map(\n        ([key, value]) =>\n          `${encodeURIComponent(key)}=${encodeURIComponent(value)}`,\n      )\n      .join(\"&\");\n  }\n\n  function applyCallbacks(xhr, options) {\n    if (!options.async) {\n      return;\n    }\n\n    xhr.timer = setTimeout(\n      onTimeout.bind(undefined, xhr, options),\n      options.timeout * 1000,\n    );\n    if (options.progress) {\n      xhr.onprogress = options.progress;\n    }\n    xhr.onreadystatechange = function () {\n      if (xhr.readyState === 4) {\n        clearTimeout(xhr.timer);\n        onComplete(xhr, options);\n      }\n    };\n  }\n\n  function applyHeaders(xhr, options) {\n    if (!options.headers) {\n      options.headers = {};\n    }\n\n    if (options.contentType) {\n      options.headers[\"Content-Type\"] = options.contentType;\n    }\n\n    if (\n      !options.headers[\"Content-Type\"] &&\n      options.data &&\n      options.type !== \"GET\"\n    ) {\n      options.headers[\"Content-Type\"] = \"application/x-www-form-urlencoded\";\n    }\n\n    if (options.dataType) {\n      options.headers[\"Accept\"] =\n        MIME_TYPES[options.dataType] || options.dataType;\n    }\n\n    for (var key in options.headers) {\n      var value = options.headers[key];\n      xhr.setRequestHeader(key, value);\n    }\n  }\n\n  function onComplete(xhr, options) {\n    if (200 <= xhr.status && xhr.status < 300) {\n      const response = parseResponse(xhr, options);\n      if (response != null) {\n        onSuccess(response, xhr, options);\n      } else {\n        onError(\"invalid\", xhr, options);\n      }\n    } else {\n      onError(\"error\", xhr, options);\n    }\n  }\n\n  function onSuccess(response, xhr, options) {\n    if (options.success != null) {\n      options.success.call(options.context, response, xhr, options);\n    }\n  }\n\n  function onError(type, xhr, options) {\n    if (options.error != null) {\n      options.error.call(options.context, type, xhr, options);\n    }\n  }\n\n  function onTimeout(xhr, options) {\n    xhr.abort();\n    onError(\"timeout\", xhr, options);\n  }\n\n  function abort(xhr) {\n    clearTimeout(xhr.timer);\n    xhr.onreadystatechange = null;\n    xhr.abort();\n  }\n\n  function parseResponse(xhr, options) {\n    if (options.dataType === \"json\") {\n      return parseJSON(xhr.responseText);\n    } else {\n      return xhr.responseText;\n    }\n  }\n\n  function parseJSON(json) {\n    try {\n      return JSON.parse(json);\n    } catch (error) {}\n  }\n}\n\najax.defaults = {\n  async: true,\n  dataType: \"json\",\n  timeout: 30,\n  type: \"GET\",\n  // contentType\n  // context\n  // data\n  // error\n  // headers\n  // progress\n  // success\n  // url\n};\n"
  },
  {
    "path": "assets/javascripts/lib/cookies_store.js",
    "content": "// Intentionally called CookiesStore instead of CookieStore\n// Calling it CookieStore causes issues when the Experimental Web Platform features flag is enabled in Chrome\n// Related issue: https://github.com/freeCodeCamp/devdocs/issues/932\nclass CookiesStore {\n  static INT = /^\\d+$/;\n\n  static onBlocked() {}\n\n  get(key) {\n    let value = Cookies.get(key);\n    if (value != null && CookiesStore.INT.test(value)) {\n      value = parseInt(value, 10);\n    }\n    return value;\n  }\n\n  set(key, value) {\n    if (value === false) {\n      this.del(key);\n      return;\n    }\n\n    if (value === true) {\n      value = 1;\n    }\n    if (\n      value &&\n      (typeof CookiesStore.INT.test === \"function\"\n        ? CookiesStore.INT.test(value)\n        : undefined)\n    ) {\n      value = parseInt(value, 10);\n    }\n    Cookies.set(key, \"\" + value, { path: \"/\", expires: 1e8 });\n    if (this.get(key) !== value) {\n      CookiesStore.onBlocked(key, value, this.get(key));\n    }\n  }\n\n  del(key) {\n    Cookies.expire(key);\n  }\n\n  reset() {\n    try {\n      for (var cookie of document.cookie.split(/;\\s?/)) {\n        Cookies.expire(cookie.split(\"=\")[0]);\n      }\n      return;\n    } catch (error) {}\n  }\n\n  dump() {\n    const result = {};\n    for (var cookie of document.cookie.split(/;\\s?/)) {\n      if (cookie[0] !== \"_\") {\n        cookie = cookie.split(\"=\");\n        result[cookie[0]] = cookie[1];\n      }\n    }\n    return result;\n  }\n}\n"
  },
  {
    "path": "assets/javascripts/lib/events.js",
    "content": "class Events {\n  on(event, callback) {\n    if (event.includes(\" \")) {\n      for (var name of event.split(\" \")) {\n        this.on(name, callback);\n      }\n    } else {\n      this._callbacks ||= {};\n      this._callbacks[event] ||= [];\n      this._callbacks[event].push(callback);\n    }\n    return this;\n  }\n\n  off(event, callback) {\n    let callbacks, index;\n    if (event.includes(\" \")) {\n      for (var name of event.split(\" \")) {\n        this.off(name, callback);\n      }\n    } else if (\n      (callbacks = this._callbacks?.[event]) &&\n      (index = callbacks.indexOf(callback)) >= 0\n    ) {\n      callbacks.splice(index, 1);\n      if (!callbacks.length) {\n        delete this._callbacks[event];\n      }\n    }\n    return this;\n  }\n\n  trigger(event, ...args) {\n    this.eventInProgress = { name: event, args };\n    const callbacks = this._callbacks?.[event];\n    if (callbacks) {\n      for (const callback of callbacks.slice(0)) {\n        if (typeof callback === \"function\") {\n          callback(...args);\n        }\n      }\n    }\n    this.eventInProgress = null;\n    if (event !== \"all\") {\n      this.trigger(\"all\", event, ...args);\n    }\n    return this;\n  }\n\n  removeEvent(event) {\n    if (this._callbacks != null) {\n      for (var name of event.split(\" \")) {\n        delete this._callbacks[name];\n      }\n    }\n    return this;\n  }\n}\n"
  },
  {
    "path": "assets/javascripts/lib/favicon.js",
    "content": "let defaultUrl = null;\nlet currentSlug = null;\n\nconst imageCache = {};\nconst urlCache = {};\n\nconst withImage = function (url, action) {\n  if (imageCache[url]) {\n    return action(imageCache[url]);\n  } else {\n    const img = new Image();\n    img.crossOrigin = \"anonymous\";\n    img.src = url;\n    return (img.onload = () => {\n      imageCache[url] = img;\n      return action(img);\n    });\n  }\n};\n\nthis.setFaviconForDoc = function (doc) {\n  if (currentSlug === doc.slug || app.settings.get(\"noDocSpecificIcon\")) {\n    return;\n  }\n\n  const favicon = $('link[rel=\"icon\"]');\n\n  if (defaultUrl === null) {\n    defaultUrl = favicon.href;\n  }\n\n  if (urlCache[doc.slug]) {\n    favicon.href = urlCache[doc.slug];\n    currentSlug = doc.slug;\n    return;\n  }\n\n  const iconEl = $(`._icon-${doc.slug.split(\"~\")[0]}`);\n  if (iconEl === null) {\n    return;\n  }\n\n  const styles = window.getComputedStyle(iconEl, \":before\");\n\n  const backgroundPositionX = styles[\"background-position-x\"];\n  const backgroundPositionY = styles[\"background-position-y\"];\n  if (backgroundPositionX === undefined || backgroundPositionY === undefined) {\n    return;\n  }\n\n  const bgUrl = app.config.favicon_spritesheet;\n  const sourceSize = 16;\n  const sourceX = Math.abs(parseInt(backgroundPositionX.slice(0, -2)));\n  const sourceY = Math.abs(parseInt(backgroundPositionY.slice(0, -2)));\n\n  return withImage(bgUrl, (docImg) =>\n    withImage(defaultUrl, function (defaultImg) {\n      const size = defaultImg.width;\n\n      const canvas = document.createElement(\"canvas\");\n      const ctx = canvas.getContext(\"2d\");\n\n      canvas.width = size;\n      canvas.height = size;\n      ctx.drawImage(defaultImg, 0, 0);\n\n      const docIconPercentage = 65;\n      const destinationCoords = (size / 100) * (100 - docIconPercentage);\n      const destinationSize = (size / 100) * docIconPercentage;\n\n      ctx.drawImage(\n        docImg,\n        sourceX,\n        sourceY,\n        sourceSize,\n        sourceSize,\n        destinationCoords,\n        destinationCoords,\n        destinationSize,\n        destinationSize,\n      );\n\n      try {\n        urlCache[doc.slug] = canvas.toDataURL();\n        favicon.href = urlCache[doc.slug];\n\n        return (currentSlug = doc.slug);\n      } catch (error) {\n        Raven.captureException(error, { level: \"info\" });\n        return this.resetFavicon();\n      }\n    }),\n  );\n};\n\nthis.resetFavicon = function () {\n  if (defaultUrl !== null && currentSlug !== null) {\n    $('link[rel=\"icon\"]').href = defaultUrl;\n    return (currentSlug = null);\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/lib/license.js",
    "content": "/*\n * Copyright 2013-2026 Thibaut Courouble and other contributors\n *\n * This source code is licensed under the terms of the Mozilla\n * Public License, v. 2.0, a copy of which may be obtained at:\n * http://mozilla.org/MPL/2.0/\n */\n"
  },
  {
    "path": "assets/javascripts/lib/local_storage_store.js",
    "content": "this.LocalStorageStore = class LocalStorageStore {\n  get(key) {\n    try {\n      return JSON.parse(localStorage.getItem(key));\n    } catch (error) {}\n  }\n\n  set(key, value) {\n    try {\n      localStorage.setItem(key, JSON.stringify(value));\n      return true;\n    } catch (error) {}\n  }\n\n  del(key) {\n    try {\n      localStorage.removeItem(key);\n      return true;\n    } catch (error) {}\n  }\n\n  reset() {\n    try {\n      localStorage.clear();\n      return true;\n    } catch (error) {}\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/lib/page.js",
    "content": "/*\n * Based on github.com/visionmedia/page.js\n * Licensed under the MIT license\n * Copyright 2012 TJ Holowaychuk <tj@vision-media.ca>\n */\n\nlet running = false;\nlet currentState = null;\nconst callbacks = [];\n\nthis.page = function (value, fn) {\n  if (typeof value === \"function\") {\n    page(\"*\", value);\n  } else if (typeof fn === \"function\") {\n    const route = new Route(value);\n    callbacks.push(route.middleware(fn));\n  } else if (typeof value === \"string\") {\n    page.show(value, fn);\n  } else {\n    page.start(value);\n  }\n};\n\npage.start = function (options) {\n  if (options == null) {\n    options = {};\n  }\n  if (!running) {\n    running = true;\n    addEventListener(\"popstate\", onpopstate);\n    addEventListener(\"click\", onclick);\n    page.replace(currentPath(), null, null, true);\n  }\n};\n\npage.stop = function () {\n  if (running) {\n    running = false;\n    removeEventListener(\"click\", onclick);\n    removeEventListener(\"popstate\", onpopstate);\n  }\n};\n\npage.show = function (path, state) {\n  if (path === currentState?.path) {\n    return;\n  }\n  const context = new Context(path, state);\n  const previousState = currentState;\n  currentState = context.state;\n  const res = page.dispatch(context);\n  if (res) {\n    currentState = previousState;\n    location.assign(res);\n  } else {\n    context.pushState();\n    updateCanonicalLink();\n    track();\n  }\n  return context;\n};\n\npage.replace = function (path, state, skipDispatch, init) {\n  let result;\n  let context = new Context(path, state || currentState);\n  context.init = init;\n  currentState = context.state;\n  if (!skipDispatch) {\n    result = page.dispatch(context);\n  }\n  if (result) {\n    context = new Context(result);\n    context.init = init;\n    currentState = context.state;\n    page.dispatch(context);\n  }\n  context.replaceState();\n  updateCanonicalLink();\n  if (!skipDispatch) {\n    track();\n  }\n  return context;\n};\n\npage.dispatch = function (context) {\n  let i = 0;\n  const next = function () {\n    let fn = callbacks[i++];\n    return fn?.(context, next);\n  };\n  return next();\n};\n\npage.canGoBack = () => !Context.isIntialState(currentState);\n\npage.canGoForward = () => !Context.isLastState(currentState);\n\nconst currentPath = () => location.pathname + location.search + location.hash;\n\nclass Context {\n  static isIntialState(state) {\n    return state.id === 0;\n  }\n\n  static isLastState(state) {\n    return state.id === this.stateId - 1;\n  }\n\n  static isInitialPopState(state) {\n    return state.path === this.initialPath && this.stateId === 1;\n  }\n\n  static isSameSession(state) {\n    return state.sessionId === this.sessionId;\n  }\n\n  constructor(path, state) {\n    this.initialPath = currentPath();\n    this.sessionId = Date.now();\n    this.stateId = 0;\n    if (path == null) {\n      path = \"/\";\n    }\n    this.path = path;\n    if (state == null) {\n      state = {};\n    }\n    this.state = state;\n    this.pathname = this.path.replace(\n      /(?:\\?([^#]*))?(?:#(.*))?$/,\n      (_, query, hash) => {\n        this.query = query;\n        this.hash = hash;\n        return \"\";\n      },\n    );\n\n    if (this.state.id == null) {\n      this.state.id = this.constructor.stateId++;\n    }\n    if (this.state.sessionId == null) {\n      this.state.sessionId = this.constructor.sessionId;\n    }\n    this.state.path = this.path;\n  }\n\n  pushState() {\n    history.pushState(this.state, \"\", this.path);\n  }\n\n  replaceState() {\n    try {\n      history.replaceState(this.state, \"\", this.path);\n    } catch (error) {} // NS_ERROR_FAILURE in Firefox\n  }\n}\n\nclass Route {\n  constructor(path, options) {\n    this.path = path;\n    if (options == null) {\n      options = {};\n    }\n    this.keys = [];\n    this.regexp = pathToRegexp(this.path, this.keys);\n  }\n\n  middleware(fn) {\n    return (context, next) => {\n      let params = [];\n      if (this.match(context.pathname, params)) {\n        context.params = params;\n        return fn(context, next);\n      } else {\n        return next();\n      }\n    };\n  }\n\n  match(path, params) {\n    const matchData = this.regexp.exec(path);\n    if (!matchData) {\n      return;\n    }\n\n    const iterable = matchData.slice(1);\n    for (let i = 0; i < iterable.length; i++) {\n      var key = this.keys[i];\n      var value = iterable[i];\n      if (typeof value === \"string\") {\n        value = decodeURIComponent(value);\n      }\n      if (key) {\n        params[key.name] = value;\n      } else {\n        params.push(value);\n      }\n    }\n    return true;\n  }\n}\n\nvar pathToRegexp = function (path, keys) {\n  if (path instanceof RegExp) {\n    return path;\n  }\n\n  if (path instanceof Array) {\n    path = `(${path.join(\"|\")})`;\n  }\n  path = path\n    .replace(/\\/\\(/g, \"(?:/\")\n    .replace(\n      /(\\/)?(\\.)?:(\\w+)(?:(\\(.*?\\)))?(\\?)?/g,\n      (_, slash, format, key, capture, optional) => {\n        if (slash == null) {\n          slash = \"\";\n        }\n        if (format == null) {\n          format = \"\";\n        }\n        keys.push({ name: key, optional: !!optional });\n        let str = optional ? \"\" : slash;\n        str += \"(?:\";\n        if (optional) {\n          str += slash;\n        }\n        str += format;\n        str += capture || (format ? \"([^/.]+?)\" : \"([^/]+?)\");\n        str += \")\";\n        if (optional) {\n          str += optional;\n        }\n        return str;\n      },\n    )\n    .replace(/([\\/.])/g, \"\\\\$1\")\n    .replace(/\\*/g, \"(.*)\");\n\n  return new RegExp(`^${path}$`);\n};\n\nvar onpopstate = function (event) {\n  if (!event.state || Context.isInitialPopState(event.state)) {\n    return;\n  }\n\n  if (Context.isSameSession(event.state)) {\n    page.replace(event.state.path, event.state);\n  } else {\n    location.reload();\n  }\n};\n\nvar onclick = function (event) {\n  try {\n    if (\n      event.which !== 1 ||\n      event.metaKey ||\n      event.ctrlKey ||\n      event.shiftKey ||\n      event.defaultPrevented\n    ) {\n      return;\n    }\n  } catch (error) {\n    return;\n  }\n\n  let link = $.eventTarget(event);\n  while (link && !(link.tagName === \"A\" || link.tagName === \"a\")) {\n    link = link.parentNode;\n  }\n\n  if (!link) return;\n\n  // If the `<a>` is in an SVG, its attributes are `SVGAnimatedString`s\n  // instead of strings\n  let href = link.href instanceof SVGAnimatedString\n    ? new URL(link.href.baseVal, location.href).href\n    : link.href;\n  let target = link.target instanceof SVGAnimatedString\n    ? link.target.baseVal\n    : link.target;\n\n  if (!target && isSameOrigin(href)) {\n    event.preventDefault();\n    let parsedHref = new URL(href);\n    let path = parsedHref.pathname + parsedHref.search + parsedHref.hash;\n    path = path.replace(/^\\/\\/+/, \"/\"); // IE11 bug\n    page.show(path);\n  }\n};\n\nvar isSameOrigin = (url) =>\n  url.startsWith(`${location.protocol}//${location.hostname}`);\n\nvar updateCanonicalLink = function () {\n  if (!this.canonicalLink) {\n    this.canonicalLink = document.head.querySelector('link[rel=\"canonical\"]');\n  }\n  return this.canonicalLink.setAttribute(\n    \"href\",\n    `https://${location.host}${location.pathname}`,\n  );\n};\n\nconst trackers = [];\n\npage.track = function (fn) {\n  trackers.push(fn);\n};\n\nvar track = function () {\n  if (app.config.env !== \"production\") {\n    return;\n  }\n  if (navigator.doNotTrack === \"1\") {\n    return;\n  }\n  if (navigator.globalPrivacyControl) {\n    return;\n  }\n\n  const consentGiven = Cookies.get(\"analyticsConsent\");\n  const consentAsked = Cookies.get(\"analyticsConsentAsked\");\n\n  if (consentGiven === \"1\") {\n    for (var tracker of trackers) {\n      tracker.call();\n    }\n  } else if (consentGiven === undefined && consentAsked === undefined) {\n    // Only ask for consent once per browser session\n    Cookies.set(\"analyticsConsentAsked\", \"1\");\n\n    new app.views.Notif(\"AnalyticsConsent\", { autoHide: null });\n  }\n};\n\nthis.resetAnalytics = function () {\n  for (var cookie of document.cookie.split(/;\\s?/)) {\n    var name = cookie.split(\"=\")[0];\n    if (name[0] === \"_\" && name[1] !== \"_\") {\n      Cookies.expire(name);\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/lib/util.js",
    "content": "//\n// Traversing\n//\n\nlet smoothDistance, smoothDuration, smoothEnd, smoothStart;\nthis.$ = function (selector, el) {\n  if (el == null) {\n    el = document;\n  }\n  try {\n    return el.querySelector(selector);\n  } catch (error) {}\n};\n\nthis.$$ = function (selector, el) {\n  if (el == null) {\n    el = document;\n  }\n  try {\n    return el.querySelectorAll(selector);\n  } catch (error) {}\n};\n\n$.id = (id) => document.getElementById(id);\n\n$.hasChild = function (parent, el) {\n  if (!parent) {\n    return;\n  }\n  while (el) {\n    if (el === parent) {\n      return true;\n    }\n    if (el === document.body) {\n      return;\n    }\n    el = el.parentNode;\n  }\n};\n\n$.closestLink = function (el, parent) {\n  if (parent == null) {\n    parent = document.body;\n  }\n  while (el) {\n    if (el.tagName === \"A\") {\n      return el;\n    }\n    if (el === parent) {\n      return;\n    }\n    el = el.parentNode;\n  }\n};\n\n//\n// Events\n//\n\n$.on = function (el, event, callback, useCapture) {\n  if (useCapture == null) {\n    useCapture = false;\n  }\n  if (event.includes(\" \")) {\n    for (var name of event.split(\" \")) {\n      $.on(el, name, callback);\n    }\n  } else {\n    el.addEventListener(event, callback, useCapture);\n  }\n};\n\n$.off = function (el, event, callback, useCapture) {\n  if (useCapture == null) {\n    useCapture = false;\n  }\n  if (event.includes(\" \")) {\n    for (var name of event.split(\" \")) {\n      $.off(el, name, callback);\n    }\n  } else {\n    el.removeEventListener(event, callback, useCapture);\n  }\n};\n\n$.trigger = function (el, type, canBubble, cancelable) {\n  const event = new Event(type, {\n    bubbles: canBubble ?? true,\n    cancelable: cancelable ?? true,\n  });\n  el.dispatchEvent(event);\n};\n\n$.click = function (el) {\n  const event = new MouseEvent(\"click\", {\n    bubbles: true,\n    cancelable: true,\n  });\n  el.dispatchEvent(event);\n};\n\n$.stopEvent = function (event) {\n  event.preventDefault();\n  event.stopPropagation();\n  event.stopImmediatePropagation();\n};\n\n$.eventTarget = (event) => event.target.correspondingUseElement || event.target;\n\n//\n// Manipulation\n//\n\nconst buildFragment = function (value) {\n  const fragment = document.createDocumentFragment();\n\n  if ($.isCollection(value)) {\n    for (var child of $.makeArray(value)) {\n      fragment.appendChild(child);\n    }\n  } else {\n    fragment.innerHTML = value;\n  }\n\n  return fragment;\n};\n\n$.append = function (el, value) {\n  if (typeof value === \"string\") {\n    el.insertAdjacentHTML(\"beforeend\", value);\n  } else {\n    if ($.isCollection(value)) {\n      value = buildFragment(value);\n    }\n    el.appendChild(value);\n  }\n};\n\n$.prepend = function (el, value) {\n  if (!el.firstChild) {\n    $.append(value);\n  } else if (typeof value === \"string\") {\n    el.insertAdjacentHTML(\"afterbegin\", value);\n  } else {\n    if ($.isCollection(value)) {\n      value = buildFragment(value);\n    }\n    el.insertBefore(value, el.firstChild);\n  }\n};\n\n$.before = function (el, value) {\n  if (typeof value === \"string\" || $.isCollection(value)) {\n    value = buildFragment(value);\n  }\n\n  el.parentNode.insertBefore(value, el);\n};\n\n$.after = function (el, value) {\n  if (typeof value === \"string\" || $.isCollection(value)) {\n    value = buildFragment(value);\n  }\n\n  if (el.nextSibling) {\n    el.parentNode.insertBefore(value, el.nextSibling);\n  } else {\n    el.parentNode.appendChild(value);\n  }\n};\n\n$.remove = function (value) {\n  if ($.isCollection(value)) {\n    for (var el of $.makeArray(value)) {\n      if (el.parentNode != null) {\n        el.parentNode.removeChild(el);\n      }\n    }\n  } else {\n    if (value.parentNode != null) {\n      value.parentNode.removeChild(value);\n    }\n  }\n};\n\n$.empty = function (el) {\n  while (el.firstChild) {\n    el.removeChild(el.firstChild);\n  }\n};\n\n// Calls the function while the element is off the DOM to avoid triggering\n// unnecessary reflows and repaints.\n$.batchUpdate = function (el, fn) {\n  const parent = el.parentNode;\n  const sibling = el.nextSibling;\n  parent.removeChild(el);\n\n  fn(el);\n\n  if (sibling) {\n    parent.insertBefore(el, sibling);\n  } else {\n    parent.appendChild(el);\n  }\n};\n\n//\n// Offset\n//\n\n$.rect = (el) => el.getBoundingClientRect();\n\n$.offset = function (el, container) {\n  if (container == null) {\n    container = document.body;\n  }\n  let top = 0;\n  let left = 0;\n\n  while (el && el !== container) {\n    top += el.offsetTop;\n    left += el.offsetLeft;\n    el = el.offsetParent;\n  }\n\n  return {\n    top,\n    left,\n  };\n};\n\n$.scrollParent = function (el) {\n  while ((el = el.parentNode) && el.nodeType === 1) {\n    if (el.scrollTop > 0) {\n      break;\n    }\n    if ([\"auto\", \"scroll\"].includes(getComputedStyle(el)?.overflowY ?? \"\")) {\n      break;\n    }\n  }\n  return el;\n};\n\n$.scrollTo = function (el, parent, position, options) {\n  if (position == null) {\n    position = \"center\";\n  }\n  if (options == null) {\n    options = {};\n  }\n  if (!el) {\n    return;\n  }\n\n  if (parent == null) {\n    parent = $.scrollParent(el);\n  }\n  if (!parent) {\n    return;\n  }\n\n  const parentHeight = parent.clientHeight;\n  const parentScrollHeight = parent.scrollHeight;\n  if (!(parentScrollHeight > parentHeight)) {\n    return;\n  }\n\n  const { top } = $.offset(el, parent);\n  const { offsetTop } = parent.firstElementChild;\n\n  switch (position) {\n    case \"top\":\n      parent.scrollTop = top - offsetTop - (options.margin || 0);\n      break;\n    case \"center\":\n      parent.scrollTop =\n        top - Math.round(parentHeight / 2 - el.offsetHeight / 2);\n      break;\n    case \"continuous\":\n      var { scrollTop } = parent;\n      var height = el.offsetHeight;\n\n      var lastElementOffset =\n        parent.lastElementChild.offsetTop +\n        parent.lastElementChild.offsetHeight;\n      var offsetBottom =\n        lastElementOffset > 0 ? parentScrollHeight - lastElementOffset : 0;\n\n      // If the target element is above the visible portion of its scrollable\n      // ancestor, move it near the top with a gap = options.topGap * target's height.\n      if (top - offsetTop <= scrollTop + height * (options.topGap || 1)) {\n        parent.scrollTop = top - offsetTop - height * (options.topGap || 1);\n        // If the target element is below the visible portion of its scrollable\n        // ancestor, move it near the bottom with a gap = options.bottomGap * target's height.\n      } else if (\n        top + offsetBottom >=\n        scrollTop + parentHeight - height * ((options.bottomGap || 1) + 1)\n      ) {\n        parent.scrollTop =\n          top +\n          offsetBottom -\n          parentHeight +\n          height * ((options.bottomGap || 1) + 1);\n      }\n      break;\n  }\n};\n\n$.scrollToWithImageLock = function (el, parent, ...args) {\n  if (parent == null) {\n    parent = $.scrollParent(el);\n  }\n  if (!parent) {\n    return;\n  }\n\n  $.scrollTo(el, parent, ...args);\n\n  // Lock the scroll position on the target element for up to 3 seconds while\n  // nearby images are loaded and rendered.\n  for (var image of parent.getElementsByTagName(\"img\")) {\n    if (!image.complete) {\n      (function () {\n        let timeout;\n        const onLoad = function (event) {\n          clearTimeout(timeout);\n          unbind(event.target);\n          return $.scrollTo(el, parent, ...args);\n        };\n\n        var unbind = (target) => $.off(target, \"load\", onLoad);\n\n        $.on(image, \"load\", onLoad);\n        return (timeout = setTimeout(unbind.bind(null, image), 3000));\n      })();\n    }\n  }\n};\n\n// Calls the function while locking the element's position relative to the window.\n$.lockScroll = function (el, fn) {\n  const parent = $.scrollParent(el);\n  if (parent) {\n    let { top } = $.rect(el);\n    if (![document.body, document.documentElement].includes(parent)) {\n      top -= $.rect(parent).top;\n    }\n    fn();\n    parent.scrollTop = $.offset(el, parent).top - top;\n  } else {\n    fn();\n  }\n};\n\n// If `el` is inside any `<details>` elements, expand them.\n$.openDetailsAncestors = function (el) {\n  while (el) {\n    if (el.tagName === \"DETAILS\") {\n      el.open = true;\n    }\n    el = el.parentElement;\n  }\n}\n\nlet smoothScroll =\n  (smoothStart =\n  smoothEnd =\n  smoothDistance =\n  smoothDuration =\n    null);\n\n$.smoothScroll = function (el, end) {\n  smoothEnd = end;\n\n  if (smoothScroll) {\n    const newDistance = smoothEnd - smoothStart;\n    smoothDuration += Math.min(300, Math.abs(smoothDistance - newDistance));\n    smoothDistance = newDistance;\n    return;\n  }\n\n  smoothStart = el.scrollTop;\n  smoothDistance = smoothEnd - smoothStart;\n  smoothDuration = Math.min(300, Math.abs(smoothDistance));\n  const startTime = Date.now();\n\n  smoothScroll = function () {\n    const p = Math.min(1, (Date.now() - startTime) / smoothDuration);\n    const y = Math.max(\n      0,\n      Math.floor(\n        smoothStart +\n          smoothDistance * (p < 0.5 ? 2 * p * p : p * (4 - p * 2) - 1),\n      ),\n    );\n    el.scrollTop = y;\n    if (p === 1) {\n      return (smoothScroll = null);\n    } else {\n      return requestAnimationFrame(smoothScroll);\n    }\n  };\n  return requestAnimationFrame(smoothScroll);\n};\n\n//\n// Utilities\n//\n\n$.makeArray = function (object) {\n  if (Array.isArray(object)) {\n    return object;\n  } else {\n    return Array.prototype.slice.apply(object);\n  }\n};\n\n$.arrayDelete = function (array, object) {\n  const index = array.indexOf(object);\n  if (index >= 0) {\n    array.splice(index, 1);\n    return true;\n  } else {\n    return false;\n  }\n};\n\n// Returns true if the object is an array or a collection of DOM elements.\n$.isCollection = (object) =>\n  Array.isArray(object) || typeof object?.item === \"function\";\n\nconst ESCAPE_HTML_MAP = {\n  \"&\": \"&amp;\",\n  \"<\": \"&lt;\",\n  \">\": \"&gt;\",\n  '\"': \"&quot;\",\n  \"'\": \"&#x27;\",\n  \"/\": \"&#x2F;\",\n};\n\nconst ESCAPE_HTML_REGEXP = /[&<>\"'\\/]/g;\n\n$.escape = (string) =>\n  string.replace(ESCAPE_HTML_REGEXP, (match) => ESCAPE_HTML_MAP[match]);\n\nconst ESCAPE_REGEXP = /([.*+?^=!:${}()|\\[\\]\\/\\\\])/g;\n\n$.escapeRegexp = (string) => string.replace(ESCAPE_REGEXP, \"\\\\$1\");\n\n$.urlDecode = (string) => decodeURIComponent(string.replace(/\\+/g, \"%20\"));\n\n$.classify = function (string) {\n  string = string.split(\"_\");\n  for (let i = 0; i < string.length; i++) {\n    var substr = string[i];\n    string[i] = substr[0].toUpperCase() + substr.slice(1);\n  }\n  return string.join(\"\");\n};\n\n//\n// Miscellaneous\n//\n\n$.noop = function () {};\n\n$.popup = function (value) {\n  try {\n    window.open(value.href || value, \"_blank\", \"noopener\");\n  } catch (error) {\n    const win = window.open();\n    if (win.opener) {\n      win.opener = null;\n    }\n    win.location = value.href || value;\n  }\n};\n\nlet isMac = null;\n$.isMac = () =>\n  isMac != null ? isMac : (isMac = navigator.userAgent.includes(\"Mac\"));\n\nlet isIE = null;\n$.isIE = () =>\n  isIE != null\n    ? isIE\n    : (isIE =\n        navigator.userAgent.includes(\"MSIE\") ||\n        navigator.userAgent.includes(\"rv:11.0\"));\n\nlet isChromeForAndroid = null;\n$.isChromeForAndroid = () =>\n  isChromeForAndroid != null\n    ? isChromeForAndroid\n    : (isChromeForAndroid =\n        navigator.userAgent.includes(\"Android\") &&\n        /Chrome\\/([.0-9])+ Mobile/.test(navigator.userAgent));\n\nlet isAndroid = null;\n$.isAndroid = () =>\n  isAndroid != null\n    ? isAndroid\n    : (isAndroid = navigator.userAgent.includes(\"Android\"));\n\nlet isIOS = null;\n$.isIOS = () =>\n  isIOS != null\n    ? isIOS\n    : (isIOS =\n        navigator.userAgent.includes(\"iPhone\") ||\n        navigator.userAgent.includes(\"iPad\"));\n\n$.overlayScrollbarsEnabled = function () {\n  if (!$.isMac()) {\n    return false;\n  }\n  const div = document.createElement(\"div\");\n  div.setAttribute(\n    \"style\",\n    \"width: 100px; height: 100px; overflow: scroll; position: absolute\",\n  );\n  document.body.appendChild(div);\n  const result = div.offsetWidth === div.clientWidth;\n  document.body.removeChild(div);\n  return result;\n};\n\nconst HIGHLIGHT_DEFAULTS = {\n  className: \"highlight\",\n  delay: 1000,\n};\n\n$.highlight = function (el, options) {\n  options = { ...HIGHLIGHT_DEFAULTS, ...(options || {}) };\n  el.classList.add(options.className);\n  setTimeout(() => el.classList.remove(options.className), options.delay);\n};\n"
  },
  {
    "path": "assets/javascripts/models/doc.js",
    "content": "app.models.Doc = class Doc extends app.Model {\n  // Attributes: name, slug, type, version, release, db_size, mtime, links\n\n  constructor() {\n    super(...arguments);\n    this.reset(this);\n    this.slug_without_version = this.slug.split(\"~\")[0];\n    this.fullName = `${this.name}` + (this.version ? ` ${this.version}` : \"\");\n    this.icon = this.slug_without_version;\n    if (this.version) {\n      this.short_version = this.version.split(\" \")[0];\n    }\n    this.text = this.toEntry().text;\n  }\n\n  reset(data) {\n    this.resetEntries(data.entries);\n    this.resetTypes(data.types);\n  }\n\n  resetEntries(entries) {\n    this.entries = new app.collections.Entries(entries);\n    this.entries.each((entry) => {\n      return (entry.doc = this);\n    });\n  }\n\n  resetTypes(types) {\n    this.types = new app.collections.Types(types);\n    this.types.each((type) => {\n      return (type.doc = this);\n    });\n  }\n\n  fullPath(path) {\n    if (path == null) {\n      path = \"\";\n    }\n    if (path[0] !== \"/\") {\n      path = `/${path}`;\n    }\n    return `/${this.slug}${path}`;\n  }\n\n  fileUrl(path) {\n    return `${app.config.docs_origin}${this.fullPath(path)}?${this.mtime}`;\n  }\n\n  dbUrl() {\n    return `${app.config.docs_origin}/${this.slug}/${app.config.db_filename}?${this.mtime}`;\n  }\n\n  indexUrl() {\n    return `${app.indexHost()}/${this.slug}/${app.config.index_filename}?${\n      this.mtime\n    }`;\n  }\n\n  toEntry() {\n    if (this.entry) {\n      return this.entry;\n    }\n    this.entry = new app.models.Entry({\n      doc: this,\n      name: this.fullName,\n      path: \"index\",\n    });\n    if (this.version) {\n      this.entry.addAlias(this.name);\n    }\n    return this.entry;\n  }\n\n  findEntryByPathAndHash(path, hash) {\n    const entry = hash && this.entries.findBy(\"path\", `${path}#${hash}`);\n    if (entry) {\n      return entry;\n    } else if (path === \"index\") {\n      return this.toEntry();\n    } else {\n      return this.entries.findBy(\"path\", path);\n    }\n  }\n\n  load(onSuccess, onError, options) {\n    if (options == null) {\n      options = {};\n    }\n    if (options.readCache && this._loadFromCache(onSuccess)) {\n      return;\n    }\n\n    const callback = (data) => {\n      this.reset(data);\n      onSuccess();\n      if (options.writeCache) {\n        this._setCache(data);\n      }\n    };\n\n    return ajax({\n      url: this.indexUrl(),\n      success: callback,\n      error: onError,\n    });\n  }\n\n  clearCache() {\n    app.localStorage.del(this.slug);\n  }\n\n  _loadFromCache(onSuccess) {\n    const data = this._getCache();\n    if (!data) {\n      return;\n    }\n\n    const callback = () => {\n      this.reset(data);\n      onSuccess();\n    };\n\n    setTimeout(callback, 0);\n    return true;\n  }\n\n  _getCache() {\n    const data = app.localStorage.get(this.slug);\n    if (!data) {\n      return;\n    }\n\n    if (data[0] === this.mtime) {\n      return data[1];\n    } else {\n      this.clearCache();\n      return;\n    }\n  }\n\n  _setCache(data) {\n    app.localStorage.set(this.slug, [this.mtime, data]);\n  }\n\n  install(onSuccess, onError, onProgress) {\n    if (this.installing) {\n      return;\n    }\n    this.installing = true;\n\n    const error = () => {\n      this.installing = null;\n      onError();\n    };\n\n    const success = (data) => {\n      this.installing = null;\n      app.db.store(this, data, onSuccess, error);\n    };\n\n    ajax({\n      url: this.dbUrl(),\n      success,\n      error,\n      progress: onProgress,\n      timeout: 3600,\n    });\n  }\n\n  uninstall(onSuccess, onError) {\n    if (this.installing) {\n      return;\n    }\n    this.installing = true;\n\n    const success = () => {\n      this.installing = null;\n      onSuccess();\n    };\n\n    const error = () => {\n      this.installing = null;\n      onError();\n    };\n\n    app.db.unstore(this, success, error);\n  }\n\n  getInstallStatus(callback) {\n    app.db.version(this, (value) =>\n      callback({ installed: !!value, mtime: value }),\n    );\n  }\n\n  isOutdated(status) {\n    if (!status) {\n      return false;\n    }\n    const isInstalled = status.installed || app.settings.get(\"autoInstall\");\n    return isInstalled && this.mtime !== status.mtime;\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/models/entry.js",
    "content": "//= require app/searcher\n\napp.models.Entry = class Entry extends app.Model {\n  static applyAliases(string) {\n    const aliases = app.config.docs_aliases;\n    if (aliases.hasOwnProperty(string)) {\n      return [string, aliases[string]];\n    } else {\n      const words = string.split(\".\");\n      for (let i = 0; i < words.length; i++) {\n        var word = words[i];\n        if (aliases.hasOwnProperty(word)) {\n          words[i] = aliases[word];\n          return [string, words.join(\".\")];\n        }\n      }\n    }\n    return string;\n  }\n\n  // Attributes: name, type, path\n  constructor() {\n    super(...arguments);\n    this.text = Entry.applyAliases(app.Searcher.normalizeString(this.name));\n  }\n\n  addAlias(name) {\n    const text = Entry.applyAliases(app.Searcher.normalizeString(name));\n    if (!Array.isArray(this.text)) {\n      this.text = [this.text];\n    }\n    this.text.push(Array.isArray(text) ? text[1] : text);\n  }\n\n  fullPath() {\n    return this.doc.fullPath(this.isIndex() ? \"\" : this.path);\n  }\n\n  dbPath() {\n    return this.path.replace(/#.*/, \"\");\n  }\n\n  filePath() {\n    return this.doc.fullPath(this._filePath());\n  }\n\n  fileUrl() {\n    return this.doc.fileUrl(this._filePath());\n  }\n\n  _filePath() {\n    let result = this.path.replace(/#.*/, \"\");\n    if (result.slice(-5) !== \".html\") {\n      result += \".html\";\n    }\n    return result;\n  }\n\n  isIndex() {\n    return this.path === \"index\";\n  }\n\n  getType() {\n    return this.doc.types.findBy(\"name\", this.type);\n  }\n\n  loadFile(onSuccess, onError) {\n    return app.db.load(this, onSuccess, onError);\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/models/model.js",
    "content": "app.Model = class Model {\n  constructor(attributes) {\n    for (var key in attributes) {\n      var value = attributes[key];\n      this[key] = value;\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/models/type.js",
    "content": "app.models.Type = class Type extends app.Model {\n  // Attributes: name, slug, count\n\n  fullPath() {\n    return `/${this.doc.slug}-${this.slug}/`;\n  }\n\n  entries() {\n    return this.doc.entries.findAllBy(\"type\", this.name);\n  }\n\n  toEntry() {\n    return new app.models.Entry({\n      doc: this.doc,\n      name: `${this.doc.name} / ${this.name}`,\n      path: \"..\" + this.fullPath(),\n    });\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/news.json",
    "content": "[\n  [\n    \"2026-02-14\",\n    \"New documentation: <a href=\\\"/couchdb/\\\">CouchDB</a>\"\n  ],\n  [\n    \"2025-10-19\",\n    \"New documentations: <a href=\\\"/lit/\\\">Lit</a>, <a href=\\\"/graphviz/\\\">Graphviz</a>, <a href=\\\"/bun/\\\">Bun</a>\"\n  ],\n  [\n    \"2025-07-14\",\n    \"New documentation: <a href=\\\"/tcllib/\\\">Tcllib</a>\"\n  ],\n  [\n    \"2025-06-27\",\n    \"New documentation: <a href=\\\"/zsh/\\\">Zsh</a>\"\n  ],\n  [\n    \"2025-06-04\",\n    \"New documentation: <a href=\\\"/es_toolkit/\\\">es-toolkit</a>\"\n  ],\n  [\n    \"2025-05-28\",\n    \"New documentation: <a href=\\\"/vertx/\\\">Vert.x</a>\"\n  ],\n  [\n    \"2025-02-23\",\n    \"New documentation: <a href=\\\"/threejs/\\\">Three.js</a>\"\n  ],\n  [\n    \"2025-02-16\",\n    \"New documentation: <a href=\\\"/openlayers/\\\">OpenLayers</a>\"\n  ],\n  [\n    \"2024-11-23\",\n    \"New documentation: <a href=\\\"/duckdb/\\\">DuckDB</a>\"\n  ],\n  [\n    \"2024-08-20\",\n    \"New documentation: <a href=\\\"/man/\\\">Linux man pages</a>\"\n  ],\n  [\n    \"2024-07-28\",\n    \"New documentation: <a href=\\\"/opengl/\\\">OpenGL</a>\"\n  ],\n  [\n    \"2024-06-12\",\n    \"New documentations: <a href=\\\"/nextjs/\\\">Next.js</a>, <a href=\\\"/click/\\\">click</a>\"\n  ],\n  [\n    \"2024-01-24\",\n    \"New documentation: <a href=\\\"/playwright/\\\">Playwright</a>\"\n  ],\n  [\n    \"2024-01-20\",\n    \"New documentation: <a href=\\\"/htmx/\\\">htmx</a>\"\n  ],\n  [\n    \"2024-01-12\",\n    \"New documentation: <a href=\\\"/hammerspoon/\\\">Hammerspoon</a>\"\n  ],\n  [\n    \"2024-01-05\",\n    \"New documentation: <a href=\\\"/bazel/\\\">Bazel</a>\"\n  ],\n  [\n    \"2023-10-09\",\n    \"New documentations: <a href=\\\"/hapi/\\\">hapi</a>, <a href=\\\"/joi/\\\">joi</a>, <a href=\\\"/nushell/\\\">Nushell</a>, <a href=\\\"/varnish/\\\">Varnish</a>\"\n  ],\n  [\n    \"2023-08-24\",\n    \"New documentation: <a href=\\\"/fluture/\\\">Fluture</a>\"\n  ],\n  [\n    \"2022-12-20\",\n    \"New documentations: <a href=\\\"/qunit/\\\">QUnit</a>, <a href=\\\"/wagtail/\\\">Wagtail</a>\"\n  ],\n  [\n    \"2022-11-04\",\n    \"New documentation: <a href=\\\"/vueuse/\\\">VueUse</a>\"\n  ],\n  [\n    \"2022-10-10\",\n    \"New documentation: <a href=\\\"/astro/\\\">Astro</a>\"\n  ],\n  [\n    \"2022-10-09\",\n    \"New documentations: <a href=\\\"/fastapi/\\\">FastAPI</a>, <a href=\\\"/vitest/\\\">Vitest</a>\"\n  ],\n  [\n    \"2022-10-02\",\n    \"New documentation: <a href=\\\"/svelte/\\\">Svelte</a>\"\n  ],\n  [\n    \"2022-09-21\",\n    \"Added HTTP/3 to <a href=\\\"/http/\\\">HTTP</a>\"\n  ],\n  [\n    \"2022-09-06\",\n    \"New documentation: <a href=\\\"/date_fns/\\\">date-fns</a>\"\n  ],\n  [\n    \"2022-08-27\",\n    \"New documentations: <a href=\\\"/sanctuary/\\\">Sanctuary</a>, <a href=\\\"/requests/\\\">Requests</a>, <a href=\\\"/axios/\\\">Axios</a>\"\n  ],\n  [\n    \"2022-05-03\",\n    \"New documentations: <a href=\\\"/kubernetes/\\\">Kubernetes</a>, <a href=\\\"/kubectl/\\\">Kubectl</a>\"\n  ],\n  [\n    \"2022-04-25\",\n    \"New documentation: <a href=\\\"/nix/\\\">Nix</a>\"\n  ],\n  [\n    \"2022-03-31\",\n    \"New documentation: <a href=\\\"/eigen3/\\\">Eigen3</a>\"\n  ],\n  [\n    \"2022-02-21\",\n    \"New documentation: <a href=\\\"/tailwindcss/\\\">Tailwind CSS</a>\"\n  ],\n  [\n    \"2022-01-12\",\n    \"New documentation: <a href=\\\"/react_router/\\\">React Router</a>\"\n  ],\n  [\n    \"2022-01-09\",\n    \"New documentation: <a href=\\\"/deno/\\\">Deno</a>\"\n  ],\n  [\n    \"2021-12-29\",\n    \"New documentation: <a href=\\\"/point_cloud_library/\\\">PointCloudLibrary</a>\"\n  ],\n  [\n    \"2021-12-27\",\n    \"New documentation: <a href=\\\"/zig/\\\">Zig</a>\"\n  ],\n  [\n    \"2021-12-26\",\n    \"New documentation: <a href=\\\"/gnu_make/\\\">GNU Make</a>\"\n  ],\n  [\n    \"2021-12-07\",\n    \"New documentation: <a href=\\\"/prettier/\\\">Prettier</a>\",\n    \"Renamed documentation: <a href=\\\"/dom/\\\">Web APIs</a>\"\n  ],\n  [\n    \"2021-12-05\",\n    \"New documentation: <a href=\\\"/esbuild/\\\">esbuild</a>\"\n  ],\n  [\n    \"2021-12-04\",\n    \"New documentation: <a href=\\\"/vite/\\\">Vite</a>\"\n  ],\n  [\n    \"2021-11-29\",\n    \"New documentation: <a href=\\\"/i3/\\\">i3</a>\"\n  ],\n  [\n    \"2021-06-09\",\n    \"New documentation: <a href=\\\"/r/\\\">R</a>\"\n  ],\n  [\n    \"2021-05-31\",\n    \"New documentation: <a href=\\\"/web_extensions/\\\">Web Extensions</a>\"\n  ],\n  [\n    \"2021-05-26\",\n    \"New documentations: <a href=\\\"/latex/\\\">LaTeX</a>, <a href=\\\"/jq/\\\">jq</a>\"\n  ],\n  [\n    \"2021-04-29\",\n    \"Added <code class=\\\"_label\\\">alt + c</code> shortcut to copy URL of original page.\"\n  ],\n  [\n    \"2021-02-26\",\n    \"New documentation: <a href=\\\"/react_bootstrap/\\\">React Bootstrap</a>\"\n  ],\n  [\n    \"2021-01-03\",\n    \"New documentation: <a href=\\\"/ocaml/\\\">OCaml</a>\"\n  ],\n  [\n    \"2020-12-23\",\n    \"New documentation: <a href=\\\"/gtk/\\\">GTK</a>\"\n  ],\n  [\n    \"2020-12-07\",\n    \"New documentations: <a href=\\\"/flask/\\\">Flask</a>, <a href=\\\"/groovy/\\\">Groovy</a>, <a href=\\\"/jinja/\\\">Jinja</a>, <a href=\\\"/werkzeug/\\\">Werkzeug</a>\"\n  ],\n  [\n    \"2020-12-04\",\n    \"New documentation: <a href=\\\"/haproxy/\\\">HAProxy</a>\"\n  ],\n  [\n    \"2020-11-17\",\n    \"TensorFlow has been split into <a href=\\\"/tensorflow/\\\">TensorFlow Python</a>, <a href=\\\"/tensorflow_cpp/\\\">TensorFlow C++</a>\"\n  ],\n  [\n    \"2020-11-14\",\n    \"New documentations: <a href=\\\"/pytorch/\\\">PyTorch</a>, <a href=\\\"/spring_boot/\\\">Spring Boot</a>\"\n  ],\n  [\n    \"2020-01-13\",\n    \"New “Automatic” theme: match your browser or system dark mode setting. <a href=\\\"/settings\\\">Enable it in preferences</a>.\"\n  ],\n  [\n    \"2020-01-13\",\n    \"New documentation: <a href=\\\"/gnuplot/\\\">Gnuplot</a>\"\n  ],\n  [\n    \"2019-10-26\",\n    \"New documentation: <a href=\\\"/sequelize/\\\">Sequelize</a>\"\n  ], [\n    \"2019-10-20\",\n    \"New documentations: <a href=\\\"/mariadb/\\\">MariaDB</a> and <a href=\\\"/reactivex/\\\">ReactiveX</a>\"\n  ], [\n    \"2019-09-02\",\n    \"New documentations added over the last 3 weeks: <a href=\\\"/scala~2.13_library/\\\">Scala</a>, <a href=\\\"/wordpress/\\\">WordPress</a>, <a href=\\\"/cypress/\\\">Cypress</a>, <a href=\\\"/saltstack/\\\">SaltStack</a>, <a href=\\\"/composer/\\\">Composer</a>, <a href=\\\"/vue_router/\\\">Vue Router</a>, <a href=\\\"/vuex/\\\">Vuex</a>, <a href=\\\"/pony/\\\">Pony</a>, <a href=\\\"/rxjs/\\\">RxJS</a>, <a href=\\\"/octave/\\\">Octave</a>, <a href=\\\"/trio/\\\">Trio</a>, <a href=\\\"/django_rest_framework/\\\">Django REST Framework</a>, <a href=\\\"/enzyme/\\\">Enzyme</a> and <a href=\\\"/gnu_cobol/\\\">GnuCOBOL</a>\"\n  ], [\n    \"2019-07-21\",\n    \"Fixed several bugs, added an option to automatically download documentation and <a href=\\\"https://github.com/freeCodeCamp/devdocs/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aclosed+sort%3Aupdated-desc+closed%3A%3E2019-07-18+977+988+986+870+886+1024+979+975+941+831+1005+848+942\\\" target=\\\"_blank\\\">more</a>.\"\n  ], [\n    \"2019-07-19\",\n    \"Replaced the AppCache with a Service Worker (which makes DevDocs an installable PWA) and fixed layout preferences on Firefox.\"\n  ], [\n    \"2018-09-23\",\n    \"New documentations: <a href=\\\"/puppeteer/\\\">Puppeteer</a> and <a href=\\\"/handlebars/\\\">Handlebars.js</a>\"\n  ], [\n    \"2018-08-12\",\n    \"New documentations: <a href=\\\"/dart/\\\">Dart</a> and <a href=\\\"/qt/\\\">Qt</a>\"\n  ], [\n    \"2018-07-29\",\n    \"New documentations: <a href=\\\"/bash/\\\">Bash</a>, <a href=\\\"/graphite/\\\">Graphite</a> and <a href=\\\"/pygame/\\\">Pygame</a>\"\n  ], [\n    \"2018-07-08\",\n    \"New documentations: <a href=\\\"/leaflet/\\\">Leaflet</a>, <a href=\\\"/terraform/\\\">Terraform</a> and <a href=\\\"/koa/\\\">Koa</a>\"\n  ], [\n    \"2018-03-26\",\n    \"DevDocs is joining the freeCodeCamp community. Read the announcement <a href=\\\"https://medium.freecodecamp.org/devdocs-is-joining-the-freecodecamp-community-ae185a1c14a6\\\" target=\\\"_blank\\\">here</a>.\"\n  ], [\n    \"2018-02-04\",\n    \"New documentations: <a href=\\\"/babel/\\\">Babel</a>, <a href=\\\"/jekyll/\\\">Jekyll</a> and <a href=\\\"/jsdoc/\\\">JSDoc</a>\"\n  ], [\n    \"2017-11-26\",\n    \"New documentations: <a href=\\\"/bluebird/\\\">Bluebird</a>, <a href=\\\"/eslint/\\\">ESLint</a> and <a href=\\\"/homebrew/\\\">Homebrew</a>\"\n  ], [\n    \"2017-11-18\",\n    \"Added print & PDF stylesheet.\\nFeedback welcome on <a href=\\\"https://twitter.com/DevDocs\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\">Twitter</a> and <a href=\\\"https://github.com/freeCodeCamp/devdocs\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\">GitHub</a>.\"\n  ], [\n    \"2017-09-10\",\n    \"<a href=\\\"/settings\\\">Preferences</a> can now be exported and imported.\"\n  ], [\n    \"2017-09-03\",\n    \"New documentations: <a href=\\\"/d/\\\">D</a>, <a href=\\\"/nim/\\\">Nim</a> and <a href=\\\"/vulkan/\\\">Vulkan</a>\"\n  ], [\n    \"2017-07-23\",\n    \"New documentation: <a href=\\\"/godot/\\\">Godot</a>\"\n  ], [\n    \"2017-06-04\",\n    \"New documentations: <a href=\\\"/electron/\\\">Electron</a>, <a href=\\\"/pug/\\\">Pug</a>, and <a href=\\\"/falcon/\\\">Falcon</a>\"\n  ], [\n    \"2017-05-14\",\n    \"New documentations: <a href=\\\"/jest/\\\">Jest</a>, <a href=\\\"/jasmine/\\\">Jasmine</a> and <a href=\\\"/liquid/\\\">Liquid</a>\"\n  ], [\n    \"2017-04-30\",\n    \"New documentation: <a href=\\\"/openjdk/\\\">OpenJDK</a>\"\n  ], [\n    \"2017-02-26\",\n    \"Refreshed design.\",\n    \"Added <a href=\\\"/settings\\\">Preferences</a>.\"\n  ], [\n    \"2017-01-22\",\n    \"New <a href=\\\"/http/\\\">HTTP</a> documentation (thanks Mozilla)\"\n  ], [\n    \"2016-12-04\",\n    \"New documentations: <a href=\\\"/sqlite/\\\">SQLite</a>, <a href=\\\"/codeception/\\\">Codeception</a> and <a href=\\\"/codeceptjs/\\\">CodeceptJS</a>\"\n  ], [\n    \"2016-11-20\",\n    \"New documentations: <a href=\\\"/yarn/\\\">Yarn</a>, <a href=\\\"/immutable/\\\">Immutable.js</a> and <a href=\\\"/async/\\\">Async</a>\"\n  ], [\n    \"2016-10-10\",\n    \"New documentations: <a href=\\\"/scikit_learn/\\\">scikit-learn</a> and <a href=\\\"/statsmodels/\\\">Statsmodels</a>\"\n  ], [\n    \"2016-09-18\",\n    \"New documentations: <a href=\\\"/pandas/\\\">pandas</a> and <a href=\\\"/twig/\\\">Twig</a>\"\n  ], [\n    \"2016-09-05\",\n    \"New documentations: <a href=\\\"/fish/\\\">Fish</a>, <a href=\\\"/bottle/\\\">Bottle</a> and <a href=\\\"/scikit_image/\\\">scikit-image</a>\"\n  ], [\n    \"2016-08-07\",\n    \"New documentation: <a href=\\\"/docker/\\\">Docker</a>\"\n  ], [\n    \"2016-07-31\",\n    \"New documentations: <a href=\\\"/bootstrap~3/\\\">Bootstrap 3</a> and <a href=\\\"/bootstrap~4/\\\">Bootstrap 4</a>\"\n  ], [\n    \"2016-07-24\",\n    \"New documentations: <a href=\\\"/julia/\\\">Julia</a>, <a href=\\\"/crystal/\\\">Crystal</a> and <a href=\\\"/redux/\\\">Redux</a>\"\n  ], [\n    \"2016-07-03\",\n    \"New documentations: <a href=\\\"/cmake/\\\">CMake</a> and <a href=\\\"/matplotlib/\\\">Matplotlib</a>\"\n  ], [\n    \"2016-06-19\",\n    \"New documentation: <a href=\\\"/love/\\\">L&Ouml;VE</a>\"\n  ], [\n    \"2016-06-12\",\n    \"New documentation: <a href=\\\"/angular/\\\">Angular 2</a>\"\n  ], [\n    \"2016-06-05\",\n    \"New documentations: <a href=\\\"/kotlin/\\\">Kotlin</a> and <a href=\\\"/padrino/\\\">Padrino</a>\"\n  ], [\n    \"2016-04-24\",\n    \"New documentations: <a href=\\\"/numpy/\\\">NumPy</a> and <a href=\\\"/apache_pig/\\\">Apache Pig</a>\"\n  ], [\n    \"2016-04-17\",\n    \"New documentation: <a href=\\\"/perl/\\\">Perl</a>\"\n  ], [\n    \"2016-04-10\",\n    \"New documentations: <a href=\\\"/browser_support_tables/\\\">Support tables (caniuse.com)</a>, <a href=\\\"/gcc/\\\">GCC</a> and <a href=\\\"/gnu_fortran/\\\">GNU Fortran</a>\"\n  ], [\n    \"2016-03-27\",\n    \"New documentation: <a href=\\\"/typescript/\\\">TypeScript</a>\"\n  ], [\n    \"2016-03-06\",\n    \"New documentations: <a href=\\\"/tensorflow/\\\">TensorFlow</a>, <a href=\\\"/haxe/\\\">Haxe</a> and <a href=\\\"/ansible/\\\">Ansible</a>\"\n  ], [\n    \"2016-02-28\",\n    \"New documentations: <a href=\\\"/codeigniter/\\\">CodeIgniter</a>, <a href=\\\"/nginx_lua_module/\\\">nginx Lua Module</a> and <a href=\\\"/influxdata/\\\">InfluxData</a>\"\n  ], [\n    \"2016-02-15\",\n    \"New documentations: <a href=\\\"/cakephp/\\\">CakePHP</a>, <a href=\\\"/chef/\\\">Chef</a> and <a href=\\\"/ramda/\\\">Ramda</a>\"\n  ], [\n    \"2016-01-31\",\n    \"New documentations: <a href=\\\"/erlang/\\\">Erlang</a> and <a href=\\\"/tcl_tk/\\\">Tcl/Tk</a>\"\n  ], [\n    \"2016-01-24\",\n    \"&ldquo;Multi-version support&rdquo; has landed!\"\n  ], [\n    \"2015-11-22\",\n    \"New documentations: <a href=\\\"/phoenix/\\\">Phoenix</a>, <a href=\\\"/dojo/\\\">Dojo</a>, <a href=\\\"/relay/\\\">Relay</a> and <a href=\\\"/flow/\\\">Flow</a>\"\n  ], [\n    \"2015-11-08\",\n    \"New documentations: <a href=\\\"/elixir/\\\">Elixir</a> and <a href=\\\"/vagrant/\\\">Vagrant</a>\"\n  ], [\n    \"2015-10-18\",\n    \"Added a \\\"Copy to clipboard\\\" button inside each code block.\"\n  ], [\n    \"2015-09-13\",\n    \"New documentation: <a href=\\\"/phalcon/\\\">Phalcon</a>\"\n  ], [\n    \"2015-08-09\",\n    \"New documentation: <a href=\\\"/react_native/\\\">React Native</a>\"\n  ], [\n    \"2015-08-03\",\n    \"Added an icon in the sidebar to constrain the width of the UI (visible when applicable).\"\n  ], [\n    \"2015-08-02\",\n    \"New documentations: <a href=\\\"/q/\\\">Q</a> and <a href=\\\"/opentsdb/\\\">OpenTSDB</a>\"\n  ], [\n    \"2015-07-26\",\n    \"Added search aliases (e.g. <code class=\\\"_label\\\">$</code> is an alias for <code class=\\\"_label\\\">jQuery</code>).\\n<a href=\\\"/help#aliases\\\">Click here</a> to see the full list. Feel free to suggest more on <a href=\\\"https://github.com/freeCodeCamp/devdocs/issues/new\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\">GitHub</a>.\",\n    \"Added <code class=\\\"_label\\\">shift + &darr;/&uarr;</code> shortcut for scrolling (same as <code class=\\\"_label\\\">alt + &darr;/&uarr;</code>).\"\n  ], [\n    \"2015-07-05\",\n    \"New documentations: <a href=\\\"/drupal/\\\">Drupal</a>, <a href=\\\"/vue/\\\">Vue.js</a>, <a href=\\\"/phaser/\\\">Phaser</a> and <a href=\\\"/webpack/\\\">webpack</a>\"\n  ], [\n    \"2015-05-24\",\n    \"New <a href=\\\"/rust/\\\">Rust</a> documentation\"\n  ], [\n    \"2015-04-26\",\n    \"New <a href=\\\"/apache_http_server/\\\">Apache HTTP Server</a> and <a href=\\\"/npm/\\\">npm</a> documentations\"\n  ], [\n    \"2015-03-22\",\n    \"New <a href=\\\"/meteor/\\\">Meteor</a> and <a href=\\\"/mocha/\\\">mocha</a> documentations\"\n  ], [\n    \"2015-02-22\",\n    \"Improved <a href=\\\"/http/\\\">HTTP</a> documentation\",\n    \"New <a href=\\\"/minitest/\\\">Minitest</a> documentation\"\n  ], [\n    \"2015-02-16\",\n    \"The sidebar is now resizable (drag & drop).\"\n  ], [\n    \"2015-02-15\",\n    \"New <a href=\\\"/iojs/\\\">io.js</a>, <a href=\\\"/symfony/\\\">Symfony</a>, <a href=\\\"/clojure/\\\">Clojure</a>, <a href=\\\"/lua/\\\">Lua</a> and <a href=\\\"/yii1/\\\">Yii 1.1</a> documentations\"\n  ], [\n    \"2015-02-08\",\n    \"New dark theme\"\n  ], [\n    \"2015-01-13\",\n    \"<a href=\\\"/offline\\\">Offline mode</a> has landed!\"\n  ], [\n    \"2014-12-21\",\n    \"New <a href=\\\"/react/\\\">React</a>, <a href=\\\"/rethinkdb/\\\">RethinkDB</a>, <a href=\\\"/socketio/\\\">Socket.IO</a>, <a href=\\\"/modernizr/\\\">Modernizr</a> and <a href=\\\"/bower/\\\">Bower</a> documentations\"\n  ], [\n    \"2014-11-30\",\n    \"New <a href=\\\"/phpunit/\\\">PHPUnit</a> and <a href=\\\"/nokogiri/\\\">Nokogiri</a> documentations\"\n  ], [\n    \"2014-11-16\",\n    \"New <a href=\\\"/python2/\\\">Python 2</a> documentation\"\n  ], [\n    \"2014-11-09\",\n    \"New design\\nFeedback welcome on <a href=\\\"https://twitter.com/DevDocs\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\">Twitter</a> and <a href=\\\"https://github.com/freeCodeCamp/devdocs\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\">GitHub</a>.\"\n  ], [\n    \"2014-10-19\",\n    \"New <a href=\\\"/svg/\\\">SVG</a>, <a href=\\\"/marionette/\\\">Marionette.js</a>, and <a href=\\\"/mongoose/\\\">Mongoose</a> documentations\"\n  ], [\n    \"2014-10-18\",\n    \"New <a href=\\\"/nginx/\\\">nginx</a> documentation\"\n  ], [\n    \"2014-10-13\",\n    \"New <a href=\\\"/xpath/\\\">XPath</a> documentation\"\n  ], [\n    \"2014-09-07\",\n    \"Updated the HTML, CSS, JavaScript, and DOM documentations with additional content.\"\n  ], [\n    \"2014-08-04\",\n    \"New <a href=\\\"/django/\\\">Django</a> documentation\"\n  ], [\n    \"2014-07-27\",\n    \"New <a href=\\\"/markdown/\\\">Markdown</a> documentation\"\n  ], [\n    \"2014-07-05\",\n    \"New <a href=\\\"/cordova/\\\">Cordova</a> documentation\"\n  ], [\n    \"2014-07-01\",\n    \"New <a href=\\\"/chai/\\\">Chai</a> and <a href=\\\"/sinon/\\\">Sinon</a> documentations\"\n  ], [\n    \"2014-06-15\",\n    \"New <a href=\\\"/requirejs/\\\">RequireJS</a> documentation\"\n  ], [\n    \"2014-06-14\",\n    \"New <a href=\\\"/haskell/\\\">Haskell</a> documentation\"\n  ], [\n    \"2014-05-25\",\n    \"New <a href=\\\"/laravel/\\\">Laravel</a> documentation\"\n  ], [\n    \"2014-05-04\",\n    \"New <a href=\\\"/express/\\\">Express</a>, <a href=\\\"/grunt/\\\">Grunt</a>, and <a href=\\\"/maxcdn/\\\">MaxCDN</a> documentations\"\n  ], [\n    \"2014-04-06\",\n    \"New <a href=\\\"/go/\\\">Go</a> documentation\"\n  ], [\n    \"2014-03-30\",\n    \"New <a href=\\\"/cpp/\\\">C++</a> documentation\"\n  ], [\n    \"2014-03-16\",\n    \"New <a href=\\\"/yii/\\\">Yii</a> documentation\"\n  ], [\n    \"2014-03-08\",\n    \"Added path bar.\"\n  ], [\n    \"2014-02-22\",\n    \"New <a href=\\\"/c/\\\">C</a> documentation\"\n  ], [\n    \"2014-02-16\",\n    \"New <a href=\\\"/moment/\\\">Moment.js</a> documentation\"\n  ], [\n    \"2014-02-12\",\n    \"The root/category pages are now included in the search index (e.g. <a href=\\\"/#q=CSS\\\">CSS</a>)\"\n  ], [\n    \"2014-01-19\",\n    \"New <a href=\\\"/d3/\\\">D3.js</a> and <a href=\\\"/knockout/\\\">Knockout.js</a> documentations\"\n  ], [\n    \"2014-01-18\",\n    \"DevDocs is now available as a <a href=\\\"https://marketplace.firefox.com/app/devdocs/\\\">Firefox web app</a>.\"\n  ], [\n    \"2014-01-12\",\n    \"Added <code class=\\\"_label\\\">alt + g</code> shortcut for searching on Google.\",\n    \"Added <code class=\\\"_label\\\">alt + r</code> shortcut for revealing the current page in the sidebar.\"\n  ], [\n    \"2013-12-14\",\n    \"New <a href=\\\"/postgresql/\\\">PostgreSQL</a> documentation\"\n  ], [\n    \"2013-12-13\",\n    \"New <a href=\\\"/git/\\\">Git</a> and <a href=\\\"/redis/\\\">Redis</a> documentations\"\n  ], [\n    \"2013-11-26\",\n    \"New <a href=\\\"/python/\\\">Python</a> documentation\"\n  ], [\n    \"2013-11-19\",\n    \"New <a href=\\\"/rails/\\\">Ruby on Rails</a> documentation\"\n  ], [\n    \"2013-11-16\",\n    \"New <a href=\\\"/ruby/\\\">Ruby</a> documentation\"\n  ], [\n    \"2013-10-24\",\n    \"DevDocs is now <a href=\\\"https://github.com/freeCodeCamp/devdocs\\\">open source</a>.\"\n  ], [\n    \"2013-10-09\",\n    \"DevDocs is now available as a <a href=\\\"https://chrome.google.com/webstore/detail/devdocs/mnfehgbmkapmjnhcnbodoamcioleeooe\\\">Chrome web app</a>.\"\n  ], [\n    \"2013-09-22\",\n    \"New <a href=\\\"/php/\\\">PHP</a> documentation\"\n  ], [\n    \"2013-09-06\",\n    \"New <a href=\\\"/lodash/\\\">Lo-Dash</a> documentation \",\n    \"On mobile devices you can now search a specific documentation by typing its name and <code class=\\\"_label\\\">Space</code>.\"\n  ], [\n    \"2013-09-01\",\n    \"New <a href=\\\"/jqueryui/\\\">jQuery UI</a> and <a href=\\\"/jquerymobile/\\\">jQuery Mobile</a> documentations\"\n  ], [\n    \"2013-08-28\",\n    \"New smartphone interface\\nTested on iOS 6+ and Android 4.1+\"\n  ], [\n    \"2013-08-25\",\n    \"New <a href=\\\"/ember/\\\">Ember.js</a> documentation\"\n  ], [\n    \"2013-08-18\",\n    \"New <a href=\\\"/coffeescript/\\\">CoffeeScript</a> documentation\",\n    \"URL search now automatically opens the first result.\"\n  ], [\n    \"2013-08-13\",\n    \"New <a href=\\\"/angularjs/\\\">Angular.js</a> documentation\"\n  ], [\n    \"2013-08-11\",\n    \"New <a href=\\\"/sass/\\\">Sass</a> and <a href=\\\"/less/\\\">Less</a> documentations\"\n  ], [\n    \"2013-08-05\",\n    \"New <a href=\\\"/node/\\\">Node.js</a> documentation\"\n  ], [\n    \"2013-08-03\",\n    \"Added support for OpenSearch\"\n  ], [\n    \"2013-07-30\",\n    \"New <a href=\\\"/backbone/\\\">Backbone.js</a> documentation\"\n  ], [\n    \"2013-07-27\",\n    \"You can now customize the list of documentations.\\nNew docs will be hidden by default, but you'll see a notification when there are new releases.\",\n    \"New <a href=\\\"/http/\\\">HTTP</a> documentation\"\n  ], [\n    \"2013-07-15\",\n    \"URL search now works with single documentations: <a href=\\\"/#q=js%20sort\\\">devdocs.io/#q=js sort</a>\"\n  ], [\n    \"2013-07-13\",\n    \"Added syntax highlighting\",\n    \"Added documentation versions\"\n  ], [\n    \"2013-07-11\",\n    \"New <a href=\\\"/underscore/\\\">Underscore.js</a> documentation \",\n    \"Improved compatibility with tablets\\nA mobile version is planned as soon as other high priority features have been implemented.\"\n  ], [\n    \"2013-07-10\",\n    \"You can now search specific documentations.\\nSimply type the documentation's name and press <code class=\\\"_label\\\">Tab</code>.\\nThe name is fuzzy matched so you can use abbreviations like <code>js</code> for <code>JavaScript</code>.\"\n  ], [\n    \"2013-07-08\",\n    \"Improved search with fuzzy matching and better results\\nFor example, searching <code>jqmka</code> now returns <code>jQuery.makeArray()</code>.\",\n    \"DevDocs finally has an icon.\",\n    \"<code class=\\\"_label\\\">space</code> has replaced <code class=\\\"_label\\\">alt + space</code> for scrolling down.\"\n  ], [\n    \"2013-07-06\",\n    \"New <a href=\\\"/dom/\\\">DOM</a> and <a href=\\\"/dom_events/\\\">DOM Events</a> documentations\\nDevDocs now includes almost all reference documents available on the Mozilla Developer Network.\\nBig thank you to Mozilla and all the people that contributed to MDN.\",\n    \"Implemented URL search: <a href=\\\"/#q=sort\\\">devdocs.io/#q=sort</a>\"\n  ], [\n    \"2013-07-02\",\n    \"New <a href=\\\"/javascript/\\\">JavaScript</a> documentation\"\n  ], [\n    \"2013-06-28\",\n    \"DevDocs made the front page of Hacker News!\\nHi everyone &mdash; thanks for trying DevDocs.\\nPlease bear with me while I fix bugs and scramble to add more docs.\\nThis is only v1. There's a lot more to come.\"\n  ], [\n    \"2013-06-18\",\n    \"Initial release\"\n  ]\n]\n"
  },
  {
    "path": "assets/javascripts/templates/base.js",
    "content": "app.templates.render = function (name, value, ...args) {\n  const template = app.templates[name];\n\n  if (Array.isArray(value)) {\n    let result = \"\";\n    for (var val of value) {\n      result += template(val, ...args);\n    }\n    return result;\n  } else if (typeof template === \"function\") {\n    return template(value, ...args);\n  } else {\n    return template;\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/templates/error_tmpl.js",
    "content": "const error = function (title, text, links) {\n  if (text == null) {\n    text = \"\";\n  }\n  if (links == null) {\n    links = \"\";\n  }\n  if (text) {\n    text = `<p class=\"_error-text\">${text}</p>`;\n  }\n  if (links) {\n    links = `<p class=\"_error-links\">${links}</p>`;\n  }\n  return `<div class=\"_error\"><h1 class=\"_error-title\">${title}</h1>${text}${links}</div>`;\n};\n\nconst back = '<a href=\"#\" data-behavior=\"back\" class=\"_error-link\">Go back</a>';\n\napp.templates.notFoundPage = () =>\n  error(\n    \" Page not found. \",\n    \" It may be missing from the source documentation or this could be a bug. \",\n    back,\n  );\n\napp.templates.pageLoadError = () =>\n  error(\n    \" The page failed to load. \",\n    ` It may be missing from the server (try reloading the app) or you could be offline (try <a href=\"/offline\">installing the documentation for offline usage</a> when online again).<br>\nIf you're online and you keep seeing this, you're likely behind a proxy or firewall that blocks cross-domain requests. `,\n    ` ${back} &middot; <a href=\"/#${location.pathname}\" target=\"_top\" class=\"_error-link\">Reload</a>\n&middot; <a href=\"#\" class=\"_error-link\" data-retry>Retry</a> `,\n  );\n\napp.templates.bootError = () =>\n  error(\n    \" The app failed to load. \",\n    ` Check your Internet connection and try <a href=\"#\" data-behavior=\"reload\">reloading</a>.<br>\nIf you keep seeing this, you're likely behind a proxy or firewall that blocks cross-domain requests. `,\n  );\n\napp.templates.offlineError = function (reason, exception) {\n  if (reason === \"cookie_blocked\") {\n    return error(\" Cookies must be enabled to use offline mode. \");\n  }\n\n  reason = (() => {\n    switch (reason) {\n      case \"not_supported\":\n        return ` DevDocs requires IndexedDB to cache documentations for offline access.<br>\nUnfortunately your browser either doesn't support IndexedDB or doesn't make it available. `;\n      case \"buggy\":\n        return ` DevDocs requires IndexedDB to cache documentations for offline access.<br>\nUnfortunately your browser's implementation of IndexedDB contains bugs that prevent DevDocs from using it. `;\n      case \"private_mode\":\n        return ` Your browser appears to be running in private mode.<br>\nThis prevents DevDocs from caching documentations for offline access.`;\n      case \"exception\":\n        return ` An error occurred when trying to open the IndexedDB database:<br>\n<code class=\"_label\">${exception.name}: ${exception.message}</code> `;\n      case \"cant_open\":\n        return ` An error occurred when trying to open the IndexedDB database:<br>\n<code class=\"_label\">${exception.name}: ${exception.message}</code><br>\nThis could be because you're browsing in private mode or have disallowed offline storage on the domain. `;\n      case \"version\":\n        return ` The IndexedDB database was modified with a newer version of the app.<br>\n<a href=\"#\" data-behavior=\"reload\">Reload the page</a> to use offline mode. `;\n      case \"empty\":\n        return ' The IndexedDB database appears to be corrupted. Try <a href=\"#\" data-behavior=\"reset\">resetting the app</a>. ';\n    }\n  })();\n\n  return error(\"Offline mode is unavailable.\", reason);\n};\n\napp.templates.unsupportedBrowser = `\\\n<div class=\"_fail\">\n  <h1 class=\"_fail-title\">Your browser is unsupported, sorry.</h1>\n  <p class=\"_fail-text\">DevDocs is an API documentation browser which supports the following browsers:\n  <ul class=\"_fail-list\">\n    <li>Recent versions of Firefox, Chrome, or Opera\n    <li>Safari 11.1+\n    <li>Edge 17+\n    <li>iOS 11.3+\n  </ul>\n  <p class=\"_fail-text\">\n    If you're unable to upgrade, we apologize.\n    We decided to prioritize speed and new features over support for older browsers.\n  <p class=\"_fail-text\">\n    Note: if you're already using one of the browsers above, check your settings and add-ons.\n    The app uses feature detection, not user agent sniffing.\n  <p class=\"_fail-text\">\n    &mdash; <a href=\"https://twitter.com/DevDocs\">@DevDocs</a>\n</div>\\\n`;\n"
  },
  {
    "path": "assets/javascripts/templates/notice_tmpl.js",
    "content": "const notice = (text) => `<p class=\"_notice-text\">${text}</p>`;\n\napp.templates.singleDocNotice = (doc) =>\n  notice(` You're browsing the ${doc.fullName} documentation. To browse all docs, go to\n<a href=\"//${app.config.production_host}\" target=\"_top\">${app.config.production_host}</a> (or press <code>esc</code>). `);\n\napp.templates.disabledDocNotice = () =>\n  notice(` <strong>This documentation is disabled.</strong>\nTo enable it, go to <a href=\"/settings\" class=\"_notice-link\">Preferences</a>. `);\n"
  },
  {
    "path": "assets/javascripts/templates/notif_tmpl.js",
    "content": "const notif = function (title, html) {\n  html = html.replace(/<a /g, '<a class=\"_notif-link\" ');\n  return ` <h5 class=\"_notif-title\">${title}</h5>\n${html}\n<button type=\"button\" class=\"_notif-close\" title=\"Close\"><svg><use xlink:href=\"#icon-close\"/></svg>Close</a>\\\n`;\n};\n\nconst textNotif = (title, message) =>\n  notif(title, `<p class=\"_notif-text\">${message}`);\n\napp.templates.notifUpdateReady = () =>\n  textNotif(\n    '<span data-behavior=\"reboot\">DevDocs has been updated.</span>',\n    '<span data-behavior=\"reboot\"><a href=\"#\" data-behavior=\"reboot\">Reload the page</a> to use the new version.</span>',\n  );\n\napp.templates.notifError = () =>\n  textNotif(\n    \" Oops, an error occurred. \",\n    ` Try <a href=\"#\" data-behavior=\"hard-reload\">reloading</a>, and if the problem persists,\n<a href=\"#\" data-behavior=\"reset\">resetting the app</a>.<br>\nYou can also report this issue on <a href=\"https://github.com/freeCodeCamp/devdocs/issues/new\" target=\"_blank\" rel=\"noopener\">GitHub</a>. `,\n  );\n\napp.templates.notifQuotaExceeded = () =>\n  textNotif(\n    \" The offline database has exceeded its size limitation. \",\n    \" Unfortunately this quota can't be detected programmatically, and the database can't be opened while over the quota, so it had to be reset. \",\n  );\n\napp.templates.notifCookieBlocked = () =>\n  textNotif(\n    \" Please enable cookies. \",\n    \" DevDocs will not work properly if cookies are disabled. \",\n  );\n\napp.templates.notifInvalidLocation = () =>\n  textNotif(\n    ` DevDocs must be loaded from ${app.config.production_host} `,\n    \" Otherwise things are likely to break. \",\n  );\n\napp.templates.notifImportInvalid = () =>\n  textNotif(\n    \" Oops, an error occurred. \",\n    \" The file you selected is invalid. \",\n  );\n\napp.templates.notifNews = (news) =>\n  notif(\n    \"Changelog\",\n    `<div class=\"_notif-content _notif-news\">${app.templates.newsList(news, {\n      years: false,\n    })}</div>`,\n  );\n\napp.templates.notifUpdates = function (docs, disabledDocs) {\n  let doc;\n  let html = '<div class=\"_notif-content _notif-news\">';\n\n  if (docs.length > 0) {\n    html += '<div class=\"_news-row\">';\n    html += '<ul class=\"_notif-list\">';\n    for (doc of docs) {\n      html += `<li>${doc.name}`;\n      if (doc.release) {\n        html += ` <code>&rarr;</code> ${doc.release}`;\n      }\n    }\n    html += \"</ul></div>\";\n  }\n\n  if (disabledDocs.length > 0) {\n    html += '<div class=\"_news-row\"><p class=\"_news-title\">Disabled:';\n    html += '<ul class=\"_notif-list\">';\n    for (doc of disabledDocs) {\n      html += `<li>${doc.name}`;\n      if (doc.release) {\n        html += ` <code>&rarr;</code> ${doc.release}`;\n      }\n      html += '<span class=\"_notif-info\"><a href=\"/settings\">Enable</a></span>';\n    }\n    html += \"</ul></div>\";\n  }\n\n  return notif(\"Updates\", `${html}</div>`);\n};\n\napp.templates.notifShare = () =>\n  textNotif(\n    \" Hi there! \",\n    ` Like DevDocs? Help us reach more developers by sharing the link with your friends on\n<a href=\"https://out.devdocs.io/s/tw\" target=\"_blank\" rel=\"noopener\">Twitter</a>, <a href=\"https://out.devdocs.io/s/fb\" target=\"_blank\" rel=\"noopener\">Facebook</a>,\n<a href=\"https://out.devdocs.io/s/re\" target=\"_blank\" rel=\"noopener\">Reddit</a>, etc.<br>Thanks :) `,\n  );\n\napp.templates.notifUpdateDocs = () =>\n  textNotif(\n    \" Documentation updates available. \",\n    ' <a href=\"/offline\">Install them</a> as soon as possible to avoid broken pages. ',\n  );\n\napp.templates.notifAnalyticsConsent = () =>\n  textNotif(\n    \" Tracking cookies \",\n    ` We would like to gather usage data about how DevDocs is used through Google Analytics and Gauges. We only collect anonymous traffic information.\nPlease confirm if you accept our tracking cookies. You can always change your decision in the settings.\n<br><span class=\"_notif-right\"><a href=\"#\" data-behavior=\"accept-analytics\">Accept</a> or <a href=\"#\" data-behavior=\"decline-analytics\">Decline</a></span> `,\n  );\n"
  },
  {
    "path": "assets/javascripts/templates/pages/about_tmpl.js",
    "content": "app.templates.aboutPage = function () {\n  let doc;\n  const all_docs = app.docs.all().concat(...(app.disabledDocs.all() || []));\n  // de-duplicate docs by doc.name\n  const docs = [];\n  for (doc of all_docs) {\n    if (!docs.find((d) => d.name === doc.name)) {\n      docs.push(doc);\n    }\n  }\n  return `\\\n<nav class=\"_toc\" role=\"directory\">\n  <h3 class=\"_toc-title\">Table of Contents</h3>\n  <ul class=\"_toc-list\">\n    <li><a href=\"#copyright\">Copyright</a>\n    <li><a href=\"#plugins\">Plugins</a>\n    <li><a href=\"#faq\">FAQ</a>\n    <li><a href=\"#credits\">Credits</a>\n    <li><a href=\"#privacy\">Privacy Policy</a>\n  </ul>\n</nav>\n\n<h1 class=\"_lined-heading\">DevDocs: API Documentation Browser</h1>\n<p>DevDocs combines multiple developer documentations in a clean and organized web UI with instant search, offline support, mobile version, dark theme, keyboard shortcuts, and more.\n<p>DevDocs is free and <a href=\"https://github.com/freeCodeCamp/devdocs\">open source</a>. It was created by <a href=\"https://thibaut.me\">Thibaut Courouble</a> and is operated by <a href=\"https://www.freecodecamp.org/\">freeCodeCamp</a>.\n<p>To keep up-to-date with the latest news:\n<ul>\n  <li>Follow <a href=\"https://twitter.com/DevDocs\">@DevDocs</a> on Twitter\n  <li>Watch the repository on <a href=\"https://github.com/freeCodeCamp/devdocs/subscription\">GitHub</a> <iframe class=\"_github-btn\" src=\"https://ghbtns.com/github-btn.html?user=freeCodeCamp&repo=devdocs&type=watch&count=true\" allowtransparency=\"true\" frameborder=\"0\" scrolling=\"0\" width=\"100\" height=\"20\" tabindex=\"-1\"></iframe>\n  <li>Join the <a href=\"https://discord.gg/PRyKn3Vbay\">Discord</a> chat room\n</ul>\n\n<h2 class=\"_block-heading\" id=\"copyright\">Copyright and License</h2>\n<p class=\"_note\">\n  <strong>Copyright 2013–2026 Thibaut Courouble and <a href=\"https://github.com/freeCodeCamp/devdocs/graphs/contributors\">other contributors</a></strong><br>\n  This software is licensed under the terms of the Mozilla Public License v2.0.<br>\n  You may obtain a copy of the source code at <a href=\"https://github.com/freeCodeCamp/devdocs\">github.com/freeCodeCamp/devdocs</a>.<br>\n  For more information, see the <a href=\"https://github.com/freeCodeCamp/devdocs/blob/main/COPYRIGHT\">COPYRIGHT</a>\n  and <a href=\"https://github.com/freeCodeCamp/devdocs/blob/main/LICENSE\">LICENSE</a> files.\n\n<h2 class=\"_block-heading\" id=\"plugins\">Plugins and Extensions</h2>\n<ul>\n  <li><a href=\"https://sublime.wbond.net/packages/DevDocs\">Sublime Text package</a>\n  <li><a href=\"https://atom.io/packages/devdocs\">Atom package</a>\n  <li><a href=\"https://marketplace.visualstudio.com/items?itemName=deibit.devdocs\">Visual Studio Code extension</a>\n  <li><a href=\"https://github.com/yannickglt/alfred-devdocs\">Alfred workflow</a>\n  <li><a href=\"https://github.com/search?q=topic%3Adevdocs&type=Repositories\">More…</a>\n</ul>\n\n<h2 class=\"_block-heading\" id=\"faq\">Questions & Answers</h2>\n<dl>\n  <dt>Where can I suggest new docs and features?\n  <dd>You can suggest and vote for new docs on the <a href=\"https://trello.com/b/6BmTulfx/devdocs-documentation\">Trello board</a>.<br>\n      If you have a specific feature request, add it to the <a href=\"https://github.com/freeCodeCamp/devdocs/issues\">issue tracker</a>.<br>\n      Otherwise, come talk to us in the <a href=\"https://discord.gg/PRyKn3Vbay\">Discord</a> chat room.\n  <dt>Where can I report bugs?\n  <dd>In the <a href=\"https://github.com/freeCodeCamp/devdocs/issues\">issue tracker</a>. Thanks!\n</dl>\n\n<h2 class=\"_block-heading\" id=\"credits\">Credits</h2>\n\n<p><strong>Special thanks to:</strong>\n<ul>\n  <li><a href=\"https://sentry.io/\">Sentry</a> and <a href=\"https://get.gaug.es/?utm_source=devdocs&utm_medium=referral&utm_campaign=sponsorships\" title=\"Real Time Web Analytics\">Gauges</a> for offering a free account to DevDocs\n  <li><a href=\"https://out.devdocs.io/s/maxcdn\">MaxCDN</a>, <a href=\"https://out.devdocs.io/s/shopify\">Shopify</a>, <a href=\"https://out.devdocs.io/s/jetbrains\">JetBrains</a> and <a href=\"https://out.devdocs.io/s/code-school\">Code School</a> for sponsoring DevDocs in the past\n  <li><a href=\"https://www.heroku.com\">Heroku</a> and <a href=\"https://newrelic.com/\">New Relic</a> for providing awesome free service\n  <li><a href=\"https://www.jeremykratz.com/\">Jeremy Kratz</a> for the C/C++ logo\n</ul>\n\n<div class=\"_table\">\n  <table class=\"_credits\">\n    <tr>\n      <th>Documentation\n      <th>Copyright/License\n      <th>Source code\n    ${docs\n      .map(\n        (doc) =>\n          `<tr><td><a href=\"${doc.links?.home}\">${doc.name}</a></td><td>${doc.attribution}</td><td><a href=\"${doc.links?.code}\">Source code</a></td></tr>`,\n      )\n      .join(\"\")}\n  </table>\n</div>\n\n<h2 class=\"_block-heading\" id=\"privacy\">Privacy Policy</h2>\n<ul>\n  <li><a href=\"https://devdocs.io\">devdocs.io</a> (\"App\") is operated by <a href=\"https://www.freecodecamp.org/\">freeCodeCamp</a> (\"We\").\n  <li>We do not collect personal information through the app.\n  <li>We use Google Analytics and Gauges to collect anonymous traffic information if you have given consent to this. You can change your decision in the <a href=\"/settings\">settings</a>.\n  <li>We use Sentry to collect crash data and improve the app.\n  <li>The app uses cookies to store user preferences.\n  <li>By using the app, you signify your acceptance of this policy. If you do not agree to this policy, please do not use the app.\n  <li>If you have any questions regarding privacy, please email <a href=\"mailto:privacy@freecodecamp.org\">privacy@freecodecamp.org</a>.\n</ul>\\\n`;\n};\n"
  },
  {
    "path": "assets/javascripts/templates/pages/help_tmpl.js",
    "content": "app.templates.helpPage = function () {\n  const ctrlKey = $.isMac() ? \"cmd\" : \"ctrl\";\n  const navKey = $.isMac() ? \"cmd\" : \"alt\";\n  const arrowScroll = app.settings.get(\"arrowScroll\");\n\n  const aliases = Object.entries(app.config.docs_aliases);\n  const middle = Math.ceil(aliases.length / 2);\n  const aliases_one = aliases.slice(0, middle);\n  const aliases_two = aliases.slice(middle);\n\n  return `\\\n<nav class=\"_toc\" role=\"directory\">\n  <h3 class=\"_toc-title\">Table of Contents</h3>\n  <ul class=\"_toc-list\">\n    <li><a href=\"#managing-documentations\">Managing Documentations</a>\n    <li><a href=\"#search\">Search</a>\n    <li><a href=\"#shortcuts\">Keyboard Shortcuts</a>\n    <li><a href=\"#aliases\">Search Aliases</a>\n  </ul>\n</nav>\n\n<h1 class=\"_lined-heading\">User Guide</h1>\n\n<h2 class=\"_block-heading\" id=\"managing-documentations\">Managing Documentations</h2>\n<p>\n  Documentations can be enabled and disabled in the <a href=\"/settings\">Preferences</a>.\n  Alternatively, you can enable a documentation by searching for it in the main search\n  and clicking the \"Enable\" link in the results.\n  For faster and better search, only enable the documentations you plan on actively using.\n<p>\n  Once a documentation is enabled, it becomes part of the search and its content can be downloaded for offline access — and faster page loads when online — in the <a href=\"/offline\">Offline</a> area.\n\n<h2 class=\"_block-heading\" id=\"search\">Search</h2>\n<p>\n  The search is case-insensitive and ignores whitespace. It supports fuzzy matching\n  (e.g. <code class=\"_label\">bgcp</code> matches <code class=\"_label\">background-clip</code>)\n  as well as aliases (full list <a href=\"#aliases\">below</a>).\n<dl>\n  <dt id=\"doc_search\">Searching a single documentation\n  <dd>\n    The search can be scoped to a single documentation by typing its name (or an abbreviation)\n    and pressing <code class=\"_label\">tab</code> (<code class=\"_label\">space</code>&nbsp;on mobile).\n    For example, to search the JavaScript documentation, enter <code class=\"_label\">javascript</code>\n    or <code class=\"_label\">js</code>, then <code class=\"_label\">tab</code>.<br>\n    To clear the current scope, empty the search field and hit <code class=\"_label\">backspace</code> or\n    <code class=\"_label\">esc</code>.\n  <dt id=\"url_search\">Prefilling the search field\n  <dd>\n    The search can be prefilled from the URL by visiting <a href=\"/#q=keyword\" target=\"_top\">devdocs.io/#q=keyword</a>.\n    Characters after <code class=\"_label\">#q=</code> will be used as search query.<br>\n    To search a single documentation, add its name (or an abbreviation) and a space before the keyword:\n    <a href=\"/#q=js%20date\" target=\"_top\">devdocs.io/#q=js date</a>.\n  <dt id=\"browser_search\">Searching using the address bar\n  <dd>\n    DevDocs supports OpenSearch. It can easily be installed as a search engine on most web browsers:\n    <ul>\n      <li>On Chrome, the setup is done automatically. Simply press <code class=\"_label\">tab</code> when devdocs.io is autocompleted\n          in the omnibox (to set a custom keyword, click <em>Manage search engines\\u2026</em> in Chrome's settings).\n      <li>On Firefox, <a href=\"https://support.mozilla.org/en-US/kb/add-or-remove-search-engine-firefox#w_add-a-search-engine-from-the-address-bar\">add the search from the address bar</a>:\n          Click ••• in the address bar, and select <em>Add Search Engine</em>. Then, you can add a keyword for this search engine in the preferences.\n</dl>\n<p>\n  <i>Note: the above search features only work for documentations that are enabled.</i>\n\n<h2 class=\"_block-heading\" id=\"shortcuts\">Keyboard Shortcuts</h2>\n<h3 class=\"_shortcuts-title\">Sidebar</h3>\n<dl class=\"_shortcuts-dl\">\n  <dt class=\"_shortcuts-dt\">\n    ${arrowScroll ? '<code class=\"_shortcut-code\">shift</code> + ' : \"\"}\n    <code class=\"_shortcut-code\">&darr;</code>\n    <code class=\"_shortcut-code\">&uarr;</code>\n  <dd class=\"_shortcuts-dd\">Move selection\n  <dt class=\"_shortcuts-dt\">\n    ${arrowScroll ? '<code class=\"_shortcut-code\">shift</code> + ' : \"\"}\n    <code class=\"_shortcut-code\">&rarr;</code>\n    <code class=\"_shortcut-code\">&larr;</code>\n  <dd class=\"_shortcuts-dd\">Show/hide sub-list\n  <dt class=\"_shortcuts-dt\">\n    <code class=\"_shortcut-code\">enter</code>\n  <dd class=\"_shortcuts-dd\">Open selection\n  <dt class=\"_shortcuts-dt\">\n    <code class=\"_shortcut-code\">${ctrlKey} + enter</code>\n  <dd class=\"_shortcuts-dd\">Open selection in a new tab\n  <dt class=\"_shortcuts-dt\">\n    <code class=\"_shortcut-code\">alt + r</code>\n  <dd class=\"_shortcuts-dd\">Reveal current page in sidebar\n</dl>\n<h3 class=\"_shortcuts-title\">Browsing</h3>\n<dl class=\"_shortcuts-dl\">\n  <dt class=\"_shortcuts-dt\">\n    <code class=\"_shortcut-code\">${navKey} + &larr;</code>\n    <code class=\"_shortcut-code\">${navKey} + &rarr;</code>\n  <dd class=\"_shortcuts-dd\">Go back/forward\n  <dt class=\"_shortcuts-dt\">\n    ${\n      arrowScroll\n        ? '<code class=\"_shortcut-code\">&darr;</code> ' +\n          '<code class=\"_shortcut-code\">&uarr;</code>'\n        : '<code class=\"_shortcut-code\">alt + &darr;</code> ' +\n          '<code class=\"_shortcut-code\">alt + &uarr;</code>' +\n          \"<br>\" +\n          '<code class=\"_shortcut-code\">shift + &darr;</code> ' +\n          '<code class=\"_shortcut-code\">shift + &uarr;</code>'\n    }\n  <dd class=\"_shortcuts-dd\">Scroll step by step<br><br>\n  <dt class=\"_shortcuts-dt\">\n    <code class=\"_shortcut-code\">space</code>\n    <code class=\"_shortcut-code\">shift + space</code>\n  <dd class=\"_shortcuts-dd\">Scroll screen by screen\n  <dt class=\"_shortcuts-dt\">\n    <code class=\"_shortcut-code\">${ctrlKey} + &uarr;</code>\n    <code class=\"_shortcut-code\">${ctrlKey} + &darr;</code>\n  <dd class=\"_shortcuts-dd\">Scroll to the top/bottom\n  <dt class=\"_shortcuts-dt\">\n    <code class=\"_shortcut-code\">alt + f</code>\n  <dd class=\"_shortcuts-dd\">Focus first link in the content area<br>(press tab to focus the other links)\n</dl>\n<h3 class=\"_shortcuts-title\">App</h3>\n<dl class=\"_shortcuts-dl\">\n  <dt class=\"_shortcuts-dt\">\n    <code class=\"_shortcut-code\">ctrl + ,</code>\n  <dd class=\"_shortcuts-dd\">Open preferences\n  <dt class=\"_shortcuts-dt\">\n    <code class=\"_shortcut-code\">esc</code>\n  <dd class=\"_shortcuts-dd\">Clear search field / reset UI\n  <dt class=\"_shortcuts-dt\">\n    <code class=\"_shortcut-code\">?</code>\n  <dd class=\"_shortcuts-dd\">Show this page\n</dl>\n<h3 class=\"_shortcuts-title\">Miscellaneous</h3>\n<dl class=\"_shortcuts-dl\">\n  <dt class=\"_shortcuts-dt\">\n    <code class=\"_shortcut-code\">alt + c</code>\n  <dd class=\"_shortcuts-dd\">Copy URL of original page\n  <dt class=\"_shortcuts-dt\">\n    <code class=\"_shortcut-code\">alt + o</code>\n  <dd class=\"_shortcuts-dd\">Open original page\n  <dt class=\"_shortcuts-dt\">\n    <code class=\"_shortcut-code\">alt + g</code>\n  <dd class=\"_shortcuts-dd\">Search on Google\n  <dt class=\"_shortcuts-dt\">\n    <code class=\"_shortcut-code\">alt + s</code>\n  <dd class=\"_shortcuts-dd\">Search on Stack Overflow\n  <dt class=\"_shortcuts-dt\">\n    <code class=\"_shortcut-code\">alt + d</code>\n  <dd class=\"_shortcuts-dd\">Search on DuckDuckGo\n</dl>\n<p class=\"_note _note-green\">\n  <strong>Tip:</strong> If the cursor is no longer in the search field, press <code class=\"_label\">/</code> or\n  continue to type and it will refocus the search field and start showing new results.\n\n<h2 class=\"_block-heading\" id=\"aliases\">Search Aliases</h2>\n<div class=\"_aliases\">\n  <table>\n    <tr>\n      <th>Word\n      <th>Alias\n    ${aliases_one\n      .map(\n        ([key, value]) =>\n          `<tr><td class=\\\"_code\\\">${key}<td class=\\\"_code\\\">${value}`,\n      )\n      .join(\"\")}\n  </table>\n  <table>\n    <tr>\n      <th>Word\n      <th>Alias\n      ${aliases_two\n        .map(\n          ([key, value]) =>\n            `<tr><td class=\\\"_code\\\">${key}<td class=\\\"_code\\\">${value}`,\n        )\n        .join(\"\")}\n  </table>\n</div>\n<p>Feel free to suggest new aliases on <a href=\"https://github.com/freeCodeCamp/devdocs/issues/new\">GitHub</a>.\\\n`;\n};\n"
  },
  {
    "path": "assets/javascripts/templates/pages/news_tmpl.js.erb",
    "content": "//= depend_on news.json\n\napp.templates.newsPage = () => ` <h1 class=\"_lined-heading\">Changelog</h1>\n<p class=\"_note\">\nFor the latest news, follow <a href=\"https://twitter.com/DevDocs\">@DevDocs</a>.<br>\nFor development updates, follow the project on <a href=\"https://github.com/freeCodeCamp/devdocs\">GitHub</a>.\n<div class=\"_news\">${app.templates.newsList(app.news)}</div> `;\n\napp.templates.newsList = function(news, options = {}) {\n  let year = new Date().getUTCFullYear();\n  let result = '';\n\n  for (let value of news) {\n    const date = new Date(value[0]);\n    if ((options.years !== false) && (year !== date.getUTCFullYear())) {\n      year = date.getUTCFullYear();\n      result += `<h2 class=\"_block-heading\">${year}</h2>`;\n    }\n    result += newsItem(date, value.slice(1));\n  }\n\n  return result;\n};\n\nconst MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];\n\nvar newsItem = function(date, news) {\n  date = `<span class=\"_news-date\">${MONTHS[date.getUTCMonth()]} ${date.getUTCDate()}</span>`;\n  let result = '';\n\n  for (let i = 0; i < news.length; i++) {\n    let text = news[i];\n    text = text.split(\"\\n\");\n    const title = `<span class=\"_news-title\">${text.shift()}</span>`;\n    result += `<div class=\"_news-row\">${i === 0 ? date : ''} ${title} ${text.join('<br>')}</div>`;\n  }\n\n  return result;\n};\n\napp.news = <%= App.news.to_json %>\n"
  },
  {
    "path": "assets/javascripts/templates/pages/offline_tmpl.js",
    "content": "app.templates.offlinePage = (docs) => `\\\n<h1 class=\"_lined-heading\">Offline Documentation</h1>\n\n<div class=\"_docs-tools\">\n  <label>\n    <input type=\"checkbox\" name=\"autoUpdate\" value=\"1\" ${\n      app.settings.get(\"manualUpdate\") ? \"\" : \"checked\"\n    }>Install updates automatically\n  </label>\n  <div class=\"_docs-links\">\n    <button type=\"button\" class=\"_btn-link\" data-action-all=\"install\">Install all</button><button type=\"button\" class=\"_btn-link\" data-action-all=\"update\"><strong>Update all</strong></button><button type=\"button\" class=\"_btn-link\" data-action-all=\"uninstall\">Uninstall all</button>\n  </div>\n</div>\n\n<div class=\"_table\">\n  <table class=\"_docs\">\n    <tr>\n      <th>Documentation</th>\n      <th class=\"_docs-size\">Size</th>\n      <th>Status</th>\n      <th>Action</th>\n    </tr>\n    ${docs}\n  </table>\n</div>\n<p class=\"_note\"><strong>Note:</strong> your browser may delete DevDocs's offline data if your computer is running low on disk space and you haven't used the app in a while. Load this page before going offline to make sure the data is still there.\n<h2 class=\"_block-heading\">Questions & Answers</h2>\n<dl>\n  <dt>How does this work?\n  <dd>Each page is cached as a key-value pair in <a href=\"https://devdocs.io/dom/indexeddb_api\">IndexedDB</a> (downloaded from a single file).<br>\n      The app also uses <a href=\"https://devdocs.io/dom/service_worker_api/using_service_workers\">Service Workers</a> and <a href=\"https://devdocs.io/dom/web_storage_api\">localStorage</a> to cache the assets and index files.\n  <dt>Can I close the tab/browser?\n  <dd>${canICloseTheTab()}\n  <dt>What if I don't update a documentation?\n  <dd>You'll see outdated content and some pages will be missing or broken, because the rest of the app (including data for the search and sidebar) uses a different caching mechanism that's updated automatically.\n  <dt>I found a bug, where do I report it?\n  <dd>In the <a href=\"https://github.com/freeCodeCamp/devdocs/issues\">issue tracker</a>. Thanks!\n  <dt>How do I uninstall/reset the app?\n  <dd>Click <a href=\"#\" data-behavior=\"reset\">here</a>.\n  <dt>Why aren't all documentations listed above?\n  <dd>You have to <a href=\"/settings\">enable</a> them first.\n</dl>\\\n`;\n\nvar canICloseTheTab = function () {\n  if (app.ServiceWorker.isEnabled()) {\n    return ' Yes! Even offline, you can open a new tab, go to <a href=\"//devdocs.io\">devdocs.io</a>, and everything will work as if you were online (provided you installed all the documentations you want to use beforehand). ';\n  } else {\n    let reason = \"aren't available in your browser (or are disabled)\";\n\n    if (app.config.env !== \"production\") {\n      reason =\n        \"are disabled in your development instance of DevDocs (enable them by setting the <code>ENABLE_SERVICE_WORKER</code> environment variable to <code>true</code>)\";\n    }\n\n    return ` No. Service Workers ${reason}, so loading <a href=\"//devdocs.io\">devdocs.io</a> offline won't work.<br>\nThe current tab will continue to function even when you go offline (provided you installed all the documentations beforehand). `;\n  }\n};\n\napp.templates.offlineDoc = function (doc, status) {\n  const outdated = doc.isOutdated(status);\n\n  let html = `\\\n<tr data-slug=\"${doc.slug}\"${outdated ? ' class=\"_highlight\"' : \"\"}>\n  <td class=\"_docs-name _icon-${doc.icon}\">${doc.fullName}</td>\n  <td class=\"_docs-size\">${\n    Math.ceil(doc.db_size / 100000) / 10\n  }&nbsp;<small>MB</small></td>\\\n`;\n\n  html += !(status && status.installed)\n    ? `\\\n<td>-</td>\n<td><button type=\"button\" class=\"_btn-link\" data-action=\"install\">Install</button></td>\\\n`\n    : outdated\n      ? `\\\n<td><strong>Outdated</strong></td>\n<td><button type=\"button\" class=\"_btn-link _bold\" data-action=\"update\">Update</button> - <button type=\"button\" class=\"_btn-link\" data-action=\"uninstall\">Uninstall</button></td>\\\n`\n      : `\\\n<td>Up&#8209;to&#8209;date</td>\n<td><button type=\"button\" class=\"_btn-link\" data-action=\"uninstall\">Uninstall</button></td>\\\n`;\n\n  return html + \"</tr>\";\n};\n"
  },
  {
    "path": "assets/javascripts/templates/pages/root_tmpl.js.erb",
    "content": "app.templates.splash = \"<div class=\\\"_splash-title\\\">DevDocs</div>\";\n\n<% if App.development? %>\napp.templates.intro = `\\\n<div class=\"_intro\"><div class=\"_intro-message\">\n  <a href=\"#\" class=\"_intro-hide\" data-hide-intro>Stop showing this message</a>\n  <h2 class=\"_intro-title\">Hi there!</h2>\n  <p>Thanks for downloading DevDocs. Here are a few things you should know:\n  <ol class=\"_intro-list\">\n    <li>Your local version of DevDocs won't self-update. Unless you're modifying the code,\n        we&nbsp;recommend using the hosted version at <a href=\"https://devdocs.io\">devdocs.io</a>.\n    <li>Run <code>thor docs:list</code> to see all available documentations.\n    <li>Run <code>thor docs:download &lt;name&gt;</code> to download documentations.\n    <li>Run <code>thor docs:download --installed</code> to update all downloaded documentations.\n    <li>To be notified about new versions, don't forget to <a href=\"https://github.com/freeCodeCamp/devdocs/subscription\">watch the repository</a> on GitHub.\n    <li>The <a href=\"https://github.com/freeCodeCamp/devdocs/issues\">issue tracker</a> is the preferred channel for bug reports and\n        feature requests. For everything else, use <a href=\"https://discord.gg/PRyKn3Vbay\">Discord</a>.\n    <li>Contributions are welcome. See the <a href=\"https://github.com/freeCodeCamp/devdocs/blob/main/CONTRIBUTING.md\">guidelines</a>.\n    <li>DevDocs is licensed under the terms of the Mozilla Public License v2.0. For more information,\n        see the <a href=\"https://github.com/freeCodeCamp/devdocs/blob/main/COPYRIGHT\">COPYRIGHT</a> and\n        <a href=\"https://github.com/freeCodeCamp/devdocs/blob/main/LICENSE\">LICENSE</a> files.\n  </ol>\n  <p>Happy coding!\n</div></div>\\\n`;\n<% else %>\napp.templates.intro = `\\\n<div class=\"_intro\"><div class=\"_intro-message\">\n  <a href=\"#\" class=\"_intro-hide\" data-hide-intro>Stop showing this message</a>\n  <h2 class=\"_intro-title\">Welcome!</h2>\n  <p>DevDocs combines multiple API documentations in a fast, organized, and searchable interface.\n     Here's what you should know before you start:\n  <ol class=\"_intro-list\">\n    <li>Open the <a href=\"/settings\">Preferences</a> to enable more docs and customize the UI.\n    <li>You don't have to use your mouse &mdash; see the list of <a href=\"/help#shortcuts\">keyboard shortcuts</a> or press <a href=\"/help#shortcuts\"><kbd>?</kbd></a>.\n    <li>The search supports fuzzy matching (e.g. \"bgcp\" brings up \"background-clip\").\n    <li>To search a specific documentation, type its name (or an abbr.), then Tab.\n    <li>You can search using your browser's address bar &mdash; <a href=\"/help#browser_search\">learn how</a>.\n    <li>DevDocs works <a href=\"/offline\">offline</a>, on mobile, and can be installed as web app.\n    <li>For the latest news, follow <a href=\"https://twitter.com/DevDocs\">@DevDocs</a>.\n    <li>DevDocs is free and <a href=\"https://github.com/freeCodeCamp/devdocs\">open source</a>.\n        <img src=\"https://img.shields.io/github/stars/freeCodeCamp/devdocs.svg?style=social\" aria-hidden=\"true\" height=\"20\">\n    <li>And if you're new to coding, check out <a href=\"https://www.freecodecamp.org/\">freeCodeCamp's open source curriculum</a>.\n  </ol>\n  <p>Happy coding!\n</div></div>\\\n`;\n<% end %>\n\napp.templates.mobileIntro = `\\\n<div class=\"_mobile-intro\">\n  <h2 class=\"_intro-title\">Welcome!</h2>\n  <p>DevDocs combines multiple API documentations in a fast, organized, and searchable interface.\n     Here's what you should know before you start:\n  <ol class=\"_intro-list\">\n    <li>Pick your docs in the <a href=\"/settings\">Preferences</a>.\n    <li>The search supports fuzzy matching.\n    <li>To search a specific documentation, type its name (or an abbr.), then Space.\n    <li>For the latest news, follow <a href=\"https://twitter.com/DevDocs\">@DevDocs</a>.\n    <li>DevDocs is <a href=\"https://github.com/freeCodeCamp/devdocs\">open source</a>.\n  </ol>\n  <p>Happy coding!\n  <a class=\"_intro-hide\" data-hide-intro>Stop showing this message</a>\n</div>\\\n`;\n\napp.templates.androidWarning = `\\\n<div class=\"_mobile-intro\">\n  <h2 class=\"_intro-title\">Hi there</h2>\n  <p>DevDocs is running inside an Android WebView. Some features may not work properly.\n  <p>If you downloaded an app called DevDocs on the Play Store, please uninstall it — it's made by someone who is using (and profiting from) the name DevDocs without permission.\n  <p>To install DevDocs on your phone, visit <a href=\"https://devdocs.io\" target=\"_blank\" rel=\"noopener\">devdocs.io</a> in Chrome and select \"Add to home screen\" in the menu.\n</div>\\\n`;\n"
  },
  {
    "path": "assets/javascripts/templates/pages/settings_tmpl.js",
    "content": "const themeOption = ({ label, value }, settings) => `\\\n<label class=\"_settings-label _theme-label\">\n  <input type=\"radio\" name=\"theme\" value=\"${value}\"${\n    settings.theme === value ? \" checked\" : \"\"\n  }>\n  ${label}\n</label>\\\n`;\n\napp.templates.settingsPage = (settings) => `\\\n<h1 class=\"_lined-heading\">Preferences</h1>\n\n<div class=\"_settings-fieldset\">\n  <h2 class=\"_settings-legend\">Theme:</h2>\n  <div class=\"_settings-inputs\">\n    ${\n      settings.autoSupported\n        ? themeOption(\n            {\n              label: \"Automatic <small>Matches system setting</small>\",\n              value: \"auto\",\n            },\n            settings,\n          )\n        : \"\"\n    }\n    ${themeOption({ label: \"Light\", value: \"default\" }, settings)}\n    ${themeOption({ label: \"Dark\", value: \"dark\" }, settings)}\n  </div>\n</div>\n\n<div class=\"_settings-fieldset\">\n  <h2 class=\"_settings-legend\">General:</h2>\n\n  <div class=\"_settings-inputs\">\n    <label class=\"_settings-label _setting-max-width\">\n      <input type=\"checkbox\" form=\"settings\" name=\"layout\" value=\"_max-width\"${\n        settings[\"_max-width\"] ? \" checked\" : \"\"\n      }>Enable fixed-width layout\n    </label>\n    <label class=\"_settings-label _setting-text-justify-hyphenate\">\n      <input type=\"checkbox\" form=\"settings\" name=\"layout\" value=\"_text-justify-hyphenate\"${\n        settings[\"_text-justify-hyphenate\"] ? \" checked\" : \"\"\n      }>Enable justified layout and automatic hyphenation\n    </label>\n    <label class=\"_settings-label _hide-on-mobile\">\n      <input type=\"checkbox\" form=\"settings\" name=\"layout\" value=\"_sidebar-hidden\"${\n        settings[\"_sidebar-hidden\"] ? \" checked\" : \"\"\n      }>Automatically hide and show the sidebar\n      <small>Tip: drag the edge of the sidebar to resize it.</small>\n    </label>\n    <label class=\"_settings-label _hide-on-mobile\">\n      <input type=\"checkbox\" form=\"settings\" name=\"noAutofocus\" value=\"_no-autofocus\"${\n        settings.noAutofocus ? \" checked\" : \"\"\n      }>Disable autofocus of search input\n    </label>\n    <label class=\"_settings-label\">\n      <input type=\"checkbox\" form=\"settings\" name=\"autoInstall\" value=\"_auto-install\"${\n        settings.autoInstall ? \" checked\" : \"\"\n      }>Automatically download documentation for offline use\n      <small>Only enable this when bandwidth isn't a concern to you.</small>\n    </label>\n    <label class=\"_settings-label _hide-in-development\">\n      <input type=\"checkbox\" form=\"settings\" name=\"analyticsConsent\"${\n        settings.analyticsConsent ? \" checked\" : \"\"\n      }>Enable tracking cookies\n      <small>With this checked, we enable Google Analytics and Gauges to collect anonymous traffic information.</small>\n    </label>\n    <label class=\"_settings-label _hide-on-mobile\">\n      <input type=\"checkbox\" form=\"settings\" name=\"noDocSpecificIcon\"${\n        settings.noDocSpecificIcon ? \" checked\" : \"\"\n      }>Disable Language-specific Doc Favicons\n      <small>With this checked, we will display the default DevDocs icon for all pages.</small>\n    </label>\n  </div>\n</div>\n\n<div class=\"_settings-fieldset _hide-on-mobile\">\n  <h2 class=\"_settings-legend\">Scrolling:</h2>\n\n  <div class=\"_settings-inputs\">\n    <label class=\"_settings-label\">\n      <input type=\"checkbox\" form=\"settings\" name=\"smoothScroll\" value=\"1\"${\n        settings.smoothScroll ? \" checked\" : \"\"\n      }>Use smooth scrolling\n    </label>\n    <label class=\"_settings-label _setting-native-scrollbar\">\n      <input type=\"checkbox\" form=\"settings\" name=\"layout\" value=\"_native-scrollbars\"${\n        settings[\"_native-scrollbars\"] ? \" checked\" : \"\"\n      }>Use native scrollbars\n    </label>\n    <label class=\"_settings-label\">\n      <input type=\"checkbox\" form=\"settings\" name=\"arrowScroll\" value=\"1\"${\n        settings.arrowScroll ? \" checked\" : \"\"\n      }>Use arrow keys to scroll the main content area\n      <small>With this checked, use <code class=\"_label\">shift</code> + <code class=\"_label\">&uarr;</code><code class=\"_label\">&darr;</code><code class=\"_label\">&larr;</code><code class=\"_label\">&rarr;</code> to navigate the sidebar.</small>\n    </label>\n    <label class=\"_settings-label\">\n      <input type=\"checkbox\" form=\"settings\" name=\"spaceScroll\" value=\"1\"${\n        settings.spaceScroll ? \" checked\" : \"\"\n      }>Use spacebar to scroll during search\n    </label>\n    <label class=\"_settings-label\">\n      <input type=\"number\" step=\"0.1\" form=\"settings\" name=\"spaceTimeout\" min=\"0\" max=\"5\" value=\"${\n        settings.spaceTimeout\n      }\"> Delay until you can scroll by pressing space\n      <small>Time in seconds</small>\n    </label>\n  </div>\n</div>\n\n<p class=\"_hide-on-mobile\">\n  <button type=\"button\" class=\"_btn\" data-action=\"export\">Export</button>\n  <label class=\"_btn _file-btn\"><input type=\"file\" form=\"settings\" name=\"import\" accept=\".json\">Import</label>\n\n<p>\n  <button type=\"button\" class=\"_btn-link _reset-btn\" data-behavior=\"reset\">Reset all preferences and data</button>\\\n`;\n"
  },
  {
    "path": "assets/javascripts/templates/pages/type_tmpl.js",
    "content": "app.templates.typePage = (type) => {\n  return ` <h1>${type.doc.fullName} / ${type.name}</h1>\n<ul class=\"_entry-list\">${app.templates.render(\n    \"typePageEntry\",\n    type.entries(),\n  )}</ul> `;\n};\n\napp.templates.typePageEntry = (entry) => {\n  return `<li><a href=\"${entry.fullPath()}\">${$.escape(entry.name)}</a></li>`;\n};\n"
  },
  {
    "path": "assets/javascripts/templates/path_tmpl.js",
    "content": "app.templates.path = function (doc, type, entry) {\n  const arrow = '<svg class=\"_path-arrow\"><use xlink:href=\"#icon-dir\"/></svg>';\n  let html = `<a href=\"${doc.fullPath()}\" class=\"_path-item _icon-${\n    doc.icon\n  }\">${doc.fullName}</a>`;\n  if (type) {\n    html += `${arrow}<a href=\"${type.fullPath()}\" class=\"_path-item\">${\n      type.name\n    }</a>`;\n  }\n  if (entry) {\n    html += `${arrow}<span class=\"_path-item\">${$.escape(entry.name)}</span>`;\n  }\n  return html;\n};\n"
  },
  {
    "path": "assets/javascripts/templates/sidebar_tmpl.js",
    "content": "const { templates } = app;\n\nconst arrow = '<svg class=\"_list-arrow\"><use xlink:href=\"#icon-dir\"/></svg>';\n\ntemplates.sidebarDoc = function (doc, options) {\n  if (options == null) {\n    options = {};\n  }\n  let link = `<a href=\"${doc.fullPath()}\" class=\"_list-item _icon-${doc.icon} `;\n  link += options.disabled ? \"_list-disabled\" : \"_list-dir\";\n  link += `\" data-slug=\"${doc.slug}\" title=\"${doc.fullName}\" tabindex=\"-1\">`;\n  if (options.disabled) {\n    link += `<span class=\"_list-enable\" data-enable=\"${doc.slug}\">Enable</span>`;\n  } else {\n    link += arrow;\n  }\n  if (doc.release) {\n    link += `<span class=\"_list-count\">${doc.release}</span>`;\n  }\n  link += `<span class=\"_list-text\">${doc.name}`;\n  if (options.fullName || (options.disabled && doc.version)) {\n    link += ` ${doc.version}`;\n  }\n  return link + \"</span></a>\";\n};\n\ntemplates.sidebarType = (type) =>\n  `<a href=\"${type.fullPath()}\" class=\"_list-item _list-dir\" data-slug=\"${\n    type.slug\n  }\" tabindex=\"-1\">${arrow}<span class=\"_list-count\">${\n    type.count\n  }</span><span class=\"_list-text\">${$.escape(type.name)}</span></a>`;\n\ntemplates.sidebarEntry = (entry) =>\n  `<a href=\"${entry.fullPath()}\" class=\"_list-item _list-hover\" tabindex=\"-1\">${$.escape(\n    entry.name,\n  )}</a>`;\n\ntemplates.sidebarResult = function (entry) {\n  let addons =\n    entry.isIndex() && app.disabledDocs.contains(entry.doc)\n      ? `<span class=\"_list-enable\" data-enable=\"${entry.doc.slug}\">Enable</span>`\n      : '<span class=\"_list-reveal\" data-reset-list title=\"Reveal in list\"></span>';\n  if (entry.doc.version && !entry.isIndex()) {\n    addons += `<span class=\"_list-count\">${entry.doc.short_version}</span>`;\n  }\n  return `<a href=\"${entry.fullPath()}\" class=\"_list-item _list-hover _list-result _icon-${\n    entry.doc.icon\n  }\" tabindex=\"-1\">${addons}<span class=\"_list-text\">${$.escape(\n    entry.name,\n  )}</span></a>`;\n};\n\ntemplates.sidebarNoResults = function () {\n  let html = ' <div class=\"_list-note\">No results.</div> ';\n  if (!app.isSingleDoc() && !app.disabledDocs.isEmpty()) {\n    html += `\\\n<div class=\"_list-note\">Note: documentations must be <a href=\"/settings\" class=\"_list-note-link\">enabled</a> to appear in the search.</div>\\\n`;\n  }\n  return html;\n};\n\ntemplates.sidebarPageLink = (count) =>\n  `<span role=\"link\" class=\"_list-item _list-pagelink\">Show more\\u2026 (${count})</span>`;\n\ntemplates.sidebarLabel = function (doc, options) {\n  if (options == null) {\n    options = {};\n  }\n  let label = '<label class=\"_list-item';\n  if (!doc.version) {\n    label += ` _icon-${doc.icon}`;\n  }\n  label += `\"><input type=\"checkbox\" name=\"${doc.slug}\" class=\"_list-checkbox\" `;\n  if (options.checked) {\n    label += \"checked\";\n  }\n  return label + `><span class=\"_list-text\">${doc.fullName}</span></label>`;\n};\n\ntemplates.sidebarVersionedDoc = function (doc, versions, options) {\n  if (options == null) {\n    options = {};\n  }\n  let html = `<div class=\"_list-item _list-dir _list-rdir _icon-${doc.icon}`;\n  if (options.open) {\n    html += \" open\";\n  }\n  return (\n    html +\n    `\" tabindex=\"0\">${arrow}${doc.name}</div><div class=\"_list _list-sub\">${versions}</div>`\n  );\n};\n\ntemplates.sidebarDisabled = (options) =>\n  `<h6 class=\"_list-title\">${arrow}Disabled (${options.count}) <a href=\"/settings\" class=\"_list-title-link\" tabindex=\"-1\">Customize</a></h6>`;\n\ntemplates.sidebarDisabledList = (html) =>\n  `<div class=\"_disabled-list\">${html}</div>`;\n\ntemplates.sidebarDisabledVersionedDoc = (doc, versions) =>\n  `<a class=\"_list-item _list-dir _icon-${doc.icon} _list-disabled\" data-slug=\"${doc.slug_without_version}\" tabindex=\"-1\">${arrow}${doc.name}</a><div class=\"_list _list-sub\">${versions}</div>`;\n\ntemplates.docPickerHeader =\n  '<div class=\"_list-picker-head\"><span>Documentation</span> <span>Enable</span></div>';\n\ntemplates.docPickerNote = `\\\n<div class=\"_list-note\">Tip: for faster and better search results, select only the docs you need.</div>\n<a href=\"https://trello.com/b/6BmTulfx/devdocs-documentation\" class=\"_list-link\" target=\"_blank\" rel=\"noopener\">Vote for new documentation</a>\\\n`;\n"
  },
  {
    "path": "assets/javascripts/templates/tip_tmpl.js",
    "content": "app.templates.tipKeyNav = () => `\\\n<p class=\"_notif-text\">\n  <strong>ProTip</strong>\n  <span class=\"_notif-info\">(click to dismiss)</span>\n<p class=\"_notif-text\">\n  Hit ${\n    app.settings.get(\"arrowScroll\") ? '<code class=\"_label\">shift</code> +' : \"\"\n  } <code class=\"_label\">&darr;</code> <code class=\"_label\">&uarr;</code> <code class=\"_label\">&larr;</code> <code class=\"_label\">&rarr;</code> to navigate the sidebar.<br>\n  Hit <code class=\"_label\">space / shift space</code>${\n    app.settings.get(\"arrowScroll\")\n      ? ' or <code class=\"_label\">&darr;/&uarr;</code>'\n      : ', <code class=\"_label\">alt &darr;/&uarr;</code> or <code class=\"_label\">shift &darr;/&uarr;</code>'\n  } to scroll the page.\n<p class=\"_notif-text\">\n  <a href=\"/help#shortcuts\" class=\"_notif-link\">See all keyboard shortcuts</a>\\\n`;\n"
  },
  {
    "path": "assets/javascripts/tracking.js",
    "content": "try {\n  if (app.config.env === \"production\") {\n    if (Cookies.get(\"analyticsConsent\") === \"1\") {\n      (function (i, s, o, g, r, a, m) {\n        i[\"GoogleAnalyticsObject\"] = r;\n        (i[r] =\n          i[r] ||\n          function () {\n            (i[r].q = i[r].q || []).push(arguments);\n          }),\n          (i[r].l = 1 * new Date());\n        (a = s.createElement(o)), (m = s.getElementsByTagName(o)[0]);\n        a.async = 1;\n        a.src = g;\n        m.parentNode.insertBefore(a, m);\n      })(\n        window,\n        document,\n        \"script\",\n        \"https://www.google-analytics.com/analytics.js\",\n        \"ga\",\n      );\n      ga(\"create\", \"UA-5544833-12\", \"devdocs.io\");\n      page.track(function () {\n        ga(\"send\", \"pageview\", {\n          page: location.pathname + location.search + location.hash,\n          dimension1:\n            app.router.context &&\n            app.router.context.doc &&\n            app.router.context.doc.slug_without_version,\n        });\n      });\n\n      page.track(function () {\n        if (window._gauges) _gauges.push([\"track\"]);\n        else\n          (function () {\n            var _gauges = _gauges || [];\n            !(function () {\n              var a = document.createElement(\"script\");\n              (a.type = \"text/javascript\"),\n                (a.async = !0),\n                (a.id = \"gauges-tracker\"),\n                a.setAttribute(\"data-site-id\", \"51c15f82613f5d7819000067\"),\n                (a.src = \"https://secure.gaug.es/track.js\");\n              var b = document.getElementsByTagName(\"script\")[0];\n              b.parentNode.insertBefore(a, b);\n            })();\n          })();\n      });\n    } else {\n      resetAnalytics();\n    }\n  }\n} catch (e) {}\n"
  },
  {
    "path": "assets/javascripts/vendor/cookies.js",
    "content": "/*\n * Cookies.js - 1.2.3 (patched for SameSite=Strict and secure=true)\n * https://github.com/ScottHamper/Cookies\n *\n * This is free and unencumbered software released into the public domain.\n */\n(function (global, undefined) {\n  \"use strict\";\n\n  var factory = function (window) {\n    if (typeof window.document !== \"object\") {\n      throw new Error(\n        \"Cookies.js requires a `window` with a `document` object\",\n      );\n    }\n\n    var Cookies = function (key, value, options) {\n      return arguments.length === 1\n        ? Cookies.get(key)\n        : Cookies.set(key, value, options);\n    };\n\n    // Allows for setter injection in unit tests\n    Cookies._document = window.document;\n\n    // Used to ensure cookie keys do not collide with\n    // built-in `Object` properties\n    Cookies._cacheKeyPrefix = \"cookey.\"; // Hurr hurr, :)\n\n    Cookies._maxExpireDate = new Date(\"Fri, 31 Dec 9999 23:59:59 UTC\");\n\n    Cookies.defaults = {\n      path: \"/\",\n      SameSite: \"Strict\",\n      secure: true,\n    };\n\n    Cookies.get = function (key) {\n      if (Cookies._cachedDocumentCookie !== Cookies._document.cookie) {\n        Cookies._renewCache();\n      }\n\n      var value = Cookies._cache[Cookies._cacheKeyPrefix + key];\n\n      return value === undefined ? undefined : decodeURIComponent(value);\n    };\n\n    Cookies.set = function (key, value, options) {\n      options = Cookies._getExtendedOptions(options);\n      options.expires = Cookies._getExpiresDate(\n        value === undefined ? -1 : options.expires,\n      );\n\n      Cookies._document.cookie = Cookies._generateCookieString(\n        key,\n        value,\n        options,\n      );\n\n      return Cookies;\n    };\n\n    Cookies.expire = function (key, options) {\n      return Cookies.set(key, undefined, options);\n    };\n\n    Cookies._getExtendedOptions = function (options) {\n      return {\n        path: (options && options.path) || Cookies.defaults.path,\n        domain: (options && options.domain) || Cookies.defaults.domain,\n        SameSite: (options && options.SameSite) || Cookies.defaults.SameSite,\n        expires: (options && options.expires) || Cookies.defaults.expires,\n        secure:\n          options && options.secure !== undefined\n            ? options.secure\n            : Cookies.defaults.secure,\n      };\n    };\n\n    Cookies._isValidDate = function (date) {\n      return (\n        Object.prototype.toString.call(date) === \"[object Date]\" &&\n        !isNaN(date.getTime())\n      );\n    };\n\n    Cookies._getExpiresDate = function (expires, now) {\n      now = now || new Date();\n\n      if (typeof expires === \"number\") {\n        expires =\n          expires === Infinity\n            ? Cookies._maxExpireDate\n            : new Date(now.getTime() + expires * 1000);\n      } else if (typeof expires === \"string\") {\n        expires = new Date(expires);\n      }\n\n      if (expires && !Cookies._isValidDate(expires)) {\n        throw new Error(\n          \"`expires` parameter cannot be converted to a valid Date instance\",\n        );\n      }\n\n      return expires;\n    };\n\n    Cookies._generateCookieString = function (key, value, options) {\n      key = key.replace(/[^#$&+\\^`|]/g, encodeURIComponent);\n      key = key.replace(/\\(/g, \"%28\").replace(/\\)/g, \"%29\");\n      value = (value + \"\").replace(\n        /[^!#$&-+\\--:<-\\[\\]-~]/g,\n        encodeURIComponent,\n      );\n      options = options || {};\n\n      var cookieString = key + \"=\" + value;\n      cookieString += options.path ? \";path=\" + options.path : \"\";\n      cookieString += options.domain ? \";domain=\" + options.domain : \"\";\n      cookieString += options.SameSite ? \";SameSite=\" + options.SameSite : \"\";\n      cookieString += options.expires\n        ? \";expires=\" + options.expires.toUTCString()\n        : \"\";\n      cookieString += options.secure ? \";secure\" : \"\";\n\n      return cookieString;\n    };\n\n    Cookies._getCacheFromString = function (documentCookie) {\n      var cookieCache = {};\n      var cookiesArray = documentCookie ? documentCookie.split(\"; \") : [];\n\n      for (var i = 0; i < cookiesArray.length; i++) {\n        var cookieKvp = Cookies._getKeyValuePairFromCookieString(\n          cookiesArray[i],\n        );\n\n        if (\n          cookieCache[Cookies._cacheKeyPrefix + cookieKvp.key] === undefined\n        ) {\n          cookieCache[Cookies._cacheKeyPrefix + cookieKvp.key] =\n            cookieKvp.value;\n        }\n      }\n\n      return cookieCache;\n    };\n\n    Cookies._getKeyValuePairFromCookieString = function (cookieString) {\n      // \"=\" is a valid character in a cookie value according to RFC6265, so cannot `split('=')`\n      var separatorIndex = cookieString.indexOf(\"=\");\n\n      // IE omits the \"=\" when the cookie value is an empty string\n      separatorIndex =\n        separatorIndex < 0 ? cookieString.length : separatorIndex;\n\n      var key = cookieString.substr(0, separatorIndex);\n      var decodedKey;\n      try {\n        decodedKey = decodeURIComponent(key);\n      } catch (e) {\n        if (console && typeof console.error === \"function\") {\n          console.error('Could not decode cookie with key \"' + key + '\"', e);\n        }\n      }\n\n      return {\n        key: decodedKey,\n        value: cookieString.substr(separatorIndex + 1), // Defer decoding value until accessed\n      };\n    };\n\n    Cookies._renewCache = function () {\n      Cookies._cache = Cookies._getCacheFromString(Cookies._document.cookie);\n      Cookies._cachedDocumentCookie = Cookies._document.cookie;\n    };\n\n    Cookies._areEnabled = function () {\n      var testKey = \"cookies.js\";\n      var areEnabled = Cookies.set(testKey, 1).get(testKey) === \"1\";\n      Cookies.expire(testKey);\n      return areEnabled;\n    };\n\n    Cookies.enabled = Cookies._areEnabled();\n\n    return Cookies;\n  };\n  var cookiesExport =\n    global && typeof global.document === \"object\" ? factory(global) : factory;\n\n  // AMD support\n  if (typeof define === \"function\" && define.amd) {\n    define(function () {\n      return cookiesExport;\n    });\n    // CommonJS/Node.js support\n  } else if (typeof exports === \"object\") {\n    // Support Node.js specific `module.exports` (which can be a function)\n    if (typeof module === \"object\" && typeof module.exports === \"object\") {\n      exports = module.exports = cookiesExport;\n    }\n    // But always support CommonJS module 1.1.1 spec (`exports` cannot be a function)\n    exports.Cookies = cookiesExport;\n  } else {\n    global.Cookies = cookiesExport;\n  }\n})(typeof window === \"undefined\" ? this : window);\n"
  },
  {
    "path": "assets/javascripts/vendor/mathml.js",
    "content": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n * Adapted from: https://github.com/fred-wang/mathml.css */\n\n(function () {\n  window.addEventListener(\"load\", function () {\n    var box, div, link, namespaceURI;\n    // First check whether the page contains any <math> element.\n    namespaceURI = \"http://www.w3.org/1998/Math/MathML\";\n    // Create a div to test mspace, using Kuma's \"offscreen\" CSS\n    document.body.insertAdjacentHTML(\n      \"afterbegin\",\n      \"<div style='border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px;'><math xmlns='\" +\n        namespaceURI +\n        \"'><mspace height='23px' width='77px'></mspace></math></div>\",\n    );\n    div = document.body.firstChild;\n    box = div.firstChild.firstChild.getBoundingClientRect();\n    document.body.removeChild(div);\n    if (Math.abs(box.height - 23) > 1 || Math.abs(box.width - 77) > 1) {\n      window.supportsMathML = false;\n    }\n  });\n})();\n"
  },
  {
    "path": "assets/javascripts/vendor/prism.js",
    "content": "/* PrismJS 1.30.0\nhttps://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+bash+c+cpp+cmake+coffeescript+crystal+d+dart+diff+django+dot+elixir+erlang+go+groovy+java+json+julia+kotlin+latex+lua+markdown+markup-templating+matlab+nginx+nim+nix+ocaml+perl+php+python+qml+r+jsx+ruby+rust+scss+scala+shell-session+sql+tcl+typescript+yaml+zig */\n/// <reference lib=\"WebWorker\"/>\n\nvar _self = (typeof window !== 'undefined')\n\t? window   // if in browser\n\t: (\n\t\t(typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope)\n\t\t\t? self // if in worker\n\t\t\t: {}   // if in node js\n\t);\n\n/**\n * Prism: Lightweight, robust, elegant syntax highlighting\n *\n * @license MIT <https://opensource.org/licenses/MIT>\n * @author Lea Verou <https://lea.verou.me>\n * @namespace\n * @public\n */\nvar Prism = (function (_self) {\n\n\t// Private helper vars\n\tvar lang = /(?:^|\\s)lang(?:uage)?-([\\w-]+)(?=\\s|$)/i;\n\tvar uniqueId = 0;\n\n\t// The grammar object for plaintext\n\tvar plainTextGrammar = {};\n\n\n\tvar _ = {\n\t\t/**\n\t\t * By default, Prism will attempt to highlight all code elements (by calling {@link Prism.highlightAll}) on the\n\t\t * current page after the page finished loading. This might be a problem if e.g. you wanted to asynchronously load\n\t\t * additional languages or plugins yourself.\n\t\t *\n\t\t * By setting this value to `true`, Prism will not automatically highlight all code elements on the page.\n\t\t *\n\t\t * You obviously have to change this value before the automatic highlighting started. To do this, you can add an\n\t\t * empty Prism object into the global scope before loading the Prism script like this:\n\t\t *\n\t\t * ```js\n\t\t * window.Prism = window.Prism || {};\n\t\t * Prism.manual = true;\n\t\t * // add a new <script> to load Prism's script\n\t\t * ```\n\t\t *\n\t\t * @default false\n\t\t * @type {boolean}\n\t\t * @memberof Prism\n\t\t * @public\n\t\t */\n\t\tmanual: _self.Prism && _self.Prism.manual,\n\t\t/**\n\t\t * By default, if Prism is in a web worker, it assumes that it is in a worker it created itself, so it uses\n\t\t * `addEventListener` to communicate with its parent instance. However, if you're using Prism manually in your\n\t\t * own worker, you don't want it to do this.\n\t\t *\n\t\t * By setting this value to `true`, Prism will not add its own listeners to the worker.\n\t\t *\n\t\t * You obviously have to change this value before Prism executes. To do this, you can add an\n\t\t * empty Prism object into the global scope before loading the Prism script like this:\n\t\t *\n\t\t * ```js\n\t\t * window.Prism = window.Prism || {};\n\t\t * Prism.disableWorkerMessageHandler = true;\n\t\t * // Load Prism's script\n\t\t * ```\n\t\t *\n\t\t * @default false\n\t\t * @type {boolean}\n\t\t * @memberof Prism\n\t\t * @public\n\t\t */\n\t\tdisableWorkerMessageHandler: _self.Prism && _self.Prism.disableWorkerMessageHandler,\n\n\t\t/**\n\t\t * A namespace for utility methods.\n\t\t *\n\t\t * All function in this namespace that are not explicitly marked as _public_ are for __internal use only__ and may\n\t\t * change or disappear at any time.\n\t\t *\n\t\t * @namespace\n\t\t * @memberof Prism\n\t\t */\n\t\tutil: {\n\t\t\tencode: function encode(tokens) {\n\t\t\t\tif (tokens instanceof Token) {\n\t\t\t\t\treturn new Token(tokens.type, encode(tokens.content), tokens.alias);\n\t\t\t\t} else if (Array.isArray(tokens)) {\n\t\t\t\t\treturn tokens.map(encode);\n\t\t\t\t} else {\n\t\t\t\t\treturn tokens.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/\\u00a0/g, ' ');\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Returns the name of the type of the given value.\n\t\t\t *\n\t\t\t * @param {any} o\n\t\t\t * @returns {string}\n\t\t\t * @example\n\t\t\t * type(null)      === 'Null'\n\t\t\t * type(undefined) === 'Undefined'\n\t\t\t * type(123)       === 'Number'\n\t\t\t * type('foo')     === 'String'\n\t\t\t * type(true)      === 'Boolean'\n\t\t\t * type([1, 2])    === 'Array'\n\t\t\t * type({})        === 'Object'\n\t\t\t * type(String)    === 'Function'\n\t\t\t * type(/abc+/)    === 'RegExp'\n\t\t\t */\n\t\t\ttype: function (o) {\n\t\t\t\treturn Object.prototype.toString.call(o).slice(8, -1);\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Returns a unique number for the given object. Later calls will still return the same number.\n\t\t\t *\n\t\t\t * @param {Object} obj\n\t\t\t * @returns {number}\n\t\t\t */\n\t\t\tobjId: function (obj) {\n\t\t\t\tif (!obj['__id']) {\n\t\t\t\t\tObject.defineProperty(obj, '__id', { value: ++uniqueId });\n\t\t\t\t}\n\t\t\t\treturn obj['__id'];\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Creates a deep clone of the given object.\n\t\t\t *\n\t\t\t * The main intended use of this function is to clone language definitions.\n\t\t\t *\n\t\t\t * @param {T} o\n\t\t\t * @param {Record<number, any>} [visited]\n\t\t\t * @returns {T}\n\t\t\t * @template T\n\t\t\t */\n\t\t\tclone: function deepClone(o, visited) {\n\t\t\t\tvisited = visited || {};\n\n\t\t\t\tvar clone; var id;\n\t\t\t\tswitch (_.util.type(o)) {\n\t\t\t\t\tcase 'Object':\n\t\t\t\t\t\tid = _.util.objId(o);\n\t\t\t\t\t\tif (visited[id]) {\n\t\t\t\t\t\t\treturn visited[id];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tclone = /** @type {Record<string, any>} */ ({});\n\t\t\t\t\t\tvisited[id] = clone;\n\n\t\t\t\t\t\tfor (var key in o) {\n\t\t\t\t\t\t\tif (o.hasOwnProperty(key)) {\n\t\t\t\t\t\t\t\tclone[key] = deepClone(o[key], visited);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn /** @type {any} */ (clone);\n\n\t\t\t\t\tcase 'Array':\n\t\t\t\t\t\tid = _.util.objId(o);\n\t\t\t\t\t\tif (visited[id]) {\n\t\t\t\t\t\t\treturn visited[id];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tclone = [];\n\t\t\t\t\t\tvisited[id] = clone;\n\n\t\t\t\t\t\t(/** @type {Array} */(/** @type {any} */(o))).forEach(function (v, i) {\n\t\t\t\t\t\t\tclone[i] = deepClone(v, visited);\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\treturn /** @type {any} */ (clone);\n\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn o;\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Returns the Prism language of the given element set by a `language-xxxx` or `lang-xxxx` class.\n\t\t\t *\n\t\t\t * If no language is set for the element or the element is `null` or `undefined`, `none` will be returned.\n\t\t\t *\n\t\t\t * @param {Element} element\n\t\t\t * @returns {string}\n\t\t\t */\n\t\t\tgetLanguage: function (element) {\n\t\t\t\twhile (element) {\n\t\t\t\t\tvar m = lang.exec(element.className);\n\t\t\t\t\tif (m) {\n\t\t\t\t\t\treturn m[1].toLowerCase();\n\t\t\t\t\t}\n\t\t\t\t\telement = element.parentElement;\n\t\t\t\t}\n\t\t\t\treturn 'none';\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Sets the Prism `language-xxxx` class of the given element.\n\t\t\t *\n\t\t\t * @param {Element} element\n\t\t\t * @param {string} language\n\t\t\t * @returns {void}\n\t\t\t */\n\t\t\tsetLanguage: function (element, language) {\n\t\t\t\t// remove all `language-xxxx` classes\n\t\t\t\t// (this might leave behind a leading space)\n\t\t\t\telement.className = element.className.replace(RegExp(lang, 'gi'), '');\n\n\t\t\t\t// add the new `language-xxxx` class\n\t\t\t\t// (using `classList` will automatically clean up spaces for us)\n\t\t\t\telement.classList.add('language-' + language);\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Returns the script element that is currently executing.\n\t\t\t *\n\t\t\t * This does __not__ work for line script element.\n\t\t\t *\n\t\t\t * @returns {HTMLScriptElement | null}\n\t\t\t */\n\t\t\tcurrentScript: function () {\n\t\t\t\tif (typeof document === 'undefined') {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tif (document.currentScript && document.currentScript.tagName === 'SCRIPT' && 1 < 2 /* hack to trip TS' flow analysis */) {\n\t\t\t\t\treturn /** @type {any} */ (document.currentScript);\n\t\t\t\t}\n\n\t\t\t\t// IE11 workaround\n\t\t\t\t// we'll get the src of the current script by parsing IE11's error stack trace\n\t\t\t\t// this will not work for inline scripts\n\n\t\t\t\ttry {\n\t\t\t\t\tthrow new Error();\n\t\t\t\t} catch (err) {\n\t\t\t\t\t// Get file src url from stack. Specifically works with the format of stack traces in IE.\n\t\t\t\t\t// A stack will look like this:\n\t\t\t\t\t//\n\t\t\t\t\t// Error\n\t\t\t\t\t//    at _.util.currentScript (http://localhost/components/prism-core.js:119:5)\n\t\t\t\t\t//    at Global code (http://localhost/components/prism-core.js:606:1)\n\n\t\t\t\t\tvar src = (/at [^(\\r\\n]*\\((.*):[^:]+:[^:]+\\)$/i.exec(err.stack) || [])[1];\n\t\t\t\t\tif (src) {\n\t\t\t\t\t\tvar scripts = document.getElementsByTagName('script');\n\t\t\t\t\t\tfor (var i in scripts) {\n\t\t\t\t\t\t\tif (scripts[i].src == src) {\n\t\t\t\t\t\t\t\treturn scripts[i];\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 null;\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Returns whether a given class is active for `element`.\n\t\t\t *\n\t\t\t * The class can be activated if `element` or one of its ancestors has the given class and it can be deactivated\n\t\t\t * if `element` or one of its ancestors has the negated version of the given class. The _negated version_ of the\n\t\t\t * given class is just the given class with a `no-` prefix.\n\t\t\t *\n\t\t\t * Whether the class is active is determined by the closest ancestor of `element` (where `element` itself is\n\t\t\t * closest ancestor) that has the given class or the negated version of it. If neither `element` nor any of its\n\t\t\t * ancestors have the given class or the negated version of it, then the default activation will be returned.\n\t\t\t *\n\t\t\t * In the paradoxical situation where the closest ancestor contains __both__ the given class and the negated\n\t\t\t * version of it, the class is considered active.\n\t\t\t *\n\t\t\t * @param {Element} element\n\t\t\t * @param {string} className\n\t\t\t * @param {boolean} [defaultActivation=false]\n\t\t\t * @returns {boolean}\n\t\t\t */\n\t\t\tisActive: function (element, className, defaultActivation) {\n\t\t\t\tvar no = 'no-' + className;\n\n\t\t\t\twhile (element) {\n\t\t\t\t\tvar classList = element.classList;\n\t\t\t\t\tif (classList.contains(className)) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t\tif (classList.contains(no)) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\telement = element.parentElement;\n\t\t\t\t}\n\t\t\t\treturn !!defaultActivation;\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * This namespace contains all currently loaded languages and the some helper functions to create and modify languages.\n\t\t *\n\t\t * @namespace\n\t\t * @memberof Prism\n\t\t * @public\n\t\t */\n\t\tlanguages: {\n\t\t\t/**\n\t\t\t * The grammar for plain, unformatted text.\n\t\t\t */\n\t\t\tplain: plainTextGrammar,\n\t\t\tplaintext: plainTextGrammar,\n\t\t\ttext: plainTextGrammar,\n\t\t\ttxt: plainTextGrammar,\n\n\t\t\t/**\n\t\t\t * Creates a deep copy of the language with the given id and appends the given tokens.\n\t\t\t *\n\t\t\t * If a token in `redef` also appears in the copied language, then the existing token in the copied language\n\t\t\t * will be overwritten at its original position.\n\t\t\t *\n\t\t\t * ## Best practices\n\t\t\t *\n\t\t\t * Since the position of overwriting tokens (token in `redef` that overwrite tokens in the copied language)\n\t\t\t * doesn't matter, they can technically be in any order. However, this can be confusing to others that trying to\n\t\t\t * understand the language definition because, normally, the order of tokens matters in Prism grammars.\n\t\t\t *\n\t\t\t * Therefore, it is encouraged to order overwriting tokens according to the positions of the overwritten tokens.\n\t\t\t * Furthermore, all non-overwriting tokens should be placed after the overwriting ones.\n\t\t\t *\n\t\t\t * @param {string} id The id of the language to extend. This has to be a key in `Prism.languages`.\n\t\t\t * @param {Grammar} redef The new tokens to append.\n\t\t\t * @returns {Grammar} The new language created.\n\t\t\t * @public\n\t\t\t * @example\n\t\t\t * Prism.languages['css-with-colors'] = Prism.languages.extend('css', {\n\t\t\t *     // Prism.languages.css already has a 'comment' token, so this token will overwrite CSS' 'comment' token\n\t\t\t *     // at its original position\n\t\t\t *     'comment': { ... },\n\t\t\t *     // CSS doesn't have a 'color' token, so this token will be appended\n\t\t\t *     'color': /\\b(?:red|green|blue)\\b/\n\t\t\t * });\n\t\t\t */\n\t\t\textend: function (id, redef) {\n\t\t\t\tvar lang = _.util.clone(_.languages[id]);\n\n\t\t\t\tfor (var key in redef) {\n\t\t\t\t\tlang[key] = redef[key];\n\t\t\t\t}\n\n\t\t\t\treturn lang;\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Inserts tokens _before_ another token in a language definition or any other grammar.\n\t\t\t *\n\t\t\t * ## Usage\n\t\t\t *\n\t\t\t * This helper method makes it easy to modify existing languages. For example, the CSS language definition\n\t\t\t * not only defines CSS highlighting for CSS documents, but also needs to define highlighting for CSS embedded\n\t\t\t * in HTML through `<style>` elements. To do this, it needs to modify `Prism.languages.markup` and add the\n\t\t\t * appropriate tokens. However, `Prism.languages.markup` is a regular JavaScript object literal, so if you do\n\t\t\t * this:\n\t\t\t *\n\t\t\t * ```js\n\t\t\t * Prism.languages.markup.style = {\n\t\t\t *     // token\n\t\t\t * };\n\t\t\t * ```\n\t\t\t *\n\t\t\t * then the `style` token will be added (and processed) at the end. `insertBefore` allows you to insert tokens\n\t\t\t * before existing tokens. For the CSS example above, you would use it like this:\n\t\t\t *\n\t\t\t * ```js\n\t\t\t * Prism.languages.insertBefore('markup', 'cdata', {\n\t\t\t *     'style': {\n\t\t\t *         // token\n\t\t\t *     }\n\t\t\t * });\n\t\t\t * ```\n\t\t\t *\n\t\t\t * ## Special cases\n\t\t\t *\n\t\t\t * If the grammars of `inside` and `insert` have tokens with the same name, the tokens in `inside`'s grammar\n\t\t\t * will be ignored.\n\t\t\t *\n\t\t\t * This behavior can be used to insert tokens after `before`:\n\t\t\t *\n\t\t\t * ```js\n\t\t\t * Prism.languages.insertBefore('markup', 'comment', {\n\t\t\t *     'comment': Prism.languages.markup.comment,\n\t\t\t *     // tokens after 'comment'\n\t\t\t * });\n\t\t\t * ```\n\t\t\t *\n\t\t\t * ## Limitations\n\t\t\t *\n\t\t\t * The main problem `insertBefore` has to solve is iteration order. Since ES2015, the iteration order for object\n\t\t\t * properties is guaranteed to be the insertion order (except for integer keys) but some browsers behave\n\t\t\t * differently when keys are deleted and re-inserted. So `insertBefore` can't be implemented by temporarily\n\t\t\t * deleting properties which is necessary to insert at arbitrary positions.\n\t\t\t *\n\t\t\t * To solve this problem, `insertBefore` doesn't actually insert the given tokens into the target object.\n\t\t\t * Instead, it will create a new object and replace all references to the target object with the new one. This\n\t\t\t * can be done without temporarily deleting properties, so the iteration order is well-defined.\n\t\t\t *\n\t\t\t * However, only references that can be reached from `Prism.languages` or `insert` will be replaced. I.e. if\n\t\t\t * you hold the target object in a variable, then the value of the variable will not change.\n\t\t\t *\n\t\t\t * ```js\n\t\t\t * var oldMarkup = Prism.languages.markup;\n\t\t\t * var newMarkup = Prism.languages.insertBefore('markup', 'comment', { ... });\n\t\t\t *\n\t\t\t * assert(oldMarkup !== Prism.languages.markup);\n\t\t\t * assert(newMarkup === Prism.languages.markup);\n\t\t\t * ```\n\t\t\t *\n\t\t\t * @param {string} inside The property of `root` (e.g. a language id in `Prism.languages`) that contains the\n\t\t\t * object to be modified.\n\t\t\t * @param {string} before The key to insert before.\n\t\t\t * @param {Grammar} insert An object containing the key-value pairs to be inserted.\n\t\t\t * @param {Object<string, any>} [root] The object containing `inside`, i.e. the object that contains the\n\t\t\t * object to be modified.\n\t\t\t *\n\t\t\t * Defaults to `Prism.languages`.\n\t\t\t * @returns {Grammar} The new grammar object.\n\t\t\t * @public\n\t\t\t */\n\t\t\tinsertBefore: function (inside, before, insert, root) {\n\t\t\t\troot = root || /** @type {any} */ (_.languages);\n\t\t\t\tvar grammar = root[inside];\n\t\t\t\t/** @type {Grammar} */\n\t\t\t\tvar ret = {};\n\n\t\t\t\tfor (var token in grammar) {\n\t\t\t\t\tif (grammar.hasOwnProperty(token)) {\n\n\t\t\t\t\t\tif (token == before) {\n\t\t\t\t\t\t\tfor (var newToken in insert) {\n\t\t\t\t\t\t\t\tif (insert.hasOwnProperty(newToken)) {\n\t\t\t\t\t\t\t\t\tret[newToken] = insert[newToken];\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// Do not insert token which also occur in insert. See #1525\n\t\t\t\t\t\tif (!insert.hasOwnProperty(token)) {\n\t\t\t\t\t\t\tret[token] = grammar[token];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tvar old = root[inside];\n\t\t\t\troot[inside] = ret;\n\n\t\t\t\t// Update references in other language definitions\n\t\t\t\t_.languages.DFS(_.languages, function (key, value) {\n\t\t\t\t\tif (value === old && key != inside) {\n\t\t\t\t\t\tthis[key] = ret;\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\treturn ret;\n\t\t\t},\n\n\t\t\t// Traverse a language definition with Depth First Search\n\t\t\tDFS: function DFS(o, callback, type, visited) {\n\t\t\t\tvisited = visited || {};\n\n\t\t\t\tvar objId = _.util.objId;\n\n\t\t\t\tfor (var i in o) {\n\t\t\t\t\tif (o.hasOwnProperty(i)) {\n\t\t\t\t\t\tcallback.call(o, i, o[i], type || i);\n\n\t\t\t\t\t\tvar property = o[i];\n\t\t\t\t\t\tvar propertyType = _.util.type(property);\n\n\t\t\t\t\t\tif (propertyType === 'Object' && !visited[objId(property)]) {\n\t\t\t\t\t\t\tvisited[objId(property)] = true;\n\t\t\t\t\t\t\tDFS(property, callback, null, visited);\n\t\t\t\t\t\t} else if (propertyType === 'Array' && !visited[objId(property)]) {\n\t\t\t\t\t\t\tvisited[objId(property)] = true;\n\t\t\t\t\t\t\tDFS(property, callback, i, visited);\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\tplugins: {},\n\n\t\t/**\n\t\t * This is the most high-level function in Prism’s API.\n\t\t * It fetches all the elements that have a `.language-xxxx` class and then calls {@link Prism.highlightElement} on\n\t\t * each one of them.\n\t\t *\n\t\t * This is equivalent to `Prism.highlightAllUnder(document, async, callback)`.\n\t\t *\n\t\t * @param {boolean} [async=false] Same as in {@link Prism.highlightAllUnder}.\n\t\t * @param {HighlightCallback} [callback] Same as in {@link Prism.highlightAllUnder}.\n\t\t * @memberof Prism\n\t\t * @public\n\t\t */\n\t\thighlightAll: function (async, callback) {\n\t\t\t_.highlightAllUnder(document, async, callback);\n\t\t},\n\n\t\t/**\n\t\t * Fetches all the descendants of `container` that have a `.language-xxxx` class and then calls\n\t\t * {@link Prism.highlightElement} on each one of them.\n\t\t *\n\t\t * The following hooks will be run:\n\t\t * 1. `before-highlightall`\n\t\t * 2. `before-all-elements-highlight`\n\t\t * 3. All hooks of {@link Prism.highlightElement} for each element.\n\t\t *\n\t\t * @param {ParentNode} container The root element, whose descendants that have a `.language-xxxx` class will be highlighted.\n\t\t * @param {boolean} [async=false] Whether each element is to be highlighted asynchronously using Web Workers.\n\t\t * @param {HighlightCallback} [callback] An optional callback to be invoked on each element after its highlighting is done.\n\t\t * @memberof Prism\n\t\t * @public\n\t\t */\n\t\thighlightAllUnder: function (container, async, callback) {\n\t\t\tvar env = {\n\t\t\t\tcallback: callback,\n\t\t\t\tcontainer: container,\n\t\t\t\tselector: 'code[class*=\"language-\"], [class*=\"language-\"] code, code[class*=\"lang-\"], [class*=\"lang-\"] code'\n\t\t\t};\n\n\t\t\t_.hooks.run('before-highlightall', env);\n\n\t\t\tenv.elements = Array.prototype.slice.apply(env.container.querySelectorAll(env.selector));\n\n\t\t\t_.hooks.run('before-all-elements-highlight', env);\n\n\t\t\tfor (var i = 0, element; (element = env.elements[i++]);) {\n\t\t\t\t_.highlightElement(element, async === true, env.callback);\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Highlights the code inside a single element.\n\t\t *\n\t\t * The following hooks will be run:\n\t\t * 1. `before-sanity-check`\n\t\t * 2. `before-highlight`\n\t\t * 3. All hooks of {@link Prism.highlight}. These hooks will be run by an asynchronous worker if `async` is `true`.\n\t\t * 4. `before-insert`\n\t\t * 5. `after-highlight`\n\t\t * 6. `complete`\n\t\t *\n\t\t * Some the above hooks will be skipped if the element doesn't contain any text or there is no grammar loaded for\n\t\t * the element's language.\n\t\t *\n\t\t * @param {Element} element The element containing the code.\n\t\t * It must have a class of `language-xxxx` to be processed, where `xxxx` is a valid language identifier.\n\t\t * @param {boolean} [async=false] Whether the element is to be highlighted asynchronously using Web Workers\n\t\t * to improve performance and avoid blocking the UI when highlighting very large chunks of code. This option is\n\t\t * [disabled by default](https://prismjs.com/faq.html#why-is-asynchronous-highlighting-disabled-by-default).\n\t\t *\n\t\t * Note: All language definitions required to highlight the code must be included in the main `prism.js` file for\n\t\t * asynchronous highlighting to work. You can build your own bundle on the\n\t\t * [Download page](https://prismjs.com/download.html).\n\t\t * @param {HighlightCallback} [callback] An optional callback to be invoked after the highlighting is done.\n\t\t * Mostly useful when `async` is `true`, since in that case, the highlighting is done asynchronously.\n\t\t * @memberof Prism\n\t\t * @public\n\t\t */\n\t\thighlightElement: function (element, async, callback) {\n\t\t\t// Find language\n\t\t\tvar language = _.util.getLanguage(element);\n\t\t\tvar grammar = _.languages[language];\n\n\t\t\t// Set language on the element, if not present\n\t\t\t_.util.setLanguage(element, language);\n\n\t\t\t// Set language on the parent, for styling\n\t\t\tvar parent = element.parentElement;\n\t\t\tif (parent && parent.nodeName.toLowerCase() === 'pre') {\n\t\t\t\t_.util.setLanguage(parent, language);\n\t\t\t}\n\n\t\t\tvar code = element.textContent;\n\n\t\t\tvar env = {\n\t\t\t\telement: element,\n\t\t\t\tlanguage: language,\n\t\t\t\tgrammar: grammar,\n\t\t\t\tcode: code\n\t\t\t};\n\n\t\t\tfunction insertHighlightedCode(highlightedCode) {\n\t\t\t\tenv.highlightedCode = highlightedCode;\n\n\t\t\t\t_.hooks.run('before-insert', env);\n\n\t\t\t\tenv.element.innerHTML = env.highlightedCode;\n\n\t\t\t\t_.hooks.run('after-highlight', env);\n\t\t\t\t_.hooks.run('complete', env);\n\t\t\t\tcallback && callback.call(env.element);\n\t\t\t}\n\n\t\t\t_.hooks.run('before-sanity-check', env);\n\n\t\t\t// plugins may change/add the parent/element\n\t\t\tparent = env.element.parentElement;\n\t\t\tif (parent && parent.nodeName.toLowerCase() === 'pre' && !parent.hasAttribute('tabindex')) {\n\t\t\t\tparent.setAttribute('tabindex', '0');\n\t\t\t}\n\n\t\t\tif (!env.code) {\n\t\t\t\t_.hooks.run('complete', env);\n\t\t\t\tcallback && callback.call(env.element);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t_.hooks.run('before-highlight', env);\n\n\t\t\tif (!env.grammar) {\n\t\t\t\tinsertHighlightedCode(_.util.encode(env.code));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (async && _self.Worker) {\n\t\t\t\tvar worker = new Worker(_.filename);\n\n\t\t\t\tworker.onmessage = function (evt) {\n\t\t\t\t\tinsertHighlightedCode(evt.data);\n\t\t\t\t};\n\n\t\t\t\tworker.postMessage(JSON.stringify({\n\t\t\t\t\tlanguage: env.language,\n\t\t\t\t\tcode: env.code,\n\t\t\t\t\timmediateClose: true\n\t\t\t\t}));\n\t\t\t} else {\n\t\t\t\tinsertHighlightedCode(_.highlight(env.code, env.grammar, env.language));\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Low-level function, only use if you know what you’re doing. It accepts a string of text as input\n\t\t * and the language definitions to use, and returns a string with the HTML produced.\n\t\t *\n\t\t * The following hooks will be run:\n\t\t * 1. `before-tokenize`\n\t\t * 2. `after-tokenize`\n\t\t * 3. `wrap`: On each {@link Token}.\n\t\t *\n\t\t * @param {string} text A string with the code to be highlighted.\n\t\t * @param {Grammar} grammar An object containing the tokens to use.\n\t\t *\n\t\t * Usually a language definition like `Prism.languages.markup`.\n\t\t * @param {string} language The name of the language definition passed to `grammar`.\n\t\t * @returns {string} The highlighted HTML.\n\t\t * @memberof Prism\n\t\t * @public\n\t\t * @example\n\t\t * Prism.highlight('var foo = true;', Prism.languages.javascript, 'javascript');\n\t\t */\n\t\thighlight: function (text, grammar, language) {\n\t\t\tvar env = {\n\t\t\t\tcode: text,\n\t\t\t\tgrammar: grammar,\n\t\t\t\tlanguage: language\n\t\t\t};\n\t\t\t_.hooks.run('before-tokenize', env);\n\t\t\tif (!env.grammar) {\n\t\t\t\tthrow new Error('The language \"' + env.language + '\" has no grammar.');\n\t\t\t}\n\t\t\tenv.tokens = _.tokenize(env.code, env.grammar);\n\t\t\t_.hooks.run('after-tokenize', env);\n\t\t\treturn Token.stringify(_.util.encode(env.tokens), env.language);\n\t\t},\n\n\t\t/**\n\t\t * This is the heart of Prism, and the most low-level function you can use. It accepts a string of text as input\n\t\t * and the language definitions to use, and returns an array with the tokenized code.\n\t\t *\n\t\t * When the language definition includes nested tokens, the function is called recursively on each of these tokens.\n\t\t *\n\t\t * This method could be useful in other contexts as well, as a very crude parser.\n\t\t *\n\t\t * @param {string} text A string with the code to be highlighted.\n\t\t * @param {Grammar} grammar An object containing the tokens to use.\n\t\t *\n\t\t * Usually a language definition like `Prism.languages.markup`.\n\t\t * @returns {TokenStream} An array of strings and tokens, a token stream.\n\t\t * @memberof Prism\n\t\t * @public\n\t\t * @example\n\t\t * let code = `var foo = 0;`;\n\t\t * let tokens = Prism.tokenize(code, Prism.languages.javascript);\n\t\t * tokens.forEach(token => {\n\t\t *     if (token instanceof Prism.Token && token.type === 'number') {\n\t\t *         console.log(`Found numeric literal: ${token.content}`);\n\t\t *     }\n\t\t * });\n\t\t */\n\t\ttokenize: function (text, grammar) {\n\t\t\tvar rest = grammar.rest;\n\t\t\tif (rest) {\n\t\t\t\tfor (var token in rest) {\n\t\t\t\t\tgrammar[token] = rest[token];\n\t\t\t\t}\n\n\t\t\t\tdelete grammar.rest;\n\t\t\t}\n\n\t\t\tvar tokenList = new LinkedList();\n\t\t\taddAfter(tokenList, tokenList.head, text);\n\n\t\t\tmatchGrammar(text, tokenList, grammar, tokenList.head, 0);\n\n\t\t\treturn toArray(tokenList);\n\t\t},\n\n\t\t/**\n\t\t * @namespace\n\t\t * @memberof Prism\n\t\t * @public\n\t\t */\n\t\thooks: {\n\t\t\tall: {},\n\n\t\t\t/**\n\t\t\t * Adds the given callback to the list of callbacks for the given hook.\n\t\t\t *\n\t\t\t * The callback will be invoked when the hook it is registered for is run.\n\t\t\t * Hooks are usually directly run by a highlight function but you can also run hooks yourself.\n\t\t\t *\n\t\t\t * One callback function can be registered to multiple hooks and the same hook multiple times.\n\t\t\t *\n\t\t\t * @param {string} name The name of the hook.\n\t\t\t * @param {HookCallback} callback The callback function which is given environment variables.\n\t\t\t * @public\n\t\t\t */\n\t\t\tadd: function (name, callback) {\n\t\t\t\tvar hooks = _.hooks.all;\n\n\t\t\t\thooks[name] = hooks[name] || [];\n\n\t\t\t\thooks[name].push(callback);\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Runs a hook invoking all registered callbacks with the given environment variables.\n\t\t\t *\n\t\t\t * Callbacks will be invoked synchronously and in the order in which they were registered.\n\t\t\t *\n\t\t\t * @param {string} name The name of the hook.\n\t\t\t * @param {Object<string, any>} env The environment variables of the hook passed to all callbacks registered.\n\t\t\t * @public\n\t\t\t */\n\t\t\trun: function (name, env) {\n\t\t\t\tvar callbacks = _.hooks.all[name];\n\n\t\t\t\tif (!callbacks || !callbacks.length) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tfor (var i = 0, callback; (callback = callbacks[i++]);) {\n\t\t\t\t\tcallback(env);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tToken: Token\n\t};\n\t_self.Prism = _;\n\n\n\t// Typescript note:\n\t// The following can be used to import the Token type in JSDoc:\n\t//\n\t//   @typedef {InstanceType<import(\"./prism-core\")[\"Token\"]>} Token\n\n\t/**\n\t * Creates a new token.\n\t *\n\t * @param {string} type See {@link Token#type type}\n\t * @param {string | TokenStream} content See {@link Token#content content}\n\t * @param {string|string[]} [alias] The alias(es) of the token.\n\t * @param {string} [matchedStr=\"\"] A copy of the full string this token was created from.\n\t * @class\n\t * @global\n\t * @public\n\t */\n\tfunction Token(type, content, alias, matchedStr) {\n\t\t/**\n\t\t * The type of the token.\n\t\t *\n\t\t * This is usually the key of a pattern in a {@link Grammar}.\n\t\t *\n\t\t * @type {string}\n\t\t * @see GrammarToken\n\t\t * @public\n\t\t */\n\t\tthis.type = type;\n\t\t/**\n\t\t * The strings or tokens contained by this token.\n\t\t *\n\t\t * This will be a token stream if the pattern matched also defined an `inside` grammar.\n\t\t *\n\t\t * @type {string | TokenStream}\n\t\t * @public\n\t\t */\n\t\tthis.content = content;\n\t\t/**\n\t\t * The alias(es) of the token.\n\t\t *\n\t\t * @type {string|string[]}\n\t\t * @see GrammarToken\n\t\t * @public\n\t\t */\n\t\tthis.alias = alias;\n\t\t// Copy of the full string this token was created from\n\t\tthis.length = (matchedStr || '').length | 0;\n\t}\n\n\t/**\n\t * A token stream is an array of strings and {@link Token Token} objects.\n\t *\n\t * Token streams have to fulfill a few properties that are assumed by most functions (mostly internal ones) that process\n\t * them.\n\t *\n\t * 1. No adjacent strings.\n\t * 2. No empty strings.\n\t *\n\t *    The only exception here is the token stream that only contains the empty string and nothing else.\n\t *\n\t * @typedef {Array<string | Token>} TokenStream\n\t * @global\n\t * @public\n\t */\n\n\t/**\n\t * Converts the given token or token stream to an HTML representation.\n\t *\n\t * The following hooks will be run:\n\t * 1. `wrap`: On each {@link Token}.\n\t *\n\t * @param {string | Token | TokenStream} o The token or token stream to be converted.\n\t * @param {string} language The name of current language.\n\t * @returns {string} The HTML representation of the token or token stream.\n\t * @memberof Token\n\t * @static\n\t */\n\tToken.stringify = function stringify(o, language) {\n\t\tif (typeof o == 'string') {\n\t\t\treturn o;\n\t\t}\n\t\tif (Array.isArray(o)) {\n\t\t\tvar s = '';\n\t\t\to.forEach(function (e) {\n\t\t\t\ts += stringify(e, language);\n\t\t\t});\n\t\t\treturn s;\n\t\t}\n\n\t\tvar env = {\n\t\t\ttype: o.type,\n\t\t\tcontent: stringify(o.content, language),\n\t\t\ttag: 'span',\n\t\t\tclasses: ['token', o.type],\n\t\t\tattributes: {},\n\t\t\tlanguage: language\n\t\t};\n\n\t\tvar aliases = o.alias;\n\t\tif (aliases) {\n\t\t\tif (Array.isArray(aliases)) {\n\t\t\t\tArray.prototype.push.apply(env.classes, aliases);\n\t\t\t} else {\n\t\t\t\tenv.classes.push(aliases);\n\t\t\t}\n\t\t}\n\n\t\t_.hooks.run('wrap', env);\n\n\t\tvar attributes = '';\n\t\tfor (var name in env.attributes) {\n\t\t\tattributes += ' ' + name + '=\"' + (env.attributes[name] || '').replace(/\"/g, '&quot;') + '\"';\n\t\t}\n\n\t\treturn '<' + env.tag + ' class=\"' + env.classes.join(' ') + '\"' + attributes + '>' + env.content + '</' + env.tag + '>';\n\t};\n\n\t/**\n\t * @param {RegExp} pattern\n\t * @param {number} pos\n\t * @param {string} text\n\t * @param {boolean} lookbehind\n\t * @returns {RegExpExecArray | null}\n\t */\n\tfunction matchPattern(pattern, pos, text, lookbehind) {\n\t\tpattern.lastIndex = pos;\n\t\tvar match = pattern.exec(text);\n\t\tif (match && lookbehind && match[1]) {\n\t\t\t// change the match to remove the text matched by the Prism lookbehind group\n\t\t\tvar lookbehindLength = match[1].length;\n\t\t\tmatch.index += lookbehindLength;\n\t\t\tmatch[0] = match[0].slice(lookbehindLength);\n\t\t}\n\t\treturn match;\n\t}\n\n\t/**\n\t * @param {string} text\n\t * @param {LinkedList<string | Token>} tokenList\n\t * @param {any} grammar\n\t * @param {LinkedListNode<string | Token>} startNode\n\t * @param {number} startPos\n\t * @param {RematchOptions} [rematch]\n\t * @returns {void}\n\t * @private\n\t *\n\t * @typedef RematchOptions\n\t * @property {string} cause\n\t * @property {number} reach\n\t */\n\tfunction matchGrammar(text, tokenList, grammar, startNode, startPos, rematch) {\n\t\tfor (var token in grammar) {\n\t\t\tif (!grammar.hasOwnProperty(token) || !grammar[token]) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvar patterns = grammar[token];\n\t\t\tpatterns = Array.isArray(patterns) ? patterns : [patterns];\n\n\t\t\tfor (var j = 0; j < patterns.length; ++j) {\n\t\t\t\tif (rematch && rematch.cause == token + ',' + j) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tvar patternObj = patterns[j];\n\t\t\t\tvar inside = patternObj.inside;\n\t\t\t\tvar lookbehind = !!patternObj.lookbehind;\n\t\t\t\tvar greedy = !!patternObj.greedy;\n\t\t\t\tvar alias = patternObj.alias;\n\n\t\t\t\tif (greedy && !patternObj.pattern.global) {\n\t\t\t\t\t// Without the global flag, lastIndex won't work\n\t\t\t\t\tvar flags = patternObj.pattern.toString().match(/[imsuy]*$/)[0];\n\t\t\t\t\tpatternObj.pattern = RegExp(patternObj.pattern.source, flags + 'g');\n\t\t\t\t}\n\n\t\t\t\t/** @type {RegExp} */\n\t\t\t\tvar pattern = patternObj.pattern || patternObj;\n\n\t\t\t\tfor ( // iterate the token list and keep track of the current token/string position\n\t\t\t\t\tvar currentNode = startNode.next, pos = startPos;\n\t\t\t\t\tcurrentNode !== tokenList.tail;\n\t\t\t\t\tpos += currentNode.value.length, currentNode = currentNode.next\n\t\t\t\t) {\n\n\t\t\t\t\tif (rematch && pos >= rematch.reach) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tvar str = currentNode.value;\n\n\t\t\t\t\tif (tokenList.length > text.length) {\n\t\t\t\t\t\t// Something went terribly wrong, ABORT, ABORT!\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (str instanceof Token) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tvar removeCount = 1; // this is the to parameter of removeBetween\n\t\t\t\t\tvar match;\n\n\t\t\t\t\tif (greedy) {\n\t\t\t\t\t\tmatch = matchPattern(pattern, pos, text, lookbehind);\n\t\t\t\t\t\tif (!match || match.index >= text.length) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tvar from = match.index;\n\t\t\t\t\t\tvar to = match.index + match[0].length;\n\t\t\t\t\t\tvar p = pos;\n\n\t\t\t\t\t\t// find the node that contains the match\n\t\t\t\t\t\tp += currentNode.value.length;\n\t\t\t\t\t\twhile (from >= p) {\n\t\t\t\t\t\t\tcurrentNode = currentNode.next;\n\t\t\t\t\t\t\tp += currentNode.value.length;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// adjust pos (and p)\n\t\t\t\t\t\tp -= currentNode.value.length;\n\t\t\t\t\t\tpos = p;\n\n\t\t\t\t\t\t// the current node is a Token, then the match starts inside another Token, which is invalid\n\t\t\t\t\t\tif (currentNode.value instanceof Token) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// find the last node which is affected by this match\n\t\t\t\t\t\tfor (\n\t\t\t\t\t\t\tvar k = currentNode;\n\t\t\t\t\t\t\tk !== tokenList.tail && (p < to || typeof k.value === 'string');\n\t\t\t\t\t\t\tk = k.next\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tremoveCount++;\n\t\t\t\t\t\t\tp += k.value.length;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tremoveCount--;\n\n\t\t\t\t\t\t// replace with the new match\n\t\t\t\t\t\tstr = text.slice(pos, p);\n\t\t\t\t\t\tmatch.index -= pos;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tmatch = matchPattern(pattern, 0, str, lookbehind);\n\t\t\t\t\t\tif (!match) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// eslint-disable-next-line no-redeclare\n\t\t\t\t\tvar from = match.index;\n\t\t\t\t\tvar matchStr = match[0];\n\t\t\t\t\tvar before = str.slice(0, from);\n\t\t\t\t\tvar after = str.slice(from + matchStr.length);\n\n\t\t\t\t\tvar reach = pos + str.length;\n\t\t\t\t\tif (rematch && reach > rematch.reach) {\n\t\t\t\t\t\trematch.reach = reach;\n\t\t\t\t\t}\n\n\t\t\t\t\tvar removeFrom = currentNode.prev;\n\n\t\t\t\t\tif (before) {\n\t\t\t\t\t\tremoveFrom = addAfter(tokenList, removeFrom, before);\n\t\t\t\t\t\tpos += before.length;\n\t\t\t\t\t}\n\n\t\t\t\t\tremoveRange(tokenList, removeFrom, removeCount);\n\n\t\t\t\t\tvar wrapped = new Token(token, inside ? _.tokenize(matchStr, inside) : matchStr, alias, matchStr);\n\t\t\t\t\tcurrentNode = addAfter(tokenList, removeFrom, wrapped);\n\n\t\t\t\t\tif (after) {\n\t\t\t\t\t\taddAfter(tokenList, currentNode, after);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (removeCount > 1) {\n\t\t\t\t\t\t// at least one Token object was removed, so we have to do some rematching\n\t\t\t\t\t\t// this can only happen if the current pattern is greedy\n\n\t\t\t\t\t\t/** @type {RematchOptions} */\n\t\t\t\t\t\tvar nestedRematch = {\n\t\t\t\t\t\t\tcause: token + ',' + j,\n\t\t\t\t\t\t\treach: reach\n\t\t\t\t\t\t};\n\t\t\t\t\t\tmatchGrammar(text, tokenList, grammar, currentNode.prev, pos, nestedRematch);\n\n\t\t\t\t\t\t// the reach might have been extended because of the rematching\n\t\t\t\t\t\tif (rematch && nestedRematch.reach > rematch.reach) {\n\t\t\t\t\t\t\trematch.reach = nestedRematch.reach;\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/**\n\t * @typedef LinkedListNode\n\t * @property {T} value\n\t * @property {LinkedListNode<T> | null} prev The previous node.\n\t * @property {LinkedListNode<T> | null} next The next node.\n\t * @template T\n\t * @private\n\t */\n\n\t/**\n\t * @template T\n\t * @private\n\t */\n\tfunction LinkedList() {\n\t\t/** @type {LinkedListNode<T>} */\n\t\tvar head = { value: null, prev: null, next: null };\n\t\t/** @type {LinkedListNode<T>} */\n\t\tvar tail = { value: null, prev: head, next: null };\n\t\thead.next = tail;\n\n\t\t/** @type {LinkedListNode<T>} */\n\t\tthis.head = head;\n\t\t/** @type {LinkedListNode<T>} */\n\t\tthis.tail = tail;\n\t\tthis.length = 0;\n\t}\n\n\t/**\n\t * Adds a new node with the given value to the list.\n\t *\n\t * @param {LinkedList<T>} list\n\t * @param {LinkedListNode<T>} node\n\t * @param {T} value\n\t * @returns {LinkedListNode<T>} The added node.\n\t * @template T\n\t */\n\tfunction addAfter(list, node, value) {\n\t\t// assumes that node != list.tail && values.length >= 0\n\t\tvar next = node.next;\n\n\t\tvar newNode = { value: value, prev: node, next: next };\n\t\tnode.next = newNode;\n\t\tnext.prev = newNode;\n\t\tlist.length++;\n\n\t\treturn newNode;\n\t}\n\t/**\n\t * Removes `count` nodes after the given node. The given node will not be removed.\n\t *\n\t * @param {LinkedList<T>} list\n\t * @param {LinkedListNode<T>} node\n\t * @param {number} count\n\t * @template T\n\t */\n\tfunction removeRange(list, node, count) {\n\t\tvar next = node.next;\n\t\tfor (var i = 0; i < count && next !== list.tail; i++) {\n\t\t\tnext = next.next;\n\t\t}\n\t\tnode.next = next;\n\t\tnext.prev = node;\n\t\tlist.length -= i;\n\t}\n\t/**\n\t * @param {LinkedList<T>} list\n\t * @returns {T[]}\n\t * @template T\n\t */\n\tfunction toArray(list) {\n\t\tvar array = [];\n\t\tvar node = list.head.next;\n\t\twhile (node !== list.tail) {\n\t\t\tarray.push(node.value);\n\t\t\tnode = node.next;\n\t\t}\n\t\treturn array;\n\t}\n\n\n\tif (!_self.document) {\n\t\tif (!_self.addEventListener) {\n\t\t\t// in Node.js\n\t\t\treturn _;\n\t\t}\n\n\t\tif (!_.disableWorkerMessageHandler) {\n\t\t\t// In worker\n\t\t\t_self.addEventListener('message', function (evt) {\n\t\t\t\tvar message = JSON.parse(evt.data);\n\t\t\t\tvar lang = message.language;\n\t\t\t\tvar code = message.code;\n\t\t\t\tvar immediateClose = message.immediateClose;\n\n\t\t\t\t_self.postMessage(_.highlight(code, _.languages[lang], lang));\n\t\t\t\tif (immediateClose) {\n\t\t\t\t\t_self.close();\n\t\t\t\t}\n\t\t\t}, false);\n\t\t}\n\n\t\treturn _;\n\t}\n\n\t// Get current script and highlight\n\tvar script = _.util.currentScript();\n\n\tif (script) {\n\t\t_.filename = script.src;\n\n\t\tif (script.hasAttribute('data-manual')) {\n\t\t\t_.manual = true;\n\t\t}\n\t}\n\n\tfunction highlightAutomaticallyCallback() {\n\t\tif (!_.manual) {\n\t\t\t_.highlightAll();\n\t\t}\n\t}\n\n\tif (!_.manual) {\n\t\t// If the document state is \"loading\", then we'll use DOMContentLoaded.\n\t\t// If the document state is \"interactive\" and the prism.js script is deferred, then we'll also use the\n\t\t// DOMContentLoaded event because there might be some plugins or languages which have also been deferred and they\n\t\t// might take longer one animation frame to execute which can create a race condition where only some plugins have\n\t\t// been loaded when Prism.highlightAll() is executed, depending on how fast resources are loaded.\n\t\t// See https://github.com/PrismJS/prism/issues/2102\n\t\tvar readyState = document.readyState;\n\t\tif (readyState === 'loading' || readyState === 'interactive' && script && script.defer) {\n\t\t\tdocument.addEventListener('DOMContentLoaded', highlightAutomaticallyCallback);\n\t\t} else {\n\t\t\tif (window.requestAnimationFrame) {\n\t\t\t\twindow.requestAnimationFrame(highlightAutomaticallyCallback);\n\t\t\t} else {\n\t\t\t\twindow.setTimeout(highlightAutomaticallyCallback, 16);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn _;\n\n}(_self));\n\nif (typeof module !== 'undefined' && module.exports) {\n\tmodule.exports = Prism;\n}\n\n// hack for components to work correctly in node.js\nif (typeof global !== 'undefined') {\n\tglobal.Prism = Prism;\n}\n\n// some additional documentation/types\n\n/**\n * The expansion of a simple `RegExp` literal to support additional properties.\n *\n * @typedef GrammarToken\n * @property {RegExp} pattern The regular expression of the token.\n * @property {boolean} [lookbehind=false] If `true`, then the first capturing group of `pattern` will (effectively)\n * behave as a lookbehind group meaning that the captured text will not be part of the matched text of the new token.\n * @property {boolean} [greedy=false] Whether the token is greedy.\n * @property {string|string[]} [alias] An optional alias or list of aliases.\n * @property {Grammar} [inside] The nested grammar of this token.\n *\n * The `inside` grammar will be used to tokenize the text value of each token of this kind.\n *\n * This can be used to make nested and even recursive language definitions.\n *\n * Note: This can cause infinite recursion. Be careful when you embed different languages or even the same language into\n * each another.\n * @global\n * @public\n */\n\n/**\n * @typedef Grammar\n * @type {Object<string, RegExp | GrammarToken | Array<RegExp | GrammarToken>>}\n * @property {Grammar} [rest] An optional grammar object that will be appended to this grammar.\n * @global\n * @public\n */\n\n/**\n * A function which will invoked after an element was successfully highlighted.\n *\n * @callback HighlightCallback\n * @param {Element} element The element successfully highlighted.\n * @returns {void}\n * @global\n * @public\n */\n\n/**\n * @callback HookCallback\n * @param {Object<string, any>} env The environment variables of the hook.\n * @returns {void}\n * @global\n * @public\n */\n;\nPrism.languages.markup = {\n\t'comment': {\n\t\tpattern: /<!--(?:(?!<!--)[\\s\\S])*?-->/,\n\t\tgreedy: true\n\t},\n\t'prolog': {\n\t\tpattern: /<\\?[\\s\\S]+?\\?>/,\n\t\tgreedy: true\n\t},\n\t'doctype': {\n\t\t// https://www.w3.org/TR/xml/#NT-doctypedecl\n\t\tpattern: /<!DOCTYPE(?:[^>\"'[\\]]|\"[^\"]*\"|'[^']*')+(?:\\[(?:[^<\"'\\]]|\"[^\"]*\"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\\]\\s*)?>/i,\n\t\tgreedy: true,\n\t\tinside: {\n\t\t\t'internal-subset': {\n\t\t\t\tpattern: /(^[^\\[]*\\[)[\\s\\S]+(?=\\]>$)/,\n\t\t\t\tlookbehind: true,\n\t\t\t\tgreedy: true,\n\t\t\t\tinside: null // see below\n\t\t\t},\n\t\t\t'string': {\n\t\t\t\tpattern: /\"[^\"]*\"|'[^']*'/,\n\t\t\t\tgreedy: true\n\t\t\t},\n\t\t\t'punctuation': /^<!|>$|[[\\]]/,\n\t\t\t'doctype-tag': /^DOCTYPE/i,\n\t\t\t'name': /[^\\s<>'\"]+/\n\t\t}\n\t},\n\t'cdata': {\n\t\tpattern: /<!\\[CDATA\\[[\\s\\S]*?\\]\\]>/i,\n\t\tgreedy: true\n\t},\n\t'tag': {\n\t\tpattern: /<\\/?(?!\\d)[^\\s>\\/=$<%]+(?:\\s(?:\\s*[^\\s>\\/=]+(?:\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))|(?=[\\s/>])))+)?\\s*\\/?>/,\n\t\tgreedy: true,\n\t\tinside: {\n\t\t\t'tag': {\n\t\t\t\tpattern: /^<\\/?[^\\s>\\/]+/,\n\t\t\t\tinside: {\n\t\t\t\t\t'punctuation': /^<\\/?/,\n\t\t\t\t\t'namespace': /^[^\\s>\\/:]+:/\n\t\t\t\t}\n\t\t\t},\n\t\t\t'special-attr': [],\n\t\t\t'attr-value': {\n\t\t\t\tpattern: /=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+)/,\n\t\t\t\tinside: {\n\t\t\t\t\t'punctuation': [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpattern: /^=/,\n\t\t\t\t\t\t\talias: 'attr-equals'\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpattern: /^(\\s*)[\"']|[\"']$/,\n\t\t\t\t\t\t\tlookbehind: true\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'punctuation': /\\/?>/,\n\t\t\t'attr-name': {\n\t\t\t\tpattern: /[^\\s>\\/]+/,\n\t\t\t\tinside: {\n\t\t\t\t\t'namespace': /^[^\\s>\\/:]+:/\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t},\n\t'entity': [\n\t\t{\n\t\t\tpattern: /&[\\da-z]{1,8};/i,\n\t\t\talias: 'named-entity'\n\t\t},\n\t\t/&#x?[\\da-f]{1,8};/i\n\t]\n};\n\nPrism.languages.markup['tag'].inside['attr-value'].inside['entity'] =\n\tPrism.languages.markup['entity'];\nPrism.languages.markup['doctype'].inside['internal-subset'].inside = Prism.languages.markup;\n\n// Plugin to make entity title show the real entity, idea by Roman Komarov\nPrism.hooks.add('wrap', function (env) {\n\n\tif (env.type === 'entity') {\n\t\tenv.attributes['title'] = env.content.replace(/&amp;/, '&');\n\t}\n});\n\nObject.defineProperty(Prism.languages.markup.tag, 'addInlined', {\n\t/**\n\t * Adds an inlined language to markup.\n\t *\n\t * An example of an inlined language is CSS with `<style>` tags.\n\t *\n\t * @param {string} tagName The name of the tag that contains the inlined language. This name will be treated as\n\t * case insensitive.\n\t * @param {string} lang The language key.\n\t * @example\n\t * addInlined('style', 'css');\n\t */\n\tvalue: function addInlined(tagName, lang) {\n\t\tvar includedCdataInside = {};\n\t\tincludedCdataInside['language-' + lang] = {\n\t\t\tpattern: /(^<!\\[CDATA\\[)[\\s\\S]+?(?=\\]\\]>$)/i,\n\t\t\tlookbehind: true,\n\t\t\tinside: Prism.languages[lang]\n\t\t};\n\t\tincludedCdataInside['cdata'] = /^<!\\[CDATA\\[|\\]\\]>$/i;\n\n\t\tvar inside = {\n\t\t\t'included-cdata': {\n\t\t\t\tpattern: /<!\\[CDATA\\[[\\s\\S]*?\\]\\]>/i,\n\t\t\t\tinside: includedCdataInside\n\t\t\t}\n\t\t};\n\t\tinside['language-' + lang] = {\n\t\t\tpattern: /[\\s\\S]+/,\n\t\t\tinside: Prism.languages[lang]\n\t\t};\n\n\t\tvar def = {};\n\t\tdef[tagName] = {\n\t\t\tpattern: RegExp(/(<__[^>]*>)(?:<!\\[CDATA\\[(?:[^\\]]|\\](?!\\]>))*\\]\\]>|(?!<!\\[CDATA\\[)[\\s\\S])*?(?=<\\/__>)/.source.replace(/__/g, function () { return tagName; }), 'i'),\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true,\n\t\t\tinside: inside\n\t\t};\n\n\t\tPrism.languages.insertBefore('markup', 'cdata', def);\n\t}\n});\nObject.defineProperty(Prism.languages.markup.tag, 'addAttribute', {\n\t/**\n\t * Adds an pattern to highlight languages embedded in HTML attributes.\n\t *\n\t * An example of an inlined language is CSS with `style` attributes.\n\t *\n\t * @param {string} attrName The name of the tag that contains the inlined language. This name will be treated as\n\t * case insensitive.\n\t * @param {string} lang The language key.\n\t * @example\n\t * addAttribute('style', 'css');\n\t */\n\tvalue: function (attrName, lang) {\n\t\tPrism.languages.markup.tag.inside['special-attr'].push({\n\t\t\tpattern: RegExp(\n\t\t\t\t/(^|[\"'\\s])/.source + '(?:' + attrName + ')' + /\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))/.source,\n\t\t\t\t'i'\n\t\t\t),\n\t\t\tlookbehind: true,\n\t\t\tinside: {\n\t\t\t\t'attr-name': /^[^\\s=]+/,\n\t\t\t\t'attr-value': {\n\t\t\t\t\tpattern: /=[\\s\\S]+/,\n\t\t\t\t\tinside: {\n\t\t\t\t\t\t'value': {\n\t\t\t\t\t\t\tpattern: /(^=\\s*([\"']|(?![\"'])))\\S[\\s\\S]*(?=\\2$)/,\n\t\t\t\t\t\t\tlookbehind: true,\n\t\t\t\t\t\t\talias: [lang, 'language-' + lang],\n\t\t\t\t\t\t\tinside: Prism.languages[lang]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t'punctuation': [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tpattern: /^=/,\n\t\t\t\t\t\t\t\talias: 'attr-equals'\n\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\t\t\t}\n\t\t});\n\t}\n});\n\nPrism.languages.html = Prism.languages.markup;\nPrism.languages.mathml = Prism.languages.markup;\nPrism.languages.svg = Prism.languages.markup;\n\nPrism.languages.xml = Prism.languages.extend('markup', {});\nPrism.languages.ssml = Prism.languages.xml;\nPrism.languages.atom = Prism.languages.xml;\nPrism.languages.rss = Prism.languages.xml;\n\n(function (Prism) {\n\n\tvar string = /(?:\"(?:\\\\(?:\\r\\n|[\\s\\S])|[^\"\\\\\\r\\n])*\"|'(?:\\\\(?:\\r\\n|[\\s\\S])|[^'\\\\\\r\\n])*')/;\n\n\tPrism.languages.css = {\n\t\t'comment': /\\/\\*[\\s\\S]*?\\*\\//,\n\t\t'atrule': {\n\t\t\tpattern: RegExp('@[\\\\w-](?:' + /[^;{\\s\"']|\\s+(?!\\s)/.source + '|' + string.source + ')*?' + /(?:;|(?=\\s*\\{))/.source),\n\t\t\tinside: {\n\t\t\t\t'rule': /^@[\\w-]+/,\n\t\t\t\t'selector-function-argument': {\n\t\t\t\t\tpattern: /(\\bselector\\s*\\(\\s*(?![\\s)]))(?:[^()\\s]|\\s+(?![\\s)])|\\((?:[^()]|\\([^()]*\\))*\\))+(?=\\s*\\))/,\n\t\t\t\t\tlookbehind: true,\n\t\t\t\t\talias: 'selector'\n\t\t\t\t},\n\t\t\t\t'keyword': {\n\t\t\t\t\tpattern: /(^|[^\\w-])(?:and|not|only|or)(?![\\w-])/,\n\t\t\t\t\tlookbehind: true\n\t\t\t\t}\n\t\t\t\t// See rest below\n\t\t\t}\n\t\t},\n\t\t'url': {\n\t\t\t// https://drafts.csswg.org/css-values-3/#urls\n\t\t\tpattern: RegExp('\\\\burl\\\\((?:' + string.source + '|' + /(?:[^\\\\\\r\\n()\"']|\\\\[\\s\\S])*/.source + ')\\\\)', 'i'),\n\t\t\tgreedy: true,\n\t\t\tinside: {\n\t\t\t\t'function': /^url/i,\n\t\t\t\t'punctuation': /^\\(|\\)$/,\n\t\t\t\t'string': {\n\t\t\t\t\tpattern: RegExp('^' + string.source + '$'),\n\t\t\t\t\talias: 'url'\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t'selector': {\n\t\t\tpattern: RegExp('(^|[{}\\\\s])[^{}\\\\s](?:[^{};\"\\'\\\\s]|\\\\s+(?![\\\\s{])|' + string.source + ')*(?=\\\\s*\\\\{)'),\n\t\t\tlookbehind: true\n\t\t},\n\t\t'string': {\n\t\t\tpattern: string,\n\t\t\tgreedy: true\n\t\t},\n\t\t'property': {\n\t\t\tpattern: /(^|[^-\\w\\xA0-\\uFFFF])(?!\\s)[-_a-z\\xA0-\\uFFFF](?:(?!\\s)[-\\w\\xA0-\\uFFFF])*(?=\\s*:)/i,\n\t\t\tlookbehind: true\n\t\t},\n\t\t'important': /!important\\b/i,\n\t\t'function': {\n\t\t\tpattern: /(^|[^-a-z0-9])[-a-z0-9]+(?=\\()/i,\n\t\t\tlookbehind: true\n\t\t},\n\t\t'punctuation': /[(){};:,]/\n\t};\n\n\tPrism.languages.css['atrule'].inside.rest = Prism.languages.css;\n\n\tvar markup = Prism.languages.markup;\n\tif (markup) {\n\t\tmarkup.tag.addInlined('style', 'css');\n\t\tmarkup.tag.addAttribute('style', 'css');\n\t}\n\n}(Prism));\n\nPrism.languages.clike = {\n\t'comment': [\n\t\t{\n\t\t\tpattern: /(^|[^\\\\])\\/\\*[\\s\\S]*?(?:\\*\\/|$)/,\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true\n\t\t},\n\t\t{\n\t\t\tpattern: /(^|[^\\\\:])\\/\\/.*/,\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true\n\t\t}\n\t],\n\t'string': {\n\t\tpattern: /([\"'])(?:\\\\(?:\\r\\n|[\\s\\S])|(?!\\1)[^\\\\\\r\\n])*\\1/,\n\t\tgreedy: true\n\t},\n\t'class-name': {\n\t\tpattern: /(\\b(?:class|extends|implements|instanceof|interface|new|trait)\\s+|\\bcatch\\s+\\()[\\w.\\\\]+/i,\n\t\tlookbehind: true,\n\t\tinside: {\n\t\t\t'punctuation': /[.\\\\]/\n\t\t}\n\t},\n\t'keyword': /\\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\\b/,\n\t'boolean': /\\b(?:false|true)\\b/,\n\t'function': /\\b\\w+(?=\\()/,\n\t'number': /\\b0x[\\da-f]+\\b|(?:\\b\\d+(?:\\.\\d*)?|\\B\\.\\d+)(?:e[+-]?\\d+)?/i,\n\t'operator': /[<>]=?|[!=]=?=?|--?|\\+\\+?|&&?|\\|\\|?|[?*/~^%]/,\n\t'punctuation': /[{}[\\];(),.:]/\n};\n\nPrism.languages.javascript = Prism.languages.extend('clike', {\n\t'class-name': [\n\t\tPrism.languages.clike['class-name'],\n\t\t{\n\t\t\tpattern: /(^|[^$\\w\\xA0-\\uFFFF])(?!\\s)[_$A-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*(?=\\.(?:constructor|prototype))/,\n\t\t\tlookbehind: true\n\t\t}\n\t],\n\t'keyword': [\n\t\t{\n\t\t\tpattern: /((?:^|\\})\\s*)catch\\b/,\n\t\t\tlookbehind: true\n\t\t},\n\t\t{\n\t\t\tpattern: /(^|[^.]|\\.\\.\\.\\s*)\\b(?:as|assert(?=\\s*\\{)|async(?=\\s*(?:function\\b|\\(|[$\\w\\xA0-\\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\\s*(?:\\{|$))|for|from(?=\\s*(?:['\"]|$))|function|(?:get|set)(?=\\s*(?:[#\\[$\\w\\xA0-\\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\\b/,\n\t\t\tlookbehind: true\n\t\t},\n\t],\n\t// Allow for all non-ASCII characters (See http://stackoverflow.com/a/2008444)\n\t'function': /#?(?!\\s)[_$a-zA-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*(?=\\s*(?:\\.\\s*(?:apply|bind|call)\\s*)?\\()/,\n\t'number': {\n\t\tpattern: RegExp(\n\t\t\t/(^|[^\\w$])/.source +\n\t\t\t'(?:' +\n\t\t\t(\n\t\t\t\t// constant\n\t\t\t\t/NaN|Infinity/.source +\n\t\t\t\t'|' +\n\t\t\t\t// binary integer\n\t\t\t\t/0[bB][01]+(?:_[01]+)*n?/.source +\n\t\t\t\t'|' +\n\t\t\t\t// octal integer\n\t\t\t\t/0[oO][0-7]+(?:_[0-7]+)*n?/.source +\n\t\t\t\t'|' +\n\t\t\t\t// hexadecimal integer\n\t\t\t\t/0[xX][\\dA-Fa-f]+(?:_[\\dA-Fa-f]+)*n?/.source +\n\t\t\t\t'|' +\n\t\t\t\t// decimal bigint\n\t\t\t\t/\\d+(?:_\\d+)*n/.source +\n\t\t\t\t'|' +\n\t\t\t\t// decimal number (integer or float) but no bigint\n\t\t\t\t/(?:\\d+(?:_\\d+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\.\\d+(?:_\\d+)*)(?:[Ee][+-]?\\d+(?:_\\d+)*)?/.source\n\t\t\t) +\n\t\t\t')' +\n\t\t\t/(?![\\w$])/.source\n\t\t),\n\t\tlookbehind: true\n\t},\n\t'operator': /--|\\+\\+|\\*\\*=?|=>|&&=?|\\|\\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\\.{3}|\\?\\?=?|\\?\\.?|[~:]/\n});\n\nPrism.languages.javascript['class-name'][0].pattern = /(\\b(?:class|extends|implements|instanceof|interface|new)\\s+)[\\w.\\\\]+/;\n\nPrism.languages.insertBefore('javascript', 'keyword', {\n\t'regex': {\n\t\tpattern: RegExp(\n\t\t\t// lookbehind\n\t\t\t// eslint-disable-next-line regexp/no-dupe-characters-character-class\n\t\t\t/((?:^|[^$\\w\\xA0-\\uFFFF.\"'\\])\\s]|\\b(?:return|yield))\\s*)/.source +\n\t\t\t// Regex pattern:\n\t\t\t// There are 2 regex patterns here. The RegExp set notation proposal added support for nested character\n\t\t\t// classes if the `v` flag is present. Unfortunately, nested CCs are both context-free and incompatible\n\t\t\t// with the only syntax, so we have to define 2 different regex patterns.\n\t\t\t/\\//.source +\n\t\t\t'(?:' +\n\t\t\t/(?:\\[(?:[^\\]\\\\\\r\\n]|\\\\.)*\\]|\\\\.|[^/\\\\\\[\\r\\n])+\\/[dgimyus]{0,7}/.source +\n\t\t\t'|' +\n\t\t\t// `v` flag syntax. This supports 3 levels of nested character classes.\n\t\t\t/(?:\\[(?:[^[\\]\\\\\\r\\n]|\\\\.|\\[(?:[^[\\]\\\\\\r\\n]|\\\\.|\\[(?:[^[\\]\\\\\\r\\n]|\\\\.)*\\])*\\])*\\]|\\\\.|[^/\\\\\\[\\r\\n])+\\/[dgimyus]{0,7}v[dgimyus]{0,7}/.source +\n\t\t\t')' +\n\t\t\t// lookahead\n\t\t\t/(?=(?:\\s|\\/\\*(?:[^*]|\\*(?!\\/))*\\*\\/)*(?:$|[\\r\\n,.;:})\\]]|\\/\\/))/.source\n\t\t),\n\t\tlookbehind: true,\n\t\tgreedy: true,\n\t\tinside: {\n\t\t\t'regex-source': {\n\t\t\t\tpattern: /^(\\/)[\\s\\S]+(?=\\/[a-z]*$)/,\n\t\t\t\tlookbehind: true,\n\t\t\t\talias: 'language-regex',\n\t\t\t\tinside: Prism.languages.regex\n\t\t\t},\n\t\t\t'regex-delimiter': /^\\/|\\/$/,\n\t\t\t'regex-flags': /^[a-z]+$/,\n\t\t}\n\t},\n\t// This must be declared before keyword because we use \"function\" inside the look-forward\n\t'function-variable': {\n\t\tpattern: /#?(?!\\s)[_$a-zA-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*(?=\\s*[=:]\\s*(?:async\\s*)?(?:\\bfunction\\b|(?:\\((?:[^()]|\\([^()]*\\))*\\)|(?!\\s)[_$a-zA-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*)\\s*=>))/,\n\t\talias: 'function'\n\t},\n\t'parameter': [\n\t\t{\n\t\t\tpattern: /(function(?:\\s+(?!\\s)[_$a-zA-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*)?\\s*\\(\\s*)(?!\\s)(?:[^()\\s]|\\s+(?![\\s)])|\\([^()]*\\))+(?=\\s*\\))/,\n\t\t\tlookbehind: true,\n\t\t\tinside: Prism.languages.javascript\n\t\t},\n\t\t{\n\t\t\tpattern: /(^|[^$\\w\\xA0-\\uFFFF])(?!\\s)[_$a-z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*(?=\\s*=>)/i,\n\t\t\tlookbehind: true,\n\t\t\tinside: Prism.languages.javascript\n\t\t},\n\t\t{\n\t\t\tpattern: /(\\(\\s*)(?!\\s)(?:[^()\\s]|\\s+(?![\\s)])|\\([^()]*\\))+(?=\\s*\\)\\s*=>)/,\n\t\t\tlookbehind: true,\n\t\t\tinside: Prism.languages.javascript\n\t\t},\n\t\t{\n\t\t\tpattern: /((?:\\b|\\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\\w\\xA0-\\uFFFF]))(?:(?!\\s)[_$a-zA-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*\\s*)\\(\\s*|\\]\\s*\\(\\s*)(?!\\s)(?:[^()\\s]|\\s+(?![\\s)])|\\([^()]*\\))+(?=\\s*\\)\\s*\\{)/,\n\t\t\tlookbehind: true,\n\t\t\tinside: Prism.languages.javascript\n\t\t}\n\t],\n\t'constant': /\\b[A-Z](?:[A-Z_]|\\dx?)*\\b/\n});\n\nPrism.languages.insertBefore('javascript', 'string', {\n\t'hashbang': {\n\t\tpattern: /^#!.*/,\n\t\tgreedy: true,\n\t\talias: 'comment'\n\t},\n\t'template-string': {\n\t\tpattern: /`(?:\\\\[\\s\\S]|\\$\\{(?:[^{}]|\\{(?:[^{}]|\\{[^}]*\\})*\\})+\\}|(?!\\$\\{)[^\\\\`])*`/,\n\t\tgreedy: true,\n\t\tinside: {\n\t\t\t'template-punctuation': {\n\t\t\t\tpattern: /^`|`$/,\n\t\t\t\talias: 'string'\n\t\t\t},\n\t\t\t'interpolation': {\n\t\t\t\tpattern: /((?:^|[^\\\\])(?:\\\\{2})*)\\$\\{(?:[^{}]|\\{(?:[^{}]|\\{[^}]*\\})*\\})+\\}/,\n\t\t\t\tlookbehind: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'interpolation-punctuation': {\n\t\t\t\t\t\tpattern: /^\\$\\{|\\}$/,\n\t\t\t\t\t\talias: 'punctuation'\n\t\t\t\t\t},\n\t\t\t\t\trest: Prism.languages.javascript\n\t\t\t\t}\n\t\t\t},\n\t\t\t'string': /[\\s\\S]+/\n\t\t}\n\t},\n\t'string-property': {\n\t\tpattern: /((?:^|[,{])[ \\t]*)([\"'])(?:\\\\(?:\\r\\n|[\\s\\S])|(?!\\2)[^\\\\\\r\\n])*\\2(?=\\s*:)/m,\n\t\tlookbehind: true,\n\t\tgreedy: true,\n\t\talias: 'property'\n\t}\n});\n\nPrism.languages.insertBefore('javascript', 'operator', {\n\t'literal-property': {\n\t\tpattern: /((?:^|[,{])[ \\t]*)(?!\\s)[_$a-zA-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*(?=\\s*:)/m,\n\t\tlookbehind: true,\n\t\talias: 'property'\n\t},\n});\n\nif (Prism.languages.markup) {\n\tPrism.languages.markup.tag.addInlined('script', 'javascript');\n\n\t// add attribute support for all DOM events.\n\t// https://developer.mozilla.org/en-US/docs/Web/Events#Standard_events\n\tPrism.languages.markup.tag.addAttribute(\n\t\t/on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,\n\t\t'javascript'\n\t);\n}\n\nPrism.languages.js = Prism.languages.javascript;\n\n(function (Prism) {\n\t// $ set | grep '^[A-Z][^[:space:]]*=' | cut -d= -f1 | tr '\\n' '|'\n\t// + LC_ALL, RANDOM, REPLY, SECONDS.\n\t// + make sure PS1..4 are here as they are not always set,\n\t// - some useless things.\n\tvar envVars = '\\\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\\\b';\n\n\tvar commandAfterHeredoc = {\n\t\tpattern: /(^([\"']?)\\w+\\2)[ \\t]+\\S.*/,\n\t\tlookbehind: true,\n\t\talias: 'punctuation', // this looks reasonably well in all themes\n\t\tinside: null // see below\n\t};\n\n\tvar insideString = {\n\t\t'bash': commandAfterHeredoc,\n\t\t'environment': {\n\t\t\tpattern: RegExp('\\\\$' + envVars),\n\t\t\talias: 'constant'\n\t\t},\n\t\t'variable': [\n\t\t\t// [0]: Arithmetic Environment\n\t\t\t{\n\t\t\t\tpattern: /\\$?\\(\\([\\s\\S]+?\\)\\)/,\n\t\t\t\tgreedy: true,\n\t\t\t\tinside: {\n\t\t\t\t\t// If there is a $ sign at the beginning highlight $(( and )) as variable\n\t\t\t\t\t'variable': [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpattern: /(^\\$\\(\\([\\s\\S]+)\\)\\)/,\n\t\t\t\t\t\t\tlookbehind: true\n\t\t\t\t\t\t},\n\t\t\t\t\t\t/^\\$\\(\\(/\n\t\t\t\t\t],\n\t\t\t\t\t'number': /\\b0x[\\dA-Fa-f]+\\b|(?:\\b\\d+(?:\\.\\d*)?|\\B\\.\\d+)(?:[Ee]-?\\d+)?/,\n\t\t\t\t\t// Operators according to https://www.gnu.org/software/bash/manual/bashref.html#Shell-Arithmetic\n\t\t\t\t\t'operator': /--|\\+\\+|\\*\\*=?|<<=?|>>=?|&&|\\|\\||[=!+\\-*/%<>^&|]=?|[?~:]/,\n\t\t\t\t\t// If there is no $ sign at the beginning highlight (( and )) as punctuation\n\t\t\t\t\t'punctuation': /\\(\\(?|\\)\\)?|,|;/\n\t\t\t\t}\n\t\t\t},\n\t\t\t// [1]: Command Substitution\n\t\t\t{\n\t\t\t\tpattern: /\\$\\((?:\\([^)]+\\)|[^()])+\\)|`[^`]+`/,\n\t\t\t\tgreedy: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'variable': /^\\$\\(|^`|\\)$|`$/\n\t\t\t\t}\n\t\t\t},\n\t\t\t// [2]: Brace expansion\n\t\t\t{\n\t\t\t\tpattern: /\\$\\{[^}]+\\}/,\n\t\t\t\tgreedy: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'operator': /:[-=?+]?|[!\\/]|##?|%%?|\\^\\^?|,,?/,\n\t\t\t\t\t'punctuation': /[\\[\\]]/,\n\t\t\t\t\t'environment': {\n\t\t\t\t\t\tpattern: RegExp('(\\\\{)' + envVars),\n\t\t\t\t\t\tlookbehind: true,\n\t\t\t\t\t\talias: 'constant'\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t/\\$(?:\\w+|[#?*!@$])/\n\t\t],\n\t\t// Escape sequences from echo and printf's manuals, and escaped quotes.\n\t\t'entity': /\\\\(?:[abceEfnrtv\\\\\"]|O?[0-7]{1,3}|U[0-9a-fA-F]{8}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{1,2})/\n\t};\n\n\tPrism.languages.bash = {\n\t\t'shebang': {\n\t\t\tpattern: /^#!\\s*\\/.*/,\n\t\t\talias: 'important'\n\t\t},\n\t\t'comment': {\n\t\t\tpattern: /(^|[^\"{\\\\$])#.*/,\n\t\t\tlookbehind: true\n\t\t},\n\t\t'function-name': [\n\t\t\t// a) function foo {\n\t\t\t// b) foo() {\n\t\t\t// c) function foo() {\n\t\t\t// but not “foo {”\n\t\t\t{\n\t\t\t\t// a) and c)\n\t\t\t\tpattern: /(\\bfunction\\s+)[\\w-]+(?=(?:\\s*\\(?:\\s*\\))?\\s*\\{)/,\n\t\t\t\tlookbehind: true,\n\t\t\t\talias: 'function'\n\t\t\t},\n\t\t\t{\n\t\t\t\t// b)\n\t\t\t\tpattern: /\\b[\\w-]+(?=\\s*\\(\\s*\\)\\s*\\{)/,\n\t\t\t\talias: 'function'\n\t\t\t}\n\t\t],\n\t\t// Highlight variable names as variables in for and select beginnings.\n\t\t'for-or-select': {\n\t\t\tpattern: /(\\b(?:for|select)\\s+)\\w+(?=\\s+in\\s)/,\n\t\t\talias: 'variable',\n\t\t\tlookbehind: true\n\t\t},\n\t\t// Highlight variable names as variables in the left-hand part\n\t\t// of assignments (“=” and “+=”).\n\t\t'assign-left': {\n\t\t\tpattern: /(^|[\\s;|&]|[<>]\\()\\w+(?:\\.\\w+)*(?=\\+?=)/,\n\t\t\tinside: {\n\t\t\t\t'environment': {\n\t\t\t\t\tpattern: RegExp('(^|[\\\\s;|&]|[<>]\\\\()' + envVars),\n\t\t\t\t\tlookbehind: true,\n\t\t\t\t\talias: 'constant'\n\t\t\t\t}\n\t\t\t},\n\t\t\talias: 'variable',\n\t\t\tlookbehind: true\n\t\t},\n\t\t// Highlight parameter names as variables\n\t\t'parameter': {\n\t\t\tpattern: /(^|\\s)-{1,2}(?:\\w+:[+-]?)?\\w+(?:\\.\\w+)*(?=[=\\s]|$)/,\n\t\t\talias: 'variable',\n\t\t\tlookbehind: true\n\t\t},\n\t\t'string': [\n\t\t\t// Support for Here-documents https://en.wikipedia.org/wiki/Here_document\n\t\t\t{\n\t\t\t\tpattern: /((?:^|[^<])<<-?\\s*)(\\w+)\\s[\\s\\S]*?(?:\\r?\\n|\\r)\\2/,\n\t\t\t\tlookbehind: true,\n\t\t\t\tgreedy: true,\n\t\t\t\tinside: insideString\n\t\t\t},\n\t\t\t// Here-document with quotes around the tag\n\t\t\t// → No expansion (so no “inside”).\n\t\t\t{\n\t\t\t\tpattern: /((?:^|[^<])<<-?\\s*)([\"'])(\\w+)\\2\\s[\\s\\S]*?(?:\\r?\\n|\\r)\\3/,\n\t\t\t\tlookbehind: true,\n\t\t\t\tgreedy: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'bash': commandAfterHeredoc\n\t\t\t\t}\n\t\t\t},\n\t\t\t// “Normal” string\n\t\t\t{\n\t\t\t\t// https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html\n\t\t\t\tpattern: /(^|[^\\\\](?:\\\\\\\\)*)\"(?:\\\\[\\s\\S]|\\$\\([^)]+\\)|\\$(?!\\()|`[^`]+`|[^\"\\\\`$])*\"/,\n\t\t\t\tlookbehind: true,\n\t\t\t\tgreedy: true,\n\t\t\t\tinside: insideString\n\t\t\t},\n\t\t\t{\n\t\t\t\t// https://www.gnu.org/software/bash/manual/html_node/Single-Quotes.html\n\t\t\t\tpattern: /(^|[^$\\\\])'[^']*'/,\n\t\t\t\tlookbehind: true,\n\t\t\t\tgreedy: true\n\t\t\t},\n\t\t\t{\n\t\t\t\t// https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html\n\t\t\t\tpattern: /\\$'(?:[^'\\\\]|\\\\[\\s\\S])*'/,\n\t\t\t\tgreedy: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'entity': insideString.entity\n\t\t\t\t}\n\t\t\t}\n\t\t],\n\t\t'environment': {\n\t\t\tpattern: RegExp('\\\\$?' + envVars),\n\t\t\talias: 'constant'\n\t\t},\n\t\t'variable': insideString.variable,\n\t\t'function': {\n\t\t\tpattern: /(^|[\\s;|&]|[<>]\\()(?:add|apropos|apt|apt-cache|apt-get|aptitude|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cargo|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|docker|docker-compose|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|java|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|node|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|podman|podman-compose|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|sysctl|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vcpkg|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\\s;|&])/,\n\t\t\tlookbehind: true\n\t\t},\n\t\t'keyword': {\n\t\t\tpattern: /(^|[\\s;|&]|[<>]\\()(?:case|do|done|elif|else|esac|fi|for|function|if|in|select|then|until|while)(?=$|[)\\s;|&])/,\n\t\t\tlookbehind: true\n\t\t},\n\t\t// https://www.gnu.org/software/bash/manual/html_node/Shell-Builtin-Commands.html\n\t\t'builtin': {\n\t\t\tpattern: /(^|[\\s;|&]|[<>]\\()(?:\\.|:|alias|bind|break|builtin|caller|cd|command|continue|declare|echo|enable|eval|exec|exit|export|getopts|hash|help|let|local|logout|mapfile|printf|pwd|read|readarray|readonly|return|set|shift|shopt|source|test|times|trap|type|typeset|ulimit|umask|unalias|unset)(?=$|[)\\s;|&])/,\n\t\t\tlookbehind: true,\n\t\t\t// Alias added to make those easier to distinguish from strings.\n\t\t\talias: 'class-name'\n\t\t},\n\t\t'boolean': {\n\t\t\tpattern: /(^|[\\s;|&]|[<>]\\()(?:false|true)(?=$|[)\\s;|&])/,\n\t\t\tlookbehind: true\n\t\t},\n\t\t'file-descriptor': {\n\t\t\tpattern: /\\B&\\d\\b/,\n\t\t\talias: 'important'\n\t\t},\n\t\t'operator': {\n\t\t\t// Lots of redirections here, but not just that.\n\t\t\tpattern: /\\d?<>|>\\||\\+=|=[=~]?|!=?|<<[<-]?|[&\\d]?>>|\\d[<>]&?|[<>][&=]?|&[>&]?|\\|[&|]?/,\n\t\t\tinside: {\n\t\t\t\t'file-descriptor': {\n\t\t\t\t\tpattern: /^\\d/,\n\t\t\t\t\talias: 'important'\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t'punctuation': /\\$?\\(\\(?|\\)\\)?|\\.\\.|[{}[\\];\\\\]/,\n\t\t'number': {\n\t\t\tpattern: /(^|\\s)(?:[1-9]\\d*|0)(?:[.,]\\d+)?\\b/,\n\t\t\tlookbehind: true\n\t\t}\n\t};\n\n\tcommandAfterHeredoc.inside = Prism.languages.bash;\n\n\t/* Patterns in command substitution. */\n\tvar toBeCopied = [\n\t\t'comment',\n\t\t'function-name',\n\t\t'for-or-select',\n\t\t'assign-left',\n\t\t'parameter',\n\t\t'string',\n\t\t'environment',\n\t\t'function',\n\t\t'keyword',\n\t\t'builtin',\n\t\t'boolean',\n\t\t'file-descriptor',\n\t\t'operator',\n\t\t'punctuation',\n\t\t'number'\n\t];\n\tvar inside = insideString.variable[1].inside;\n\tfor (var i = 0; i < toBeCopied.length; i++) {\n\t\tinside[toBeCopied[i]] = Prism.languages.bash[toBeCopied[i]];\n\t}\n\n\tPrism.languages.sh = Prism.languages.bash;\n\tPrism.languages.shell = Prism.languages.bash;\n}(Prism));\n\nPrism.languages.c = Prism.languages.extend('clike', {\n\t'comment': {\n\t\tpattern: /\\/\\/(?:[^\\r\\n\\\\]|\\\\(?:\\r\\n?|\\n|(?![\\r\\n])))*|\\/\\*[\\s\\S]*?(?:\\*\\/|$)/,\n\t\tgreedy: true\n\t},\n\t'string': {\n\t\t// https://en.cppreference.com/w/c/language/string_literal\n\t\tpattern: /\"(?:\\\\(?:\\r\\n|[\\s\\S])|[^\"\\\\\\r\\n])*\"/,\n\t\tgreedy: true\n\t},\n\t'class-name': {\n\t\tpattern: /(\\b(?:enum|struct)\\s+(?:__attribute__\\s*\\(\\([\\s\\S]*?\\)\\)\\s*)?)\\w+|\\b[a-z]\\w*_t\\b/,\n\t\tlookbehind: true\n\t},\n\t'keyword': /\\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|__attribute__|asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|inline|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|typeof|union|unsigned|void|volatile|while)\\b/,\n\t'function': /\\b[a-z_]\\w*(?=\\s*\\()/i,\n\t'number': /(?:\\b0x(?:[\\da-f]+(?:\\.[\\da-f]*)?|\\.[\\da-f]+)(?:p[+-]?\\d+)?|(?:\\b\\d+(?:\\.\\d*)?|\\B\\.\\d+)(?:e[+-]?\\d+)?)[ful]{0,4}/i,\n\t'operator': />>=?|<<=?|->|([-+&|:])\\1|[?:~]|[-+*/%&|^!=<>]=?/\n});\n\nPrism.languages.insertBefore('c', 'string', {\n\t'char': {\n\t\t// https://en.cppreference.com/w/c/language/character_constant\n\t\tpattern: /'(?:\\\\(?:\\r\\n|[\\s\\S])|[^'\\\\\\r\\n]){0,32}'/,\n\t\tgreedy: true\n\t}\n});\n\nPrism.languages.insertBefore('c', 'string', {\n\t'macro': {\n\t\t// allow for multiline macro definitions\n\t\t// spaces after the # character compile fine with gcc\n\t\tpattern: /(^[\\t ]*)#\\s*[a-z](?:[^\\r\\n\\\\/]|\\/(?!\\*)|\\/\\*(?:[^*]|\\*(?!\\/))*\\*\\/|\\\\(?:\\r\\n|[\\s\\S]))*/im,\n\t\tlookbehind: true,\n\t\tgreedy: true,\n\t\talias: 'property',\n\t\tinside: {\n\t\t\t'string': [\n\t\t\t\t{\n\t\t\t\t\t// highlight the path of the include statement as a string\n\t\t\t\t\tpattern: /^(#\\s*include\\s*)<[^>]+>/,\n\t\t\t\t\tlookbehind: true\n\t\t\t\t},\n\t\t\t\tPrism.languages.c['string']\n\t\t\t],\n\t\t\t'char': Prism.languages.c['char'],\n\t\t\t'comment': Prism.languages.c['comment'],\n\t\t\t'macro-name': [\n\t\t\t\t{\n\t\t\t\t\tpattern: /(^#\\s*define\\s+)\\w+\\b(?!\\()/i,\n\t\t\t\t\tlookbehind: true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tpattern: /(^#\\s*define\\s+)\\w+\\b(?=\\()/i,\n\t\t\t\t\tlookbehind: true,\n\t\t\t\t\talias: 'function'\n\t\t\t\t}\n\t\t\t],\n\t\t\t// highlight macro directives as keywords\n\t\t\t'directive': {\n\t\t\t\tpattern: /^(#\\s*)[a-z]+/,\n\t\t\t\tlookbehind: true,\n\t\t\t\talias: 'keyword'\n\t\t\t},\n\t\t\t'directive-hash': /^#/,\n\t\t\t'punctuation': /##|\\\\(?=[\\r\\n])/,\n\t\t\t'expression': {\n\t\t\t\tpattern: /\\S[\\s\\S]*/,\n\t\t\t\tinside: Prism.languages.c\n\t\t\t}\n\t\t}\n\t}\n});\n\nPrism.languages.insertBefore('c', 'function', {\n\t// highlight predefined macros as constants\n\t'constant': /\\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\\b/\n});\n\ndelete Prism.languages.c['boolean'];\n\n(function (Prism) {\n\n\tvar keyword = /\\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|char8_t|class|co_await|co_return|co_yield|compl|concept|const|const_cast|consteval|constexpr|constinit|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int16_t|int32_t|int64_t|int8_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|using|virtual|void|volatile|wchar_t|while)\\b/;\n\tvar modName = /\\b(?!<keyword>)\\w+(?:\\s*\\.\\s*\\w+)*\\b/.source.replace(/<keyword>/g, function () { return keyword.source; });\n\n\tPrism.languages.cpp = Prism.languages.extend('c', {\n\t\t'class-name': [\n\t\t\t{\n\t\t\t\tpattern: RegExp(/(\\b(?:class|concept|enum|struct|typename)\\s+)(?!<keyword>)\\w+/.source\n\t\t\t\t\t.replace(/<keyword>/g, function () { return keyword.source; })),\n\t\t\t\tlookbehind: true\n\t\t\t},\n\t\t\t// This is intended to capture the class name of method implementations like:\n\t\t\t//   void foo::bar() const {}\n\t\t\t// However! The `foo` in the above example could also be a namespace, so we only capture the class name if\n\t\t\t// it starts with an uppercase letter. This approximation should give decent results.\n\t\t\t/\\b[A-Z]\\w*(?=\\s*::\\s*\\w+\\s*\\()/,\n\t\t\t// This will capture the class name before destructors like:\n\t\t\t//   Foo::~Foo() {}\n\t\t\t/\\b[A-Z_]\\w*(?=\\s*::\\s*~\\w+\\s*\\()/i,\n\t\t\t// This also intends to capture the class name of method implementations but here the class has template\n\t\t\t// parameters, so it can't be a namespace (until C++ adds generic namespaces).\n\t\t\t/\\b\\w+(?=\\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\\s*::\\s*\\w+\\s*\\()/\n\t\t],\n\t\t'keyword': keyword,\n\t\t'number': {\n\t\t\tpattern: /(?:\\b0b[01']+|\\b0x(?:[\\da-f']+(?:\\.[\\da-f']*)?|\\.[\\da-f']+)(?:p[+-]?[\\d']+)?|(?:\\b[\\d']+(?:\\.[\\d']*)?|\\B\\.[\\d']+)(?:e[+-]?[\\d']+)?)[ful]{0,4}/i,\n\t\t\tgreedy: true\n\t\t},\n\t\t'operator': />>=?|<<=?|->|--|\\+\\+|&&|\\|\\||[?:~]|<=>|[-+*/%&|^!=<>]=?|\\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\\b/,\n\t\t'boolean': /\\b(?:false|true)\\b/\n\t});\n\n\tPrism.languages.insertBefore('cpp', 'string', {\n\t\t'module': {\n\t\t\t// https://en.cppreference.com/w/cpp/language/modules\n\t\t\tpattern: RegExp(\n\t\t\t\t/(\\b(?:import|module)\\s+)/.source +\n\t\t\t\t'(?:' +\n\t\t\t\t// header-name\n\t\t\t\t/\"(?:\\\\(?:\\r\\n|[\\s\\S])|[^\"\\\\\\r\\n])*\"|<[^<>\\r\\n]*>/.source +\n\t\t\t\t'|' +\n\t\t\t\t// module name or partition or both\n\t\t\t\t/<mod-name>(?:\\s*:\\s*<mod-name>)?|:\\s*<mod-name>/.source.replace(/<mod-name>/g, function () { return modName; }) +\n\t\t\t\t')'\n\t\t\t),\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true,\n\t\t\tinside: {\n\t\t\t\t'string': /^[<\"][\\s\\S]+/,\n\t\t\t\t'operator': /:/,\n\t\t\t\t'punctuation': /\\./\n\t\t\t}\n\t\t},\n\t\t'raw-string': {\n\t\t\tpattern: /R\"([^()\\\\ ]{0,16})\\([\\s\\S]*?\\)\\1\"/,\n\t\t\talias: 'string',\n\t\t\tgreedy: true\n\t\t}\n\t});\n\n\tPrism.languages.insertBefore('cpp', 'keyword', {\n\t\t'generic-function': {\n\t\t\tpattern: /\\b(?!operator\\b)[a-z_]\\w*\\s*<(?:[^<>]|<[^<>]*>)*>(?=\\s*\\()/i,\n\t\t\tinside: {\n\t\t\t\t'function': /^\\w+/,\n\t\t\t\t'generic': {\n\t\t\t\t\tpattern: /<[\\s\\S]+/,\n\t\t\t\t\talias: 'class-name',\n\t\t\t\t\tinside: Prism.languages.cpp\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t});\n\n\tPrism.languages.insertBefore('cpp', 'operator', {\n\t\t'double-colon': {\n\t\t\tpattern: /::/,\n\t\t\talias: 'punctuation'\n\t\t}\n\t});\n\n\tPrism.languages.insertBefore('cpp', 'class-name', {\n\t\t// the base clause is an optional list of parent classes\n\t\t// https://en.cppreference.com/w/cpp/language/class\n\t\t'base-clause': {\n\t\t\tpattern: /(\\b(?:class|struct)\\s+\\w+\\s*:\\s*)[^;{}\"'\\s]+(?:\\s+[^;{}\"'\\s]+)*(?=\\s*[;{])/,\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true,\n\t\t\tinside: Prism.languages.extend('cpp', {})\n\t\t}\n\t});\n\n\tPrism.languages.insertBefore('inside', 'double-colon', {\n\t\t// All untokenized words that are not namespaces should be class names\n\t\t'class-name': /\\b[a-z_]\\w*\\b(?!\\s*::)/i\n\t}, Prism.languages.cpp['base-clause']);\n\n}(Prism));\n\nPrism.languages.cmake = {\n\t'comment': /#.*/,\n\t'string': {\n\t\tpattern: /\"(?:[^\\\\\"]|\\\\.)*\"/,\n\t\tgreedy: true,\n\t\tinside: {\n\t\t\t'interpolation': {\n\t\t\t\tpattern: /\\$\\{(?:[^{}$]|\\$\\{[^{}$]*\\})*\\}/,\n\t\t\t\tinside: {\n\t\t\t\t\t'punctuation': /\\$\\{|\\}/,\n\t\t\t\t\t'variable': /\\w+/\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t'variable': /\\b(?:CMAKE_\\w+|\\w+_(?:(?:BINARY|SOURCE)_DIR|DESCRIPTION|HOMEPAGE_URL|ROOT|VERSION(?:_MAJOR|_MINOR|_PATCH|_TWEAK)?)|(?:ANDROID|APPLE|BORLAND|BUILD_SHARED_LIBS|CACHE|CPACK_(?:ABSOLUTE_DESTINATION_FILES|COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY|ERROR_ON_ABSOLUTE_INSTALL_DESTINATION|INCLUDE_TOPLEVEL_DIRECTORY|INSTALL_DEFAULT_DIRECTORY_PERMISSIONS|INSTALL_SCRIPT|PACKAGING_INSTALL_PREFIX|SET_DESTDIR|WARN_ON_ABSOLUTE_INSTALL_DESTINATION)|CTEST_(?:BINARY_DIRECTORY|BUILD_COMMAND|BUILD_NAME|BZR_COMMAND|BZR_UPDATE_OPTIONS|CHANGE_ID|CHECKOUT_COMMAND|CONFIGURATION_TYPE|CONFIGURE_COMMAND|COVERAGE_COMMAND|COVERAGE_EXTRA_FLAGS|CURL_OPTIONS|CUSTOM_(?:COVERAGE_EXCLUDE|ERROR_EXCEPTION|ERROR_MATCH|ERROR_POST_CONTEXT|ERROR_PRE_CONTEXT|MAXIMUM_FAILED_TEST_OUTPUT_SIZE|MAXIMUM_NUMBER_OF_(?:ERRORS|WARNINGS)|MAXIMUM_PASSED_TEST_OUTPUT_SIZE|MEMCHECK_IGNORE|POST_MEMCHECK|POST_TEST|PRE_MEMCHECK|PRE_TEST|TESTS_IGNORE|WARNING_EXCEPTION|WARNING_MATCH)|CVS_CHECKOUT|CVS_COMMAND|CVS_UPDATE_OPTIONS|DROP_LOCATION|DROP_METHOD|DROP_SITE|DROP_SITE_CDASH|DROP_SITE_PASSWORD|DROP_SITE_USER|EXTRA_COVERAGE_GLOB|GIT_COMMAND|GIT_INIT_SUBMODULES|GIT_UPDATE_CUSTOM|GIT_UPDATE_OPTIONS|HG_COMMAND|HG_UPDATE_OPTIONS|LABELS_FOR_SUBPROJECTS|MEMORYCHECK_(?:COMMAND|COMMAND_OPTIONS|SANITIZER_OPTIONS|SUPPRESSIONS_FILE|TYPE)|NIGHTLY_START_TIME|P4_CLIENT|P4_COMMAND|P4_OPTIONS|P4_UPDATE_OPTIONS|RUN_CURRENT_SCRIPT|SCP_COMMAND|SITE|SOURCE_DIRECTORY|SUBMIT_URL|SVN_COMMAND|SVN_OPTIONS|SVN_UPDATE_OPTIONS|TEST_LOAD|TEST_TIMEOUT|TRIGGER_SITE|UPDATE_COMMAND|UPDATE_OPTIONS|UPDATE_VERSION_ONLY|USE_LAUNCHERS)|CYGWIN|ENV|EXECUTABLE_OUTPUT_PATH|GHS-MULTI|IOS|LIBRARY_OUTPUT_PATH|MINGW|MSVC(?:10|11|12|14|60|70|71|80|90|_IDE|_TOOLSET_VERSION|_VERSION)?|MSYS|PROJECT_NAME|UNIX|WIN32|WINCE|WINDOWS_PHONE|WINDOWS_STORE|XCODE))\\b/,\n\t'property': /\\b(?:cxx_\\w+|(?:ARCHIVE_OUTPUT_(?:DIRECTORY|NAME)|COMPILE_DEFINITIONS|COMPILE_PDB_NAME|COMPILE_PDB_OUTPUT_DIRECTORY|EXCLUDE_FROM_DEFAULT_BUILD|IMPORTED_(?:IMPLIB|LIBNAME|LINK_DEPENDENT_LIBRARIES|LINK_INTERFACE_LANGUAGES|LINK_INTERFACE_LIBRARIES|LINK_INTERFACE_MULTIPLICITY|LOCATION|NO_SONAME|OBJECTS|SONAME)|INTERPROCEDURAL_OPTIMIZATION|LIBRARY_OUTPUT_DIRECTORY|LIBRARY_OUTPUT_NAME|LINK_FLAGS|LINK_INTERFACE_LIBRARIES|LINK_INTERFACE_MULTIPLICITY|LOCATION|MAP_IMPORTED_CONFIG|OSX_ARCHITECTURES|OUTPUT_NAME|PDB_NAME|PDB_OUTPUT_DIRECTORY|RUNTIME_OUTPUT_DIRECTORY|RUNTIME_OUTPUT_NAME|STATIC_LIBRARY_FLAGS|VS_CSHARP|VS_DOTNET_REFERENCEPROP|VS_DOTNET_REFERENCE|VS_GLOBAL_SECTION_POST|VS_GLOBAL_SECTION_PRE|VS_GLOBAL|XCODE_ATTRIBUTE)_\\w+|\\w+_(?:CLANG_TIDY|COMPILER_LAUNCHER|CPPCHECK|CPPLINT|INCLUDE_WHAT_YOU_USE|OUTPUT_NAME|POSTFIX|VISIBILITY_PRESET)|ABSTRACT|ADDITIONAL_MAKE_CLEAN_FILES|ADVANCED|ALIASED_TARGET|ALLOW_DUPLICATE_CUSTOM_TARGETS|ANDROID_(?:ANT_ADDITIONAL_OPTIONS|API|API_MIN|ARCH|ASSETS_DIRECTORIES|GUI|JAR_DEPENDENCIES|NATIVE_LIB_DEPENDENCIES|NATIVE_LIB_DIRECTORIES|PROCESS_MAX|PROGUARD|PROGUARD_CONFIG_PATH|SECURE_PROPS_PATH|SKIP_ANT_STEP|STL_TYPE)|ARCHIVE_OUTPUT_DIRECTORY|ATTACHED_FILES|ATTACHED_FILES_ON_FAIL|AUTOGEN_(?:BUILD_DIR|ORIGIN_DEPENDS|PARALLEL|SOURCE_GROUP|TARGETS_FOLDER|TARGET_DEPENDS)|AUTOMOC|AUTOMOC_(?:COMPILER_PREDEFINES|DEPEND_FILTERS|EXECUTABLE|MACRO_NAMES|MOC_OPTIONS|SOURCE_GROUP|TARGETS_FOLDER)|AUTORCC|AUTORCC_EXECUTABLE|AUTORCC_OPTIONS|AUTORCC_SOURCE_GROUP|AUTOUIC|AUTOUIC_EXECUTABLE|AUTOUIC_OPTIONS|AUTOUIC_SEARCH_PATHS|BINARY_DIR|BUILDSYSTEM_TARGETS|BUILD_RPATH|BUILD_RPATH_USE_ORIGIN|BUILD_WITH_INSTALL_NAME_DIR|BUILD_WITH_INSTALL_RPATH|BUNDLE|BUNDLE_EXTENSION|CACHE_VARIABLES|CLEAN_NO_CUSTOM|COMMON_LANGUAGE_RUNTIME|COMPATIBLE_INTERFACE_(?:BOOL|NUMBER_MAX|NUMBER_MIN|STRING)|COMPILE_(?:DEFINITIONS|FEATURES|FLAGS|OPTIONS|PDB_NAME|PDB_OUTPUT_DIRECTORY)|COST|CPACK_DESKTOP_SHORTCUTS|CPACK_NEVER_OVERWRITE|CPACK_PERMANENT|CPACK_STARTUP_SHORTCUTS|CPACK_START_MENU_SHORTCUTS|CPACK_WIX_ACL|CROSSCOMPILING_EMULATOR|CUDA_EXTENSIONS|CUDA_PTX_COMPILATION|CUDA_RESOLVE_DEVICE_SYMBOLS|CUDA_SEPARABLE_COMPILATION|CUDA_STANDARD|CUDA_STANDARD_REQUIRED|CXX_EXTENSIONS|CXX_STANDARD|CXX_STANDARD_REQUIRED|C_EXTENSIONS|C_STANDARD|C_STANDARD_REQUIRED|DEBUG_CONFIGURATIONS|DEFINE_SYMBOL|DEFINITIONS|DEPENDS|DEPLOYMENT_ADDITIONAL_FILES|DEPLOYMENT_REMOTE_DIRECTORY|DISABLED|DISABLED_FEATURES|ECLIPSE_EXTRA_CPROJECT_CONTENTS|ECLIPSE_EXTRA_NATURES|ENABLED_FEATURES|ENABLED_LANGUAGES|ENABLE_EXPORTS|ENVIRONMENT|EXCLUDE_FROM_ALL|EXCLUDE_FROM_DEFAULT_BUILD|EXPORT_NAME|EXPORT_PROPERTIES|EXTERNAL_OBJECT|EchoString|FAIL_REGULAR_EXPRESSION|FIND_LIBRARY_USE_LIB32_PATHS|FIND_LIBRARY_USE_LIB64_PATHS|FIND_LIBRARY_USE_LIBX32_PATHS|FIND_LIBRARY_USE_OPENBSD_VERSIONING|FIXTURES_CLEANUP|FIXTURES_REQUIRED|FIXTURES_SETUP|FOLDER|FRAMEWORK|Fortran_FORMAT|Fortran_MODULE_DIRECTORY|GENERATED|GENERATOR_FILE_NAME|GENERATOR_IS_MULTI_CONFIG|GHS_INTEGRITY_APP|GHS_NO_SOURCE_GROUP_FILE|GLOBAL_DEPENDS_DEBUG_MODE|GLOBAL_DEPENDS_NO_CYCLES|GNUtoMS|HAS_CXX|HEADER_FILE_ONLY|HELPSTRING|IMPLICIT_DEPENDS_INCLUDE_TRANSFORM|IMPORTED|IMPORTED_(?:COMMON_LANGUAGE_RUNTIME|CONFIGURATIONS|GLOBAL|IMPLIB|LIBNAME|LINK_DEPENDENT_LIBRARIES|LINK_INTERFACE_(?:LANGUAGES|LIBRARIES|MULTIPLICITY)|LOCATION|NO_SONAME|OBJECTS|SONAME)|IMPORT_PREFIX|IMPORT_SUFFIX|INCLUDE_DIRECTORIES|INCLUDE_REGULAR_EXPRESSION|INSTALL_NAME_DIR|INSTALL_RPATH|INSTALL_RPATH_USE_LINK_PATH|INTERFACE_(?:AUTOUIC_OPTIONS|COMPILE_DEFINITIONS|COMPILE_FEATURES|COMPILE_OPTIONS|INCLUDE_DIRECTORIES|LINK_DEPENDS|LINK_DIRECTORIES|LINK_LIBRARIES|LINK_OPTIONS|POSITION_INDEPENDENT_CODE|SOURCES|SYSTEM_INCLUDE_DIRECTORIES)|INTERPROCEDURAL_OPTIMIZATION|IN_TRY_COMPILE|IOS_INSTALL_COMBINED|JOB_POOLS|JOB_POOL_COMPILE|JOB_POOL_LINK|KEEP_EXTENSION|LABELS|LANGUAGE|LIBRARY_OUTPUT_DIRECTORY|LINKER_LANGUAGE|LINK_(?:DEPENDS|DEPENDS_NO_SHARED|DIRECTORIES|FLAGS|INTERFACE_LIBRARIES|INTERFACE_MULTIPLICITY|LIBRARIES|OPTIONS|SEARCH_END_STATIC|SEARCH_START_STATIC|WHAT_YOU_USE)|LISTFILE_STACK|LOCATION|MACOSX_BUNDLE|MACOSX_BUNDLE_INFO_PLIST|MACOSX_FRAMEWORK_INFO_PLIST|MACOSX_PACKAGE_LOCATION|MACOSX_RPATH|MACROS|MANUALLY_ADDED_DEPENDENCIES|MEASUREMENT|MODIFIED|NAME|NO_SONAME|NO_SYSTEM_FROM_IMPORTED|OBJECT_DEPENDS|OBJECT_OUTPUTS|OSX_ARCHITECTURES|OUTPUT_NAME|PACKAGES_FOUND|PACKAGES_NOT_FOUND|PARENT_DIRECTORY|PASS_REGULAR_EXPRESSION|PDB_NAME|PDB_OUTPUT_DIRECTORY|POSITION_INDEPENDENT_CODE|POST_INSTALL_SCRIPT|PREDEFINED_TARGETS_FOLDER|PREFIX|PRE_INSTALL_SCRIPT|PRIVATE_HEADER|PROCESSORS|PROCESSOR_AFFINITY|PROJECT_LABEL|PUBLIC_HEADER|REPORT_UNDEFINED_PROPERTIES|REQUIRED_FILES|RESOURCE|RESOURCE_LOCK|RULE_LAUNCH_COMPILE|RULE_LAUNCH_CUSTOM|RULE_LAUNCH_LINK|RULE_MESSAGES|RUNTIME_OUTPUT_DIRECTORY|RUN_SERIAL|SKIP_AUTOGEN|SKIP_AUTOMOC|SKIP_AUTORCC|SKIP_AUTOUIC|SKIP_BUILD_RPATH|SKIP_RETURN_CODE|SOURCES|SOURCE_DIR|SOVERSION|STATIC_LIBRARY_FLAGS|STATIC_LIBRARY_OPTIONS|STRINGS|SUBDIRECTORIES|SUFFIX|SYMBOLIC|TARGET_ARCHIVES_MAY_BE_SHARED_LIBS|TARGET_MESSAGES|TARGET_SUPPORTS_SHARED_LIBS|TESTS|TEST_INCLUDE_FILE|TEST_INCLUDE_FILES|TIMEOUT|TIMEOUT_AFTER_MATCH|TYPE|USE_FOLDERS|VALUE|VARIABLES|VERSION|VISIBILITY_INLINES_HIDDEN|VS_(?:CONFIGURATION_TYPE|COPY_TO_OUT_DIR|DEBUGGER_(?:COMMAND|COMMAND_ARGUMENTS|ENVIRONMENT|WORKING_DIRECTORY)|DEPLOYMENT_CONTENT|DEPLOYMENT_LOCATION|DOTNET_REFERENCES|DOTNET_REFERENCES_COPY_LOCAL|INCLUDE_IN_VSIX|IOT_STARTUP_TASK|KEYWORD|RESOURCE_GENERATOR|SCC_AUXPATH|SCC_LOCALPATH|SCC_PROJECTNAME|SCC_PROVIDER|SDK_REFERENCES|SHADER_(?:DISABLE_OPTIMIZATIONS|ENABLE_DEBUG|ENTRYPOINT|FLAGS|MODEL|OBJECT_FILE_NAME|OUTPUT_HEADER_FILE|TYPE|VARIABLE_NAME)|STARTUP_PROJECT|TOOL_OVERRIDE|USER_PROPS|WINRT_COMPONENT|WINRT_EXTENSIONS|WINRT_REFERENCES|XAML_TYPE)|WILL_FAIL|WIN32_EXECUTABLE|WINDOWS_EXPORT_ALL_SYMBOLS|WORKING_DIRECTORY|WRAP_EXCLUDE|XCODE_(?:EMIT_EFFECTIVE_PLATFORM_NAME|EXPLICIT_FILE_TYPE|FILE_ATTRIBUTES|LAST_KNOWN_FILE_TYPE|PRODUCT_TYPE|SCHEME_(?:ADDRESS_SANITIZER|ADDRESS_SANITIZER_USE_AFTER_RETURN|ARGUMENTS|DISABLE_MAIN_THREAD_CHECKER|DYNAMIC_LIBRARY_LOADS|DYNAMIC_LINKER_API_USAGE|ENVIRONMENT|EXECUTABLE|GUARD_MALLOC|MAIN_THREAD_CHECKER_STOP|MALLOC_GUARD_EDGES|MALLOC_SCRIBBLE|MALLOC_STACK|THREAD_SANITIZER(?:_STOP)?|UNDEFINED_BEHAVIOUR_SANITIZER(?:_STOP)?|ZOMBIE_OBJECTS))|XCTEST)\\b/,\n\t'keyword': /\\b(?:add_compile_definitions|add_compile_options|add_custom_command|add_custom_target|add_definitions|add_dependencies|add_executable|add_library|add_link_options|add_subdirectory|add_test|aux_source_directory|break|build_command|build_name|cmake_host_system_information|cmake_minimum_required|cmake_parse_arguments|cmake_policy|configure_file|continue|create_test_sourcelist|ctest_build|ctest_configure|ctest_coverage|ctest_empty_binary_directory|ctest_memcheck|ctest_read_custom_files|ctest_run_script|ctest_sleep|ctest_start|ctest_submit|ctest_test|ctest_update|ctest_upload|define_property|else|elseif|enable_language|enable_testing|endforeach|endfunction|endif|endmacro|endwhile|exec_program|execute_process|export|export_library_dependencies|file|find_file|find_library|find_package|find_path|find_program|fltk_wrap_ui|foreach|function|get_cmake_property|get_directory_property|get_filename_component|get_property|get_source_file_property|get_target_property|get_test_property|if|include|include_directories|include_external_msproject|include_guard|include_regular_expression|install|install_files|install_programs|install_targets|link_directories|link_libraries|list|load_cache|load_command|macro|make_directory|mark_as_advanced|math|message|option|output_required_files|project|qt_wrap_cpp|qt_wrap_ui|remove|remove_definitions|return|separate_arguments|set|set_directory_properties|set_property|set_source_files_properties|set_target_properties|set_tests_properties|site_name|source_group|string|subdir_depends|subdirs|target_compile_definitions|target_compile_features|target_compile_options|target_include_directories|target_link_directories|target_link_libraries|target_link_options|target_sources|try_compile|try_run|unset|use_mangled_mesa|utility_source|variable_requires|variable_watch|while|write_file)(?=\\s*\\()\\b/,\n\t'boolean': /\\b(?:FALSE|OFF|ON|TRUE)\\b/,\n\t'namespace': /\\b(?:INTERFACE|PRIVATE|PROPERTIES|PUBLIC|SHARED|STATIC|TARGET_OBJECTS)\\b/,\n\t'operator': /\\b(?:AND|DEFINED|EQUAL|GREATER|LESS|MATCHES|NOT|OR|STREQUAL|STRGREATER|STRLESS|VERSION_EQUAL|VERSION_GREATER|VERSION_LESS)\\b/,\n\t'inserted': {\n\t\tpattern: /\\b\\w+::\\w+\\b/,\n\t\talias: 'class-name'\n\t},\n\t'number': /\\b\\d+(?:\\.\\d+)*\\b/,\n\t'function': /\\b[a-z_]\\w*(?=\\s*\\()\\b/i,\n\t'punctuation': /[()>}]|\\$[<{]/\n};\n\n(function (Prism) {\n\n\t// Ignore comments starting with { to privilege string interpolation highlighting\n\tvar comment = /#(?!\\{).+/;\n\tvar interpolation = {\n\t\tpattern: /#\\{[^}]+\\}/,\n\t\talias: 'variable'\n\t};\n\n\tPrism.languages.coffeescript = Prism.languages.extend('javascript', {\n\t\t'comment': comment,\n\t\t'string': [\n\n\t\t\t// Strings are multiline\n\t\t\t{\n\t\t\t\tpattern: /'(?:\\\\[\\s\\S]|[^\\\\'])*'/,\n\t\t\t\tgreedy: true\n\t\t\t},\n\n\t\t\t{\n\t\t\t\t// Strings are multiline\n\t\t\t\tpattern: /\"(?:\\\\[\\s\\S]|[^\\\\\"])*\"/,\n\t\t\t\tgreedy: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'interpolation': interpolation\n\t\t\t\t}\n\t\t\t}\n\t\t],\n\t\t'keyword': /\\b(?:and|break|by|catch|class|continue|debugger|delete|do|each|else|extend|extends|false|finally|for|if|in|instanceof|is|isnt|let|loop|namespace|new|no|not|null|of|off|on|or|own|return|super|switch|then|this|throw|true|try|typeof|undefined|unless|until|when|while|window|with|yes|yield)\\b/,\n\t\t'class-member': {\n\t\t\tpattern: /@(?!\\d)\\w+/,\n\t\t\talias: 'variable'\n\t\t}\n\t});\n\n\tPrism.languages.insertBefore('coffeescript', 'comment', {\n\t\t'multiline-comment': {\n\t\t\tpattern: /###[\\s\\S]+?###/,\n\t\t\talias: 'comment'\n\t\t},\n\n\t\t// Block regexp can contain comments and interpolation\n\t\t'block-regex': {\n\t\t\tpattern: /\\/{3}[\\s\\S]*?\\/{3}/,\n\t\t\talias: 'regex',\n\t\t\tinside: {\n\t\t\t\t'comment': comment,\n\t\t\t\t'interpolation': interpolation\n\t\t\t}\n\t\t}\n\t});\n\n\tPrism.languages.insertBefore('coffeescript', 'string', {\n\t\t'inline-javascript': {\n\t\t\tpattern: /`(?:\\\\[\\s\\S]|[^\\\\`])*`/,\n\t\t\tinside: {\n\t\t\t\t'delimiter': {\n\t\t\t\t\tpattern: /^`|`$/,\n\t\t\t\t\talias: 'punctuation'\n\t\t\t\t},\n\t\t\t\t'script': {\n\t\t\t\t\tpattern: /[\\s\\S]+/,\n\t\t\t\t\talias: 'language-javascript',\n\t\t\t\t\tinside: Prism.languages.javascript\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t// Block strings\n\t\t'multiline-string': [\n\t\t\t{\n\t\t\t\tpattern: /'''[\\s\\S]*?'''/,\n\t\t\t\tgreedy: true,\n\t\t\t\talias: 'string'\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /\"\"\"[\\s\\S]*?\"\"\"/,\n\t\t\t\tgreedy: true,\n\t\t\t\talias: 'string',\n\t\t\t\tinside: {\n\t\t\t\t\tinterpolation: interpolation\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\n\t});\n\n\tPrism.languages.insertBefore('coffeescript', 'keyword', {\n\t\t// Object property\n\t\t'property': /(?!\\d)\\w+(?=\\s*:(?!:))/\n\t});\n\n\tdelete Prism.languages.coffeescript['template-string'];\n\n\tPrism.languages.coffee = Prism.languages.coffeescript;\n}(Prism));\n\n/**\n * Original by Samuel Flores\n *\n * Adds the following new token classes:\n *     constant, builtin, variable, symbol, regex\n */\n(function (Prism) {\n\tPrism.languages.ruby = Prism.languages.extend('clike', {\n\t\t'comment': {\n\t\t\tpattern: /#.*|^=begin\\s[\\s\\S]*?^=end/m,\n\t\t\tgreedy: true\n\t\t},\n\t\t'class-name': {\n\t\t\tpattern: /(\\b(?:class|module)\\s+|\\bcatch\\s+\\()[\\w.\\\\]+|\\b[A-Z_]\\w*(?=\\s*\\.\\s*new\\b)/,\n\t\t\tlookbehind: true,\n\t\t\tinside: {\n\t\t\t\t'punctuation': /[.\\\\]/\n\t\t\t}\n\t\t},\n\t\t'keyword': /\\b(?:BEGIN|END|alias|and|begin|break|case|class|def|define_method|defined|do|each|else|elsif|end|ensure|extend|for|if|in|include|module|new|next|nil|not|or|prepend|private|protected|public|raise|redo|require|rescue|retry|return|self|super|then|throw|undef|unless|until|when|while|yield)\\b/,\n\t\t'operator': /\\.{2,3}|&\\.|===|<?=>|[!=]?~|(?:&&|\\|\\||<<|>>|\\*\\*|[+\\-*/%<>!^&|=])=?|[?:]/,\n\t\t'punctuation': /[(){}[\\].,;]/,\n\t});\n\n\tPrism.languages.insertBefore('ruby', 'operator', {\n\t\t'double-colon': {\n\t\t\tpattern: /::/,\n\t\t\talias: 'punctuation'\n\t\t},\n\t});\n\n\tvar interpolation = {\n\t\tpattern: /((?:^|[^\\\\])(?:\\\\{2})*)#\\{(?:[^{}]|\\{[^{}]*\\})*\\}/,\n\t\tlookbehind: true,\n\t\tinside: {\n\t\t\t'content': {\n\t\t\t\tpattern: /^(#\\{)[\\s\\S]+(?=\\}$)/,\n\t\t\t\tlookbehind: true,\n\t\t\t\tinside: Prism.languages.ruby\n\t\t\t},\n\t\t\t'delimiter': {\n\t\t\t\tpattern: /^#\\{|\\}$/,\n\t\t\t\talias: 'punctuation'\n\t\t\t}\n\t\t}\n\t};\n\n\tdelete Prism.languages.ruby.function;\n\n\tvar percentExpression = '(?:' + [\n\t\t/([^a-zA-Z0-9\\s{(\\[<=])(?:(?!\\1)[^\\\\]|\\\\[\\s\\S])*\\1/.source,\n\t\t/\\((?:[^()\\\\]|\\\\[\\s\\S]|\\((?:[^()\\\\]|\\\\[\\s\\S])*\\))*\\)/.source,\n\t\t/\\{(?:[^{}\\\\]|\\\\[\\s\\S]|\\{(?:[^{}\\\\]|\\\\[\\s\\S])*\\})*\\}/.source,\n\t\t/\\[(?:[^\\[\\]\\\\]|\\\\[\\s\\S]|\\[(?:[^\\[\\]\\\\]|\\\\[\\s\\S])*\\])*\\]/.source,\n\t\t/<(?:[^<>\\\\]|\\\\[\\s\\S]|<(?:[^<>\\\\]|\\\\[\\s\\S])*>)*>/.source\n\t].join('|') + ')';\n\n\tvar symbolName = /(?:\"(?:\\\\.|[^\"\\\\\\r\\n])*\"|(?:\\b[a-zA-Z_]\\w*|[^\\s\\0-\\x7F]+)[?!]?|\\$.)/.source;\n\n\tPrism.languages.insertBefore('ruby', 'keyword', {\n\t\t'regex-literal': [\n\t\t\t{\n\t\t\t\tpattern: RegExp(/%r/.source + percentExpression + /[egimnosux]{0,6}/.source),\n\t\t\t\tgreedy: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'interpolation': interpolation,\n\t\t\t\t\t'regex': /[\\s\\S]+/\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /(^|[^/])\\/(?!\\/)(?:\\[[^\\r\\n\\]]+\\]|\\\\.|[^[/\\\\\\r\\n])+\\/[egimnosux]{0,6}(?=\\s*(?:$|[\\r\\n,.;})#]))/,\n\t\t\t\tlookbehind: true,\n\t\t\t\tgreedy: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'interpolation': interpolation,\n\t\t\t\t\t'regex': /[\\s\\S]+/\n\t\t\t\t}\n\t\t\t}\n\t\t],\n\t\t'variable': /[@$]+[a-zA-Z_]\\w*(?:[?!]|\\b)/,\n\t\t'symbol': [\n\t\t\t{\n\t\t\t\tpattern: RegExp(/(^|[^:]):/.source + symbolName),\n\t\t\t\tlookbehind: true,\n\t\t\t\tgreedy: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: RegExp(/([\\r\\n{(,][ \\t]*)/.source + symbolName + /(?=:(?!:))/.source),\n\t\t\t\tlookbehind: true,\n\t\t\t\tgreedy: true\n\t\t\t},\n\t\t],\n\t\t'method-definition': {\n\t\t\tpattern: /(\\bdef\\s+)\\w+(?:\\s*\\.\\s*\\w+)?/,\n\t\t\tlookbehind: true,\n\t\t\tinside: {\n\t\t\t\t'function': /\\b\\w+$/,\n\t\t\t\t'keyword': /^self\\b/,\n\t\t\t\t'class-name': /^\\w+/,\n\t\t\t\t'punctuation': /\\./\n\t\t\t}\n\t\t}\n\t});\n\n\tPrism.languages.insertBefore('ruby', 'string', {\n\t\t'string-literal': [\n\t\t\t{\n\t\t\t\tpattern: RegExp(/%[qQiIwWs]?/.source + percentExpression),\n\t\t\t\tgreedy: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'interpolation': interpolation,\n\t\t\t\t\t'string': /[\\s\\S]+/\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /(\"|')(?:#\\{[^}]+\\}|#(?!\\{)|\\\\(?:\\r\\n|[\\s\\S])|(?!\\1)[^\\\\#\\r\\n])*\\1/,\n\t\t\t\tgreedy: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'interpolation': interpolation,\n\t\t\t\t\t'string': /[\\s\\S]+/\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /<<[-~]?([a-z_]\\w*)[\\r\\n](?:.*[\\r\\n])*?[\\t ]*\\1/i,\n\t\t\t\talias: 'heredoc-string',\n\t\t\t\tgreedy: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'delimiter': {\n\t\t\t\t\t\tpattern: /^<<[-~]?[a-z_]\\w*|\\b[a-z_]\\w*$/i,\n\t\t\t\t\t\tinside: {\n\t\t\t\t\t\t\t'symbol': /\\b\\w+/,\n\t\t\t\t\t\t\t'punctuation': /^<<[-~]?/\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t'interpolation': interpolation,\n\t\t\t\t\t'string': /[\\s\\S]+/\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /<<[-~]?'([a-z_]\\w*)'[\\r\\n](?:.*[\\r\\n])*?[\\t ]*\\1/i,\n\t\t\t\talias: 'heredoc-string',\n\t\t\t\tgreedy: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'delimiter': {\n\t\t\t\t\t\tpattern: /^<<[-~]?'[a-z_]\\w*'|\\b[a-z_]\\w*$/i,\n\t\t\t\t\t\tinside: {\n\t\t\t\t\t\t\t'symbol': /\\b\\w+/,\n\t\t\t\t\t\t\t'punctuation': /^<<[-~]?'|'$/,\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t'string': /[\\s\\S]+/\n\t\t\t\t}\n\t\t\t}\n\t\t],\n\t\t'command-literal': [\n\t\t\t{\n\t\t\t\tpattern: RegExp(/%x/.source + percentExpression),\n\t\t\t\tgreedy: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'interpolation': interpolation,\n\t\t\t\t\t'command': {\n\t\t\t\t\t\tpattern: /[\\s\\S]+/,\n\t\t\t\t\t\talias: 'string'\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /`(?:#\\{[^}]+\\}|#(?!\\{)|\\\\(?:\\r\\n|[\\s\\S])|[^\\\\`#\\r\\n])*`/,\n\t\t\t\tgreedy: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'interpolation': interpolation,\n\t\t\t\t\t'command': {\n\t\t\t\t\t\tpattern: /[\\s\\S]+/,\n\t\t\t\t\t\talias: 'string'\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t});\n\n\tdelete Prism.languages.ruby.string;\n\n\tPrism.languages.insertBefore('ruby', 'number', {\n\t\t'builtin': /\\b(?:Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Fixnum|Float|Hash|IO|Integer|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|Stat|String|Struct|Symbol|TMS|Thread|ThreadGroup|Time|TrueClass)\\b/,\n\t\t'constant': /\\b[A-Z][A-Z0-9_]*(?:[?!]|\\b)/\n\t});\n\n\tPrism.languages.rb = Prism.languages.ruby;\n}(Prism));\n\n(function (Prism) {\n\tPrism.languages.crystal = Prism.languages.extend('ruby', {\n\t\t'keyword': [\n\t\t\t/\\b(?:__DIR__|__END_LINE__|__FILE__|__LINE__|abstract|alias|annotation|as|asm|begin|break|case|class|def|do|else|elsif|end|ensure|enum|extend|for|fun|if|ifdef|include|instance_sizeof|lib|macro|module|next|of|out|pointerof|private|protected|ptr|require|rescue|return|select|self|sizeof|struct|super|then|type|typeof|undef|uninitialized|union|unless|until|when|while|with|yield)\\b/,\n\t\t\t{\n\t\t\t\tpattern: /(\\.\\s*)(?:is_a|responds_to)\\?/,\n\t\t\t\tlookbehind: true\n\t\t\t}\n\t\t],\n\t\t'number': /\\b(?:0b[01_]*[01]|0o[0-7_]*[0-7]|0x[\\da-fA-F_]*[\\da-fA-F]|(?:\\d(?:[\\d_]*\\d)?)(?:\\.[\\d_]*\\d)?(?:[eE][+-]?[\\d_]*\\d)?)(?:_(?:[uif](?:8|16|32|64))?)?\\b/,\n\t\t'operator': [\n\t\t\t/->/,\n\t\t\tPrism.languages.ruby.operator,\n\t\t],\n\t\t'punctuation': /[(){}[\\].,;\\\\]/,\n\t});\n\n\tPrism.languages.insertBefore('crystal', 'string-literal', {\n\t\t'attribute': {\n\t\t\tpattern: /@\\[.*?\\]/,\n\t\t\tinside: {\n\t\t\t\t'delimiter': {\n\t\t\t\t\tpattern: /^@\\[|\\]$/,\n\t\t\t\t\talias: 'punctuation'\n\t\t\t\t},\n\t\t\t\t'attribute': {\n\t\t\t\t\tpattern: /^(\\s*)\\w+/,\n\t\t\t\t\tlookbehind: true,\n\t\t\t\t\talias: 'class-name'\n\t\t\t\t},\n\t\t\t\t'args': {\n\t\t\t\t\tpattern: /\\S(?:[\\s\\S]*\\S)?/,\n\t\t\t\t\tinside: Prism.languages.crystal\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t\t'expansion': {\n\t\t\tpattern: /\\{(?:\\{.*?\\}|%.*?%)\\}/,\n\t\t\tinside: {\n\t\t\t\t'content': {\n\t\t\t\t\tpattern: /^(\\{.)[\\s\\S]+(?=.\\}$)/,\n\t\t\t\t\tlookbehind: true,\n\t\t\t\t\tinside: Prism.languages.crystal\n\t\t\t\t},\n\t\t\t\t'delimiter': {\n\t\t\t\t\tpattern: /^\\{[\\{%]|[\\}%]\\}$/,\n\t\t\t\t\talias: 'operator'\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t'char': {\n\t\t\tpattern: /'(?:[^\\\\\\r\\n]{1,2}|\\\\(?:.|u(?:[A-Fa-f0-9]{1,4}|\\{[A-Fa-f0-9]{1,6}\\})))'/,\n\t\t\tgreedy: true\n\t\t}\n\t});\n\n}(Prism));\n\nPrism.languages.d = Prism.languages.extend('clike', {\n\t'comment': [\n\t\t{\n\t\t\t// Shebang\n\t\t\tpattern: /^\\s*#!.+/,\n\t\t\tgreedy: true\n\t\t},\n\t\t{\n\t\t\tpattern: RegExp(/(^|[^\\\\])/.source + '(?:' + [\n\t\t\t\t// /+ comment +/\n\t\t\t\t// Allow one level of nesting\n\t\t\t\t/\\/\\+(?:\\/\\+(?:[^+]|\\+(?!\\/))*\\+\\/|(?!\\/\\+)[\\s\\S])*?\\+\\//.source,\n\t\t\t\t// // comment\n\t\t\t\t/\\/\\/.*/.source,\n\t\t\t\t// /* comment */\n\t\t\t\t/\\/\\*[\\s\\S]*?\\*\\//.source\n\t\t\t].join('|') + ')'),\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true\n\t\t}\n\t],\n\t'string': [\n\t\t{\n\t\t\tpattern: RegExp([\n\t\t\t\t// r\"\", x\"\"\n\t\t\t\t/\\b[rx]\"(?:\\\\[\\s\\S]|[^\\\\\"])*\"[cwd]?/.source,\n\n\t\t\t\t// q\"[]\", q\"()\", q\"<>\", q\"{}\"\n\t\t\t\t/\\bq\"(?:\\[[\\s\\S]*?\\]|\\([\\s\\S]*?\\)|<[\\s\\S]*?>|\\{[\\s\\S]*?\\})\"/.source,\n\n\t\t\t\t// q\"IDENT\n\t\t\t\t// ...\n\t\t\t\t// IDENT\"\n\t\t\t\t/\\bq\"((?!\\d)\\w+)$[\\s\\S]*?^\\1\"/.source,\n\n\t\t\t\t// q\"//\", q\"||\", etc.\n\t\t\t\t// eslint-disable-next-line regexp/strict\n\t\t\t\t/\\bq\"(.)[\\s\\S]*?\\2\"/.source,\n\n\t\t\t\t// eslint-disable-next-line regexp/strict\n\t\t\t\t/([\"`])(?:\\\\[\\s\\S]|(?!\\3)[^\\\\])*\\3[cwd]?/.source\n\t\t\t].join('|'), 'm'),\n\t\t\tgreedy: true\n\t\t},\n\t\t{\n\t\t\tpattern: /\\bq\\{(?:\\{[^{}]*\\}|[^{}])*\\}/,\n\t\t\tgreedy: true,\n\t\t\talias: 'token-string'\n\t\t}\n\t],\n\n\t// In order: $, keywords and special tokens, globally defined symbols\n\t'keyword': /\\$|\\b(?:__(?:(?:DATE|EOF|FILE|FUNCTION|LINE|MODULE|PRETTY_FUNCTION|TIMESTAMP|TIME|VENDOR|VERSION)__|gshared|parameters|traits|vector)|abstract|alias|align|asm|assert|auto|body|bool|break|byte|case|cast|catch|cdouble|cent|cfloat|char|class|const|continue|creal|dchar|debug|default|delegate|delete|deprecated|do|double|dstring|else|enum|export|extern|false|final|finally|float|for|foreach|foreach_reverse|function|goto|idouble|if|ifloat|immutable|import|inout|int|interface|invariant|ireal|lazy|long|macro|mixin|module|new|nothrow|null|out|override|package|pragma|private|protected|ptrdiff_t|public|pure|real|ref|return|scope|shared|short|size_t|static|string|struct|super|switch|synchronized|template|this|throw|true|try|typedef|typeid|typeof|ubyte|ucent|uint|ulong|union|unittest|ushort|version|void|volatile|wchar|while|with|wstring)\\b/,\n\n\t'number': [\n\t\t// The lookbehind and the negative look-ahead try to prevent bad highlighting of the .. operator\n\t\t// Hexadecimal numbers must be handled separately to avoid problems with exponent \"e\"\n\t\t/\\b0x\\.?[a-f\\d_]+(?:(?!\\.\\.)\\.[a-f\\d_]*)?(?:p[+-]?[a-f\\d_]+)?[ulfi]{0,4}/i,\n\t\t{\n\t\t\tpattern: /((?:\\.\\.)?)(?:\\b0b\\.?|\\b|\\.)\\d[\\d_]*(?:(?!\\.\\.)\\.[\\d_]*)?(?:e[+-]?\\d[\\d_]*)?[ulfi]{0,4}/i,\n\t\t\tlookbehind: true\n\t\t}\n\t],\n\n\t'operator': /\\|[|=]?|&[&=]?|\\+[+=]?|-[-=]?|\\.?\\.\\.|=[>=]?|!(?:i[ns]\\b|<>?=?|>=?|=)?|\\bi[ns]\\b|(?:<[<>]?|>>?>?|\\^\\^|[*\\/%^~])=?/\n});\n\nPrism.languages.insertBefore('d', 'string', {\n\t// Characters\n\t// 'a', '\\\\', '\\n', '\\xFF', '\\377', '\\uFFFF', '\\U0010FFFF', '\\quot'\n\t'char': /'(?:\\\\(?:\\W|\\w+)|[^\\\\])'/\n});\n\nPrism.languages.insertBefore('d', 'keyword', {\n\t'property': /\\B@\\w*/\n});\n\nPrism.languages.insertBefore('d', 'function', {\n\t'register': {\n\t\t// Iasm registers\n\t\tpattern: /\\b(?:[ABCD][LHX]|E?(?:BP|DI|SI|SP)|[BS]PL|[ECSDGF]S|CR[0234]|[DS]IL|DR[012367]|E[ABCD]X|X?MM[0-7]|R(?:1[0-5]|[89])[BWD]?|R[ABCD]X|R[BS]P|R[DS]I|TR[3-7]|XMM(?:1[0-5]|[89])|YMM(?:1[0-5]|\\d))\\b|\\bST(?:\\([0-7]\\)|\\b)/,\n\t\talias: 'variable'\n\t}\n});\n\n(function (Prism) {\n\tvar keywords = [\n\t\t/\\b(?:async|sync|yield)\\*/,\n\t\t/\\b(?:abstract|assert|async|await|break|case|catch|class|const|continue|covariant|default|deferred|do|dynamic|else|enum|export|extends|extension|external|factory|final|finally|for|get|hide|if|implements|import|in|interface|library|mixin|new|null|on|operator|part|rethrow|return|set|show|static|super|switch|sync|this|throw|try|typedef|var|void|while|with|yield)\\b/\n\t];\n\n\t// Handles named imports, such as http.Client\n\tvar packagePrefix = /(^|[^\\w.])(?:[a-z]\\w*\\s*\\.\\s*)*(?:[A-Z]\\w*\\s*\\.\\s*)*/.source;\n\n\t// based on the dart naming conventions\n\tvar className = {\n\t\tpattern: RegExp(packagePrefix + /[A-Z](?:[\\d_A-Z]*[a-z]\\w*)?\\b/.source),\n\t\tlookbehind: true,\n\t\tinside: {\n\t\t\t'namespace': {\n\t\t\t\tpattern: /^[a-z]\\w*(?:\\s*\\.\\s*[a-z]\\w*)*(?:\\s*\\.)?/,\n\t\t\t\tinside: {\n\t\t\t\t\t'punctuation': /\\./\n\t\t\t\t}\n\t\t\t},\n\t\t}\n\t};\n\n\tPrism.languages.dart = Prism.languages.extend('clike', {\n\t\t'class-name': [\n\t\t\tclassName,\n\t\t\t{\n\t\t\t\t// variables and parameters\n\t\t\t\t// this to support class names (or generic parameters) which do not contain a lower case letter (also works for methods)\n\t\t\t\tpattern: RegExp(packagePrefix + /[A-Z]\\w*(?=\\s+\\w+\\s*[;,=()])/.source),\n\t\t\t\tlookbehind: true,\n\t\t\t\tinside: className.inside\n\t\t\t}\n\t\t],\n\t\t'keyword': keywords,\n\t\t'operator': /\\bis!|\\b(?:as|is)\\b|\\+\\+|--|&&|\\|\\||<<=?|>>=?|~(?:\\/=?)?|[+\\-*\\/%&^|=!<>]=?|\\?/\n\t});\n\n\tPrism.languages.insertBefore('dart', 'string', {\n\t\t'string-literal': {\n\t\t\tpattern: /r?(?:(\"\"\"|''')[\\s\\S]*?\\1|([\"'])(?:\\\\.|(?!\\2)[^\\\\\\r\\n])*\\2(?!\\2))/,\n\t\t\tgreedy: true,\n\t\t\tinside: {\n\t\t\t\t'interpolation': {\n\t\t\t\t\tpattern: /((?:^|[^\\\\])(?:\\\\{2})*)\\$(?:\\w+|\\{(?:[^{}]|\\{[^{}]*\\})*\\})/,\n\t\t\t\t\tlookbehind: true,\n\t\t\t\t\tinside: {\n\t\t\t\t\t\t'punctuation': /^\\$\\{?|\\}$/,\n\t\t\t\t\t\t'expression': {\n\t\t\t\t\t\t\tpattern: /[\\s\\S]+/,\n\t\t\t\t\t\t\tinside: Prism.languages.dart\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t'string': /[\\s\\S]+/\n\t\t\t}\n\t\t},\n\t\t'string': undefined\n\t});\n\n\tPrism.languages.insertBefore('dart', 'class-name', {\n\t\t'metadata': {\n\t\t\tpattern: /@\\w+/,\n\t\t\talias: 'function'\n\t\t}\n\t});\n\n\tPrism.languages.insertBefore('dart', 'class-name', {\n\t\t'generics': {\n\t\t\tpattern: /<(?:[\\w\\s,.&?]|<(?:[\\w\\s,.&?]|<(?:[\\w\\s,.&?]|<[\\w\\s,.&?]*>)*>)*>)*>/,\n\t\t\tinside: {\n\t\t\t\t'class-name': className,\n\t\t\t\t'keyword': keywords,\n\t\t\t\t'punctuation': /[<>(),.:]/,\n\t\t\t\t'operator': /[?&|]/\n\t\t\t}\n\t\t},\n\t});\n}(Prism));\n\n(function (Prism) {\n\n\tPrism.languages.diff = {\n\t\t'coord': [\n\t\t\t// Match all kinds of coord lines (prefixed by \"+++\", \"---\" or \"***\").\n\t\t\t/^(?:\\*{3}|-{3}|\\+{3}).*$/m,\n\t\t\t// Match \"@@ ... @@\" coord lines in unified diff.\n\t\t\t/^@@.*@@$/m,\n\t\t\t// Match coord lines in normal diff (starts with a number).\n\t\t\t/^\\d.*$/m\n\t\t]\n\n\t\t// deleted, inserted, unchanged, diff\n\t};\n\n\t/**\n\t * A map from the name of a block to its line prefix.\n\t *\n\t * @type {Object<string, string>}\n\t */\n\tvar PREFIXES = {\n\t\t'deleted-sign': '-',\n\t\t'deleted-arrow': '<',\n\t\t'inserted-sign': '+',\n\t\t'inserted-arrow': '>',\n\t\t'unchanged': ' ',\n\t\t'diff': '!',\n\t};\n\n\t// add a token for each prefix\n\tObject.keys(PREFIXES).forEach(function (name) {\n\t\tvar prefix = PREFIXES[name];\n\n\t\tvar alias = [];\n\t\tif (!/^\\w+$/.test(name)) { // \"deleted-sign\" -> \"deleted\"\n\t\t\talias.push(/\\w+/.exec(name)[0]);\n\t\t}\n\t\tif (name === 'diff') {\n\t\t\talias.push('bold');\n\t\t}\n\n\t\tPrism.languages.diff[name] = {\n\t\t\tpattern: RegExp('^(?:[' + prefix + '].*(?:\\r\\n?|\\n|(?![\\\\s\\\\S])))+', 'm'),\n\t\t\talias: alias,\n\t\t\tinside: {\n\t\t\t\t'line': {\n\t\t\t\t\tpattern: /(.)(?=[\\s\\S]).*(?:\\r\\n?|\\n)?/,\n\t\t\t\t\tlookbehind: true\n\t\t\t\t},\n\t\t\t\t'prefix': {\n\t\t\t\t\tpattern: /[\\s\\S]/,\n\t\t\t\t\talias: /\\w+/.exec(name)[0]\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t});\n\n\t// make prefixes available to Diff plugin\n\tObject.defineProperty(Prism.languages.diff, 'PREFIXES', {\n\t\tvalue: PREFIXES\n\t});\n\n}(Prism));\n\n(function (Prism) {\n\n\t/**\n\t * Returns the placeholder for the given language id and index.\n\t *\n\t * @param {string} language\n\t * @param {string|number} index\n\t * @returns {string}\n\t */\n\tfunction getPlaceholder(language, index) {\n\t\treturn '___' + language.toUpperCase() + index + '___';\n\t}\n\n\tObject.defineProperties(Prism.languages['markup-templating'] = {}, {\n\t\tbuildPlaceholders: {\n\t\t\t/**\n\t\t\t * Tokenize all inline templating expressions matching `placeholderPattern`.\n\t\t\t *\n\t\t\t * If `replaceFilter` is provided, only matches of `placeholderPattern` for which `replaceFilter` returns\n\t\t\t * `true` will be replaced.\n\t\t\t *\n\t\t\t * @param {object} env The environment of the `before-tokenize` hook.\n\t\t\t * @param {string} language The language id.\n\t\t\t * @param {RegExp} placeholderPattern The matches of this pattern will be replaced by placeholders.\n\t\t\t * @param {(match: string) => boolean} [replaceFilter]\n\t\t\t */\n\t\t\tvalue: function (env, language, placeholderPattern, replaceFilter) {\n\t\t\t\tif (env.language !== language) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tvar tokenStack = env.tokenStack = [];\n\n\t\t\t\tenv.code = env.code.replace(placeholderPattern, function (match) {\n\t\t\t\t\tif (typeof replaceFilter === 'function' && !replaceFilter(match)) {\n\t\t\t\t\t\treturn match;\n\t\t\t\t\t}\n\t\t\t\t\tvar i = tokenStack.length;\n\t\t\t\t\tvar placeholder;\n\n\t\t\t\t\t// Check for existing strings\n\t\t\t\t\twhile (env.code.indexOf(placeholder = getPlaceholder(language, i)) !== -1) {\n\t\t\t\t\t\t++i;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Create a sparse array\n\t\t\t\t\ttokenStack[i] = match;\n\n\t\t\t\t\treturn placeholder;\n\t\t\t\t});\n\n\t\t\t\t// Switch the grammar to markup\n\t\t\t\tenv.grammar = Prism.languages.markup;\n\t\t\t}\n\t\t},\n\t\ttokenizePlaceholders: {\n\t\t\t/**\n\t\t\t * Replace placeholders with proper tokens after tokenizing.\n\t\t\t *\n\t\t\t * @param {object} env The environment of the `after-tokenize` hook.\n\t\t\t * @param {string} language The language id.\n\t\t\t */\n\t\t\tvalue: function (env, language) {\n\t\t\t\tif (env.language !== language || !env.tokenStack) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Switch the grammar back\n\t\t\t\tenv.grammar = Prism.languages[language];\n\n\t\t\t\tvar j = 0;\n\t\t\t\tvar keys = Object.keys(env.tokenStack);\n\n\t\t\t\tfunction walkTokens(tokens) {\n\t\t\t\t\tfor (var i = 0; i < tokens.length; i++) {\n\t\t\t\t\t\t// all placeholders are replaced already\n\t\t\t\t\t\tif (j >= keys.length) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tvar token = tokens[i];\n\t\t\t\t\t\tif (typeof token === 'string' || (token.content && typeof token.content === 'string')) {\n\t\t\t\t\t\t\tvar k = keys[j];\n\t\t\t\t\t\t\tvar t = env.tokenStack[k];\n\t\t\t\t\t\t\tvar s = typeof token === 'string' ? token : token.content;\n\t\t\t\t\t\t\tvar placeholder = getPlaceholder(language, k);\n\n\t\t\t\t\t\t\tvar index = s.indexOf(placeholder);\n\t\t\t\t\t\t\tif (index > -1) {\n\t\t\t\t\t\t\t\t++j;\n\n\t\t\t\t\t\t\t\tvar before = s.substring(0, index);\n\t\t\t\t\t\t\t\tvar middle = new Prism.Token(language, Prism.tokenize(t, env.grammar), 'language-' + language, t);\n\t\t\t\t\t\t\t\tvar after = s.substring(index + placeholder.length);\n\n\t\t\t\t\t\t\t\tvar replacement = [];\n\t\t\t\t\t\t\t\tif (before) {\n\t\t\t\t\t\t\t\t\treplacement.push.apply(replacement, walkTokens([before]));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treplacement.push(middle);\n\t\t\t\t\t\t\t\tif (after) {\n\t\t\t\t\t\t\t\t\treplacement.push.apply(replacement, walkTokens([after]));\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (typeof token === 'string') {\n\t\t\t\t\t\t\t\t\ttokens.splice.apply(tokens, [i, 1].concat(replacement));\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\ttoken.content = replacement;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (token.content /* && typeof token.content !== 'string' */) {\n\t\t\t\t\t\t\twalkTokens(token.content);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn tokens;\n\t\t\t\t}\n\n\t\t\t\twalkTokens(env.tokens);\n\t\t\t}\n\t\t}\n\t});\n\n}(Prism));\n\n// Django/Jinja2 syntax definition for Prism.js <http://prismjs.com> syntax highlighter.\n// Mostly it works OK but can paint code incorrectly on complex html/template tag combinations.\n\n(function (Prism) {\n\n\tPrism.languages.django = {\n\t\t'comment': /^\\{#[\\s\\S]*?#\\}$/,\n\t\t'tag': {\n\t\t\tpattern: /(^\\{%[+-]?\\s*)\\w+/,\n\t\t\tlookbehind: true,\n\t\t\talias: 'keyword'\n\t\t},\n\t\t'delimiter': {\n\t\t\tpattern: /^\\{[{%][+-]?|[+-]?[}%]\\}$/,\n\t\t\talias: 'punctuation'\n\t\t},\n\t\t'string': {\n\t\t\tpattern: /(\"|')(?:\\\\.|(?!\\1)[^\\\\\\r\\n])*\\1/,\n\t\t\tgreedy: true\n\t\t},\n\t\t'filter': {\n\t\t\tpattern: /(\\|)\\w+/,\n\t\t\tlookbehind: true,\n\t\t\talias: 'function'\n\t\t},\n\t\t'test': {\n\t\t\tpattern: /(\\bis\\s+(?:not\\s+)?)(?!not\\b)\\w+/,\n\t\t\tlookbehind: true,\n\t\t\talias: 'function'\n\t\t},\n\t\t'function': /\\b[a-z_]\\w+(?=\\s*\\()/i,\n\t\t'keyword': /\\b(?:and|as|by|else|for|if|import|in|is|loop|not|or|recursive|with|without)\\b/,\n\t\t'operator': /[-+%=]=?|!=|\\*\\*?=?|\\/\\/?=?|<[<=>]?|>[=>]?|[&|^~]/,\n\t\t'number': /\\b\\d+(?:\\.\\d+)?\\b/,\n\t\t'boolean': /[Ff]alse|[Nn]one|[Tt]rue/,\n\t\t'variable': /\\b\\w+\\b/,\n\t\t'punctuation': /[{}[\\](),.:;]/\n\t};\n\n\n\tvar pattern = /\\{\\{[\\s\\S]*?\\}\\}|\\{%[\\s\\S]*?%\\}|\\{#[\\s\\S]*?#\\}/g;\n\tvar markupTemplating = Prism.languages['markup-templating'];\n\n\tPrism.hooks.add('before-tokenize', function (env) {\n\t\tmarkupTemplating.buildPlaceholders(env, 'django', pattern);\n\t});\n\tPrism.hooks.add('after-tokenize', function (env) {\n\t\tmarkupTemplating.tokenizePlaceholders(env, 'django');\n\t});\n\n\t// Add an Jinja2 alias\n\tPrism.languages.jinja2 = Prism.languages.django;\n\tPrism.hooks.add('before-tokenize', function (env) {\n\t\tmarkupTemplating.buildPlaceholders(env, 'jinja2', pattern);\n\t});\n\tPrism.hooks.add('after-tokenize', function (env) {\n\t\tmarkupTemplating.tokenizePlaceholders(env, 'jinja2');\n\t});\n\n}(Prism));\n\n// https://www.graphviz.org/doc/info/lang.html\n\n(function (Prism) {\n\n\tvar ID = '(?:' + [\n\t\t// an identifier\n\t\t/[a-zA-Z_\\x80-\\uFFFF][\\w\\x80-\\uFFFF]*/.source,\n\t\t// a number\n\t\t/-?(?:\\.\\d+|\\d+(?:\\.\\d*)?)/.source,\n\t\t// a double-quoted string\n\t\t/\"[^\"\\\\]*(?:\\\\[\\s\\S][^\"\\\\]*)*\"/.source,\n\t\t// HTML-like string\n\t\t/<(?:[^<>]|(?!<!--)<(?:[^<>\"']|\"[^\"]*\"|'[^']*')+>|<!--(?:[^-]|-(?!->))*-->)*>/.source\n\t].join('|') + ')';\n\n\tvar IDInside = {\n\t\t'markup': {\n\t\t\tpattern: /(^<)[\\s\\S]+(?=>$)/,\n\t\t\tlookbehind: true,\n\t\t\talias: ['language-markup', 'language-html', 'language-xml'],\n\t\t\tinside: Prism.languages.markup\n\t\t}\n\t};\n\n\t/**\n\t * @param {string} source\n\t * @param {string} flags\n\t * @returns {RegExp}\n\t */\n\tfunction withID(source, flags) {\n\t\treturn RegExp(source.replace(/<ID>/g, function () { return ID; }), flags);\n\t}\n\n\tPrism.languages.dot = {\n\t\t'comment': {\n\t\t\tpattern: /\\/\\/.*|\\/\\*[\\s\\S]*?\\*\\/|^#.*/m,\n\t\t\tgreedy: true\n\t\t},\n\t\t'graph-name': {\n\t\t\tpattern: withID(/(\\b(?:digraph|graph|subgraph)[ \\t\\r\\n]+)<ID>/.source, 'i'),\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true,\n\t\t\talias: 'class-name',\n\t\t\tinside: IDInside\n\t\t},\n\t\t'attr-value': {\n\t\t\tpattern: withID(/(=[ \\t\\r\\n]*)<ID>/.source),\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true,\n\t\t\tinside: IDInside\n\t\t},\n\t\t'attr-name': {\n\t\t\tpattern: withID(/([\\[;, \\t\\r\\n])<ID>(?=[ \\t\\r\\n]*=)/.source),\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true,\n\t\t\tinside: IDInside\n\t\t},\n\t\t'keyword': /\\b(?:digraph|edge|graph|node|strict|subgraph)\\b/i,\n\t\t'compass-point': {\n\t\t\tpattern: /(:[ \\t\\r\\n]*)(?:[ewc_]|[ns][ew]?)(?![\\w\\x80-\\uFFFF])/,\n\t\t\tlookbehind: true,\n\t\t\talias: 'builtin'\n\t\t},\n\t\t'node': {\n\t\t\tpattern: withID(/(^|[^-.\\w\\x80-\\uFFFF\\\\])<ID>/.source),\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true,\n\t\t\tinside: IDInside\n\t\t},\n\t\t'operator': /[=:]|-[->]/,\n\t\t'punctuation': /[\\[\\]{};,]/\n\t};\n\n\tPrism.languages.gv = Prism.languages.dot;\n\n}(Prism));\n\nPrism.languages.elixir = {\n\t'doc': {\n\t\tpattern: /@(?:doc|moduledoc)\\s+(?:(\"\"\"|''')[\\s\\S]*?\\1|(\"|')(?:\\\\(?:\\r\\n|[\\s\\S])|(?!\\2)[^\\\\\\r\\n])*\\2)/,\n\t\tinside: {\n\t\t\t'attribute': /^@\\w+/,\n\t\t\t'string': /['\"][\\s\\S]+/\n\t\t}\n\t},\n\t'comment': {\n\t\tpattern: /#.*/,\n\t\tgreedy: true\n\t},\n\t// ~r\"\"\"foo\"\"\" (multi-line), ~r'''foo''' (multi-line), ~r/foo/, ~r|foo|, ~r\"foo\", ~r'foo', ~r(foo), ~r[foo], ~r{foo}, ~r<foo>\n\t'regex': {\n\t\tpattern: /~[rR](?:(\"\"\"|''')(?:\\\\[\\s\\S]|(?!\\1)[^\\\\])+\\1|([\\/|\"'])(?:\\\\.|(?!\\2)[^\\\\\\r\\n])+\\2|\\((?:\\\\.|[^\\\\)\\r\\n])+\\)|\\[(?:\\\\.|[^\\\\\\]\\r\\n])+\\]|\\{(?:\\\\.|[^\\\\}\\r\\n])+\\}|<(?:\\\\.|[^\\\\>\\r\\n])+>)[uismxfr]*/,\n\t\tgreedy: true\n\t},\n\t'string': [\n\t\t{\n\t\t\t// ~s\"\"\"foo\"\"\" (multi-line), ~s'''foo''' (multi-line), ~s/foo/, ~s|foo|, ~s\"foo\", ~s'foo', ~s(foo), ~s[foo], ~s{foo} (with interpolation care), ~s<foo>\n\t\t\tpattern: /~[cCsSwW](?:(\"\"\"|''')(?:\\\\[\\s\\S]|(?!\\1)[^\\\\])+\\1|([\\/|\"'])(?:\\\\.|(?!\\2)[^\\\\\\r\\n])+\\2|\\((?:\\\\.|[^\\\\)\\r\\n])+\\)|\\[(?:\\\\.|[^\\\\\\]\\r\\n])+\\]|\\{(?:\\\\.|#\\{[^}]+\\}|#(?!\\{)|[^#\\\\}\\r\\n])+\\}|<(?:\\\\.|[^\\\\>\\r\\n])+>)[csa]?/,\n\t\t\tgreedy: true,\n\t\t\tinside: {\n\t\t\t\t// See interpolation below\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\tpattern: /(\"\"\"|''')[\\s\\S]*?\\1/,\n\t\t\tgreedy: true,\n\t\t\tinside: {\n\t\t\t\t// See interpolation below\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t// Multi-line strings are allowed\n\t\t\tpattern: /(\"|')(?:\\\\(?:\\r\\n|[\\s\\S])|(?!\\1)[^\\\\\\r\\n])*\\1/,\n\t\t\tgreedy: true,\n\t\t\tinside: {\n\t\t\t\t// See interpolation below\n\t\t\t}\n\t\t}\n\t],\n\t'atom': {\n\t\t// Look-behind prevents bad highlighting of the :: operator\n\t\tpattern: /(^|[^:]):\\w+/,\n\t\tlookbehind: true,\n\t\talias: 'symbol'\n\t},\n\t'module': {\n\t\tpattern: /\\b[A-Z]\\w*\\b/,\n\t\talias: 'class-name'\n\t},\n\t// Look-ahead prevents bad highlighting of the :: operator\n\t'attr-name': /\\b\\w+\\??:(?!:)/,\n\t'argument': {\n\t\t// Look-behind prevents bad highlighting of the && operator\n\t\tpattern: /(^|[^&])&\\d+/,\n\t\tlookbehind: true,\n\t\talias: 'variable'\n\t},\n\t'attribute': {\n\t\tpattern: /@\\w+/,\n\t\talias: 'variable'\n\t},\n\t'function': /\\b[_a-zA-Z]\\w*[?!]?(?:(?=\\s*(?:\\.\\s*)?\\()|(?=\\/\\d))/,\n\t'number': /\\b(?:0[box][a-f\\d_]+|\\d[\\d_]*)(?:\\.[\\d_]+)?(?:e[+-]?[\\d_]+)?\\b/i,\n\t'keyword': /\\b(?:after|alias|and|case|catch|cond|def(?:callback|delegate|exception|impl|macro|module|n|np|p|protocol|struct)?|do|else|end|fn|for|if|import|not|or|quote|raise|require|rescue|try|unless|unquote|use|when)\\b/,\n\t'boolean': /\\b(?:false|nil|true)\\b/,\n\t'operator': [\n\t\t/\\bin\\b|&&?|\\|[|>]?|\\\\\\\\|::|\\.\\.\\.?|\\+\\+?|-[->]?|<[-=>]|>=|!==?|\\B!|=(?:==?|[>~])?|[*\\/^]/,\n\t\t{\n\t\t\t// We don't want to match <<\n\t\t\tpattern: /([^<])<(?!<)/,\n\t\t\tlookbehind: true\n\t\t},\n\t\t{\n\t\t\t// We don't want to match >>\n\t\t\tpattern: /([^>])>(?!>)/,\n\t\t\tlookbehind: true\n\t\t}\n\t],\n\t'punctuation': /<<|>>|[.,%\\[\\]{}()]/\n};\n\nPrism.languages.elixir.string.forEach(function (o) {\n\to.inside = {\n\t\t'interpolation': {\n\t\t\tpattern: /#\\{[^}]+\\}/,\n\t\t\tinside: {\n\t\t\t\t'delimiter': {\n\t\t\t\t\tpattern: /^#\\{|\\}$/,\n\t\t\t\t\talias: 'punctuation'\n\t\t\t\t},\n\t\t\t\trest: Prism.languages.elixir\n\t\t\t}\n\t\t}\n\t};\n});\n\nPrism.languages.erlang = {\n\t'comment': /%.+/,\n\t'string': {\n\t\tpattern: /\"(?:\\\\.|[^\\\\\"\\r\\n])*\"/,\n\t\tgreedy: true\n\t},\n\t'quoted-function': {\n\t\tpattern: /'(?:\\\\.|[^\\\\'\\r\\n])+'(?=\\()/,\n\t\talias: 'function'\n\t},\n\t'quoted-atom': {\n\t\tpattern: /'(?:\\\\.|[^\\\\'\\r\\n])+'/,\n\t\talias: 'atom'\n\t},\n\t'boolean': /\\b(?:false|true)\\b/,\n\t'keyword': /\\b(?:after|begin|case|catch|end|fun|if|of|receive|try|when)\\b/,\n\t'number': [\n\t\t/\\$\\\\?./,\n\t\t/\\b\\d+#[a-z0-9]+/i,\n\t\t/(?:\\b\\d+(?:\\.\\d*)?|\\B\\.\\d+)(?:e[+-]?\\d+)?/i\n\t],\n\t'function': /\\b[a-z][\\w@]*(?=\\()/,\n\t'variable': {\n\t\t// Look-behind is used to prevent wrong highlighting of atoms containing \"@\"\n\t\tpattern: /(^|[^@])(?:\\b|\\?)[A-Z_][\\w@]*/,\n\t\tlookbehind: true\n\t},\n\t'operator': [\n\t\t/[=\\/<>:]=|=[:\\/]=|\\+\\+?|--?|[=*\\/!]|\\b(?:and|andalso|band|bnot|bor|bsl|bsr|bxor|div|not|or|orelse|rem|xor)\\b/,\n\t\t{\n\t\t\t// We don't want to match <<\n\t\t\tpattern: /(^|[^<])<(?!<)/,\n\t\t\tlookbehind: true\n\t\t},\n\t\t{\n\t\t\t// We don't want to match >>\n\t\t\tpattern: /(^|[^>])>(?!>)/,\n\t\t\tlookbehind: true\n\t\t}\n\t],\n\t'atom': /\\b[a-z][\\w@]*/,\n\t'punctuation': /[()[\\]{}:;,.#|]|<<|>>/\n\n};\n\nPrism.languages.go = Prism.languages.extend('clike', {\n\t'string': {\n\t\tpattern: /(^|[^\\\\])\"(?:\\\\.|[^\"\\\\\\r\\n])*\"|`[^`]*`/,\n\t\tlookbehind: true,\n\t\tgreedy: true\n\t},\n\t'keyword': /\\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\\b/,\n\t'boolean': /\\b(?:_|false|iota|nil|true)\\b/,\n\t'number': [\n\t\t// binary and octal integers\n\t\t/\\b0(?:b[01_]+|o[0-7_]+)i?\\b/i,\n\t\t// hexadecimal integers and floats\n\t\t/\\b0x(?:[a-f\\d_]+(?:\\.[a-f\\d_]*)?|\\.[a-f\\d_]+)(?:p[+-]?\\d+(?:_\\d+)*)?i?(?!\\w)/i,\n\t\t// decimal integers and floats\n\t\t/(?:\\b\\d[\\d_]*(?:\\.[\\d_]*)?|\\B\\.\\d[\\d_]*)(?:e[+-]?[\\d_]+)?i?(?!\\w)/i\n\t],\n\t'operator': /[*\\/%^!=]=?|\\+[=+]?|-[=-]?|\\|[=|]?|&(?:=|&|\\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\\.\\.\\./,\n\t'builtin': /\\b(?:append|bool|byte|cap|close|complex|complex(?:64|128)|copy|delete|error|float(?:32|64)|u?int(?:8|16|32|64)?|imag|len|make|new|panic|print(?:ln)?|real|recover|rune|string|uintptr)\\b/\n});\n\nPrism.languages.insertBefore('go', 'string', {\n\t'char': {\n\t\tpattern: /'(?:\\\\.|[^'\\\\\\r\\n]){0,10}'/,\n\t\tgreedy: true\n\t}\n});\n\ndelete Prism.languages.go['class-name'];\n\n(function (Prism) {\n\n\tvar interpolation = {\n\t\tpattern: /((?:^|[^\\\\$])(?:\\\\{2})*)\\$(?:\\w+|\\{[^{}]*\\})/,\n\t\tlookbehind: true,\n\t\tinside: {\n\t\t\t'interpolation-punctuation': {\n\t\t\t\tpattern: /^\\$\\{?|\\}$/,\n\t\t\t\talias: 'punctuation'\n\t\t\t},\n\t\t\t'expression': {\n\t\t\t\tpattern: /[\\s\\S]+/,\n\t\t\t\tinside: null // see below\n\t\t\t}\n\t\t}\n\t};\n\n\tPrism.languages.groovy = Prism.languages.extend('clike', {\n\t\t'string': {\n\t\t\t// https://groovy-lang.org/syntax.html#_dollar_slashy_string\n\t\t\tpattern: /'''(?:[^\\\\]|\\\\[\\s\\S])*?'''|'(?:\\\\.|[^\\\\'\\r\\n])*'/,\n\t\t\tgreedy: true\n\t\t},\n\t\t'keyword': /\\b(?:abstract|as|assert|boolean|break|byte|case|catch|char|class|const|continue|def|default|do|double|else|enum|extends|final|finally|float|for|goto|if|implements|import|in|instanceof|int|interface|long|native|new|package|private|protected|public|return|short|static|strictfp|super|switch|synchronized|this|throw|throws|trait|transient|try|void|volatile|while)\\b/,\n\t\t'number': /\\b(?:0b[01_]+|0x[\\da-f_]+(?:\\.[\\da-f_p\\-]+)?|[\\d_]+(?:\\.[\\d_]+)?(?:e[+-]?\\d+)?)[glidf]?\\b/i,\n\t\t'operator': {\n\t\t\tpattern: /(^|[^.])(?:~|==?~?|\\?[.:]?|\\*(?:[.=]|\\*=?)?|\\.[@&]|\\.\\.<|\\.\\.(?!\\.)|-[-=>]?|\\+[+=]?|!=?|<(?:<=?|=>?)?|>(?:>>?=?|=)?|&[&=]?|\\|[|=]?|\\/=?|\\^=?|%=?)/,\n\t\t\tlookbehind: true\n\t\t},\n\t\t'punctuation': /\\.+|[{}[\\];(),:$]/\n\t});\n\n\tPrism.languages.insertBefore('groovy', 'string', {\n\t\t'shebang': {\n\t\t\tpattern: /#!.+/,\n\t\t\talias: 'comment',\n\t\t\tgreedy: true\n\t\t},\n\t\t'interpolation-string': {\n\t\t\t// TODO: Slash strings (e.g. /foo/) can contain line breaks but this will cause a lot of trouble with\n\t\t\t// simple division (see JS regex), so find a fix maybe?\n\t\t\tpattern: /\"\"\"(?:[^\\\\]|\\\\[\\s\\S])*?\"\"\"|([\"/])(?:\\\\.|(?!\\1)[^\\\\\\r\\n])*\\1|\\$\\/(?:[^/$]|\\$(?:[/$]|(?![/$]))|\\/(?!\\$))*\\/\\$/,\n\t\t\tgreedy: true,\n\t\t\tinside: {\n\t\t\t\t'interpolation': interpolation,\n\t\t\t\t'string': /[\\s\\S]+/\n\t\t\t}\n\t\t}\n\t});\n\n\tPrism.languages.insertBefore('groovy', 'punctuation', {\n\t\t'spock-block': /\\b(?:and|cleanup|expect|given|setup|then|when|where):/\n\t});\n\n\tPrism.languages.insertBefore('groovy', 'function', {\n\t\t'annotation': {\n\t\t\tpattern: /(^|[^.])@\\w+/,\n\t\t\tlookbehind: true,\n\t\t\talias: 'punctuation'\n\t\t}\n\t});\n\n\tinterpolation.inside.expression.inside = Prism.languages.groovy;\n\n}(Prism));\n\n(function (Prism) {\n\n\tvar keywords = /\\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|non-sealed|null|open|opens|package|permits|private|protected|provides|public|record(?!\\s*[(){}[\\]<>=%~.:,;?+\\-*/&|^])|requires|return|sealed|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\\b/;\n\n\t// full package (optional) + parent classes (optional)\n\tvar classNamePrefix = /(?:[a-z]\\w*\\s*\\.\\s*)*(?:[A-Z]\\w*\\s*\\.\\s*)*/.source;\n\n\t// based on the java naming conventions\n\tvar className = {\n\t\tpattern: RegExp(/(^|[^\\w.])/.source + classNamePrefix + /[A-Z](?:[\\d_A-Z]*[a-z]\\w*)?\\b/.source),\n\t\tlookbehind: true,\n\t\tinside: {\n\t\t\t'namespace': {\n\t\t\t\tpattern: /^[a-z]\\w*(?:\\s*\\.\\s*[a-z]\\w*)*(?:\\s*\\.)?/,\n\t\t\t\tinside: {\n\t\t\t\t\t'punctuation': /\\./\n\t\t\t\t}\n\t\t\t},\n\t\t\t'punctuation': /\\./\n\t\t}\n\t};\n\n\tPrism.languages.java = Prism.languages.extend('clike', {\n\t\t'string': {\n\t\t\tpattern: /(^|[^\\\\])\"(?:\\\\.|[^\"\\\\\\r\\n])*\"/,\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true\n\t\t},\n\t\t'class-name': [\n\t\t\tclassName,\n\t\t\t{\n\t\t\t\t// variables, parameters, and constructor references\n\t\t\t\t// this to support class names (or generic parameters) which do not contain a lower case letter (also works for methods)\n\t\t\t\tpattern: RegExp(/(^|[^\\w.])/.source + classNamePrefix + /[A-Z]\\w*(?=\\s+\\w+\\s*[;,=()]|\\s*(?:\\[[\\s,]*\\]\\s*)?::\\s*new\\b)/.source),\n\t\t\t\tlookbehind: true,\n\t\t\t\tinside: className.inside\n\t\t\t},\n\t\t\t{\n\t\t\t\t// class names based on keyword\n\t\t\t\t// this to support class names (or generic parameters) which do not contain a lower case letter (also works for methods)\n\t\t\t\tpattern: RegExp(/(\\b(?:class|enum|extends|implements|instanceof|interface|new|record|throws)\\s+)/.source + classNamePrefix + /[A-Z]\\w*\\b/.source),\n\t\t\t\tlookbehind: true,\n\t\t\t\tinside: className.inside\n\t\t\t}\n\t\t],\n\t\t'keyword': keywords,\n\t\t'function': [\n\t\t\tPrism.languages.clike.function,\n\t\t\t{\n\t\t\t\tpattern: /(::\\s*)[a-z_]\\w*/,\n\t\t\t\tlookbehind: true\n\t\t\t}\n\t\t],\n\t\t'number': /\\b0b[01][01_]*L?\\b|\\b0x(?:\\.[\\da-f_p+-]+|[\\da-f_]+(?:\\.[\\da-f_p+-]+)?)\\b|(?:\\b\\d[\\d_]*(?:\\.[\\d_]*)?|\\B\\.\\d[\\d_]*)(?:e[+-]?\\d[\\d_]*)?[dfl]?/i,\n\t\t'operator': {\n\t\t\tpattern: /(^|[^.])(?:<<=?|>>>?=?|->|--|\\+\\+|&&|\\|\\||::|[?:~]|[-+*/%&|^!=<>]=?)/m,\n\t\t\tlookbehind: true\n\t\t},\n\t\t'constant': /\\b[A-Z][A-Z_\\d]+\\b/\n\t});\n\n\tPrism.languages.insertBefore('java', 'string', {\n\t\t'triple-quoted-string': {\n\t\t\t// http://openjdk.java.net/jeps/355#Description\n\t\t\tpattern: /\"\"\"[ \\t]*[\\r\\n](?:(?:\"|\"\")?(?:\\\\.|[^\"\\\\]))*\"\"\"/,\n\t\t\tgreedy: true,\n\t\t\talias: 'string'\n\t\t},\n\t\t'char': {\n\t\t\tpattern: /'(?:\\\\.|[^'\\\\\\r\\n]){1,6}'/,\n\t\t\tgreedy: true\n\t\t}\n\t});\n\n\tPrism.languages.insertBefore('java', 'class-name', {\n\t\t'annotation': {\n\t\t\tpattern: /(^|[^.])@\\w+(?:\\s*\\.\\s*\\w+)*/,\n\t\t\tlookbehind: true,\n\t\t\talias: 'punctuation'\n\t\t},\n\t\t'generics': {\n\t\t\tpattern: /<(?:[\\w\\s,.?]|&(?!&)|<(?:[\\w\\s,.?]|&(?!&)|<(?:[\\w\\s,.?]|&(?!&)|<(?:[\\w\\s,.?]|&(?!&))*>)*>)*>)*>/,\n\t\t\tinside: {\n\t\t\t\t'class-name': className,\n\t\t\t\t'keyword': keywords,\n\t\t\t\t'punctuation': /[<>(),.:]/,\n\t\t\t\t'operator': /[?&|]/\n\t\t\t}\n\t\t},\n\t\t'import': [\n\t\t\t{\n\t\t\t\tpattern: RegExp(/(\\bimport\\s+)/.source + classNamePrefix + /(?:[A-Z]\\w*|\\*)(?=\\s*;)/.source),\n\t\t\t\tlookbehind: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'namespace': className.inside.namespace,\n\t\t\t\t\t'punctuation': /\\./,\n\t\t\t\t\t'operator': /\\*/,\n\t\t\t\t\t'class-name': /\\w+/\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: RegExp(/(\\bimport\\s+static\\s+)/.source + classNamePrefix + /(?:\\w+|\\*)(?=\\s*;)/.source),\n\t\t\t\tlookbehind: true,\n\t\t\t\talias: 'static',\n\t\t\t\tinside: {\n\t\t\t\t\t'namespace': className.inside.namespace,\n\t\t\t\t\t'static': /\\b\\w+$/,\n\t\t\t\t\t'punctuation': /\\./,\n\t\t\t\t\t'operator': /\\*/,\n\t\t\t\t\t'class-name': /\\w+/\n\t\t\t\t}\n\t\t\t}\n\t\t],\n\t\t'namespace': {\n\t\t\tpattern: RegExp(\n\t\t\t\t/(\\b(?:exports|import(?:\\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\\s+)(?!<keyword>)[a-z]\\w*(?:\\.[a-z]\\w*)*\\.?/\n\t\t\t\t\t.source.replace(/<keyword>/g, function () { return keywords.source; })),\n\t\t\tlookbehind: true,\n\t\t\tinside: {\n\t\t\t\t'punctuation': /\\./,\n\t\t\t}\n\t\t}\n\t});\n}(Prism));\n\n// https://www.json.org/json-en.html\nPrism.languages.json = {\n\t'property': {\n\t\tpattern: /(^|[^\\\\])\"(?:\\\\.|[^\\\\\"\\r\\n])*\"(?=\\s*:)/,\n\t\tlookbehind: true,\n\t\tgreedy: true\n\t},\n\t'string': {\n\t\tpattern: /(^|[^\\\\])\"(?:\\\\.|[^\\\\\"\\r\\n])*\"(?!\\s*:)/,\n\t\tlookbehind: true,\n\t\tgreedy: true\n\t},\n\t'comment': {\n\t\tpattern: /\\/\\/.*|\\/\\*[\\s\\S]*?(?:\\*\\/|$)/,\n\t\tgreedy: true\n\t},\n\t'number': /-?\\b\\d+(?:\\.\\d+)?(?:e[+-]?\\d+)?\\b/i,\n\t'punctuation': /[{}[\\],]/,\n\t'operator': /:/,\n\t'boolean': /\\b(?:false|true)\\b/,\n\t'null': {\n\t\tpattern: /\\bnull\\b/,\n\t\talias: 'keyword'\n\t}\n};\n\nPrism.languages.webmanifest = Prism.languages.json;\n\nPrism.languages.julia = {\n\t'comment': {\n\t\t// support one level of nested comments\n\t\t// https://github.com/JuliaLang/julia/pull/6128\n\t\tpattern: /(^|[^\\\\])(?:#=(?:[^#=]|=(?!#)|#(?!=)|#=(?:[^#=]|=(?!#)|#(?!=))*=#)*=#|#.*)/,\n\t\tlookbehind: true\n\t},\n\t'regex': {\n\t\t// https://docs.julialang.org/en/v1/manual/strings/#Regular-Expressions-1\n\t\tpattern: /r\"(?:\\\\.|[^\"\\\\\\r\\n])*\"[imsx]{0,4}/,\n\t\tgreedy: true\n\t},\n\t'string': {\n\t\t// https://docs.julialang.org/en/v1/manual/strings/#String-Basics-1\n\t\t// https://docs.julialang.org/en/v1/manual/strings/#non-standard-string-literals-1\n\t\t// https://docs.julialang.org/en/v1/manual/running-external-programs/#Running-External-Programs-1\n\t\tpattern: /\"\"\"[\\s\\S]+?\"\"\"|(?:\\b\\w+)?\"(?:\\\\.|[^\"\\\\\\r\\n])*\"|`(?:[^\\\\`\\r\\n]|\\\\.)*`/,\n\t\tgreedy: true\n\t},\n\t'char': {\n\t\t// https://docs.julialang.org/en/v1/manual/strings/#man-characters-1\n\t\tpattern: /(^|[^\\w'])'(?:\\\\[^\\r\\n][^'\\r\\n]*|[^\\\\\\r\\n])'/,\n\t\tlookbehind: true,\n\t\tgreedy: true\n\t},\n\t'keyword': /\\b(?:abstract|baremodule|begin|bitstype|break|catch|ccall|const|continue|do|else|elseif|end|export|finally|for|function|global|if|immutable|import|importall|in|let|local|macro|module|print|println|quote|return|struct|try|type|typealias|using|while)\\b/,\n\t'boolean': /\\b(?:false|true)\\b/,\n\t'number': /(?:\\b(?=\\d)|\\B(?=\\.))(?:0[box])?(?:[\\da-f]+(?:_[\\da-f]+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\.\\d+(?:_\\d+)*)(?:[efp][+-]?\\d+(?:_\\d+)*)?j?/i,\n\t// https://docs.julialang.org/en/v1/manual/mathematical-operations/\n\t// https://docs.julialang.org/en/v1/manual/mathematical-operations/#Operator-Precedence-and-Associativity-1\n\t'operator': /&&|\\|\\||[-+*^%÷⊻&$\\\\]=?|\\/[\\/=]?|!=?=?|\\|[=>]?|<(?:<=?|[=:|])?|>(?:=|>>?=?)?|==?=?|[~≠≤≥'√∛]/,\n\t'punctuation': /::?|[{}[\\]();,.?]/,\n\t// https://docs.julialang.org/en/v1/base/numbers/#Base.im\n\t'constant': /\\b(?:(?:Inf|NaN)(?:16|32|64)?|im|pi)\\b|[πℯ]/\n};\n\n(function (Prism) {\n\tPrism.languages.kotlin = Prism.languages.extend('clike', {\n\t\t'keyword': {\n\t\t\t// The lookbehind prevents wrong highlighting of e.g. kotlin.properties.get\n\t\t\tpattern: /(^|[^.])\\b(?:abstract|actual|annotation|as|break|by|catch|class|companion|const|constructor|continue|crossinline|data|do|dynamic|else|enum|expect|external|final|finally|for|fun|get|if|import|in|infix|init|inline|inner|interface|internal|is|lateinit|noinline|null|object|open|operator|out|override|package|private|protected|public|reified|return|sealed|set|super|suspend|tailrec|this|throw|to|try|typealias|val|var|vararg|when|where|while)\\b/,\n\t\t\tlookbehind: true\n\t\t},\n\t\t'function': [\n\t\t\t{\n\t\t\t\tpattern: /(?:`[^\\r\\n`]+`|\\b\\w+)(?=\\s*\\()/,\n\t\t\t\tgreedy: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /(\\.)(?:`[^\\r\\n`]+`|\\w+)(?=\\s*\\{)/,\n\t\t\t\tlookbehind: true,\n\t\t\t\tgreedy: true\n\t\t\t}\n\t\t],\n\t\t'number': /\\b(?:0[xX][\\da-fA-F]+(?:_[\\da-fA-F]+)*|0[bB][01]+(?:_[01]+)*|\\d+(?:_\\d+)*(?:\\.\\d+(?:_\\d+)*)?(?:[eE][+-]?\\d+(?:_\\d+)*)?[fFL]?)\\b/,\n\t\t'operator': /\\+[+=]?|-[-=>]?|==?=?|!(?:!|==?)?|[\\/*%<>]=?|[?:]:?|\\.\\.|&&|\\|\\||\\b(?:and|inv|or|shl|shr|ushr|xor)\\b/\n\t});\n\n\tdelete Prism.languages.kotlin['class-name'];\n\n\tvar interpolationInside = {\n\t\t'interpolation-punctuation': {\n\t\t\tpattern: /^\\$\\{?|\\}$/,\n\t\t\talias: 'punctuation'\n\t\t},\n\t\t'expression': {\n\t\t\tpattern: /[\\s\\S]+/,\n\t\t\tinside: Prism.languages.kotlin\n\t\t}\n\t};\n\n\tPrism.languages.insertBefore('kotlin', 'string', {\n\t\t// https://kotlinlang.org/spec/expressions.html#string-interpolation-expressions\n\t\t'string-literal': [\n\t\t\t{\n\t\t\t\tpattern: /\"\"\"(?:[^$]|\\$(?:(?!\\{)|\\{[^{}]*\\}))*?\"\"\"/,\n\t\t\t\talias: 'multiline',\n\t\t\t\tinside: {\n\t\t\t\t\t'interpolation': {\n\t\t\t\t\t\tpattern: /\\$(?:[a-z_]\\w*|\\{[^{}]*\\})/i,\n\t\t\t\t\t\tinside: interpolationInside\n\t\t\t\t\t},\n\t\t\t\t\t'string': /[\\s\\S]+/\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /\"(?:[^\"\\\\\\r\\n$]|\\\\.|\\$(?:(?!\\{)|\\{[^{}]*\\}))*\"/,\n\t\t\t\talias: 'singleline',\n\t\t\t\tinside: {\n\t\t\t\t\t'interpolation': {\n\t\t\t\t\t\tpattern: /((?:^|[^\\\\])(?:\\\\{2})*)\\$(?:[a-z_]\\w*|\\{[^{}]*\\})/i,\n\t\t\t\t\t\tlookbehind: true,\n\t\t\t\t\t\tinside: interpolationInside\n\t\t\t\t\t},\n\t\t\t\t\t'string': /[\\s\\S]+/\n\t\t\t\t}\n\t\t\t}\n\t\t],\n\t\t'char': {\n\t\t\t// https://kotlinlang.org/spec/expressions.html#character-literals\n\t\t\tpattern: /'(?:[^'\\\\\\r\\n]|\\\\(?:.|u[a-fA-F0-9]{0,4}))'/,\n\t\t\tgreedy: true\n\t\t}\n\t});\n\n\tdelete Prism.languages.kotlin['string'];\n\n\tPrism.languages.insertBefore('kotlin', 'keyword', {\n\t\t'annotation': {\n\t\t\tpattern: /\\B@(?:\\w+:)?(?:[A-Z]\\w*|\\[[^\\]]+\\])/,\n\t\t\talias: 'builtin'\n\t\t}\n\t});\n\n\tPrism.languages.insertBefore('kotlin', 'function', {\n\t\t'label': {\n\t\t\tpattern: /\\b\\w+@|@\\w+\\b/,\n\t\t\talias: 'symbol'\n\t\t}\n\t});\n\n\tPrism.languages.kt = Prism.languages.kotlin;\n\tPrism.languages.kts = Prism.languages.kotlin;\n}(Prism));\n\n(function (Prism) {\n\tvar funcPattern = /\\\\(?:[^a-z()[\\]]|[a-z*]+)/i;\n\tvar insideEqu = {\n\t\t'equation-command': {\n\t\t\tpattern: funcPattern,\n\t\t\talias: 'regex'\n\t\t}\n\t};\n\n\tPrism.languages.latex = {\n\t\t'comment': /%.*/,\n\t\t// the verbatim environment prints whitespace to the document\n\t\t'cdata': {\n\t\t\tpattern: /(\\\\begin\\{((?:lstlisting|verbatim)\\*?)\\})[\\s\\S]*?(?=\\\\end\\{\\2\\})/,\n\t\t\tlookbehind: true\n\t\t},\n\t\t/*\n\t\t * equations can be between $$ $$ or $ $ or \\( \\) or \\[ \\]\n\t\t * (all are multiline)\n\t\t */\n\t\t'equation': [\n\t\t\t{\n\t\t\t\tpattern: /\\$\\$(?:\\\\[\\s\\S]|[^\\\\$])+\\$\\$|\\$(?:\\\\[\\s\\S]|[^\\\\$])+\\$|\\\\\\([\\s\\S]*?\\\\\\)|\\\\\\[[\\s\\S]*?\\\\\\]/,\n\t\t\t\tinside: insideEqu,\n\t\t\t\talias: 'string'\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /(\\\\begin\\{((?:align|eqnarray|equation|gather|math|multline)\\*?)\\})[\\s\\S]*?(?=\\\\end\\{\\2\\})/,\n\t\t\t\tlookbehind: true,\n\t\t\t\tinside: insideEqu,\n\t\t\t\talias: 'string'\n\t\t\t}\n\t\t],\n\t\t/*\n\t\t * arguments which are keywords or references are highlighted\n\t\t * as keywords\n\t\t */\n\t\t'keyword': {\n\t\t\tpattern: /(\\\\(?:begin|cite|documentclass|end|label|ref|usepackage)(?:\\[[^\\]]+\\])?\\{)[^}]+(?=\\})/,\n\t\t\tlookbehind: true\n\t\t},\n\t\t'url': {\n\t\t\tpattern: /(\\\\url\\{)[^}]+(?=\\})/,\n\t\t\tlookbehind: true\n\t\t},\n\t\t/*\n\t\t * section or chapter headlines are highlighted as bold so that\n\t\t * they stand out more\n\t\t */\n\t\t'headline': {\n\t\t\tpattern: /(\\\\(?:chapter|frametitle|paragraph|part|section|subparagraph|subsection|subsubparagraph|subsubsection|subsubsubparagraph)\\*?(?:\\[[^\\]]+\\])?\\{)[^}]+(?=\\})/,\n\t\t\tlookbehind: true,\n\t\t\talias: 'class-name'\n\t\t},\n\t\t'function': {\n\t\t\tpattern: funcPattern,\n\t\t\talias: 'selector'\n\t\t},\n\t\t'punctuation': /[[\\]{}&]/\n\t};\n\n\tPrism.languages.tex = Prism.languages.latex;\n\tPrism.languages.context = Prism.languages.latex;\n}(Prism));\n\nPrism.languages.lua = {\n\t'comment': /^#!.+|--(?:\\[(=*)\\[[\\s\\S]*?\\]\\1\\]|.*)/m,\n\t// \\z may be used to skip the following space\n\t'string': {\n\t\tpattern: /([\"'])(?:(?!\\1)[^\\\\\\r\\n]|\\\\z(?:\\r\\n|\\s)|\\\\(?:\\r\\n|[^z]))*\\1|\\[(=*)\\[[\\s\\S]*?\\]\\2\\]/,\n\t\tgreedy: true\n\t},\n\t'number': /\\b0x[a-f\\d]+(?:\\.[a-f\\d]*)?(?:p[+-]?\\d+)?\\b|\\b\\d+(?:\\.\\B|(?:\\.\\d*)?(?:e[+-]?\\d+)?\\b)|\\B\\.\\d+(?:e[+-]?\\d+)?\\b/i,\n\t'keyword': /\\b(?:and|break|do|else|elseif|end|false|for|function|goto|if|in|local|nil|not|or|repeat|return|then|true|until|while)\\b/,\n\t'function': /(?!\\d)\\w+(?=\\s*(?:[({]))/,\n\t'operator': [\n\t\t/[-+*%^&|#]|\\/\\/?|<[<=]?|>[>=]?|[=~]=?/,\n\t\t{\n\t\t\t// Match \"..\" but don't break \"...\"\n\t\t\tpattern: /(^|[^.])\\.\\.(?!\\.)/,\n\t\t\tlookbehind: true\n\t\t}\n\t],\n\t'punctuation': /[\\[\\](){},;]|\\.+|:+/\n};\n\n(function (Prism) {\n\n\t// Allow only one line break\n\tvar inner = /(?:\\\\.|[^\\\\\\n\\r]|(?:\\n|\\r\\n?)(?![\\r\\n]))/.source;\n\n\t/**\n\t * This function is intended for the creation of the bold or italic pattern.\n\t *\n\t * This also adds a lookbehind group to the given pattern to ensure that the pattern is not backslash-escaped.\n\t *\n\t * _Note:_ Keep in mind that this adds a capturing group.\n\t *\n\t * @param {string} pattern\n\t * @returns {RegExp}\n\t */\n\tfunction createInline(pattern) {\n\t\tpattern = pattern.replace(/<inner>/g, function () { return inner; });\n\t\treturn RegExp(/((?:^|[^\\\\])(?:\\\\{2})*)/.source + '(?:' + pattern + ')');\n\t}\n\n\n\tvar tableCell = /(?:\\\\.|``(?:[^`\\r\\n]|`(?!`))+``|`[^`\\r\\n]+`|[^\\\\|\\r\\n`])+/.source;\n\tvar tableRow = /\\|?__(?:\\|__)+\\|?(?:(?:\\n|\\r\\n?)|(?![\\s\\S]))/.source.replace(/__/g, function () { return tableCell; });\n\tvar tableLine = /\\|?[ \\t]*:?-{3,}:?[ \\t]*(?:\\|[ \\t]*:?-{3,}:?[ \\t]*)+\\|?(?:\\n|\\r\\n?)/.source;\n\n\n\tPrism.languages.markdown = Prism.languages.extend('markup', {});\n\tPrism.languages.insertBefore('markdown', 'prolog', {\n\t\t'front-matter-block': {\n\t\t\tpattern: /(^(?:\\s*[\\r\\n])?)---(?!.)[\\s\\S]*?[\\r\\n]---(?!.)/,\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true,\n\t\t\tinside: {\n\t\t\t\t'punctuation': /^---|---$/,\n\t\t\t\t'front-matter': {\n\t\t\t\t\tpattern: /\\S+(?:\\s+\\S+)*/,\n\t\t\t\t\talias: ['yaml', 'language-yaml'],\n\t\t\t\t\tinside: Prism.languages.yaml\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t'blockquote': {\n\t\t\t// > ...\n\t\t\tpattern: /^>(?:[\\t ]*>)*/m,\n\t\t\talias: 'punctuation'\n\t\t},\n\t\t'table': {\n\t\t\tpattern: RegExp('^' + tableRow + tableLine + '(?:' + tableRow + ')*', 'm'),\n\t\t\tinside: {\n\t\t\t\t'table-data-rows': {\n\t\t\t\t\tpattern: RegExp('^(' + tableRow + tableLine + ')(?:' + tableRow + ')*$'),\n\t\t\t\t\tlookbehind: true,\n\t\t\t\t\tinside: {\n\t\t\t\t\t\t'table-data': {\n\t\t\t\t\t\t\tpattern: RegExp(tableCell),\n\t\t\t\t\t\t\tinside: Prism.languages.markdown\n\t\t\t\t\t\t},\n\t\t\t\t\t\t'punctuation': /\\|/\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t'table-line': {\n\t\t\t\t\tpattern: RegExp('^(' + tableRow + ')' + tableLine + '$'),\n\t\t\t\t\tlookbehind: true,\n\t\t\t\t\tinside: {\n\t\t\t\t\t\t'punctuation': /\\||:?-{3,}:?/\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t'table-header-row': {\n\t\t\t\t\tpattern: RegExp('^' + tableRow + '$'),\n\t\t\t\t\tinside: {\n\t\t\t\t\t\t'table-header': {\n\t\t\t\t\t\t\tpattern: RegExp(tableCell),\n\t\t\t\t\t\t\talias: 'important',\n\t\t\t\t\t\t\tinside: Prism.languages.markdown\n\t\t\t\t\t\t},\n\t\t\t\t\t\t'punctuation': /\\|/\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t'code': [\n\t\t\t{\n\t\t\t\t// Prefixed by 4 spaces or 1 tab and preceded by an empty line\n\t\t\t\tpattern: /((?:^|\\n)[ \\t]*\\n|(?:^|\\r\\n?)[ \\t]*\\r\\n?)(?: {4}|\\t).+(?:(?:\\n|\\r\\n?)(?: {4}|\\t).+)*/,\n\t\t\t\tlookbehind: true,\n\t\t\t\talias: 'keyword'\n\t\t\t},\n\t\t\t{\n\t\t\t\t// ```optional language\n\t\t\t\t// code block\n\t\t\t\t// ```\n\t\t\t\tpattern: /^```[\\s\\S]*?^```$/m,\n\t\t\t\tgreedy: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'code-block': {\n\t\t\t\t\t\tpattern: /^(```.*(?:\\n|\\r\\n?))[\\s\\S]+?(?=(?:\\n|\\r\\n?)^```$)/m,\n\t\t\t\t\t\tlookbehind: true\n\t\t\t\t\t},\n\t\t\t\t\t'code-language': {\n\t\t\t\t\t\tpattern: /^(```).+/,\n\t\t\t\t\t\tlookbehind: true\n\t\t\t\t\t},\n\t\t\t\t\t'punctuation': /```/\n\t\t\t\t}\n\t\t\t}\n\t\t],\n\t\t'title': [\n\t\t\t{\n\t\t\t\t// title 1\n\t\t\t\t// =======\n\n\t\t\t\t// title 2\n\t\t\t\t// -------\n\t\t\t\tpattern: /\\S.*(?:\\n|\\r\\n?)(?:==+|--+)(?=[ \\t]*$)/m,\n\t\t\t\talias: 'important',\n\t\t\t\tinside: {\n\t\t\t\t\tpunctuation: /==+$|--+$/\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t// # title 1\n\t\t\t\t// ###### title 6\n\t\t\t\tpattern: /(^\\s*)#.+/m,\n\t\t\t\tlookbehind: true,\n\t\t\t\talias: 'important',\n\t\t\t\tinside: {\n\t\t\t\t\tpunctuation: /^#+|#+$/\n\t\t\t\t}\n\t\t\t}\n\t\t],\n\t\t'hr': {\n\t\t\t// ***\n\t\t\t// ---\n\t\t\t// * * *\n\t\t\t// -----------\n\t\t\tpattern: /(^\\s*)([*-])(?:[\\t ]*\\2){2,}(?=\\s*$)/m,\n\t\t\tlookbehind: true,\n\t\t\talias: 'punctuation'\n\t\t},\n\t\t'list': {\n\t\t\t// * item\n\t\t\t// + item\n\t\t\t// - item\n\t\t\t// 1. item\n\t\t\tpattern: /(^\\s*)(?:[*+-]|\\d+\\.)(?=[\\t ].)/m,\n\t\t\tlookbehind: true,\n\t\t\talias: 'punctuation'\n\t\t},\n\t\t'url-reference': {\n\t\t\t// [id]: http://example.com \"Optional title\"\n\t\t\t// [id]: http://example.com 'Optional title'\n\t\t\t// [id]: http://example.com (Optional title)\n\t\t\t// [id]: <http://example.com> \"Optional title\"\n\t\t\tpattern: /!?\\[[^\\]]+\\]:[\\t ]+(?:\\S+|<(?:\\\\.|[^>\\\\])+>)(?:[\\t ]+(?:\"(?:\\\\.|[^\"\\\\])*\"|'(?:\\\\.|[^'\\\\])*'|\\((?:\\\\.|[^)\\\\])*\\)))?/,\n\t\t\tinside: {\n\t\t\t\t'variable': {\n\t\t\t\t\tpattern: /^(!?\\[)[^\\]]+/,\n\t\t\t\t\tlookbehind: true\n\t\t\t\t},\n\t\t\t\t'string': /(?:\"(?:\\\\.|[^\"\\\\])*\"|'(?:\\\\.|[^'\\\\])*'|\\((?:\\\\.|[^)\\\\])*\\))$/,\n\t\t\t\t'punctuation': /^[\\[\\]!:]|[<>]/\n\t\t\t},\n\t\t\talias: 'url'\n\t\t},\n\t\t'bold': {\n\t\t\t// **strong**\n\t\t\t// __strong__\n\n\t\t\t// allow one nested instance of italic text using the same delimiter\n\t\t\tpattern: createInline(/\\b__(?:(?!_)<inner>|_(?:(?!_)<inner>)+_)+__\\b|\\*\\*(?:(?!\\*)<inner>|\\*(?:(?!\\*)<inner>)+\\*)+\\*\\*/.source),\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true,\n\t\t\tinside: {\n\t\t\t\t'content': {\n\t\t\t\t\tpattern: /(^..)[\\s\\S]+(?=..$)/,\n\t\t\t\t\tlookbehind: true,\n\t\t\t\t\tinside: {} // see below\n\t\t\t\t},\n\t\t\t\t'punctuation': /\\*\\*|__/\n\t\t\t}\n\t\t},\n\t\t'italic': {\n\t\t\t// *em*\n\t\t\t// _em_\n\n\t\t\t// allow one nested instance of bold text using the same delimiter\n\t\t\tpattern: createInline(/\\b_(?:(?!_)<inner>|__(?:(?!_)<inner>)+__)+_\\b|\\*(?:(?!\\*)<inner>|\\*\\*(?:(?!\\*)<inner>)+\\*\\*)+\\*/.source),\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true,\n\t\t\tinside: {\n\t\t\t\t'content': {\n\t\t\t\t\tpattern: /(^.)[\\s\\S]+(?=.$)/,\n\t\t\t\t\tlookbehind: true,\n\t\t\t\t\tinside: {} // see below\n\t\t\t\t},\n\t\t\t\t'punctuation': /[*_]/\n\t\t\t}\n\t\t},\n\t\t'strike': {\n\t\t\t// ~~strike through~~\n\t\t\t// ~strike~\n\t\t\t// eslint-disable-next-line regexp/strict\n\t\t\tpattern: createInline(/(~~?)(?:(?!~)<inner>)+\\2/.source),\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true,\n\t\t\tinside: {\n\t\t\t\t'content': {\n\t\t\t\t\tpattern: /(^~~?)[\\s\\S]+(?=\\1$)/,\n\t\t\t\t\tlookbehind: true,\n\t\t\t\t\tinside: {} // see below\n\t\t\t\t},\n\t\t\t\t'punctuation': /~~?/\n\t\t\t}\n\t\t},\n\t\t'code-snippet': {\n\t\t\t// `code`\n\t\t\t// ``code``\n\t\t\tpattern: /(^|[^\\\\`])(?:``[^`\\r\\n]+(?:`[^`\\r\\n]+)*``(?!`)|`[^`\\r\\n]+`(?!`))/,\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true,\n\t\t\talias: ['code', 'keyword']\n\t\t},\n\t\t'url': {\n\t\t\t// [example](http://example.com \"Optional title\")\n\t\t\t// [example][id]\n\t\t\t// [example] [id]\n\t\t\tpattern: createInline(/!?\\[(?:(?!\\])<inner>)+\\](?:\\([^\\s)]+(?:[\\t ]+\"(?:\\\\.|[^\"\\\\])*\")?\\)|[ \\t]?\\[(?:(?!\\])<inner>)+\\])/.source),\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true,\n\t\t\tinside: {\n\t\t\t\t'operator': /^!/,\n\t\t\t\t'content': {\n\t\t\t\t\tpattern: /(^\\[)[^\\]]+(?=\\])/,\n\t\t\t\t\tlookbehind: true,\n\t\t\t\t\tinside: {} // see below\n\t\t\t\t},\n\t\t\t\t'variable': {\n\t\t\t\t\tpattern: /(^\\][ \\t]?\\[)[^\\]]+(?=\\]$)/,\n\t\t\t\t\tlookbehind: true\n\t\t\t\t},\n\t\t\t\t'url': {\n\t\t\t\t\tpattern: /(^\\]\\()[^\\s)]+/,\n\t\t\t\t\tlookbehind: true\n\t\t\t\t},\n\t\t\t\t'string': {\n\t\t\t\t\tpattern: /(^[ \\t]+)\"(?:\\\\.|[^\"\\\\])*\"(?=\\)$)/,\n\t\t\t\t\tlookbehind: true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t});\n\n\t['url', 'bold', 'italic', 'strike'].forEach(function (token) {\n\t\t['url', 'bold', 'italic', 'strike', 'code-snippet'].forEach(function (inside) {\n\t\t\tif (token !== inside) {\n\t\t\t\tPrism.languages.markdown[token].inside.content.inside[inside] = Prism.languages.markdown[inside];\n\t\t\t}\n\t\t});\n\t});\n\n\tPrism.hooks.add('after-tokenize', function (env) {\n\t\tif (env.language !== 'markdown' && env.language !== 'md') {\n\t\t\treturn;\n\t\t}\n\n\t\tfunction walkTokens(tokens) {\n\t\t\tif (!tokens || typeof tokens === 'string') {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tfor (var i = 0, l = tokens.length; i < l; i++) {\n\t\t\t\tvar token = tokens[i];\n\n\t\t\t\tif (token.type !== 'code') {\n\t\t\t\t\twalkTokens(token.content);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t/*\n\t\t\t\t * Add the correct `language-xxxx` class to this code block. Keep in mind that the `code-language` token\n\t\t\t\t * is optional. But the grammar is defined so that there is only one case we have to handle:\n\t\t\t\t *\n\t\t\t\t * token.content = [\n\t\t\t\t *     <span class=\"punctuation\">```</span>,\n\t\t\t\t *     <span class=\"code-language\">xxxx</span>,\n\t\t\t\t *     '\\n', // exactly one new lines (\\r or \\n or \\r\\n)\n\t\t\t\t *     <span class=\"code-block\">...</span>,\n\t\t\t\t *     '\\n', // exactly one new lines again\n\t\t\t\t *     <span class=\"punctuation\">```</span>\n\t\t\t\t * ];\n\t\t\t\t */\n\n\t\t\t\tvar codeLang = token.content[1];\n\t\t\t\tvar codeBlock = token.content[3];\n\n\t\t\t\tif (codeLang && codeBlock &&\n\t\t\t\t\tcodeLang.type === 'code-language' && codeBlock.type === 'code-block' &&\n\t\t\t\t\ttypeof codeLang.content === 'string') {\n\n\t\t\t\t\t// this might be a language that Prism does not support\n\n\t\t\t\t\t// do some replacements to support C++, C#, and F#\n\t\t\t\t\tvar lang = codeLang.content.replace(/\\b#/g, 'sharp').replace(/\\b\\+\\+/g, 'pp');\n\t\t\t\t\t// only use the first word\n\t\t\t\t\tlang = (/[a-z][\\w-]*/i.exec(lang) || [''])[0].toLowerCase();\n\t\t\t\t\tvar alias = 'language-' + lang;\n\n\t\t\t\t\t// add alias\n\t\t\t\t\tif (!codeBlock.alias) {\n\t\t\t\t\t\tcodeBlock.alias = [alias];\n\t\t\t\t\t} else if (typeof codeBlock.alias === 'string') {\n\t\t\t\t\t\tcodeBlock.alias = [codeBlock.alias, alias];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcodeBlock.alias.push(alias);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\twalkTokens(env.tokens);\n\t});\n\n\tPrism.hooks.add('wrap', function (env) {\n\t\tif (env.type !== 'code-block') {\n\t\t\treturn;\n\t\t}\n\n\t\tvar codeLang = '';\n\t\tfor (var i = 0, l = env.classes.length; i < l; i++) {\n\t\t\tvar cls = env.classes[i];\n\t\t\tvar match = /language-(.+)/.exec(cls);\n\t\t\tif (match) {\n\t\t\t\tcodeLang = match[1];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tvar grammar = Prism.languages[codeLang];\n\n\t\tif (!grammar) {\n\t\t\tif (codeLang && codeLang !== 'none' && Prism.plugins.autoloader) {\n\t\t\t\tvar id = 'md-' + new Date().valueOf() + '-' + Math.floor(Math.random() * 1e16);\n\t\t\t\tenv.attributes['id'] = id;\n\n\t\t\t\tPrism.plugins.autoloader.loadLanguages(codeLang, function () {\n\t\t\t\t\tvar ele = document.getElementById(id);\n\t\t\t\t\tif (ele) {\n\t\t\t\t\t\tele.innerHTML = Prism.highlight(ele.textContent, Prism.languages[codeLang], codeLang);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t} else {\n\t\t\tenv.content = Prism.highlight(textContent(env.content), grammar, codeLang);\n\t\t}\n\t});\n\n\tvar tagPattern = RegExp(Prism.languages.markup.tag.pattern.source, 'gi');\n\n\t/**\n\t * A list of known entity names.\n\t *\n\t * This will always be incomplete to save space. The current list is the one used by lowdash's unescape function.\n\t *\n\t * @see {@link https://github.com/lodash/lodash/blob/2da024c3b4f9947a48517639de7560457cd4ec6c/unescape.js#L2}\n\t */\n\tvar KNOWN_ENTITY_NAMES = {\n\t\t'amp': '&',\n\t\t'lt': '<',\n\t\t'gt': '>',\n\t\t'quot': '\"',\n\t};\n\n\t// IE 11 doesn't support `String.fromCodePoint`\n\tvar fromCodePoint = String.fromCodePoint || String.fromCharCode;\n\n\t/**\n\t * Returns the text content of a given HTML source code string.\n\t *\n\t * @param {string} html\n\t * @returns {string}\n\t */\n\tfunction textContent(html) {\n\t\t// remove all tags\n\t\tvar text = html.replace(tagPattern, '');\n\n\t\t// decode known entities\n\t\ttext = text.replace(/&(\\w{1,8}|#x?[\\da-f]{1,8});/gi, function (m, code) {\n\t\t\tcode = code.toLowerCase();\n\n\t\t\tif (code[0] === '#') {\n\t\t\t\tvar value;\n\t\t\t\tif (code[1] === 'x') {\n\t\t\t\t\tvalue = parseInt(code.slice(2), 16);\n\t\t\t\t} else {\n\t\t\t\t\tvalue = Number(code.slice(1));\n\t\t\t\t}\n\n\t\t\t\treturn fromCodePoint(value);\n\t\t\t} else {\n\t\t\t\tvar known = KNOWN_ENTITY_NAMES[code];\n\t\t\t\tif (known) {\n\t\t\t\t\treturn known;\n\t\t\t\t}\n\n\t\t\t\t// unable to decode\n\t\t\t\treturn m;\n\t\t\t}\n\t\t});\n\n\t\treturn text;\n\t}\n\n\tPrism.languages.md = Prism.languages.markdown;\n\n}(Prism));\n\nPrism.languages.matlab = {\n\t'comment': [\n\t\t/%\\{[\\s\\S]*?\\}%/,\n\t\t/%.+/\n\t],\n\t'string': {\n\t\tpattern: /\\B'(?:''|[^'\\r\\n])*'/,\n\t\tgreedy: true\n\t},\n\t// FIXME We could handle imaginary numbers as a whole\n\t'number': /(?:\\b\\d+(?:\\.\\d*)?|\\B\\.\\d+)(?:[eE][+-]?\\d+)?(?:[ij])?|\\b[ij]\\b/,\n\t'keyword': /\\b(?:NaN|break|case|catch|continue|else|elseif|end|for|function|if|inf|otherwise|parfor|pause|pi|return|switch|try|while)\\b/,\n\t'function': /\\b(?!\\d)\\w+(?=\\s*\\()/,\n\t'operator': /\\.?[*^\\/\\\\']|[+\\-:@]|[<>=~]=?|&&?|\\|\\|?/,\n\t'punctuation': /\\.{3}|[.,;\\[\\](){}!]/\n};\n\n(function (Prism) {\n\n\tvar variable = /\\$(?:\\w[a-z\\d]*(?:_[^\\x00-\\x1F\\s\"'\\\\()$]*)?|\\{[^}\\s\"'\\\\]+\\})/i;\n\n\tPrism.languages.nginx = {\n\t\t'comment': {\n\t\t\tpattern: /(^|[\\s{};])#.*/,\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true\n\t\t},\n\t\t'directive': {\n\t\t\tpattern: /(^|\\s)\\w(?:[^;{}\"'\\\\\\s]|\\\\.|\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|\\s+(?:#.*(?!.)|(?![#\\s])))*?(?=\\s*[;{])/,\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true,\n\t\t\tinside: {\n\t\t\t\t'string': {\n\t\t\t\t\tpattern: /((?:^|[^\\\\])(?:\\\\\\\\)*)(?:\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*')/,\n\t\t\t\t\tlookbehind: true,\n\t\t\t\t\tgreedy: true,\n\t\t\t\t\tinside: {\n\t\t\t\t\t\t'escape': {\n\t\t\t\t\t\t\tpattern: /\\\\[\"'\\\\nrt]/,\n\t\t\t\t\t\t\talias: 'entity'\n\t\t\t\t\t\t},\n\t\t\t\t\t\t'variable': variable\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t'comment': {\n\t\t\t\t\tpattern: /(\\s)#.*/,\n\t\t\t\t\tlookbehind: true,\n\t\t\t\t\tgreedy: true\n\t\t\t\t},\n\t\t\t\t'keyword': {\n\t\t\t\t\tpattern: /^\\S+/,\n\t\t\t\t\tgreedy: true\n\t\t\t\t},\n\n\t\t\t\t// other patterns\n\n\t\t\t\t'boolean': {\n\t\t\t\t\tpattern: /(\\s)(?:off|on)(?!\\S)/,\n\t\t\t\t\tlookbehind: true\n\t\t\t\t},\n\t\t\t\t'number': {\n\t\t\t\t\tpattern: /(\\s)\\d+[a-z]*(?!\\S)/i,\n\t\t\t\t\tlookbehind: true\n\t\t\t\t},\n\t\t\t\t'variable': variable\n\t\t\t}\n\t\t},\n\t\t'punctuation': /[{};]/\n\t};\n\n}(Prism));\n\nPrism.languages.nim = {\n\t'comment': {\n\t\tpattern: /#.*/,\n\t\tgreedy: true\n\t},\n\t'string': {\n\t\t// Double-quoted strings can be prefixed by an identifier (Generalized raw string literals)\n\t\tpattern: /(?:\\b(?!\\d)(?:\\w|\\\\x[89a-fA-F][0-9a-fA-F])+)?(?:\"\"\"[\\s\\S]*?\"\"\"(?!\")|\"(?:\\\\[\\s\\S]|\"\"|[^\"\\\\])*\")/,\n\t\tgreedy: true\n\t},\n\t'char': {\n\t\t// Character literals are handled specifically to prevent issues with numeric type suffixes\n\t\tpattern: /'(?:\\\\(?:\\d+|x[\\da-fA-F]{0,2}|.)|[^'])'/,\n\t\tgreedy: true\n\t},\n\n\t'function': {\n\t\tpattern: /(?:(?!\\d)(?:\\w|\\\\x[89a-fA-F][0-9a-fA-F])+|`[^`\\r\\n]+`)\\*?(?:\\[[^\\]]+\\])?(?=\\s*\\()/,\n\t\tgreedy: true,\n\t\tinside: {\n\t\t\t'operator': /\\*$/\n\t\t}\n\t},\n\t// We don't want to highlight operators (and anything really) inside backticks\n\t'identifier': {\n\t\tpattern: /`[^`\\r\\n]+`/,\n\t\tgreedy: true,\n\t\tinside: {\n\t\t\t'punctuation': /`/\n\t\t}\n\t},\n\n\t// The negative look ahead prevents wrong highlighting of the .. operator\n\t'number': /\\b(?:0[xXoObB][\\da-fA-F_]+|\\d[\\d_]*(?:(?!\\.\\.)\\.[\\d_]*)?(?:[eE][+-]?\\d[\\d_]*)?)(?:'?[iuf]\\d*)?/,\n\t'keyword': /\\b(?:addr|as|asm|atomic|bind|block|break|case|cast|concept|const|continue|converter|defer|discard|distinct|do|elif|else|end|enum|except|export|finally|for|from|func|generic|if|import|include|interface|iterator|let|macro|method|mixin|nil|object|out|proc|ptr|raise|ref|return|static|template|try|tuple|type|using|var|when|while|with|without|yield)\\b/,\n\t'operator': {\n\t\t// Look behind and look ahead prevent wrong highlighting of punctuations [. .] {. .} (. .)\n\t\t// but allow the slice operator .. to take precedence over them\n\t\t// One can define his own operators in Nim so all combination of operators might be an operator.\n\t\tpattern: /(^|[({\\[](?=\\.\\.)|(?![({\\[]\\.).)(?:(?:[=+\\-*\\/<>@$~&%|!?^:\\\\]|\\.\\.|\\.(?![)}\\]]))+|\\b(?:and|div|in|is|isnot|mod|not|notin|of|or|shl|shr|xor)\\b)/m,\n\t\tlookbehind: true\n\t},\n\t'punctuation': /[({\\[]\\.|\\.[)}\\]]|[`(){}\\[\\],:]/\n};\n\nPrism.languages.nix = {\n\t'comment': {\n\t\tpattern: /\\/\\*[\\s\\S]*?\\*\\/|#.*/,\n\t\tgreedy: true\n\t},\n\t'string': {\n\t\tpattern: /\"(?:[^\"\\\\]|\\\\[\\s\\S])*\"|''(?:(?!'')[\\s\\S]|''(?:'|\\\\|\\$\\{))*''/,\n\t\tgreedy: true,\n\t\tinside: {\n\t\t\t'interpolation': {\n\t\t\t\t// The lookbehind ensures the ${} is not preceded by \\ or ''\n\t\t\t\tpattern: /(^|(?:^|(?!'').)[^\\\\])\\$\\{(?:[^{}]|\\{[^}]*\\})*\\}/,\n\t\t\t\tlookbehind: true,\n\t\t\t\tinside: null // see below\n\t\t\t}\n\t\t}\n\t},\n\t'url': [\n\t\t/\\b(?:[a-z]{3,7}:\\/\\/)[\\w\\-+%~\\/.:#=?&]+/,\n\t\t{\n\t\t\tpattern: /([^\\/])(?:[\\w\\-+%~.:#=?&]*(?!\\/\\/)[\\w\\-+%~\\/.:#=?&])?(?!\\/\\/)\\/[\\w\\-+%~\\/.:#=?&]*/,\n\t\t\tlookbehind: true\n\t\t}\n\t],\n\t'antiquotation': {\n\t\tpattern: /\\$(?=\\{)/,\n\t\talias: 'important'\n\t},\n\t'number': /\\b\\d+\\b/,\n\t'keyword': /\\b(?:assert|builtins|else|if|in|inherit|let|null|or|then|with)\\b/,\n\t'function': /\\b(?:abort|add|all|any|attrNames|attrValues|baseNameOf|compareVersions|concatLists|currentSystem|deepSeq|derivation|dirOf|div|elem(?:At)?|fetch(?:Tarball|url)|filter(?:Source)?|fromJSON|genList|getAttr|getEnv|hasAttr|hashString|head|import|intersectAttrs|is(?:Attrs|Bool|Function|Int|List|Null|String)|length|lessThan|listToAttrs|map|mul|parseDrvName|pathExists|read(?:Dir|File)|removeAttrs|replaceStrings|seq|sort|stringLength|sub(?:string)?|tail|throw|to(?:File|JSON|Path|String|XML)|trace|typeOf)\\b|\\bfoldl'\\B/,\n\t'boolean': /\\b(?:false|true)\\b/,\n\t'operator': /[=!<>]=?|\\+\\+?|\\|\\||&&|\\/\\/|->?|[?@]/,\n\t'punctuation': /[{}()[\\].,:;]/\n};\n\nPrism.languages.nix.string.inside.interpolation.inside = Prism.languages.nix;\n\n// https://ocaml.org/manual/lex.html\n\nPrism.languages.ocaml = {\n\t'comment': {\n\t\tpattern: /\\(\\*[\\s\\S]*?\\*\\)/,\n\t\tgreedy: true\n\t},\n\t'char': {\n\t\tpattern: /'(?:[^\\\\\\r\\n']|\\\\(?:.|[ox]?[0-9a-f]{1,3}))'/i,\n\t\tgreedy: true\n\t},\n\t'string': [\n\t\t{\n\t\t\tpattern: /\"(?:\\\\(?:[\\s\\S]|\\r\\n)|[^\\\\\\r\\n\"])*\"/,\n\t\t\tgreedy: true\n\t\t},\n\t\t{\n\t\t\tpattern: /\\{([a-z_]*)\\|[\\s\\S]*?\\|\\1\\}/,\n\t\t\tgreedy: true\n\t\t}\n\t],\n\t'number': [\n\t\t// binary and octal\n\t\t/\\b(?:0b[01][01_]*|0o[0-7][0-7_]*)\\b/i,\n\t\t// hexadecimal\n\t\t/\\b0x[a-f0-9][a-f0-9_]*(?:\\.[a-f0-9_]*)?(?:p[+-]?\\d[\\d_]*)?(?!\\w)/i,\n\t\t// decimal\n\t\t/\\b\\d[\\d_]*(?:\\.[\\d_]*)?(?:e[+-]?\\d[\\d_]*)?(?!\\w)/i,\n\t],\n\t'directive': {\n\t\tpattern: /\\B#\\w+/,\n\t\talias: 'property'\n\t},\n\t'label': {\n\t\tpattern: /\\B~\\w+/,\n\t\talias: 'property'\n\t},\n\t'type-variable': {\n\t\tpattern: /\\B'\\w+/,\n\t\talias: 'function'\n\t},\n\t'variant': {\n\t\tpattern: /`\\w+/,\n\t\talias: 'symbol'\n\t},\n\t// For the list of keywords and operators,\n\t// see: http://caml.inria.fr/pub/docs/manual-ocaml/lex.html#sec84\n\t'keyword': /\\b(?:as|assert|begin|class|constraint|do|done|downto|else|end|exception|external|for|fun|function|functor|if|in|include|inherit|initializer|lazy|let|match|method|module|mutable|new|nonrec|object|of|open|private|rec|sig|struct|then|to|try|type|val|value|virtual|when|where|while|with)\\b/,\n\t'boolean': /\\b(?:false|true)\\b/,\n\n\t'operator-like-punctuation': {\n\t\tpattern: /\\[[<>|]|[>|]\\]|\\{<|>\\}/,\n\t\talias: 'punctuation'\n\t},\n\t// Custom operators are allowed\n\t'operator': /\\.[.~]|:[=>]|[=<>@^|&+\\-*\\/$%!?~][!$%&*+\\-.\\/:<=>?@^|~]*|\\b(?:and|asr|land|lor|lsl|lsr|lxor|mod|or)\\b/,\n\t'punctuation': /;;|::|[(){}\\[\\].,:;#]|\\b_\\b/\n};\n\n(function (Prism) {\n\n\tvar brackets = /(?:\\((?:[^()\\\\]|\\\\[\\s\\S])*\\)|\\{(?:[^{}\\\\]|\\\\[\\s\\S])*\\}|\\[(?:[^[\\]\\\\]|\\\\[\\s\\S])*\\]|<(?:[^<>\\\\]|\\\\[\\s\\S])*>)/.source;\n\n\tPrism.languages.perl = {\n\t\t'comment': [\n\t\t\t{\n\t\t\t\t// POD\n\t\t\t\tpattern: /(^\\s*)=\\w[\\s\\S]*?=cut.*/m,\n\t\t\t\tlookbehind: true,\n\t\t\t\tgreedy: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /(^|[^\\\\$])#.*/,\n\t\t\t\tlookbehind: true,\n\t\t\t\tgreedy: true\n\t\t\t}\n\t\t],\n\t\t// TODO Could be nice to handle Heredoc too.\n\t\t'string': [\n\t\t\t{\n\t\t\t\tpattern: RegExp(\n\t\t\t\t\t/\\b(?:q|qq|qw|qx)(?![a-zA-Z0-9])\\s*/.source +\n\t\t\t\t\t'(?:' +\n\t\t\t\t\t[\n\t\t\t\t\t\t// q/.../\n\t\t\t\t\t\t/([^a-zA-Z0-9\\s{(\\[<])(?:(?!\\1)[^\\\\]|\\\\[\\s\\S])*\\1/.source,\n\n\t\t\t\t\t\t// q a...a\n\t\t\t\t\t\t// eslint-disable-next-line regexp/strict\n\t\t\t\t\t\t/([a-zA-Z0-9])(?:(?!\\2)[^\\\\]|\\\\[\\s\\S])*\\2/.source,\n\n\t\t\t\t\t\t// q(...)\n\t\t\t\t\t\t// q{...}\n\t\t\t\t\t\t// q[...]\n\t\t\t\t\t\t// q<...>\n\t\t\t\t\t\tbrackets,\n\t\t\t\t\t].join('|') +\n\t\t\t\t\t')'\n\t\t\t\t),\n\t\t\t\tgreedy: true\n\t\t\t},\n\n\t\t\t// \"...\", `...`\n\t\t\t{\n\t\t\t\tpattern: /(\"|`)(?:(?!\\1)[^\\\\]|\\\\[\\s\\S])*\\1/,\n\t\t\t\tgreedy: true\n\t\t\t},\n\n\t\t\t// '...'\n\t\t\t// FIXME Multi-line single-quoted strings are not supported as they would break variables containing '\n\t\t\t{\n\t\t\t\tpattern: /'(?:[^'\\\\\\r\\n]|\\\\.)*'/,\n\t\t\t\tgreedy: true\n\t\t\t}\n\t\t],\n\t\t'regex': [\n\t\t\t{\n\t\t\t\tpattern: RegExp(\n\t\t\t\t\t/\\b(?:m|qr)(?![a-zA-Z0-9])\\s*/.source +\n\t\t\t\t\t'(?:' +\n\t\t\t\t\t[\n\t\t\t\t\t\t// m/.../\n\t\t\t\t\t\t/([^a-zA-Z0-9\\s{(\\[<])(?:(?!\\1)[^\\\\]|\\\\[\\s\\S])*\\1/.source,\n\n\t\t\t\t\t\t// m a...a\n\t\t\t\t\t\t// eslint-disable-next-line regexp/strict\n\t\t\t\t\t\t/([a-zA-Z0-9])(?:(?!\\2)[^\\\\]|\\\\[\\s\\S])*\\2/.source,\n\n\t\t\t\t\t\t// m(...)\n\t\t\t\t\t\t// m{...}\n\t\t\t\t\t\t// m[...]\n\t\t\t\t\t\t// m<...>\n\t\t\t\t\t\tbrackets,\n\t\t\t\t\t].join('|') +\n\t\t\t\t\t')' +\n\t\t\t\t\t/[msixpodualngc]*/.source\n\t\t\t\t),\n\t\t\t\tgreedy: true\n\t\t\t},\n\n\t\t\t// The lookbehinds prevent -s from breaking\n\t\t\t{\n\t\t\t\tpattern: RegExp(\n\t\t\t\t\t/(^|[^-])\\b(?:s|tr|y)(?![a-zA-Z0-9])\\s*/.source +\n\t\t\t\t\t'(?:' +\n\t\t\t\t\t[\n\t\t\t\t\t\t// s/.../.../\n\t\t\t\t\t\t// eslint-disable-next-line regexp/strict\n\t\t\t\t\t\t/([^a-zA-Z0-9\\s{(\\[<])(?:(?!\\2)[^\\\\]|\\\\[\\s\\S])*\\2(?:(?!\\2)[^\\\\]|\\\\[\\s\\S])*\\2/.source,\n\n\t\t\t\t\t\t// s a...a...a\n\t\t\t\t\t\t// eslint-disable-next-line regexp/strict\n\t\t\t\t\t\t/([a-zA-Z0-9])(?:(?!\\3)[^\\\\]|\\\\[\\s\\S])*\\3(?:(?!\\3)[^\\\\]|\\\\[\\s\\S])*\\3/.source,\n\n\t\t\t\t\t\t// s(...)(...)\n\t\t\t\t\t\t// s{...}{...}\n\t\t\t\t\t\t// s[...][...]\n\t\t\t\t\t\t// s<...><...>\n\t\t\t\t\t\t// s(...)[...]\n\t\t\t\t\t\tbrackets + /\\s*/.source + brackets,\n\t\t\t\t\t].join('|') +\n\t\t\t\t\t')' +\n\t\t\t\t\t/[msixpodualngcer]*/.source\n\t\t\t\t),\n\t\t\t\tlookbehind: true,\n\t\t\t\tgreedy: true\n\t\t\t},\n\n\t\t\t// /.../\n\t\t\t// The look-ahead tries to prevent two divisions on\n\t\t\t// the same line from being highlighted as regex.\n\t\t\t// This does not support multi-line regex.\n\t\t\t{\n\t\t\t\tpattern: /\\/(?:[^\\/\\\\\\r\\n]|\\\\.)*\\/[msixpodualngc]*(?=\\s*(?:$|[\\r\\n,.;})&|\\-+*~<>!?^]|(?:and|cmp|eq|ge|gt|le|lt|ne|not|or|x|xor)\\b))/,\n\t\t\t\tgreedy: true\n\t\t\t}\n\t\t],\n\n\t\t// FIXME Not sure about the handling of ::, ', and #\n\t\t'variable': [\n\t\t\t// ${^POSTMATCH}\n\t\t\t/[&*$@%]\\{\\^[A-Z]+\\}/,\n\t\t\t// $^V\n\t\t\t/[&*$@%]\\^[A-Z_]/,\n\t\t\t// ${...}\n\t\t\t/[&*$@%]#?(?=\\{)/,\n\t\t\t// $foo\n\t\t\t/[&*$@%]#?(?:(?:::)*'?(?!\\d)[\\w$]+(?![\\w$]))+(?:::)*/,\n\t\t\t// $1\n\t\t\t/[&*$@%]\\d+/,\n\t\t\t// $_, @_, %!\n\t\t\t// The negative lookahead prevents from breaking the %= operator\n\t\t\t/(?!%=)[$@%][!\"#$%&'()*+,\\-.\\/:;<=>?@[\\\\\\]^_`{|}~]/\n\t\t],\n\t\t'filehandle': {\n\t\t\t// <>, <FOO>, _\n\t\t\tpattern: /<(?![<=])\\S*?>|\\b_\\b/,\n\t\t\talias: 'symbol'\n\t\t},\n\t\t'v-string': {\n\t\t\t// v1.2, 1.2.3\n\t\t\tpattern: /v\\d+(?:\\.\\d+)*|\\d+(?:\\.\\d+){2,}/,\n\t\t\talias: 'string'\n\t\t},\n\t\t'function': {\n\t\t\tpattern: /(\\bsub[ \\t]+)\\w+/,\n\t\t\tlookbehind: true\n\t\t},\n\t\t'keyword': /\\b(?:any|break|continue|default|delete|die|do|else|elsif|eval|for|foreach|given|goto|if|last|local|my|next|our|package|print|redo|require|return|say|state|sub|switch|undef|unless|until|use|when|while)\\b/,\n\t\t'number': /\\b(?:0x[\\dA-Fa-f](?:_?[\\dA-Fa-f])*|0b[01](?:_?[01])*|(?:(?:\\d(?:_?\\d)*)?\\.)?\\d(?:_?\\d)*(?:[Ee][+-]?\\d+)?)\\b/,\n\t\t'operator': /-[rwxoRWXOezsfdlpSbctugkTBMAC]\\b|\\+[+=]?|-[-=>]?|\\*\\*?=?|\\/\\/?=?|=[=~>]?|~[~=]?|\\|\\|?=?|&&?=?|<(?:=>?|<=?)?|>>?=?|![~=]?|[%^]=?|\\.(?:=|\\.\\.?)?|[\\\\?]|\\bx(?:=|\\b)|\\b(?:and|cmp|eq|ge|gt|le|lt|ne|not|or|xor)\\b/,\n\t\t'punctuation': /[{}[\\];(),:]/\n\t};\n\n}(Prism));\n\n/**\n * Original by Aaron Harun: http://aahacreative.com/2012/07/31/php-syntax-highlighting-prism/\n * Modified by Miles Johnson: http://milesj.me\n * Rewritten by Tom Pavelec\n *\n * Supports PHP 5.3 - 8.0\n */\n(function (Prism) {\n\tvar comment = /\\/\\*[\\s\\S]*?\\*\\/|\\/\\/.*|#(?!\\[).*/;\n\tvar constant = [\n\t\t{\n\t\t\tpattern: /\\b(?:false|true)\\b/i,\n\t\t\talias: 'boolean'\n\t\t},\n\t\t{\n\t\t\tpattern: /(::\\s*)\\b[a-z_]\\w*\\b(?!\\s*\\()/i,\n\t\t\tgreedy: true,\n\t\t\tlookbehind: true,\n\t\t},\n\t\t{\n\t\t\tpattern: /(\\b(?:case|const)\\s+)\\b[a-z_]\\w*(?=\\s*[;=])/i,\n\t\t\tgreedy: true,\n\t\t\tlookbehind: true,\n\t\t},\n\t\t/\\b(?:null)\\b/i,\n\t\t/\\b[A-Z_][A-Z0-9_]*\\b(?!\\s*\\()/,\n\t];\n\tvar number = /\\b0b[01]+(?:_[01]+)*\\b|\\b0o[0-7]+(?:_[0-7]+)*\\b|\\b0x[\\da-f]+(?:_[\\da-f]+)*\\b|(?:\\b\\d+(?:_\\d+)*\\.?(?:\\d+(?:_\\d+)*)?|\\B\\.\\d+)(?:e[+-]?\\d+)?/i;\n\tvar operator = /<?=>|\\?\\?=?|\\.{3}|\\??->|[!=]=?=?|::|\\*\\*=?|--|\\+\\+|&&|\\|\\||<<|>>|[?~]|[/^|%*&<>.+-]=?/;\n\tvar punctuation = /[{}\\[\\](),:;]/;\n\n\tPrism.languages.php = {\n\t\t'delimiter': {\n\t\t\tpattern: /\\?>$|^<\\?(?:php(?=\\s)|=)?/i,\n\t\t\talias: 'important'\n\t\t},\n\t\t'comment': comment,\n\t\t'variable': /\\$+(?:\\w+\\b|(?=\\{))/,\n\t\t'package': {\n\t\t\tpattern: /(namespace\\s+|use\\s+(?:function\\s+)?)(?:\\\\?\\b[a-z_]\\w*)+\\b(?!\\\\)/i,\n\t\t\tlookbehind: true,\n\t\t\tinside: {\n\t\t\t\t'punctuation': /\\\\/\n\t\t\t}\n\t\t},\n\t\t'class-name-definition': {\n\t\t\tpattern: /(\\b(?:class|enum|interface|trait)\\s+)\\b[a-z_]\\w*(?!\\\\)\\b/i,\n\t\t\tlookbehind: true,\n\t\t\talias: 'class-name'\n\t\t},\n\t\t'function-definition': {\n\t\t\tpattern: /(\\bfunction\\s+)[a-z_]\\w*(?=\\s*\\()/i,\n\t\t\tlookbehind: true,\n\t\t\talias: 'function'\n\t\t},\n\t\t'keyword': [\n\t\t\t{\n\t\t\t\tpattern: /(\\(\\s*)\\b(?:array|bool|boolean|float|int|integer|object|string)\\b(?=\\s*\\))/i,\n\t\t\t\talias: 'type-casting',\n\t\t\t\tgreedy: true,\n\t\t\t\tlookbehind: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /([(,?]\\s*)\\b(?:array(?!\\s*\\()|bool|callable|(?:false|null)(?=\\s*\\|)|float|int|iterable|mixed|object|self|static|string)\\b(?=\\s*\\$)/i,\n\t\t\t\talias: 'type-hint',\n\t\t\t\tgreedy: true,\n\t\t\t\tlookbehind: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /(\\)\\s*:\\s*(?:\\?\\s*)?)\\b(?:array(?!\\s*\\()|bool|callable|(?:false|null)(?=\\s*\\|)|float|int|iterable|mixed|never|object|self|static|string|void)\\b/i,\n\t\t\t\talias: 'return-type',\n\t\t\t\tgreedy: true,\n\t\t\t\tlookbehind: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /\\b(?:array(?!\\s*\\()|bool|float|int|iterable|mixed|object|string|void)\\b/i,\n\t\t\t\talias: 'type-declaration',\n\t\t\t\tgreedy: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /(\\|\\s*)(?:false|null)\\b|\\b(?:false|null)(?=\\s*\\|)/i,\n\t\t\t\talias: 'type-declaration',\n\t\t\t\tgreedy: true,\n\t\t\t\tlookbehind: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /\\b(?:parent|self|static)(?=\\s*::)/i,\n\t\t\t\talias: 'static-context',\n\t\t\t\tgreedy: true\n\t\t\t},\n\t\t\t{\n\t\t\t\t// yield from\n\t\t\t\tpattern: /(\\byield\\s+)from\\b/i,\n\t\t\t\tlookbehind: true\n\t\t\t},\n\t\t\t// `class` is always a keyword unlike other keywords\n\t\t\t/\\bclass\\b/i,\n\t\t\t{\n\t\t\t\t// https://www.php.net/manual/en/reserved.keywords.php\n\t\t\t\t//\n\t\t\t\t// keywords cannot be preceded by \"->\"\n\t\t\t\t// the complex lookbehind means `(?<!(?:->|::)\\s*)`\n\t\t\t\tpattern: /((?:^|[^\\s>:]|(?:^|[^-])>|(?:^|[^:]):)\\s*)\\b(?:abstract|and|array|as|break|callable|case|catch|clone|const|continue|declare|default|die|do|echo|else|elseif|empty|enddeclare|endfor|endforeach|endif|endswitch|endwhile|enum|eval|exit|extends|final|finally|fn|for|foreach|function|global|goto|if|implements|include|include_once|instanceof|insteadof|interface|isset|list|match|namespace|never|new|or|parent|print|private|protected|public|readonly|require|require_once|return|self|static|switch|throw|trait|try|unset|use|var|while|xor|yield|__halt_compiler)\\b/i,\n\t\t\t\tlookbehind: true\n\t\t\t}\n\t\t],\n\t\t'argument-name': {\n\t\t\tpattern: /([(,]\\s*)\\b[a-z_]\\w*(?=\\s*:(?!:))/i,\n\t\t\tlookbehind: true\n\t\t},\n\t\t'class-name': [\n\t\t\t{\n\t\t\t\tpattern: /(\\b(?:extends|implements|instanceof|new(?!\\s+self|\\s+static))\\s+|\\bcatch\\s*\\()\\b[a-z_]\\w*(?!\\\\)\\b/i,\n\t\t\t\tgreedy: true,\n\t\t\t\tlookbehind: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /(\\|\\s*)\\b[a-z_]\\w*(?!\\\\)\\b/i,\n\t\t\t\tgreedy: true,\n\t\t\t\tlookbehind: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /\\b[a-z_]\\w*(?!\\\\)\\b(?=\\s*\\|)/i,\n\t\t\t\tgreedy: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /(\\|\\s*)(?:\\\\?\\b[a-z_]\\w*)+\\b/i,\n\t\t\t\talias: 'class-name-fully-qualified',\n\t\t\t\tgreedy: true,\n\t\t\t\tlookbehind: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'punctuation': /\\\\/\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /(?:\\\\?\\b[a-z_]\\w*)+\\b(?=\\s*\\|)/i,\n\t\t\t\talias: 'class-name-fully-qualified',\n\t\t\t\tgreedy: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'punctuation': /\\\\/\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /(\\b(?:extends|implements|instanceof|new(?!\\s+self\\b|\\s+static\\b))\\s+|\\bcatch\\s*\\()(?:\\\\?\\b[a-z_]\\w*)+\\b(?!\\\\)/i,\n\t\t\t\talias: 'class-name-fully-qualified',\n\t\t\t\tgreedy: true,\n\t\t\t\tlookbehind: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'punctuation': /\\\\/\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /\\b[a-z_]\\w*(?=\\s*\\$)/i,\n\t\t\t\talias: 'type-declaration',\n\t\t\t\tgreedy: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /(?:\\\\?\\b[a-z_]\\w*)+(?=\\s*\\$)/i,\n\t\t\t\talias: ['class-name-fully-qualified', 'type-declaration'],\n\t\t\t\tgreedy: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'punctuation': /\\\\/\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /\\b[a-z_]\\w*(?=\\s*::)/i,\n\t\t\t\talias: 'static-context',\n\t\t\t\tgreedy: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /(?:\\\\?\\b[a-z_]\\w*)+(?=\\s*::)/i,\n\t\t\t\talias: ['class-name-fully-qualified', 'static-context'],\n\t\t\t\tgreedy: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'punctuation': /\\\\/\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /([(,?]\\s*)[a-z_]\\w*(?=\\s*\\$)/i,\n\t\t\t\talias: 'type-hint',\n\t\t\t\tgreedy: true,\n\t\t\t\tlookbehind: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /([(,?]\\s*)(?:\\\\?\\b[a-z_]\\w*)+(?=\\s*\\$)/i,\n\t\t\t\talias: ['class-name-fully-qualified', 'type-hint'],\n\t\t\t\tgreedy: true,\n\t\t\t\tlookbehind: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'punctuation': /\\\\/\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /(\\)\\s*:\\s*(?:\\?\\s*)?)\\b[a-z_]\\w*(?!\\\\)\\b/i,\n\t\t\t\talias: 'return-type',\n\t\t\t\tgreedy: true,\n\t\t\t\tlookbehind: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /(\\)\\s*:\\s*(?:\\?\\s*)?)(?:\\\\?\\b[a-z_]\\w*)+\\b(?!\\\\)/i,\n\t\t\t\talias: ['class-name-fully-qualified', 'return-type'],\n\t\t\t\tgreedy: true,\n\t\t\t\tlookbehind: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'punctuation': /\\\\/\n\t\t\t\t}\n\t\t\t}\n\t\t],\n\t\t'constant': constant,\n\t\t'function': {\n\t\t\tpattern: /(^|[^\\\\\\w])\\\\?[a-z_](?:[\\w\\\\]*\\w)?(?=\\s*\\()/i,\n\t\t\tlookbehind: true,\n\t\t\tinside: {\n\t\t\t\t'punctuation': /\\\\/\n\t\t\t}\n\t\t},\n\t\t'property': {\n\t\t\tpattern: /(->\\s*)\\w+/,\n\t\t\tlookbehind: true\n\t\t},\n\t\t'number': number,\n\t\t'operator': operator,\n\t\t'punctuation': punctuation\n\t};\n\n\tvar string_interpolation = {\n\t\tpattern: /\\{\\$(?:\\{(?:\\{[^{}]+\\}|[^{}]+)\\}|[^{}])+\\}|(^|[^\\\\{])\\$+(?:\\w+(?:\\[[^\\r\\n\\[\\]]+\\]|->\\w+)?)/,\n\t\tlookbehind: true,\n\t\tinside: Prism.languages.php\n\t};\n\n\tvar string = [\n\t\t{\n\t\t\tpattern: /<<<'([^']+)'[\\r\\n](?:.*[\\r\\n])*?\\1;/,\n\t\t\talias: 'nowdoc-string',\n\t\t\tgreedy: true,\n\t\t\tinside: {\n\t\t\t\t'delimiter': {\n\t\t\t\t\tpattern: /^<<<'[^']+'|[a-z_]\\w*;$/i,\n\t\t\t\t\talias: 'symbol',\n\t\t\t\t\tinside: {\n\t\t\t\t\t\t'punctuation': /^<<<'?|[';]$/\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\tpattern: /<<<(?:\"([^\"]+)\"[\\r\\n](?:.*[\\r\\n])*?\\1;|([a-z_]\\w*)[\\r\\n](?:.*[\\r\\n])*?\\2;)/i,\n\t\t\talias: 'heredoc-string',\n\t\t\tgreedy: true,\n\t\t\tinside: {\n\t\t\t\t'delimiter': {\n\t\t\t\t\tpattern: /^<<<(?:\"[^\"]+\"|[a-z_]\\w*)|[a-z_]\\w*;$/i,\n\t\t\t\t\talias: 'symbol',\n\t\t\t\t\tinside: {\n\t\t\t\t\t\t'punctuation': /^<<<\"?|[\";]$/\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t'interpolation': string_interpolation\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\tpattern: /`(?:\\\\[\\s\\S]|[^\\\\`])*`/,\n\t\t\talias: 'backtick-quoted-string',\n\t\t\tgreedy: true\n\t\t},\n\t\t{\n\t\t\tpattern: /'(?:\\\\[\\s\\S]|[^\\\\'])*'/,\n\t\t\talias: 'single-quoted-string',\n\t\t\tgreedy: true\n\t\t},\n\t\t{\n\t\t\tpattern: /\"(?:\\\\[\\s\\S]|[^\\\\\"])*\"/,\n\t\t\talias: 'double-quoted-string',\n\t\t\tgreedy: true,\n\t\t\tinside: {\n\t\t\t\t'interpolation': string_interpolation\n\t\t\t}\n\t\t}\n\t];\n\n\tPrism.languages.insertBefore('php', 'variable', {\n\t\t'string': string,\n\t\t'attribute': {\n\t\t\tpattern: /#\\[(?:[^\"'\\/#]|\\/(?![*/])|\\/\\/.*$|#(?!\\[).*$|\\/\\*(?:[^*]|\\*(?!\\/))*\\*\\/|\"(?:\\\\[\\s\\S]|[^\\\\\"])*\"|'(?:\\\\[\\s\\S]|[^\\\\'])*')+\\](?=\\s*[a-z$#])/im,\n\t\t\tgreedy: true,\n\t\t\tinside: {\n\t\t\t\t'attribute-content': {\n\t\t\t\t\tpattern: /^(#\\[)[\\s\\S]+(?=\\]$)/,\n\t\t\t\t\tlookbehind: true,\n\t\t\t\t\t// inside can appear subset of php\n\t\t\t\t\tinside: {\n\t\t\t\t\t\t'comment': comment,\n\t\t\t\t\t\t'string': string,\n\t\t\t\t\t\t'attribute-class-name': [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tpattern: /([^:]|^)\\b[a-z_]\\w*(?!\\\\)\\b/i,\n\t\t\t\t\t\t\t\talias: 'class-name',\n\t\t\t\t\t\t\t\tgreedy: true,\n\t\t\t\t\t\t\t\tlookbehind: true\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tpattern: /([^:]|^)(?:\\\\?\\b[a-z_]\\w*)+/i,\n\t\t\t\t\t\t\t\talias: [\n\t\t\t\t\t\t\t\t\t'class-name',\n\t\t\t\t\t\t\t\t\t'class-name-fully-qualified'\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tgreedy: true,\n\t\t\t\t\t\t\t\tlookbehind: true,\n\t\t\t\t\t\t\t\tinside: {\n\t\t\t\t\t\t\t\t\t'punctuation': /\\\\/\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\t'constant': constant,\n\t\t\t\t\t\t'number': number,\n\t\t\t\t\t\t'operator': operator,\n\t\t\t\t\t\t'punctuation': punctuation\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t'delimiter': {\n\t\t\t\t\tpattern: /^#\\[|\\]$/,\n\t\t\t\t\talias: 'punctuation'\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t});\n\n\tPrism.hooks.add('before-tokenize', function (env) {\n\t\tif (!/<\\?/.test(env.code)) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar phpPattern = /<\\?(?:[^\"'/#]|\\/(?![*/])|(\"|')(?:\\\\[\\s\\S]|(?!\\1)[^\\\\])*\\1|(?:\\/\\/|#(?!\\[))(?:[^?\\n\\r]|\\?(?!>))*(?=$|\\?>|[\\r\\n])|#\\[|\\/\\*(?:[^*]|\\*(?!\\/))*(?:\\*\\/|$))*?(?:\\?>|$)/g;\n\t\tPrism.languages['markup-templating'].buildPlaceholders(env, 'php', phpPattern);\n\t});\n\n\tPrism.hooks.add('after-tokenize', function (env) {\n\t\tPrism.languages['markup-templating'].tokenizePlaceholders(env, 'php');\n\t});\n\n}(Prism));\n\nPrism.languages.python = {\n\t'comment': {\n\t\tpattern: /(^|[^\\\\])#.*/,\n\t\tlookbehind: true,\n\t\tgreedy: true\n\t},\n\t'string-interpolation': {\n\t\tpattern: /(?:f|fr|rf)(?:(\"\"\"|''')[\\s\\S]*?\\1|(\"|')(?:\\\\.|(?!\\2)[^\\\\\\r\\n])*\\2)/i,\n\t\tgreedy: true,\n\t\tinside: {\n\t\t\t'interpolation': {\n\t\t\t\t// \"{\" <expression> <optional \"!s\", \"!r\", or \"!a\"> <optional \":\" format specifier> \"}\"\n\t\t\t\tpattern: /((?:^|[^{])(?:\\{\\{)*)\\{(?!\\{)(?:[^{}]|\\{(?!\\{)(?:[^{}]|\\{(?!\\{)(?:[^{}])+\\})+\\})+\\}/,\n\t\t\t\tlookbehind: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'format-spec': {\n\t\t\t\t\t\tpattern: /(:)[^:(){}]+(?=\\}$)/,\n\t\t\t\t\t\tlookbehind: true\n\t\t\t\t\t},\n\t\t\t\t\t'conversion-option': {\n\t\t\t\t\t\tpattern: /![sra](?=[:}]$)/,\n\t\t\t\t\t\talias: 'punctuation'\n\t\t\t\t\t},\n\t\t\t\t\trest: null\n\t\t\t\t}\n\t\t\t},\n\t\t\t'string': /[\\s\\S]+/\n\t\t}\n\t},\n\t'triple-quoted-string': {\n\t\tpattern: /(?:[rub]|br|rb)?(\"\"\"|''')[\\s\\S]*?\\1/i,\n\t\tgreedy: true,\n\t\talias: 'string'\n\t},\n\t'string': {\n\t\tpattern: /(?:[rub]|br|rb)?(\"|')(?:\\\\.|(?!\\1)[^\\\\\\r\\n])*\\1/i,\n\t\tgreedy: true\n\t},\n\t'function': {\n\t\tpattern: /((?:^|\\s)def[ \\t]+)[a-zA-Z_]\\w*(?=\\s*\\()/g,\n\t\tlookbehind: true\n\t},\n\t'class-name': {\n\t\tpattern: /(\\bclass\\s+)\\w+/i,\n\t\tlookbehind: true\n\t},\n\t'decorator': {\n\t\tpattern: /(^[\\t ]*)@\\w+(?:\\.\\w+)*/m,\n\t\tlookbehind: true,\n\t\talias: ['annotation', 'punctuation'],\n\t\tinside: {\n\t\t\t'punctuation': /\\./\n\t\t}\n\t},\n\t'keyword': /\\b(?:_(?=\\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\\b/,\n\t'builtin': /\\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\\b/,\n\t'boolean': /\\b(?:False|None|True)\\b/,\n\t'number': /\\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\\b|(?:\\b\\d+(?:_\\d+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\B\\.\\d+(?:_\\d+)*)(?:e[+-]?\\d+(?:_\\d+)*)?j?(?!\\w)/i,\n\t'operator': /[-+%=]=?|!=|:=|\\*\\*?=?|\\/\\/?=?|<[<=>]?|>[=>]?|[&|^~]/,\n\t'punctuation': /[{}[\\];(),.:]/\n};\n\nPrism.languages.python['string-interpolation'].inside['interpolation'].inside.rest = Prism.languages.python;\n\nPrism.languages.py = Prism.languages.python;\n\n(function (Prism) {\n\n\tvar jsString = /\"(?:\\\\.|[^\\\\\"\\r\\n])*\"|'(?:\\\\.|[^\\\\'\\r\\n])*'/.source;\n\tvar jsComment = /\\/\\/.*(?!.)|\\/\\*(?:[^*]|\\*(?!\\/))*\\*\\//.source;\n\n\tvar jsExpr = /(?:[^\\\\()[\\]{}\"'/]|<string>|\\/(?![*/])|<comment>|\\(<expr>*\\)|\\[<expr>*\\]|\\{<expr>*\\}|\\\\[\\s\\S])/\n\t\t.source.replace(/<string>/g, function () { return jsString; }).replace(/<comment>/g, function () { return jsComment; });\n\n\t// the pattern will blow up, so only a few iterations\n\tfor (var i = 0; i < 2; i++) {\n\t\tjsExpr = jsExpr.replace(/<expr>/g, function () { return jsExpr; });\n\t}\n\tjsExpr = jsExpr.replace(/<expr>/g, '[^\\\\s\\\\S]');\n\n\n\tPrism.languages.qml = {\n\t\t'comment': {\n\t\t\tpattern: /\\/\\/.*|\\/\\*[\\s\\S]*?\\*\\//,\n\t\t\tgreedy: true\n\t\t},\n\t\t'javascript-function': {\n\t\t\tpattern: RegExp(/((?:^|;)[ \\t]*)function\\s+(?!\\s)[_$a-zA-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*\\s*\\(<js>*\\)\\s*\\{<js>*\\}/.source.replace(/<js>/g, function () { return jsExpr; }), 'm'),\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true,\n\t\t\talias: 'language-javascript',\n\t\t\tinside: Prism.languages.javascript\n\t\t},\n\t\t'class-name': {\n\t\t\tpattern: /((?:^|[:;])[ \\t]*)(?!\\d)\\w+(?=[ \\t]*\\{|[ \\t]+on\\b)/m,\n\t\t\tlookbehind: true\n\t\t},\n\t\t'property': [\n\t\t\t{\n\t\t\t\tpattern: /((?:^|[;{])[ \\t]*)(?!\\d)\\w+(?:\\.\\w+)*(?=[ \\t]*:)/m,\n\t\t\t\tlookbehind: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /((?:^|[;{])[ \\t]*)property[ \\t]+(?!\\d)\\w+(?:\\.\\w+)*[ \\t]+(?!\\d)\\w+(?:\\.\\w+)*(?=[ \\t]*:)/m,\n\t\t\t\tlookbehind: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'keyword': /^property/,\n\t\t\t\t\t'property': /\\w+(?:\\.\\w+)*/\n\t\t\t\t}\n\t\t\t}\n\t\t],\n\t\t'javascript-expression': {\n\t\t\tpattern: RegExp(/(:[ \\t]*)(?![\\s;}[])(?:(?!$|[;}])<js>)+/.source.replace(/<js>/g, function () { return jsExpr; }), 'm'),\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true,\n\t\t\talias: 'language-javascript',\n\t\t\tinside: Prism.languages.javascript\n\t\t},\n\t\t'string': {\n\t\t\tpattern: /\"(?:\\\\.|[^\\\\\"\\r\\n])*\"/,\n\t\t\tgreedy: true\n\t\t},\n\t\t'keyword': /\\b(?:as|import|on)\\b/,\n\t\t'punctuation': /[{}[\\]:;,]/\n\t};\n\n}(Prism));\n\nPrism.languages.r = {\n\t'comment': /#.*/,\n\t'string': {\n\t\tpattern: /(['\"])(?:\\\\.|(?!\\1)[^\\\\\\r\\n])*\\1/,\n\t\tgreedy: true\n\t},\n\t'percent-operator': {\n\t\t// Includes user-defined operators\n\t\t// and %%, %*%, %/%, %in%, %o%, %x%\n\t\tpattern: /%[^%\\s]*%/,\n\t\talias: 'operator'\n\t},\n\t'boolean': /\\b(?:FALSE|TRUE)\\b/,\n\t'ellipsis': /\\.\\.(?:\\.|\\d+)/,\n\t'number': [\n\t\t/\\b(?:Inf|NaN)\\b/,\n\t\t/(?:\\b0x[\\dA-Fa-f]+(?:\\.\\d*)?|\\b\\d+(?:\\.\\d*)?|\\B\\.\\d+)(?:[EePp][+-]?\\d+)?[iL]?/\n\t],\n\t'keyword': /\\b(?:NA|NA_character_|NA_complex_|NA_integer_|NA_real_|NULL|break|else|for|function|if|in|next|repeat|while)\\b/,\n\t'operator': /->?>?|<(?:=|<?-)?|[>=!]=?|::?|&&?|\\|\\|?|[+*\\/^$@~]/,\n\t'punctuation': /[(){}\\[\\],;]/\n};\n\n(function (Prism) {\n\n\tvar javascript = Prism.util.clone(Prism.languages.javascript);\n\n\tvar space = /(?:\\s|\\/\\/.*(?!.)|\\/\\*(?:[^*]|\\*(?!\\/))\\*\\/)/.source;\n\tvar braces = /(?:\\{(?:\\{(?:\\{[^{}]*\\}|[^{}])*\\}|[^{}])*\\})/.source;\n\tvar spread = /(?:\\{<S>*\\.{3}(?:[^{}]|<BRACES>)*\\})/.source;\n\n\t/**\n\t * @param {string} source\n\t * @param {string} [flags]\n\t */\n\tfunction re(source, flags) {\n\t\tsource = source\n\t\t\t.replace(/<S>/g, function () { return space; })\n\t\t\t.replace(/<BRACES>/g, function () { return braces; })\n\t\t\t.replace(/<SPREAD>/g, function () { return spread; });\n\t\treturn RegExp(source, flags);\n\t}\n\n\tspread = re(spread).source;\n\n\n\tPrism.languages.jsx = Prism.languages.extend('markup', javascript);\n\tPrism.languages.jsx.tag.pattern = re(\n\t\t/<\\/?(?:[\\w.:-]+(?:<S>+(?:[\\w.:$-]+(?:=(?:\"(?:\\\\[\\s\\S]|[^\\\\\"])*\"|'(?:\\\\[\\s\\S]|[^\\\\'])*'|[^\\s{'\"/>=]+|<BRACES>))?|<SPREAD>))*<S>*\\/?)?>/.source\n\t);\n\n\tPrism.languages.jsx.tag.inside['tag'].pattern = /^<\\/?[^\\s>\\/]*/;\n\tPrism.languages.jsx.tag.inside['attr-value'].pattern = /=(?!\\{)(?:\"(?:\\\\[\\s\\S]|[^\\\\\"])*\"|'(?:\\\\[\\s\\S]|[^\\\\'])*'|[^\\s'\">]+)/;\n\tPrism.languages.jsx.tag.inside['tag'].inside['class-name'] = /^[A-Z]\\w*(?:\\.[A-Z]\\w*)*$/;\n\tPrism.languages.jsx.tag.inside['comment'] = javascript['comment'];\n\n\tPrism.languages.insertBefore('inside', 'attr-name', {\n\t\t'spread': {\n\t\t\tpattern: re(/<SPREAD>/.source),\n\t\t\tinside: Prism.languages.jsx\n\t\t}\n\t}, Prism.languages.jsx.tag);\n\n\tPrism.languages.insertBefore('inside', 'special-attr', {\n\t\t'script': {\n\t\t\t// Allow for two levels of nesting\n\t\t\tpattern: re(/=<BRACES>/.source),\n\t\t\talias: 'language-javascript',\n\t\t\tinside: {\n\t\t\t\t'script-punctuation': {\n\t\t\t\t\tpattern: /^=(?=\\{)/,\n\t\t\t\t\talias: 'punctuation'\n\t\t\t\t},\n\t\t\t\trest: Prism.languages.jsx\n\t\t\t},\n\t\t}\n\t}, Prism.languages.jsx.tag);\n\n\t// The following will handle plain text inside tags\n\tvar stringifyToken = function (token) {\n\t\tif (!token) {\n\t\t\treturn '';\n\t\t}\n\t\tif (typeof token === 'string') {\n\t\t\treturn token;\n\t\t}\n\t\tif (typeof token.content === 'string') {\n\t\t\treturn token.content;\n\t\t}\n\t\treturn token.content.map(stringifyToken).join('');\n\t};\n\n\tvar walkTokens = function (tokens) {\n\t\tvar openedTags = [];\n\t\tfor (var i = 0; i < tokens.length; i++) {\n\t\t\tvar token = tokens[i];\n\t\t\tvar notTagNorBrace = false;\n\n\t\t\tif (typeof token !== 'string') {\n\t\t\t\tif (token.type === 'tag' && token.content[0] && token.content[0].type === 'tag') {\n\t\t\t\t\t// We found a tag, now find its kind\n\n\t\t\t\t\tif (token.content[0].content[0].content === '</') {\n\t\t\t\t\t\t// Closing tag\n\t\t\t\t\t\tif (openedTags.length > 0 && openedTags[openedTags.length - 1].tagName === stringifyToken(token.content[0].content[1])) {\n\t\t\t\t\t\t\t// Pop matching opening tag\n\t\t\t\t\t\t\topenedTags.pop();\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (token.content[token.content.length - 1].content === '/>') {\n\t\t\t\t\t\t\t// Autoclosed tag, ignore\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Opening tag\n\t\t\t\t\t\t\topenedTags.push({\n\t\t\t\t\t\t\t\ttagName: stringifyToken(token.content[0].content[1]),\n\t\t\t\t\t\t\t\topenedBraces: 0\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} else if (openedTags.length > 0 && token.type === 'punctuation' && token.content === '{') {\n\n\t\t\t\t\t// Here we might have entered a JSX context inside a tag\n\t\t\t\t\topenedTags[openedTags.length - 1].openedBraces++;\n\n\t\t\t\t} else if (openedTags.length > 0 && openedTags[openedTags.length - 1].openedBraces > 0 && token.type === 'punctuation' && token.content === '}') {\n\n\t\t\t\t\t// Here we might have left a JSX context inside a tag\n\t\t\t\t\topenedTags[openedTags.length - 1].openedBraces--;\n\n\t\t\t\t} else {\n\t\t\t\t\tnotTagNorBrace = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (notTagNorBrace || typeof token === 'string') {\n\t\t\t\tif (openedTags.length > 0 && openedTags[openedTags.length - 1].openedBraces === 0) {\n\t\t\t\t\t// Here we are inside a tag, and not inside a JSX context.\n\t\t\t\t\t// That's plain text: drop any tokens matched.\n\t\t\t\t\tvar plainText = stringifyToken(token);\n\n\t\t\t\t\t// And merge text with adjacent text\n\t\t\t\t\tif (i < tokens.length - 1 && (typeof tokens[i + 1] === 'string' || tokens[i + 1].type === 'plain-text')) {\n\t\t\t\t\t\tplainText += stringifyToken(tokens[i + 1]);\n\t\t\t\t\t\ttokens.splice(i + 1, 1);\n\t\t\t\t\t}\n\t\t\t\t\tif (i > 0 && (typeof tokens[i - 1] === 'string' || tokens[i - 1].type === 'plain-text')) {\n\t\t\t\t\t\tplainText = stringifyToken(tokens[i - 1]) + plainText;\n\t\t\t\t\t\ttokens.splice(i - 1, 1);\n\t\t\t\t\t\ti--;\n\t\t\t\t\t}\n\n\t\t\t\t\ttokens[i] = new Prism.Token('plain-text', plainText, null, plainText);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (token.content && typeof token.content !== 'string') {\n\t\t\t\twalkTokens(token.content);\n\t\t\t}\n\t\t}\n\t};\n\n\tPrism.hooks.add('after-tokenize', function (env) {\n\t\tif (env.language !== 'jsx' && env.language !== 'tsx') {\n\t\t\treturn;\n\t\t}\n\t\twalkTokens(env.tokens);\n\t});\n\n}(Prism));\n\n(function (Prism) {\n\n\tvar multilineComment = /\\/\\*(?:[^*/]|\\*(?!\\/)|\\/(?!\\*)|<self>)*\\*\\//.source;\n\tfor (var i = 0; i < 2; i++) {\n\t\t// support 4 levels of nested comments\n\t\tmultilineComment = multilineComment.replace(/<self>/g, function () { return multilineComment; });\n\t}\n\tmultilineComment = multilineComment.replace(/<self>/g, function () { return /[^\\s\\S]/.source; });\n\n\n\tPrism.languages.rust = {\n\t\t'comment': [\n\t\t\t{\n\t\t\t\tpattern: RegExp(/(^|[^\\\\])/.source + multilineComment),\n\t\t\t\tlookbehind: true,\n\t\t\t\tgreedy: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /(^|[^\\\\:])\\/\\/.*/,\n\t\t\t\tlookbehind: true,\n\t\t\t\tgreedy: true\n\t\t\t}\n\t\t],\n\t\t'string': {\n\t\t\tpattern: /b?\"(?:\\\\[\\s\\S]|[^\\\\\"])*\"|b?r(#*)\"(?:[^\"]|\"(?!\\1))*\"\\1/,\n\t\t\tgreedy: true\n\t\t},\n\t\t'char': {\n\t\t\tpattern: /b?'(?:\\\\(?:x[0-7][\\da-fA-F]|u\\{(?:[\\da-fA-F]_*){1,6}\\}|.)|[^\\\\\\r\\n\\t'])'/,\n\t\t\tgreedy: true\n\t\t},\n\t\t'attribute': {\n\t\t\tpattern: /#!?\\[(?:[^\\[\\]\"]|\"(?:\\\\[\\s\\S]|[^\\\\\"])*\")*\\]/,\n\t\t\tgreedy: true,\n\t\t\talias: 'attr-name',\n\t\t\tinside: {\n\t\t\t\t'string': null // see below\n\t\t\t}\n\t\t},\n\n\t\t// Closure params should not be confused with bitwise OR |\n\t\t'closure-params': {\n\t\t\tpattern: /([=(,:]\\s*|\\bmove\\s*)\\|[^|]*\\||\\|[^|]*\\|(?=\\s*(?:\\{|->))/,\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true,\n\t\t\tinside: {\n\t\t\t\t'closure-punctuation': {\n\t\t\t\t\tpattern: /^\\||\\|$/,\n\t\t\t\t\talias: 'punctuation'\n\t\t\t\t},\n\t\t\t\trest: null // see below\n\t\t\t}\n\t\t},\n\n\t\t'lifetime-annotation': {\n\t\t\tpattern: /'\\w+/,\n\t\t\talias: 'symbol'\n\t\t},\n\n\t\t'fragment-specifier': {\n\t\t\tpattern: /(\\$\\w+:)[a-z]+/,\n\t\t\tlookbehind: true,\n\t\t\talias: 'punctuation'\n\t\t},\n\t\t'variable': /\\$\\w+/,\n\n\t\t'function-definition': {\n\t\t\tpattern: /(\\bfn\\s+)\\w+/,\n\t\t\tlookbehind: true,\n\t\t\talias: 'function'\n\t\t},\n\t\t'type-definition': {\n\t\t\tpattern: /(\\b(?:enum|struct|trait|type|union)\\s+)\\w+/,\n\t\t\tlookbehind: true,\n\t\t\talias: 'class-name'\n\t\t},\n\t\t'module-declaration': [\n\t\t\t{\n\t\t\t\tpattern: /(\\b(?:crate|mod)\\s+)[a-z][a-z_\\d]*/,\n\t\t\t\tlookbehind: true,\n\t\t\t\talias: 'namespace'\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: /(\\b(?:crate|self|super)\\s*)::\\s*[a-z][a-z_\\d]*\\b(?:\\s*::(?:\\s*[a-z][a-z_\\d]*\\s*::)*)?/,\n\t\t\t\tlookbehind: true,\n\t\t\t\talias: 'namespace',\n\t\t\t\tinside: {\n\t\t\t\t\t'punctuation': /::/\n\t\t\t\t}\n\t\t\t}\n\t\t],\n\t\t'keyword': [\n\t\t\t// https://github.com/rust-lang/reference/blob/master/src/keywords.md\n\t\t\t/\\b(?:Self|abstract|as|async|await|become|box|break|const|continue|crate|do|dyn|else|enum|extern|final|fn|for|if|impl|in|let|loop|macro|match|mod|move|mut|override|priv|pub|ref|return|self|static|struct|super|trait|try|type|typeof|union|unsafe|unsized|use|virtual|where|while|yield)\\b/,\n\t\t\t// primitives and str\n\t\t\t// https://doc.rust-lang.org/stable/rust-by-example/primitives.html\n\t\t\t/\\b(?:bool|char|f(?:32|64)|[ui](?:8|16|32|64|128|size)|str)\\b/\n\t\t],\n\n\t\t// functions can technically start with an upper-case letter, but this will introduce a lot of false positives\n\t\t// and Rust's naming conventions recommend snake_case anyway.\n\t\t// https://doc.rust-lang.org/1.0.0/style/style/naming/README.html\n\t\t'function': /\\b[a-z_]\\w*(?=\\s*(?:::\\s*<|\\())/,\n\t\t'macro': {\n\t\t\tpattern: /\\b\\w+!/,\n\t\t\talias: 'property'\n\t\t},\n\t\t'constant': /\\b[A-Z_][A-Z_\\d]+\\b/,\n\t\t'class-name': /\\b[A-Z]\\w*\\b/,\n\n\t\t'namespace': {\n\t\t\tpattern: /(?:\\b[a-z][a-z_\\d]*\\s*::\\s*)*\\b[a-z][a-z_\\d]*\\s*::(?!\\s*<)/,\n\t\t\tinside: {\n\t\t\t\t'punctuation': /::/\n\t\t\t}\n\t\t},\n\n\t\t// Hex, oct, bin, dec numbers with visual separators and type suffix\n\t\t'number': /\\b(?:0x[\\dA-Fa-f](?:_?[\\dA-Fa-f])*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*|(?:(?:\\d(?:_?\\d)*)?\\.)?\\d(?:_?\\d)*(?:[Ee][+-]?\\d+)?)(?:_?(?:f32|f64|[iu](?:8|16|32|64|size)?))?\\b/,\n\t\t'boolean': /\\b(?:false|true)\\b/,\n\t\t'punctuation': /->|\\.\\.=|\\.{1,3}|::|[{}[\\];(),:]/,\n\t\t'operator': /[-+*\\/%!^]=?|=[=>]?|&[&=]?|\\|[|=]?|<<?=?|>>?=?|[@?]/\n\t};\n\n\tPrism.languages.rust['closure-params'].inside.rest = Prism.languages.rust;\n\tPrism.languages.rust['attribute'].inside['string'] = Prism.languages.rust['string'];\n\n}(Prism));\n\nPrism.languages.scss = Prism.languages.extend('css', {\n\t'comment': {\n\t\tpattern: /(^|[^\\\\])(?:\\/\\*[\\s\\S]*?\\*\\/|\\/\\/.*)/,\n\t\tlookbehind: true\n\t},\n\t'atrule': {\n\t\tpattern: /@[\\w-](?:\\([^()]+\\)|[^()\\s]|\\s+(?!\\s))*?(?=\\s+[{;])/,\n\t\tinside: {\n\t\t\t'rule': /@[\\w-]+/\n\t\t\t// See rest below\n\t\t}\n\t},\n\t// url, compassified\n\t'url': /(?:[-a-z]+-)?url(?=\\()/i,\n\t// CSS selector regex is not appropriate for Sass\n\t// since there can be lot more things (var, @ directive, nesting..)\n\t// a selector must start at the end of a property or after a brace (end of other rules or nesting)\n\t// it can contain some characters that aren't used for defining rules or end of selector, & (parent selector), or interpolated variable\n\t// the end of a selector is found when there is no rules in it ( {} or {\\s}) or if there is a property (because an interpolated var\n\t// can \"pass\" as a selector- e.g: proper#{$erty})\n\t// this one was hard to do, so please be careful if you edit this one :)\n\t'selector': {\n\t\t// Initial look-ahead is used to prevent matching of blank selectors\n\t\tpattern: /(?=\\S)[^@;{}()]?(?:[^@;{}()\\s]|\\s+(?!\\s)|#\\{\\$[-\\w]+\\})+(?=\\s*\\{(?:\\}|\\s|[^}][^:{}]*[:{][^}]))/,\n\t\tinside: {\n\t\t\t'parent': {\n\t\t\t\tpattern: /&/,\n\t\t\t\talias: 'important'\n\t\t\t},\n\t\t\t'placeholder': /%[-\\w]+/,\n\t\t\t'variable': /\\$[-\\w]+|#\\{\\$[-\\w]+\\}/\n\t\t}\n\t},\n\t'property': {\n\t\tpattern: /(?:[-\\w]|\\$[-\\w]|#\\{\\$[-\\w]+\\})+(?=\\s*:)/,\n\t\tinside: {\n\t\t\t'variable': /\\$[-\\w]+|#\\{\\$[-\\w]+\\}/\n\t\t}\n\t}\n});\n\nPrism.languages.insertBefore('scss', 'atrule', {\n\t'keyword': [\n\t\t/@(?:content|debug|each|else(?: if)?|extend|for|forward|function|if|import|include|mixin|return|use|warn|while)\\b/i,\n\t\t{\n\t\t\tpattern: /( )(?:from|through)(?= )/,\n\t\t\tlookbehind: true\n\t\t}\n\t]\n});\n\nPrism.languages.insertBefore('scss', 'important', {\n\t// var and interpolated vars\n\t'variable': /\\$[-\\w]+|#\\{\\$[-\\w]+\\}/\n});\n\nPrism.languages.insertBefore('scss', 'function', {\n\t'module-modifier': {\n\t\tpattern: /\\b(?:as|hide|show|with)\\b/i,\n\t\talias: 'keyword'\n\t},\n\t'placeholder': {\n\t\tpattern: /%[-\\w]+/,\n\t\talias: 'selector'\n\t},\n\t'statement': {\n\t\tpattern: /\\B!(?:default|optional)\\b/i,\n\t\talias: 'keyword'\n\t},\n\t'boolean': /\\b(?:false|true)\\b/,\n\t'null': {\n\t\tpattern: /\\bnull\\b/,\n\t\talias: 'keyword'\n\t},\n\t'operator': {\n\t\tpattern: /(\\s)(?:[-+*\\/%]|[=!]=|<=?|>=?|and|not|or)(?=\\s)/,\n\t\tlookbehind: true\n\t}\n});\n\nPrism.languages.scss['atrule'].inside.rest = Prism.languages.scss;\n\nPrism.languages.scala = Prism.languages.extend('java', {\n\t'triple-quoted-string': {\n\t\tpattern: /\"\"\"[\\s\\S]*?\"\"\"/,\n\t\tgreedy: true,\n\t\talias: 'string'\n\t},\n\t'string': {\n\t\tpattern: /(\"|')(?:\\\\.|(?!\\1)[^\\\\\\r\\n])*\\1/,\n\t\tgreedy: true\n\t},\n\t'keyword': /<-|=>|\\b(?:abstract|case|catch|class|def|derives|do|else|enum|extends|extension|final|finally|for|forSome|given|if|implicit|import|infix|inline|lazy|match|new|null|object|opaque|open|override|package|private|protected|return|sealed|self|super|this|throw|trait|transparent|try|type|using|val|var|while|with|yield)\\b/,\n\t'number': /\\b0x(?:[\\da-f]*\\.)?[\\da-f]+|(?:\\b\\d+(?:\\.\\d*)?|\\B\\.\\d+)(?:e\\d+)?[dfl]?/i,\n\t'builtin': /\\b(?:Any|AnyRef|AnyVal|Boolean|Byte|Char|Double|Float|Int|Long|Nothing|Short|String|Unit)\\b/,\n\t'symbol': /'[^\\d\\s\\\\]\\w*/\n});\n\nPrism.languages.insertBefore('scala', 'triple-quoted-string', {\n\t'string-interpolation': {\n\t\tpattern: /\\b[a-z]\\w*(?:\"\"\"(?:[^$]|\\$(?:[^{]|\\{(?:[^{}]|\\{[^{}]*\\})*\\}))*?\"\"\"|\"(?:[^$\"\\r\\n]|\\$(?:[^{]|\\{(?:[^{}]|\\{[^{}]*\\})*\\}))*\")/i,\n\t\tgreedy: true,\n\t\tinside: {\n\t\t\t'id': {\n\t\t\t\tpattern: /^\\w+/,\n\t\t\t\tgreedy: true,\n\t\t\t\talias: 'function'\n\t\t\t},\n\t\t\t'escape': {\n\t\t\t\tpattern: /\\\\\\$\"|\\$[$\"]/,\n\t\t\t\tgreedy: true,\n\t\t\t\talias: 'symbol'\n\t\t\t},\n\t\t\t'interpolation': {\n\t\t\t\tpattern: /\\$(?:\\w+|\\{(?:[^{}]|\\{[^{}]*\\})*\\})/,\n\t\t\t\tgreedy: true,\n\t\t\t\tinside: {\n\t\t\t\t\t'punctuation': /^\\$\\{?|\\}$/,\n\t\t\t\t\t'expression': {\n\t\t\t\t\t\tpattern: /[\\s\\S]+/,\n\t\t\t\t\t\tinside: Prism.languages.scala\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t'string': /[\\s\\S]+/\n\t\t}\n\t}\n});\n\ndelete Prism.languages.scala['class-name'];\ndelete Prism.languages.scala['function'];\ndelete Prism.languages.scala['constant'];\n\n(function (Prism) {\n\n\t// CAREFUL!\n\t// The following patterns are concatenated, so the group referenced by a back reference is non-obvious!\n\n\tvar strings = [\n\t\t// normal string\n\t\t/\"(?:\\\\[\\s\\S]|\\$\\([^)]+\\)|\\$(?!\\()|`[^`]+`|[^\"\\\\`$])*\"/.source,\n\t\t/'[^']*'/.source,\n\t\t/\\$'(?:[^'\\\\]|\\\\[\\s\\S])*'/.source,\n\n\t\t// here doc\n\t\t// 2 capturing groups\n\t\t/<<-?\\s*([\"']?)(\\w+)\\1\\s[\\s\\S]*?[\\r\\n]\\2/.source\n\t].join('|');\n\n\tPrism.languages['shell-session'] = {\n\t\t'command': {\n\t\t\tpattern: RegExp(\n\t\t\t\t// user info\n\t\t\t\t/^/.source +\n\t\t\t\t'(?:' +\n\t\t\t\t(\n\t\t\t\t\t// <user> \":\" ( <path> )?\n\t\t\t\t\t/[^\\s@:$#%*!/\\\\]+@[^\\r\\n@:$#%*!/\\\\]+(?::[^\\0-\\x1F$#%*?\"<>:;|]+)?/.source +\n\t\t\t\t\t'|' +\n\t\t\t\t\t// <path>\n\t\t\t\t\t// Since the path pattern is quite general, we will require it to start with a special character to\n\t\t\t\t\t// prevent false positives.\n\t\t\t\t\t/[/~.][^\\0-\\x1F$#%*?\"<>@:;|]*/.source\n\t\t\t\t) +\n\t\t\t\t')?' +\n\t\t\t\t// shell symbol\n\t\t\t\t/[$#%](?=\\s)/.source +\n\t\t\t\t// bash command\n\t\t\t\t/(?:[^\\\\\\r\\n \\t'\"<$]|[ \\t](?:(?!#)|#.*$)|\\\\(?:[^\\r]|\\r\\n?)|\\$(?!')|<(?!<)|<<str>>)+/.source.replace(/<<str>>/g, function () { return strings; }),\n\t\t\t\t'm'\n\t\t\t),\n\t\t\tgreedy: true,\n\t\t\tinside: {\n\t\t\t\t'info': {\n\t\t\t\t\t// foo@bar:~/files$ exit\n\t\t\t\t\t// foo@bar$ exit\n\t\t\t\t\t// ~/files$ exit\n\t\t\t\t\tpattern: /^[^#$%]+/,\n\t\t\t\t\talias: 'punctuation',\n\t\t\t\t\tinside: {\n\t\t\t\t\t\t'user': /^[^\\s@:$#%*!/\\\\]+@[^\\r\\n@:$#%*!/\\\\]+/,\n\t\t\t\t\t\t'punctuation': /:/,\n\t\t\t\t\t\t'path': /[\\s\\S]+/\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t'bash': {\n\t\t\t\t\tpattern: /(^[$#%]\\s*)\\S[\\s\\S]*/,\n\t\t\t\t\tlookbehind: true,\n\t\t\t\t\talias: 'language-bash',\n\t\t\t\t\tinside: Prism.languages.bash\n\t\t\t\t},\n\t\t\t\t'shell-symbol': {\n\t\t\t\t\tpattern: /^[$#%]/,\n\t\t\t\t\talias: 'important'\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t'output': /.(?:.*(?:[\\r\\n]|.$))*/\n\t};\n\n\tPrism.languages['sh-session'] = Prism.languages['shellsession'] = Prism.languages['shell-session'];\n\n}(Prism));\n\nPrism.languages.sql = {\n\t'comment': {\n\t\tpattern: /(^|[^\\\\])(?:\\/\\*[\\s\\S]*?\\*\\/|(?:--|\\/\\/|#).*)/,\n\t\tlookbehind: true\n\t},\n\t'variable': [\n\t\t{\n\t\t\tpattern: /@([\"'`])(?:\\\\[\\s\\S]|(?!\\1)[^\\\\])+\\1/,\n\t\t\tgreedy: true\n\t\t},\n\t\t/@[\\w.$]+/\n\t],\n\t'string': {\n\t\tpattern: /(^|[^@\\\\])(\"|')(?:\\\\[\\s\\S]|(?!\\2)[^\\\\]|\\2\\2)*\\2/,\n\t\tgreedy: true,\n\t\tlookbehind: true\n\t},\n\t'identifier': {\n\t\tpattern: /(^|[^@\\\\])`(?:\\\\[\\s\\S]|[^`\\\\]|``)*`/,\n\t\tgreedy: true,\n\t\tlookbehind: true,\n\t\tinside: {\n\t\t\t'punctuation': /^`|`$/\n\t\t}\n\t},\n\t'function': /\\b(?:AVG|COUNT|FIRST|FORMAT|LAST|LCASE|LEN|MAX|MID|MIN|MOD|NOW|ROUND|SUM|UCASE)(?=\\s*\\()/i, // Should we highlight user defined functions too?\n\t'keyword': /\\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR(?:ACTER|SET)?|CHECK(?:POINT)?|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMNS?|COMMENT|COMMIT(?:TED)?|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS(?:TABLE)?|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|CYCLE|DATA(?:BASES?)?|DATE(?:TIME)?|DAY|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITERS?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE|ELSE(?:IF)?|ENABLE|ENCLOSED|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPED?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|HOUR|IDENTITY(?:COL|_INSERT)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTERVAL|INTO|INVOKER|ISOLATION|ITERATE|JOIN|KEYS?|KILL|LANGUAGE|LAST|LEAVE|LEFT|LEVEL|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|LOOP|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MINUTE|MODE|MODIFIES|MODIFY|MONTH|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL|NATURAL|NCHAR|NEXT|NO|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREPARE|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READS?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEAT(?:ABLE)?|REPLACE|REPLICATION|REQUIRE|RESIGNAL|RESTORE|RESTRICT|RETURN(?:ING|S)?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SECOND|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|SQL|START(?:ING)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNLOCK|UNPIVOT|UNSIGNED|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?|YEAR)\\b/i,\n\t'boolean': /\\b(?:FALSE|NULL|TRUE)\\b/i,\n\t'number': /\\b0x[\\da-f]+\\b|\\b\\d+(?:\\.\\d*)?|\\B\\.\\d+\\b/i,\n\t'operator': /[-+*\\/=%^~]|&&?|\\|\\|?|!=?|<(?:=>?|<|>)?|>[>=]?|\\b(?:AND|BETWEEN|DIV|ILIKE|IN|IS|LIKE|NOT|OR|REGEXP|RLIKE|SOUNDS LIKE|XOR)\\b/i,\n\t'punctuation': /[;[\\]()`,.]/\n};\n\nPrism.languages.tcl = {\n\t'comment': {\n\t\tpattern: /(^|[^\\\\])#.*/,\n\t\tlookbehind: true\n\t},\n\t'string': {\n\t\tpattern: /\"(?:[^\"\\\\\\r\\n]|\\\\(?:\\r\\n|[\\s\\S]))*\"/,\n\t\tgreedy: true\n\t},\n\t'variable': [\n\t\t{\n\t\t\tpattern: /(\\$)(?:::)?(?:[a-zA-Z0-9]+::)*\\w+/,\n\t\t\tlookbehind: true\n\t\t},\n\t\t{\n\t\t\tpattern: /(\\$)\\{[^}]+\\}/,\n\t\t\tlookbehind: true\n\t\t},\n\t\t{\n\t\t\tpattern: /(^[\\t ]*set[ \\t]+)(?:::)?(?:[a-zA-Z0-9]+::)*\\w+/m,\n\t\t\tlookbehind: true\n\t\t}\n\t],\n\t'function': {\n\t\tpattern: /(^[\\t ]*proc[ \\t]+)\\S+/m,\n\t\tlookbehind: true\n\t},\n\t'builtin': [\n\t\t{\n\t\t\tpattern: /(^[\\t ]*)(?:break|class|continue|error|eval|exit|for|foreach|if|proc|return|switch|while)\\b/m,\n\t\t\tlookbehind: true\n\t\t},\n\t\t/\\b(?:else|elseif)\\b/\n\t],\n\t'scope': {\n\t\tpattern: /(^[\\t ]*)(?:global|upvar|variable)\\b/m,\n\t\tlookbehind: true,\n\t\talias: 'constant'\n\t},\n\t'keyword': {\n\t\tpattern: /(^[\\t ]*|\\[)(?:Safe_Base|Tcl|after|append|apply|array|auto_(?:execok|import|load|mkindex|qualify|reset)|automkindex_old|bgerror|binary|catch|cd|chan|clock|close|concat|dde|dict|encoding|eof|exec|expr|fblocked|fconfigure|fcopy|file(?:event|name)?|flush|gets|glob|history|http|incr|info|interp|join|lappend|lassign|lindex|linsert|list|llength|load|lrange|lrepeat|lreplace|lreverse|lsearch|lset|lsort|math(?:func|op)|memory|msgcat|namespace|open|package|parray|pid|pkg_mkIndex|platform|puts|pwd|re_syntax|read|refchan|regexp|registry|regsub|rename|scan|seek|set|socket|source|split|string|subst|tcl(?:_endOfWord|_findLibrary|startOf(?:Next|Previous)Word|test|vars|wordBreak(?:After|Before))|tell|time|tm|trace|unknown|unload|unset|update|uplevel|vwait)\\b/m,\n\t\tlookbehind: true\n\t},\n\t'operator': /!=?|\\*\\*?|==|&&?|\\|\\|?|<[=<]?|>[=>]?|[-+~\\/%?^]|\\b(?:eq|in|ne|ni)\\b/,\n\t'punctuation': /[{}()\\[\\]]/\n};\n\n(function (Prism) {\n\n\tPrism.languages.typescript = Prism.languages.extend('javascript', {\n\t\t'class-name': {\n\t\t\tpattern: /(\\b(?:class|extends|implements|instanceof|interface|new|type)\\s+)(?!keyof\\b)(?!\\s)[_$a-zA-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*(?:\\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>)?/,\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true,\n\t\t\tinside: null // see below\n\t\t},\n\t\t'builtin': /\\b(?:Array|Function|Promise|any|boolean|console|never|number|string|symbol|unknown)\\b/,\n\t});\n\n\t// The keywords TypeScript adds to JavaScript\n\tPrism.languages.typescript.keyword.push(\n\t\t/\\b(?:abstract|declare|is|keyof|readonly|require)\\b/,\n\t\t// keywords that have to be followed by an identifier\n\t\t/\\b(?:asserts|infer|interface|module|namespace|type)\\b(?=\\s*(?:[{_$a-zA-Z\\xA0-\\uFFFF]|$))/,\n\t\t// This is for `import type *, {}`\n\t\t/\\btype\\b(?=\\s*(?:[\\{*]|$))/\n\t);\n\n\t// doesn't work with TS because TS is too complex\n\tdelete Prism.languages.typescript['parameter'];\n\tdelete Prism.languages.typescript['literal-property'];\n\n\t// a version of typescript specifically for highlighting types\n\tvar typeInside = Prism.languages.extend('typescript', {});\n\tdelete typeInside['class-name'];\n\n\tPrism.languages.typescript['class-name'].inside = typeInside;\n\n\tPrism.languages.insertBefore('typescript', 'function', {\n\t\t'decorator': {\n\t\t\tpattern: /@[$\\w\\xA0-\\uFFFF]+/,\n\t\t\tinside: {\n\t\t\t\t'at': {\n\t\t\t\t\tpattern: /^@/,\n\t\t\t\t\talias: 'operator'\n\t\t\t\t},\n\t\t\t\t'function': /^[\\s\\S]+/\n\t\t\t}\n\t\t},\n\t\t'generic-function': {\n\t\t\t// e.g. foo<T extends \"bar\" | \"baz\">( ...\n\t\t\tpattern: /#?(?!\\s)[_$a-zA-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*\\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>(?=\\s*\\()/,\n\t\t\tgreedy: true,\n\t\t\tinside: {\n\t\t\t\t'function': /^#?(?!\\s)[_$a-zA-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*/,\n\t\t\t\t'generic': {\n\t\t\t\t\tpattern: /<[\\s\\S]+/, // everything after the first <\n\t\t\t\t\talias: 'class-name',\n\t\t\t\t\tinside: typeInside\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t});\n\n\tPrism.languages.ts = Prism.languages.typescript;\n\n}(Prism));\n\n(function (Prism) {\n\n\t// https://yaml.org/spec/1.2/spec.html#c-ns-anchor-property\n\t// https://yaml.org/spec/1.2/spec.html#c-ns-alias-node\n\tvar anchorOrAlias = /[*&][^\\s[\\]{},]+/;\n\t// https://yaml.org/spec/1.2/spec.html#c-ns-tag-property\n\tvar tag = /!(?:<[\\w\\-%#;/?:@&=+$,.!~*'()[\\]]+>|(?:[a-zA-Z\\d-]*!)?[\\w\\-%#;/?:@&=+$.~*'()]+)?/;\n\t// https://yaml.org/spec/1.2/spec.html#c-ns-properties(n,c)\n\tvar properties = '(?:' + tag.source + '(?:[ \\t]+' + anchorOrAlias.source + ')?|'\n\t\t+ anchorOrAlias.source + '(?:[ \\t]+' + tag.source + ')?)';\n\t// https://yaml.org/spec/1.2/spec.html#ns-plain(n,c)\n\t// This is a simplified version that doesn't support \"#\" and multiline keys\n\t// All these long scarry character classes are simplified versions of YAML's characters\n\tvar plainKey = /(?:[^\\s\\x00-\\x08\\x0e-\\x1f!\"#%&'*,\\-:>?@[\\]`{|}\\x7f-\\x84\\x86-\\x9f\\ud800-\\udfff\\ufffe\\uffff]|[?:-]<PLAIN>)(?:[ \\t]*(?:(?![#:])<PLAIN>|:<PLAIN>))*/.source\n\t\t.replace(/<PLAIN>/g, function () { return /[^\\s\\x00-\\x08\\x0e-\\x1f,[\\]{}\\x7f-\\x84\\x86-\\x9f\\ud800-\\udfff\\ufffe\\uffff]/.source; });\n\tvar string = /\"(?:[^\"\\\\\\r\\n]|\\\\.)*\"|'(?:[^'\\\\\\r\\n]|\\\\.)*'/.source;\n\n\t/**\n\t *\n\t * @param {string} value\n\t * @param {string} [flags]\n\t * @returns {RegExp}\n\t */\n\tfunction createValuePattern(value, flags) {\n\t\tflags = (flags || '').replace(/m/g, '') + 'm'; // add m flag\n\t\tvar pattern = /([:\\-,[{]\\s*(?:\\s<<prop>>[ \\t]+)?)(?:<<value>>)(?=[ \\t]*(?:$|,|\\]|\\}|(?:[\\r\\n]\\s*)?#))/.source\n\t\t\t.replace(/<<prop>>/g, function () { return properties; }).replace(/<<value>>/g, function () { return value; });\n\t\treturn RegExp(pattern, flags);\n\t}\n\n\tPrism.languages.yaml = {\n\t\t'scalar': {\n\t\t\tpattern: RegExp(/([\\-:]\\s*(?:\\s<<prop>>[ \\t]+)?[|>])[ \\t]*(?:((?:\\r?\\n|\\r)[ \\t]+)\\S[^\\r\\n]*(?:\\2[^\\r\\n]+)*)/.source\n\t\t\t\t.replace(/<<prop>>/g, function () { return properties; })),\n\t\t\tlookbehind: true,\n\t\t\talias: 'string'\n\t\t},\n\t\t'comment': /#.*/,\n\t\t'key': {\n\t\t\tpattern: RegExp(/((?:^|[:\\-,[{\\r\\n?])[ \\t]*(?:<<prop>>[ \\t]+)?)<<key>>(?=\\s*:\\s)/.source\n\t\t\t\t.replace(/<<prop>>/g, function () { return properties; })\n\t\t\t\t.replace(/<<key>>/g, function () { return '(?:' + plainKey + '|' + string + ')'; })),\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true,\n\t\t\talias: 'atrule'\n\t\t},\n\t\t'directive': {\n\t\t\tpattern: /(^[ \\t]*)%.+/m,\n\t\t\tlookbehind: true,\n\t\t\talias: 'important'\n\t\t},\n\t\t'datetime': {\n\t\t\tpattern: createValuePattern(/\\d{4}-\\d\\d?-\\d\\d?(?:[tT]|[ \\t]+)\\d\\d?:\\d{2}:\\d{2}(?:\\.\\d*)?(?:[ \\t]*(?:Z|[-+]\\d\\d?(?::\\d{2})?))?|\\d{4}-\\d{2}-\\d{2}|\\d\\d?:\\d{2}(?::\\d{2}(?:\\.\\d*)?)?/.source),\n\t\t\tlookbehind: true,\n\t\t\talias: 'number'\n\t\t},\n\t\t'boolean': {\n\t\t\tpattern: createValuePattern(/false|true/.source, 'i'),\n\t\t\tlookbehind: true,\n\t\t\talias: 'important'\n\t\t},\n\t\t'null': {\n\t\t\tpattern: createValuePattern(/null|~/.source, 'i'),\n\t\t\tlookbehind: true,\n\t\t\talias: 'important'\n\t\t},\n\t\t'string': {\n\t\t\tpattern: createValuePattern(string),\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true\n\t\t},\n\t\t'number': {\n\t\t\tpattern: createValuePattern(/[+-]?(?:0x[\\da-f]+|0o[0-7]+|(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:e[+-]?\\d+)?|\\.inf|\\.nan)/.source, 'i'),\n\t\t\tlookbehind: true\n\t\t},\n\t\t'tag': tag,\n\t\t'important': anchorOrAlias,\n\t\t'punctuation': /---|[:[\\]{}\\-,|>?]|\\.\\.\\./\n\t};\n\n\tPrism.languages.yml = Prism.languages.yaml;\n\n}(Prism));\n\n(function (Prism) {\n\n\tfunction literal(str) {\n\t\treturn function () { return str; };\n\t}\n\n\tvar keyword = /\\b(?:align|allowzero|and|anyframe|anytype|asm|async|await|break|cancel|catch|comptime|const|continue|defer|else|enum|errdefer|error|export|extern|fn|for|if|inline|linksection|nakedcc|noalias|nosuspend|null|or|orelse|packed|promise|pub|resume|return|stdcallcc|struct|suspend|switch|test|threadlocal|try|undefined|union|unreachable|usingnamespace|var|volatile|while)\\b/;\n\n\tvar IDENTIFIER = '\\\\b(?!' + keyword.source + ')(?!\\\\d)\\\\w+\\\\b';\n\tvar ALIGN = /align\\s*\\((?:[^()]|\\([^()]*\\))*\\)/.source;\n\tvar PREFIX_TYPE_OP = /(?:\\?|\\bpromise->|(?:\\[[^[\\]]*\\]|\\*(?!\\*)|\\*\\*)(?:\\s*<ALIGN>|\\s*const\\b|\\s*volatile\\b|\\s*allowzero\\b)*)/.source.replace(/<ALIGN>/g, literal(ALIGN));\n\tvar SUFFIX_EXPR = /(?:\\bpromise\\b|(?:\\berror\\.)?<ID>(?:\\.<ID>)*(?!\\s+<ID>))/.source.replace(/<ID>/g, literal(IDENTIFIER));\n\tvar TYPE = '(?!\\\\s)(?:!?\\\\s*(?:' + PREFIX_TYPE_OP + '\\\\s*)*' + SUFFIX_EXPR + ')+';\n\n\t/*\n\t * A simplified grammar for Zig compile time type literals:\n\t *\n\t * TypeExpr = ( \"!\"? PREFIX_TYPE_OP* SUFFIX_EXPR )+\n\t *\n\t * SUFFIX_EXPR = ( \\b \"promise\" \\b | ( \\b \"error\" \".\" )? IDENTIFIER ( \".\" IDENTIFIER )* (?! \\s+ IDENTIFIER ) )\n\t *\n\t * PREFIX_TYPE_OP = \"?\"\n\t *                | \\b \"promise\" \"->\"\n\t *                | ( \"[\" [^\\[\\]]* \"]\" | \"*\" | \"**\" ) ( ALIGN | \"const\" \\b | \"volatile\" \\b | \"allowzero\" \\b )*\n\t *\n\t * ALIGN = \"align\" \"(\" ( [^()] | \"(\" [^()]* \")\" )* \")\"\n\t *\n\t * IDENTIFIER = \\b (?! KEYWORD ) [a-zA-Z_] \\w* \\b\n\t *\n\t*/\n\n\tPrism.languages.zig = {\n\t\t'comment': [\n\t\t\t{\n\t\t\t\tpattern: /\\/\\/[/!].*/,\n\t\t\t\talias: 'doc-comment'\n\t\t\t},\n\t\t\t/\\/{2}.*/\n\t\t],\n\t\t'string': [\n\t\t\t{\n\t\t\t\t// \"string\" and c\"string\"\n\t\t\t\tpattern: /(^|[^\\\\@])c?\"(?:[^\"\\\\\\r\\n]|\\\\.)*\"/,\n\t\t\t\tlookbehind: true,\n\t\t\t\tgreedy: true\n\t\t\t},\n\t\t\t{\n\t\t\t\t// multiline strings and c-strings\n\t\t\t\tpattern: /([\\r\\n])([ \\t]+c?\\\\{2}).*(?:(?:\\r\\n?|\\n)\\2.*)*/,\n\t\t\t\tlookbehind: true,\n\t\t\t\tgreedy: true\n\t\t\t}\n\t\t],\n\t\t'char': {\n\t\t\t// characters 'a', '\\n', '\\xFF', '\\u{10FFFF}'\n\t\t\tpattern: /(^|[^\\\\])'(?:[^'\\\\\\r\\n]|[\\uD800-\\uDFFF]{2}|\\\\(?:.|x[a-fA-F\\d]{2}|u\\{[a-fA-F\\d]{1,6}\\}))'/,\n\t\t\tlookbehind: true,\n\t\t\tgreedy: true\n\t\t},\n\t\t'builtin': /\\B@(?!\\d)\\w+(?=\\s*\\()/,\n\t\t'label': {\n\t\t\tpattern: /(\\b(?:break|continue)\\s*:\\s*)\\w+\\b|\\b(?!\\d)\\w+\\b(?=\\s*:\\s*(?:\\{|while\\b))/,\n\t\t\tlookbehind: true\n\t\t},\n\t\t'class-name': [\n\t\t\t// const Foo = struct {};\n\t\t\t/\\b(?!\\d)\\w+(?=\\s*=\\s*(?:(?:extern|packed)\\s+)?(?:enum|struct|union)\\s*[({])/,\n\t\t\t{\n\t\t\t\t// const x: i32 = 9;\n\t\t\t\t// var x: Bar;\n\t\t\t\t// fn foo(x: bool, y: f32) void {}\n\t\t\t\tpattern: RegExp(/(:\\s*)<TYPE>(?=\\s*(?:<ALIGN>\\s*)?[=;,)])|<TYPE>(?=\\s*(?:<ALIGN>\\s*)?\\{)/.source.replace(/<TYPE>/g, literal(TYPE)).replace(/<ALIGN>/g, literal(ALIGN))),\n\t\t\t\tlookbehind: true,\n\t\t\t\tinside: null // see below\n\t\t\t},\n\t\t\t{\n\t\t\t\t// extern fn foo(x: f64) f64; (optional alignment)\n\t\t\t\tpattern: RegExp(/(\\)\\s*)<TYPE>(?=\\s*(?:<ALIGN>\\s*)?;)/.source.replace(/<TYPE>/g, literal(TYPE)).replace(/<ALIGN>/g, literal(ALIGN))),\n\t\t\t\tlookbehind: true,\n\t\t\t\tinside: null // see below\n\t\t\t}\n\t\t],\n\t\t'builtin-type': {\n\t\t\tpattern: /\\b(?:anyerror|bool|c_u?(?:int|long|longlong|short)|c_longdouble|c_void|comptime_(?:float|int)|f(?:16|32|64|128)|[iu](?:8|16|32|64|128|size)|noreturn|type|void)\\b/,\n\t\t\talias: 'keyword'\n\t\t},\n\t\t'keyword': keyword,\n\t\t'function': /\\b(?!\\d)\\w+(?=\\s*\\()/,\n\t\t'number': /\\b(?:0b[01]+|0o[0-7]+|0x[a-fA-F\\d]+(?:\\.[a-fA-F\\d]*)?(?:[pP][+-]?[a-fA-F\\d]+)?|\\d+(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)\\b/,\n\t\t'boolean': /\\b(?:false|true)\\b/,\n\t\t'operator': /\\.[*?]|\\.{2,3}|[-=]>|\\*\\*|\\+\\+|\\|\\||(?:<<|>>|[-+*]%|[-+*/%^&|<>!=])=?|[?~]/,\n\t\t'punctuation': /[.:,;(){}[\\]]/\n\t};\n\n\tPrism.languages.zig['class-name'].forEach(function (obj) {\n\t\tif (obj.inside === null) {\n\t\t\tobj.inside = Prism.languages.zig;\n\t\t}\n\t});\n\n}(Prism));\n\n"
  },
  {
    "path": "assets/javascripts/vendor/raven.js",
    "content": "/*! Raven.js 3.20.1 (42adaf5) | github.com/getsentry/raven-js */\n\n/*\n * Includes TraceKit\n * https://github.com/getsentry/TraceKit\n *\n * Copyright 2017 Matt Robenolt and other contributors\n * Released under the BSD license\n * https://github.com/getsentry/raven-js/blob/master/LICENSE\n *\n */\n\n(function (f) {\n  if (typeof exports === \"object\" && typeof module !== \"undefined\") {\n    module.exports = f();\n  } else if (typeof define === \"function\" && define.amd) {\n    define([], f);\n  } else {\n    var g;\n    if (typeof window !== \"undefined\") {\n      g = window;\n    } else if (typeof global !== \"undefined\") {\n      g = global;\n    } else if (typeof self !== \"undefined\") {\n      g = self;\n    } else {\n      g = this;\n    }\n    g.Raven = f();\n  }\n})(function () {\n  var define, module, exports;\n  return (function e(t, n, r) {\n    function s(o, u) {\n      if (!n[o]) {\n        if (!t[o]) {\n          var a = typeof require == \"function\" && require;\n          if (!u && a) return a(o, !0);\n          if (i) return i(o, !0);\n          var f = new Error(\"Cannot find module '\" + o + \"'\");\n          throw ((f.code = \"MODULE_NOT_FOUND\"), f);\n        }\n        var l = (n[o] = { exports: {} });\n        t[o][0].call(\n          l.exports,\n          function (e) {\n            var n = t[o][1][e];\n            return s(n ? n : e);\n          },\n          l,\n          l.exports,\n          e,\n          t,\n          n,\n          r,\n        );\n      }\n      return n[o].exports;\n    }\n    var i = typeof require == \"function\" && require;\n    for (var o = 0; o < r.length; o++) s(r[o]);\n    return s;\n  })(\n    {\n      1: [\n        function (_dereq_, module, exports) {\n          function RavenConfigError(message) {\n            this.name = \"RavenConfigError\";\n            this.message = message;\n          }\n          RavenConfigError.prototype = new Error();\n          RavenConfigError.prototype.constructor = RavenConfigError;\n\n          module.exports = RavenConfigError;\n        },\n        {},\n      ],\n      2: [\n        function (_dereq_, module, exports) {\n          var wrapMethod = function (console, level, callback) {\n            var originalConsoleLevel = console[level];\n            var originalConsole = console;\n\n            if (!(level in console)) {\n              return;\n            }\n\n            var sentryLevel = level === \"warn\" ? \"warning\" : level;\n\n            console[level] = function () {\n              var args = [].slice.call(arguments);\n\n              var msg = \"\" + args.join(\" \");\n              var data = {\n                level: sentryLevel,\n                logger: \"console\",\n                extra: { arguments: args },\n              };\n\n              if (level === \"assert\") {\n                if (args[0] === false) {\n                  // Default browsers message\n                  msg =\n                    \"Assertion failed: \" +\n                    (args.slice(1).join(\" \") || \"console.assert\");\n                  data.extra.arguments = args.slice(1);\n                  callback && callback(msg, data);\n                }\n              } else {\n                callback && callback(msg, data);\n              }\n\n              // this fails for some browsers. :(\n              if (originalConsoleLevel) {\n                // IE9 doesn't allow calling apply on console functions directly\n                // See: https://stackoverflow.com/questions/5472938/does-ie9-support-console-log-and-is-it-a-real-function#answer-5473193\n                Function.prototype.apply.call(\n                  originalConsoleLevel,\n                  originalConsole,\n                  args,\n                );\n              }\n            };\n          };\n\n          module.exports = {\n            wrapMethod: wrapMethod,\n          };\n        },\n        {},\n      ],\n      3: [\n        function (_dereq_, module, exports) {\n          (function (global) {\n            /*global XDomainRequest:false */\n\n            var TraceKit = _dereq_(6);\n            var stringify = _dereq_(7);\n            var RavenConfigError = _dereq_(1);\n\n            var utils = _dereq_(5);\n            var isError = utils.isError;\n            var isObject = utils.isObject;\n            var isErrorEvent = utils.isErrorEvent;\n            var isUndefined = utils.isUndefined;\n            var isFunction = utils.isFunction;\n            var isString = utils.isString;\n            var isArray = utils.isArray;\n            var isEmptyObject = utils.isEmptyObject;\n            var each = utils.each;\n            var objectMerge = utils.objectMerge;\n            var truncate = utils.truncate;\n            var objectFrozen = utils.objectFrozen;\n            var hasKey = utils.hasKey;\n            var joinRegExp = utils.joinRegExp;\n            var urlencode = utils.urlencode;\n            var uuid4 = utils.uuid4;\n            var htmlTreeAsString = utils.htmlTreeAsString;\n            var isSameException = utils.isSameException;\n            var isSameStacktrace = utils.isSameStacktrace;\n            var parseUrl = utils.parseUrl;\n            var fill = utils.fill;\n\n            var wrapConsoleMethod = _dereq_(2).wrapMethod;\n\n            var dsnKeys = \"source protocol user pass host port path\".split(\" \"),\n              dsnPattern =\n                /^(?:(\\w+):)?\\/\\/(?:(\\w+)(:\\w+)?@)?([\\w\\.-]+)(?::(\\d+))?(\\/.*)/;\n\n            function now() {\n              return +new Date();\n            }\n\n            // This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785)\n            var _window =\n              typeof window !== \"undefined\"\n                ? window\n                : typeof global !== \"undefined\"\n                  ? global\n                  : typeof self !== \"undefined\"\n                    ? self\n                    : {};\n            var _document = _window.document;\n            var _navigator = _window.navigator;\n\n            function keepOriginalCallback(original, callback) {\n              return isFunction(callback)\n                ? function (data) {\n                    return callback(data, original);\n                  }\n                : callback;\n            }\n\n            // First, check for JSON support\n            // If there is no JSON, we no-op the core features of Raven\n            // since JSON is required to encode the payload\n            function Raven() {\n              this._hasJSON = !!(typeof JSON === \"object\" && JSON.stringify);\n              // Raven can run in contexts where there's no document (react-native)\n              this._hasDocument = !isUndefined(_document);\n              this._hasNavigator = !isUndefined(_navigator);\n              this._lastCapturedException = null;\n              this._lastData = null;\n              this._lastEventId = null;\n              this._globalServer = null;\n              this._globalKey = null;\n              this._globalProject = null;\n              this._globalContext = {};\n              this._globalOptions = {\n                logger: \"javascript\",\n                ignoreErrors: [],\n                ignoreUrls: [],\n                whitelistUrls: [],\n                includePaths: [],\n                collectWindowErrors: true,\n                maxMessageLength: 0,\n\n                // By default, truncates URL values to 250 chars\n                maxUrlLength: 250,\n                stackTraceLimit: 50,\n                autoBreadcrumbs: true,\n                instrument: true,\n                sampleRate: 1,\n              };\n              this._ignoreOnError = 0;\n              this._isRavenInstalled = false;\n              this._originalErrorStackTraceLimit = Error.stackTraceLimit;\n              // capture references to window.console *and* all its methods first\n              // before the console plugin has a chance to monkey patch\n              this._originalConsole = _window.console || {};\n              this._originalConsoleMethods = {};\n              this._plugins = [];\n              this._startTime = now();\n              this._wrappedBuiltIns = [];\n              this._breadcrumbs = [];\n              this._lastCapturedEvent = null;\n              this._keypressTimeout;\n              this._location = _window.location;\n              this._lastHref = this._location && this._location.href;\n              this._resetBackoff();\n\n              // eslint-disable-next-line guard-for-in\n              for (var method in this._originalConsole) {\n                this._originalConsoleMethods[method] =\n                  this._originalConsole[method];\n              }\n            }\n\n            /*\n             * The core Raven singleton\n             *\n             * @this {Raven}\n             */\n\n            Raven.prototype = {\n              // Hardcode version string so that raven source can be loaded directly via\n              // webpack (using a build step causes webpack #1617). Grunt verifies that\n              // this value matches package.json during build.\n              //   See: https://github.com/getsentry/raven-js/issues/465\n              VERSION: \"3.20.1\",\n\n              debug: false,\n\n              TraceKit: TraceKit, // alias to TraceKit\n\n              /*\n               * Configure Raven with a DSN and extra options\n               *\n               * @param {string} dsn The public Sentry DSN\n               * @param {object} options Set of global options [optional]\n               * @return {Raven}\n               */\n              config: function (dsn, options) {\n                var self = this;\n\n                if (self._globalServer) {\n                  this._logDebug(\n                    \"error\",\n                    \"Error: Raven has already been configured\",\n                  );\n                  return self;\n                }\n                if (!dsn) return self;\n\n                var globalOptions = self._globalOptions;\n\n                // merge in options\n                if (options) {\n                  each(options, function (key, value) {\n                    // tags and extra are special and need to be put into context\n                    if (key === \"tags\" || key === \"extra\" || key === \"user\") {\n                      self._globalContext[key] = value;\n                    } else {\n                      globalOptions[key] = value;\n                    }\n                  });\n                }\n\n                self.setDSN(dsn);\n\n                // \"Script error.\" is hard coded into browsers for errors that it can't read.\n                // this is the result of a script being pulled in from an external domain and CORS.\n                globalOptions.ignoreErrors.push(/^Script error\\.?$/);\n                globalOptions.ignoreErrors.push(\n                  /^Javascript error: Script error\\.? on line 0$/,\n                );\n\n                // join regexp rules into one big rule\n                globalOptions.ignoreErrors = joinRegExp(\n                  globalOptions.ignoreErrors,\n                );\n                globalOptions.ignoreUrls = globalOptions.ignoreUrls.length\n                  ? joinRegExp(globalOptions.ignoreUrls)\n                  : false;\n                globalOptions.whitelistUrls = globalOptions.whitelistUrls.length\n                  ? joinRegExp(globalOptions.whitelistUrls)\n                  : false;\n                globalOptions.includePaths = joinRegExp(\n                  globalOptions.includePaths,\n                );\n                globalOptions.maxBreadcrumbs = Math.max(\n                  0,\n                  Math.min(globalOptions.maxBreadcrumbs || 100, 100),\n                ); // default and hard limit is 100\n\n                var autoBreadcrumbDefaults = {\n                  xhr: true,\n                  console: true,\n                  dom: true,\n                  location: true,\n                  sentry: true,\n                };\n\n                var autoBreadcrumbs = globalOptions.autoBreadcrumbs;\n                if ({}.toString.call(autoBreadcrumbs) === \"[object Object]\") {\n                  autoBreadcrumbs = objectMerge(\n                    autoBreadcrumbDefaults,\n                    autoBreadcrumbs,\n                  );\n                } else if (autoBreadcrumbs !== false) {\n                  autoBreadcrumbs = autoBreadcrumbDefaults;\n                }\n                globalOptions.autoBreadcrumbs = autoBreadcrumbs;\n\n                var instrumentDefaults = {\n                  tryCatch: true,\n                };\n\n                var instrument = globalOptions.instrument;\n                if ({}.toString.call(instrument) === \"[object Object]\") {\n                  instrument = objectMerge(instrumentDefaults, instrument);\n                } else if (instrument !== false) {\n                  instrument = instrumentDefaults;\n                }\n                globalOptions.instrument = instrument;\n\n                TraceKit.collectWindowErrors =\n                  !!globalOptions.collectWindowErrors;\n\n                // return for chaining\n                return self;\n              },\n\n              /*\n               * Installs a global window.onerror error handler\n               * to capture and report uncaught exceptions.\n               * At this point, install() is required to be called due\n               * to the way TraceKit is set up.\n               *\n               * @return {Raven}\n               */\n              install: function () {\n                var self = this;\n                if (self.isSetup() && !self._isRavenInstalled) {\n                  TraceKit.report.subscribe(function () {\n                    self._handleOnErrorStackInfo.apply(self, arguments);\n                  });\n\n                  self._patchFunctionToString();\n\n                  if (\n                    self._globalOptions.instrument &&\n                    self._globalOptions.instrument.tryCatch\n                  ) {\n                    self._instrumentTryCatch();\n                  }\n\n                  if (self._globalOptions.autoBreadcrumbs)\n                    self._instrumentBreadcrumbs();\n\n                  // Install all of the plugins\n                  self._drainPlugins();\n\n                  self._isRavenInstalled = true;\n                }\n\n                Error.stackTraceLimit = self._globalOptions.stackTraceLimit;\n                return this;\n              },\n\n              /*\n               * Set the DSN (can be called multiple time unlike config)\n               *\n               * @param {string} dsn The public Sentry DSN\n               */\n              setDSN: function (dsn) {\n                var self = this,\n                  uri = self._parseDSN(dsn),\n                  lastSlash = uri.path.lastIndexOf(\"/\"),\n                  path = uri.path.substr(1, lastSlash);\n\n                self._dsn = dsn;\n                self._globalKey = uri.user;\n                self._globalSecret = uri.pass && uri.pass.substr(1);\n                self._globalProject = uri.path.substr(lastSlash + 1);\n\n                self._globalServer = self._getGlobalServer(uri);\n\n                self._globalEndpoint =\n                  self._globalServer +\n                  \"/\" +\n                  path +\n                  \"api/\" +\n                  self._globalProject +\n                  \"/store/\";\n\n                // Reset backoff state since we may be pointing at a\n                // new project/server\n                this._resetBackoff();\n              },\n\n              /*\n               * Wrap code within a context so Raven can capture errors\n               * reliably across domains that is executed immediately.\n               *\n               * @param {object} options A specific set of options for this context [optional]\n               * @param {function} func The callback to be immediately executed within the context\n               * @param {array} args An array of arguments to be called with the callback [optional]\n               */\n              context: function (options, func, args) {\n                if (isFunction(options)) {\n                  args = func || [];\n                  func = options;\n                  options = undefined;\n                }\n\n                return this.wrap(options, func).apply(this, args);\n              },\n\n              /*\n               * Wrap code within a context and returns back a new function to be executed\n               *\n               * @param {object} options A specific set of options for this context [optional]\n               * @param {function} func The function to be wrapped in a new context\n               * @param {function} func A function to call before the try/catch wrapper [optional, private]\n               * @return {function} The newly wrapped functions with a context\n               */\n              wrap: function (options, func, _before) {\n                var self = this;\n                // 1 argument has been passed, and it's not a function\n                // so just return it\n                if (isUndefined(func) && !isFunction(options)) {\n                  return options;\n                }\n\n                // options is optional\n                if (isFunction(options)) {\n                  func = options;\n                  options = undefined;\n                }\n\n                // At this point, we've passed along 2 arguments, and the second one\n                // is not a function either, so we'll just return the second argument.\n                if (!isFunction(func)) {\n                  return func;\n                }\n\n                // We don't wanna wrap it twice!\n                try {\n                  if (func.__raven__) {\n                    return func;\n                  }\n\n                  // If this has already been wrapped in the past, return that\n                  if (func.__raven_wrapper__) {\n                    return func.__raven_wrapper__;\n                  }\n                } catch (e) {\n                  // Just accessing custom props in some Selenium environments\n                  // can cause a \"Permission denied\" exception (see raven-js#495).\n                  // Bail on wrapping and return the function as-is (defers to window.onerror).\n                  return func;\n                }\n\n                function wrapped() {\n                  var args = [],\n                    i = arguments.length,\n                    deep = !options || (options && options.deep !== false);\n\n                  if (_before && isFunction(_before)) {\n                    _before.apply(this, arguments);\n                  }\n\n                  // Recursively wrap all of a function's arguments that are\n                  // functions themselves.\n                  while (i--)\n                    args[i] = deep\n                      ? self.wrap(options, arguments[i])\n                      : arguments[i];\n\n                  try {\n                    // Attempt to invoke user-land function\n                    // NOTE: If you are a Sentry user, and you are seeing this stack frame, it\n                    //       means Raven caught an error invoking your application code. This is\n                    //       expected behavior and NOT indicative of a bug with Raven.js.\n                    return func.apply(this, args);\n                  } catch (e) {\n                    self._ignoreNextOnError();\n                    self.captureException(e, options);\n                    throw e;\n                  }\n                }\n\n                // copy over properties of the old function\n                for (var property in func) {\n                  if (hasKey(func, property)) {\n                    wrapped[property] = func[property];\n                  }\n                }\n                wrapped.prototype = func.prototype;\n\n                func.__raven_wrapper__ = wrapped;\n                // Signal that this function has been wrapped/filled already\n                // for both debugging and to prevent it to being wrapped/filled twice\n                wrapped.__raven__ = true;\n                wrapped.__orig__ = func;\n\n                return wrapped;\n              },\n\n              /*\n               * Uninstalls the global error handler.\n               *\n               * @return {Raven}\n               */\n              uninstall: function () {\n                TraceKit.report.uninstall();\n\n                this._unpatchFunctionToString();\n                this._restoreBuiltIns();\n\n                Error.stackTraceLimit = this._originalErrorStackTraceLimit;\n                this._isRavenInstalled = false;\n\n                return this;\n              },\n\n              /*\n               * Manually capture an exception and send it over to Sentry\n               *\n               * @param {error} ex An exception to be logged\n               * @param {object} options A specific set of options for this error [optional]\n               * @return {Raven}\n               */\n              captureException: function (ex, options) {\n                // Cases for sending ex as a message, rather than an exception\n                var isNotError = !isError(ex);\n                var isNotErrorEvent = !isErrorEvent(ex);\n                var isErrorEventWithoutError = isErrorEvent(ex) && !ex.error;\n\n                if (\n                  (isNotError && isNotErrorEvent) ||\n                  isErrorEventWithoutError\n                ) {\n                  return this.captureMessage(\n                    ex,\n                    objectMerge(\n                      {\n                        trimHeadFrames: 1,\n                        stacktrace: true, // if we fall back to captureMessage, default to attempting a new trace\n                      },\n                      options,\n                    ),\n                  );\n                }\n\n                // Get actual Error from ErrorEvent\n                if (isErrorEvent(ex)) ex = ex.error;\n\n                // Store the raw exception object for potential debugging and introspection\n                this._lastCapturedException = ex;\n\n                // TraceKit.report will re-raise any exception passed to it,\n                // which means you have to wrap it in try/catch. Instead, we\n                // can wrap it here and only re-raise if TraceKit.report\n                // raises an exception different from the one we asked to\n                // report on.\n                try {\n                  var stack = TraceKit.computeStackTrace(ex);\n                  this._handleStackInfo(stack, options);\n                } catch (ex1) {\n                  if (ex !== ex1) {\n                    throw ex1;\n                  }\n                }\n\n                return this;\n              },\n\n              /*\n               * Manually send a message to Sentry\n               *\n               * @param {string} msg A plain message to be captured in Sentry\n               * @param {object} options A specific set of options for this message [optional]\n               * @return {Raven}\n               */\n              captureMessage: function (msg, options) {\n                // config() automagically converts ignoreErrors from a list to a RegExp so we need to test for an\n                // early call; we'll error on the side of logging anything called before configuration since it's\n                // probably something you should see:\n                if (\n                  !!this._globalOptions.ignoreErrors.test &&\n                  this._globalOptions.ignoreErrors.test(msg)\n                ) {\n                  return;\n                }\n\n                options = options || {};\n\n                var data = objectMerge(\n                  {\n                    message: msg + \"\", // Make sure it's actually a string\n                  },\n                  options,\n                );\n\n                var ex;\n                // Generate a \"synthetic\" stack trace from this point.\n                // NOTE: If you are a Sentry user, and you are seeing this stack frame, it is NOT indicative\n                //       of a bug with Raven.js. Sentry generates synthetic traces either by configuration,\n                //       or if it catches a thrown object without a \"stack\" property.\n                try {\n                  throw new Error(msg);\n                } catch (ex1) {\n                  ex = ex1;\n                }\n\n                // null exception name so `Error` isn't prefixed to msg\n                ex.name = null;\n                var stack = TraceKit.computeStackTrace(ex);\n\n                // stack[0] is `throw new Error(msg)` call itself, we are interested in the frame that was just before that, stack[1]\n                var initialCall = isArray(stack.stack) && stack.stack[1];\n                var fileurl = (initialCall && initialCall.url) || \"\";\n\n                if (\n                  !!this._globalOptions.ignoreUrls.test &&\n                  this._globalOptions.ignoreUrls.test(fileurl)\n                ) {\n                  return;\n                }\n\n                if (\n                  !!this._globalOptions.whitelistUrls.test &&\n                  !this._globalOptions.whitelistUrls.test(fileurl)\n                ) {\n                  return;\n                }\n\n                if (\n                  this._globalOptions.stacktrace ||\n                  (options && options.stacktrace)\n                ) {\n                  options = objectMerge(\n                    {\n                      // fingerprint on msg, not stack trace (legacy behavior, could be\n                      // revisited)\n                      fingerprint: msg,\n                      // since we know this is a synthetic trace, the top N-most frames\n                      // MUST be from Raven.js, so mark them as in_app later by setting\n                      // trimHeadFrames\n                      trimHeadFrames: (options.trimHeadFrames || 0) + 1,\n                    },\n                    options,\n                  );\n\n                  var frames = this._prepareFrames(stack, options);\n                  data.stacktrace = {\n                    // Sentry expects frames oldest to newest\n                    frames: frames.reverse(),\n                  };\n                }\n\n                // Fire away!\n                this._send(data);\n\n                return this;\n              },\n\n              captureBreadcrumb: function (obj) {\n                var crumb = objectMerge(\n                  {\n                    timestamp: now() / 1000,\n                  },\n                  obj,\n                );\n\n                if (isFunction(this._globalOptions.breadcrumbCallback)) {\n                  var result = this._globalOptions.breadcrumbCallback(crumb);\n\n                  if (isObject(result) && !isEmptyObject(result)) {\n                    crumb = result;\n                  } else if (result === false) {\n                    return this;\n                  }\n                }\n\n                this._breadcrumbs.push(crumb);\n                if (\n                  this._breadcrumbs.length > this._globalOptions.maxBreadcrumbs\n                ) {\n                  this._breadcrumbs.shift();\n                }\n                return this;\n              },\n\n              addPlugin: function (plugin /*arg1, arg2, ... argN*/) {\n                var pluginArgs = [].slice.call(arguments, 1);\n\n                this._plugins.push([plugin, pluginArgs]);\n                if (this._isRavenInstalled) {\n                  this._drainPlugins();\n                }\n\n                return this;\n              },\n\n              /*\n               * Set/clear a user to be sent along with the payload.\n               *\n               * @param {object} user An object representing user data [optional]\n               * @return {Raven}\n               */\n              setUserContext: function (user) {\n                // Intentionally do not merge here since that's an unexpected behavior.\n                this._globalContext.user = user;\n\n                return this;\n              },\n\n              /*\n               * Merge extra attributes to be sent along with the payload.\n               *\n               * @param {object} extra An object representing extra data [optional]\n               * @return {Raven}\n               */\n              setExtraContext: function (extra) {\n                this._mergeContext(\"extra\", extra);\n\n                return this;\n              },\n\n              /*\n               * Merge tags to be sent along with the payload.\n               *\n               * @param {object} tags An object representing tags [optional]\n               * @return {Raven}\n               */\n              setTagsContext: function (tags) {\n                this._mergeContext(\"tags\", tags);\n\n                return this;\n              },\n\n              /*\n               * Clear all of the context.\n               *\n               * @return {Raven}\n               */\n              clearContext: function () {\n                this._globalContext = {};\n\n                return this;\n              },\n\n              /*\n               * Get a copy of the current context. This cannot be mutated.\n               *\n               * @return {object} copy of context\n               */\n              getContext: function () {\n                // lol javascript\n                return JSON.parse(stringify(this._globalContext));\n              },\n\n              /*\n               * Set environment of application\n               *\n               * @param {string} environment Typically something like 'production'.\n               * @return {Raven}\n               */\n              setEnvironment: function (environment) {\n                this._globalOptions.environment = environment;\n\n                return this;\n              },\n\n              /*\n               * Set release version of application\n               *\n               * @param {string} release Typically something like a git SHA to identify version\n               * @return {Raven}\n               */\n              setRelease: function (release) {\n                this._globalOptions.release = release;\n\n                return this;\n              },\n\n              /*\n               * Set the dataCallback option\n               *\n               * @param {function} callback The callback to run which allows the\n               *                            data blob to be mutated before sending\n               * @return {Raven}\n               */\n              setDataCallback: function (callback) {\n                var original = this._globalOptions.dataCallback;\n                this._globalOptions.dataCallback = keepOriginalCallback(\n                  original,\n                  callback,\n                );\n                return this;\n              },\n\n              /*\n               * Set the breadcrumbCallback option\n               *\n               * @param {function} callback The callback to run which allows filtering\n               *                            or mutating breadcrumbs\n               * @return {Raven}\n               */\n              setBreadcrumbCallback: function (callback) {\n                var original = this._globalOptions.breadcrumbCallback;\n                this._globalOptions.breadcrumbCallback = keepOriginalCallback(\n                  original,\n                  callback,\n                );\n                return this;\n              },\n\n              /*\n               * Set the shouldSendCallback option\n               *\n               * @param {function} callback The callback to run which allows\n               *                            introspecting the blob before sending\n               * @return {Raven}\n               */\n              setShouldSendCallback: function (callback) {\n                var original = this._globalOptions.shouldSendCallback;\n                this._globalOptions.shouldSendCallback = keepOriginalCallback(\n                  original,\n                  callback,\n                );\n                return this;\n              },\n\n              /**\n               * Override the default HTTP transport mechanism that transmits data\n               * to the Sentry server.\n               *\n               * @param {function} transport Function invoked instead of the default\n               *                             `makeRequest` handler.\n               *\n               * @return {Raven}\n               */\n              setTransport: function (transport) {\n                this._globalOptions.transport = transport;\n\n                return this;\n              },\n\n              /*\n               * Get the latest raw exception that was captured by Raven.\n               *\n               * @return {error}\n               */\n              lastException: function () {\n                return this._lastCapturedException;\n              },\n\n              /*\n               * Get the last event id\n               *\n               * @return {string}\n               */\n              lastEventId: function () {\n                return this._lastEventId;\n              },\n\n              /*\n               * Determine if Raven is setup and ready to go.\n               *\n               * @return {boolean}\n               */\n              isSetup: function () {\n                if (!this._hasJSON) return false; // needs JSON support\n                if (!this._globalServer) {\n                  if (!this.ravenNotConfiguredError) {\n                    this.ravenNotConfiguredError = true;\n                    this._logDebug(\n                      \"error\",\n                      \"Error: Raven has not been configured.\",\n                    );\n                  }\n                  return false;\n                }\n                return true;\n              },\n\n              afterLoad: function () {\n                // TODO: remove window dependence?\n\n                // Attempt to initialize Raven on load\n                var RavenConfig = _window.RavenConfig;\n                if (RavenConfig) {\n                  this.config(RavenConfig.dsn, RavenConfig.config).install();\n                }\n              },\n\n              showReportDialog: function (options) {\n                if (\n                  !_document // doesn't work without a document (React native)\n                )\n                  return;\n\n                options = options || {};\n\n                var lastEventId = options.eventId || this.lastEventId();\n                if (!lastEventId) {\n                  throw new RavenConfigError(\"Missing eventId\");\n                }\n\n                var dsn = options.dsn || this._dsn;\n                if (!dsn) {\n                  throw new RavenConfigError(\"Missing DSN\");\n                }\n\n                var encode = encodeURIComponent;\n                var qs = \"\";\n                qs += \"?eventId=\" + encode(lastEventId);\n                qs += \"&dsn=\" + encode(dsn);\n\n                var user = options.user || this._globalContext.user;\n                if (user) {\n                  if (user.name) qs += \"&name=\" + encode(user.name);\n                  if (user.email) qs += \"&email=\" + encode(user.email);\n                }\n\n                var globalServer = this._getGlobalServer(this._parseDSN(dsn));\n\n                var script = _document.createElement(\"script\");\n                script.async = true;\n                script.src = globalServer + \"/api/embed/error-page/\" + qs;\n                (_document.head || _document.body).appendChild(script);\n              },\n\n              /**** Private functions ****/\n              _ignoreNextOnError: function () {\n                var self = this;\n                this._ignoreOnError += 1;\n                setTimeout(function () {\n                  // onerror should trigger before setTimeout\n                  self._ignoreOnError -= 1;\n                });\n              },\n\n              _triggerEvent: function (eventType, options) {\n                // NOTE: `event` is a native browser thing, so let's avoid conflicting with it\n                var evt, key;\n\n                if (!this._hasDocument) return;\n\n                options = options || {};\n\n                eventType =\n                  \"raven\" +\n                  eventType.substr(0, 1).toUpperCase() +\n                  eventType.substr(1);\n\n                if (_document.createEvent) {\n                  evt = _document.createEvent(\"HTMLEvents\");\n                  evt.initEvent(eventType, true, true);\n                } else {\n                  evt = _document.createEventObject();\n                  evt.eventType = eventType;\n                }\n\n                for (key in options)\n                  if (hasKey(options, key)) {\n                    evt[key] = options[key];\n                  }\n\n                if (_document.createEvent) {\n                  // IE9 if standards\n                  _document.dispatchEvent(evt);\n                } else {\n                  // IE8 regardless of Quirks or Standards\n                  // IE9 if quirks\n                  try {\n                    _document.fireEvent(\n                      \"on\" + evt.eventType.toLowerCase(),\n                      evt,\n                    );\n                  } catch (e) {\n                    // Do nothing\n                  }\n                }\n              },\n\n              /**\n               * Wraps addEventListener to capture UI breadcrumbs\n               * @param evtName the event name (e.g. \"click\")\n               * @returns {Function}\n               * @private\n               */\n              _breadcrumbEventHandler: function (evtName) {\n                var self = this;\n                return function (evt) {\n                  // reset keypress timeout; e.g. triggering a 'click' after\n                  // a 'keypress' will reset the keypress debounce so that a new\n                  // set of keypresses can be recorded\n                  self._keypressTimeout = null;\n\n                  // It's possible this handler might trigger multiple times for the same\n                  // event (e.g. event propagation through node ancestors). Ignore if we've\n                  // already captured the event.\n                  if (self._lastCapturedEvent === evt) return;\n\n                  self._lastCapturedEvent = evt;\n\n                  // try/catch both:\n                  // - accessing evt.target (see getsentry/raven-js#838, #768)\n                  // - `htmlTreeAsString` because it's complex, and just accessing the DOM incorrectly\n                  //   can throw an exception in some circumstances.\n                  var target;\n                  try {\n                    target = htmlTreeAsString(evt.target);\n                  } catch (e) {\n                    target = \"<unknown>\";\n                  }\n\n                  self.captureBreadcrumb({\n                    category: \"ui.\" + evtName, // e.g. ui.click, ui.input\n                    message: target,\n                  });\n                };\n              },\n\n              /**\n               * Wraps addEventListener to capture keypress UI events\n               * @returns {Function}\n               * @private\n               */\n              _keypressEventHandler: function () {\n                var self = this,\n                  debounceDuration = 1000; // milliseconds\n\n                // TODO: if somehow user switches keypress target before\n                //       debounce timeout is triggered, we will only capture\n                //       a single breadcrumb from the FIRST target (acceptable?)\n                return function (evt) {\n                  var target;\n                  try {\n                    target = evt.target;\n                  } catch (e) {\n                    // just accessing event properties can throw an exception in some rare circumstances\n                    // see: https://github.com/getsentry/raven-js/issues/838\n                    return;\n                  }\n                  var tagName = target && target.tagName;\n\n                  // only consider keypress events on actual input elements\n                  // this will disregard keypresses targeting body (e.g. tabbing\n                  // through elements, hotkeys, etc)\n                  if (\n                    !tagName ||\n                    (tagName !== \"INPUT\" &&\n                      tagName !== \"TEXTAREA\" &&\n                      !target.isContentEditable)\n                  )\n                    return;\n\n                  // record first keypress in a series, but ignore subsequent\n                  // keypresses until debounce clears\n                  var timeout = self._keypressTimeout;\n                  if (!timeout) {\n                    self._breadcrumbEventHandler(\"input\")(evt);\n                  }\n                  clearTimeout(timeout);\n                  self._keypressTimeout = setTimeout(function () {\n                    self._keypressTimeout = null;\n                  }, debounceDuration);\n                };\n              },\n\n              /**\n               * Captures a breadcrumb of type \"navigation\", normalizing input URLs\n               * @param to the originating URL\n               * @param from the target URL\n               * @private\n               */\n              _captureUrlChange: function (from, to) {\n                var parsedLoc = parseUrl(this._location.href);\n                var parsedTo = parseUrl(to);\n                var parsedFrom = parseUrl(from);\n\n                // because onpopstate only tells you the \"new\" (to) value of location.href, and\n                // not the previous (from) value, we need to track the value of the current URL\n                // state ourselves\n                this._lastHref = to;\n\n                // Use only the path component of the URL if the URL matches the current\n                // document (almost all the time when using pushState)\n                if (\n                  parsedLoc.protocol === parsedTo.protocol &&\n                  parsedLoc.host === parsedTo.host\n                )\n                  to = parsedTo.relative;\n                if (\n                  parsedLoc.protocol === parsedFrom.protocol &&\n                  parsedLoc.host === parsedFrom.host\n                )\n                  from = parsedFrom.relative;\n\n                this.captureBreadcrumb({\n                  category: \"navigation\",\n                  data: {\n                    to: to,\n                    from: from,\n                  },\n                });\n              },\n\n              _patchFunctionToString: function () {\n                var self = this;\n                self._originalFunctionToString = Function.prototype.toString;\n                // eslint-disable-next-line no-extend-native\n                Function.prototype.toString = function () {\n                  if (typeof this === \"function\" && this.__raven__) {\n                    return self._originalFunctionToString.apply(\n                      this.__orig__,\n                      arguments,\n                    );\n                  }\n                  return self._originalFunctionToString.apply(this, arguments);\n                };\n              },\n\n              _unpatchFunctionToString: function () {\n                if (this._originalFunctionToString) {\n                  // eslint-disable-next-line no-extend-native\n                  Function.prototype.toString = this._originalFunctionToString;\n                }\n              },\n\n              /**\n               * Wrap timer functions and event targets to catch errors and provide\n               * better metadata.\n               */\n              _instrumentTryCatch: function () {\n                var self = this;\n\n                var wrappedBuiltIns = self._wrappedBuiltIns;\n\n                function wrapTimeFn(orig) {\n                  return function (fn, t) {\n                    // preserve arity\n                    // Make a copy of the arguments to prevent deoptimization\n                    // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments\n                    var args = new Array(arguments.length);\n                    for (var i = 0; i < args.length; ++i) {\n                      args[i] = arguments[i];\n                    }\n                    var originalCallback = args[0];\n                    if (isFunction(originalCallback)) {\n                      args[0] = self.wrap(originalCallback);\n                    }\n\n                    // IE < 9 doesn't support .call/.apply on setInterval/setTimeout, but it\n                    // also supports only two arguments and doesn't care what this is, so we\n                    // can just call the original function directly.\n                    if (orig.apply) {\n                      return orig.apply(this, args);\n                    } else {\n                      return orig(args[0], args[1]);\n                    }\n                  };\n                }\n\n                var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs;\n\n                function wrapEventTarget(global) {\n                  var proto = _window[global] && _window[global].prototype;\n                  if (\n                    proto &&\n                    proto.hasOwnProperty &&\n                    proto.hasOwnProperty(\"addEventListener\")\n                  ) {\n                    fill(\n                      proto,\n                      \"addEventListener\",\n                      function (orig) {\n                        return function (evtName, fn, capture, secure) {\n                          // preserve arity\n                          try {\n                            if (fn && fn.handleEvent) {\n                              fn.handleEvent = self.wrap(fn.handleEvent);\n                            }\n                          } catch (err) {\n                            // can sometimes get 'Permission denied to access property \"handle Event'\n                          }\n\n                          // More breadcrumb DOM capture ... done here and not in `_instrumentBreadcrumbs`\n                          // so that we don't have more than one wrapper function\n                          var before, clickHandler, keypressHandler;\n\n                          if (\n                            autoBreadcrumbs &&\n                            autoBreadcrumbs.dom &&\n                            (global === \"EventTarget\" || global === \"Node\")\n                          ) {\n                            // NOTE: generating multiple handlers per addEventListener invocation, should\n                            //       revisit and verify we can just use one (almost certainly)\n                            clickHandler =\n                              self._breadcrumbEventHandler(\"click\");\n                            keypressHandler = self._keypressEventHandler();\n                            before = function (evt) {\n                              // need to intercept every DOM event in `before` argument, in case that\n                              // same wrapped method is re-used for different events (e.g. mousemove THEN click)\n                              // see #724\n                              if (!evt) return;\n\n                              var eventType;\n                              try {\n                                eventType = evt.type;\n                              } catch (e) {\n                                // just accessing event properties can throw an exception in some rare circumstances\n                                // see: https://github.com/getsentry/raven-js/issues/838\n                                return;\n                              }\n                              if (eventType === \"click\")\n                                return clickHandler(evt);\n                              else if (eventType === \"keypress\")\n                                return keypressHandler(evt);\n                            };\n                          }\n                          return orig.call(\n                            this,\n                            evtName,\n                            self.wrap(fn, undefined, before),\n                            capture,\n                            secure,\n                          );\n                        };\n                      },\n                      wrappedBuiltIns,\n                    );\n                    fill(\n                      proto,\n                      \"removeEventListener\",\n                      function (orig) {\n                        return function (evt, fn, capture, secure) {\n                          try {\n                            fn =\n                              fn &&\n                              (fn.__raven_wrapper__\n                                ? fn.__raven_wrapper__\n                                : fn);\n                          } catch (e) {\n                            // ignore, accessing __raven_wrapper__ will throw in some Selenium environments\n                          }\n                          return orig.call(this, evt, fn, capture, secure);\n                        };\n                      },\n                      wrappedBuiltIns,\n                    );\n                  }\n                }\n\n                fill(_window, \"setTimeout\", wrapTimeFn, wrappedBuiltIns);\n                fill(_window, \"setInterval\", wrapTimeFn, wrappedBuiltIns);\n                if (_requestAnimationFrame) {\n                  fill(\n                    _window,\n                    \"requestAnimationFrame\",\n                    function (orig) {\n                      return function (cb) {\n                        return orig(self.wrap(cb));\n                      };\n                    },\n                    wrappedBuiltIns,\n                  );\n                }\n\n                // event targets borrowed from bugsnag-js:\n                // https://github.com/bugsnag/bugsnag-js/blob/master/src/bugsnag.js#L666\n                var eventTargets = [\n                  \"EventTarget\",\n                  \"Window\",\n                  \"Node\",\n                  \"ApplicationCache\",\n                  \"AudioTrackList\",\n                  \"ChannelMergerNode\",\n                  \"CryptoOperation\",\n                  \"EventSource\",\n                  \"FileReader\",\n                  \"HTMLUnknownElement\",\n                  \"IDBDatabase\",\n                  \"IDBRequest\",\n                  \"IDBTransaction\",\n                  \"KeyOperation\",\n                  \"MediaController\",\n                  \"MessagePort\",\n                  \"ModalWindow\",\n                  \"Notification\",\n                  \"SVGElementInstance\",\n                  \"Screen\",\n                  \"TextTrack\",\n                  \"TextTrackCue\",\n                  \"TextTrackList\",\n                  \"WebSocket\",\n                  \"WebSocketWorker\",\n                  \"Worker\",\n                  \"XMLHttpRequest\",\n                  \"XMLHttpRequestEventTarget\",\n                  \"XMLHttpRequestUpload\",\n                ];\n                for (var i = 0; i < eventTargets.length; i++) {\n                  wrapEventTarget(eventTargets[i]);\n                }\n              },\n\n              /**\n               * Instrument browser built-ins w/ breadcrumb capturing\n               *  - XMLHttpRequests\n               *  - DOM interactions (click/typing)\n               *  - window.location changes\n               *  - console\n               *\n               * Can be disabled or individually configured via the `autoBreadcrumbs` config option\n               */\n              _instrumentBreadcrumbs: function () {\n                var self = this;\n                var autoBreadcrumbs = this._globalOptions.autoBreadcrumbs;\n\n                var wrappedBuiltIns = self._wrappedBuiltIns;\n\n                function wrapProp(prop, xhr) {\n                  if (prop in xhr && isFunction(xhr[prop])) {\n                    fill(xhr, prop, function (orig) {\n                      return self.wrap(orig);\n                    }); // intentionally don't track filled methods on XHR instances\n                  }\n                }\n\n                if (autoBreadcrumbs.xhr && \"XMLHttpRequest\" in _window) {\n                  var xhrproto = XMLHttpRequest.prototype;\n                  fill(\n                    xhrproto,\n                    \"open\",\n                    function (origOpen) {\n                      return function (method, url) {\n                        // preserve arity\n\n                        // if Sentry key appears in URL, don't capture\n                        if (\n                          isString(url) &&\n                          url.indexOf(self._globalKey) === -1\n                        ) {\n                          this.__raven_xhr = {\n                            method: method,\n                            url: url,\n                            status_code: null,\n                          };\n                        }\n\n                        return origOpen.apply(this, arguments);\n                      };\n                    },\n                    wrappedBuiltIns,\n                  );\n\n                  fill(\n                    xhrproto,\n                    \"send\",\n                    function (origSend) {\n                      return function (data) {\n                        // preserve arity\n                        var xhr = this;\n\n                        function onreadystatechangeHandler() {\n                          if (xhr.__raven_xhr && xhr.readyState === 4) {\n                            try {\n                              // touching statusCode in some platforms throws\n                              // an exception\n                              xhr.__raven_xhr.status_code = xhr.status;\n                            } catch (e) {\n                              /* do nothing */\n                            }\n\n                            self.captureBreadcrumb({\n                              type: \"http\",\n                              category: \"xhr\",\n                              data: xhr.__raven_xhr,\n                            });\n                          }\n                        }\n\n                        var props = [\"onload\", \"onerror\", \"onprogress\"];\n                        for (var j = 0; j < props.length; j++) {\n                          wrapProp(props[j], xhr);\n                        }\n\n                        if (\n                          \"onreadystatechange\" in xhr &&\n                          isFunction(xhr.onreadystatechange)\n                        ) {\n                          fill(\n                            xhr,\n                            \"onreadystatechange\",\n                            function (orig) {\n                              return self.wrap(\n                                orig,\n                                undefined,\n                                onreadystatechangeHandler,\n                              );\n                            } /* intentionally don't track this instrumentation */,\n                          );\n                        } else {\n                          // if onreadystatechange wasn't actually set by the page on this xhr, we\n                          // are free to set our own and capture the breadcrumb\n                          xhr.onreadystatechange = onreadystatechangeHandler;\n                        }\n\n                        return origSend.apply(this, arguments);\n                      };\n                    },\n                    wrappedBuiltIns,\n                  );\n                }\n\n                if (autoBreadcrumbs.xhr && \"fetch\" in _window) {\n                  fill(\n                    _window,\n                    \"fetch\",\n                    function (origFetch) {\n                      return function (fn, t) {\n                        // preserve arity\n                        // Make a copy of the arguments to prevent deoptimization\n                        // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments\n                        var args = new Array(arguments.length);\n                        for (var i = 0; i < args.length; ++i) {\n                          args[i] = arguments[i];\n                        }\n\n                        var fetchInput = args[0];\n                        var method = \"GET\";\n                        var url;\n\n                        if (typeof fetchInput === \"string\") {\n                          url = fetchInput;\n                        } else if (\n                          \"Request\" in _window &&\n                          fetchInput instanceof _window.Request\n                        ) {\n                          url = fetchInput.url;\n                          if (fetchInput.method) {\n                            method = fetchInput.method;\n                          }\n                        } else {\n                          url = \"\" + fetchInput;\n                        }\n\n                        if (args[1] && args[1].method) {\n                          method = args[1].method;\n                        }\n\n                        var fetchData = {\n                          method: method,\n                          url: url,\n                          status_code: null,\n                        };\n\n                        self.captureBreadcrumb({\n                          type: \"http\",\n                          category: \"fetch\",\n                          data: fetchData,\n                        });\n\n                        return origFetch\n                          .apply(this, args)\n                          .then(function (response) {\n                            fetchData.status_code = response.status;\n\n                            return response;\n                          });\n                      };\n                    },\n                    wrappedBuiltIns,\n                  );\n                }\n\n                // Capture breadcrumbs from any click that is unhandled / bubbled up all the way\n                // to the document. Do this before we instrument addEventListener.\n                if (autoBreadcrumbs.dom && this._hasDocument) {\n                  if (_document.addEventListener) {\n                    _document.addEventListener(\n                      \"click\",\n                      self._breadcrumbEventHandler(\"click\"),\n                      false,\n                    );\n                    _document.addEventListener(\n                      \"keypress\",\n                      self._keypressEventHandler(),\n                      false,\n                    );\n                  } else {\n                    // IE8 Compatibility\n                    _document.attachEvent(\n                      \"onclick\",\n                      self._breadcrumbEventHandler(\"click\"),\n                    );\n                    _document.attachEvent(\n                      \"onkeypress\",\n                      self._keypressEventHandler(),\n                    );\n                  }\n                }\n\n                // record navigation (URL) changes\n                // NOTE: in Chrome App environment, touching history.pushState, *even inside\n                //       a try/catch block*, will cause Chrome to output an error to console.error\n                // borrowed from: https://github.com/angular/angular.js/pull/13945/files\n                var chrome = _window.chrome;\n                var isChromePackagedApp =\n                  chrome && chrome.app && chrome.app.runtime;\n                var hasPushAndReplaceState =\n                  !isChromePackagedApp &&\n                  _window.history &&\n                  history.pushState &&\n                  history.replaceState;\n                if (autoBreadcrumbs.location && hasPushAndReplaceState) {\n                  // TODO: remove onpopstate handler on uninstall()\n                  var oldOnPopState = _window.onpopstate;\n                  _window.onpopstate = function () {\n                    var currentHref = self._location.href;\n                    self._captureUrlChange(self._lastHref, currentHref);\n\n                    if (oldOnPopState) {\n                      return oldOnPopState.apply(this, arguments);\n                    }\n                  };\n\n                  var historyReplacementFunction = function (origHistFunction) {\n                    // note history.pushState.length is 0; intentionally not declaring\n                    // params to preserve 0 arity\n                    return function (/* state, title, url */) {\n                      var url = arguments.length > 2 ? arguments[2] : undefined;\n\n                      // url argument is optional\n                      if (url) {\n                        // coerce to string (this is what pushState does)\n                        self._captureUrlChange(self._lastHref, url + \"\");\n                      }\n\n                      return origHistFunction.apply(this, arguments);\n                    };\n                  };\n\n                  fill(\n                    history,\n                    \"pushState\",\n                    historyReplacementFunction,\n                    wrappedBuiltIns,\n                  );\n                  fill(\n                    history,\n                    \"replaceState\",\n                    historyReplacementFunction,\n                    wrappedBuiltIns,\n                  );\n                }\n\n                if (\n                  autoBreadcrumbs.console &&\n                  \"console\" in _window &&\n                  console.log\n                ) {\n                  // console\n                  var consoleMethodCallback = function (msg, data) {\n                    self.captureBreadcrumb({\n                      message: msg,\n                      level: data.level,\n                      category: \"console\",\n                    });\n                  };\n\n                  each(\n                    [\"debug\", \"info\", \"warn\", \"error\", \"log\"],\n                    function (_, level) {\n                      wrapConsoleMethod(console, level, consoleMethodCallback);\n                    },\n                  );\n                }\n              },\n\n              _restoreBuiltIns: function () {\n                // restore any wrapped builtins\n                var builtin;\n                while (this._wrappedBuiltIns.length) {\n                  builtin = this._wrappedBuiltIns.shift();\n\n                  var obj = builtin[0],\n                    name = builtin[1],\n                    orig = builtin[2];\n\n                  obj[name] = orig;\n                }\n              },\n\n              _drainPlugins: function () {\n                var self = this;\n\n                // FIX ME TODO\n                each(this._plugins, function (_, plugin) {\n                  var installer = plugin[0];\n                  var args = plugin[1];\n                  installer.apply(self, [self].concat(args));\n                });\n              },\n\n              _parseDSN: function (str) {\n                var m = dsnPattern.exec(str),\n                  dsn = {},\n                  i = 7;\n\n                try {\n                  while (i--) dsn[dsnKeys[i]] = m[i] || \"\";\n                } catch (e) {\n                  throw new RavenConfigError(\"Invalid DSN: \" + str);\n                }\n\n                if (dsn.pass && !this._globalOptions.allowSecretKey) {\n                  throw new RavenConfigError(\n                    \"Do not specify your secret key in the DSN. See: http://bit.ly/raven-secret-key\",\n                  );\n                }\n\n                return dsn;\n              },\n\n              _getGlobalServer: function (uri) {\n                // assemble the endpoint from the uri pieces\n                var globalServer =\n                  \"//\" + uri.host + (uri.port ? \":\" + uri.port : \"\");\n\n                if (uri.protocol) {\n                  globalServer = uri.protocol + \":\" + globalServer;\n                }\n                return globalServer;\n              },\n\n              _handleOnErrorStackInfo: function () {\n                // if we are intentionally ignoring errors via onerror, bail out\n                if (!this._ignoreOnError) {\n                  this._handleStackInfo.apply(this, arguments);\n                }\n              },\n\n              _handleStackInfo: function (stackInfo, options) {\n                var frames = this._prepareFrames(stackInfo, options);\n\n                this._triggerEvent(\"handle\", {\n                  stackInfo: stackInfo,\n                  options: options,\n                });\n\n                this._processException(\n                  stackInfo.name,\n                  stackInfo.message,\n                  stackInfo.url,\n                  stackInfo.lineno,\n                  frames,\n                  options,\n                );\n              },\n\n              _prepareFrames: function (stackInfo, options) {\n                var self = this;\n                var frames = [];\n                if (stackInfo.stack && stackInfo.stack.length) {\n                  each(stackInfo.stack, function (i, stack) {\n                    var frame = self._normalizeFrame(stack, stackInfo.url);\n                    if (frame) {\n                      frames.push(frame);\n                    }\n                  });\n\n                  // e.g. frames captured via captureMessage throw\n                  if (options && options.trimHeadFrames) {\n                    for (\n                      var j = 0;\n                      j < options.trimHeadFrames && j < frames.length;\n                      j++\n                    ) {\n                      frames[j].in_app = false;\n                    }\n                  }\n                }\n                frames = frames.slice(0, this._globalOptions.stackTraceLimit);\n                return frames;\n              },\n\n              _normalizeFrame: function (frame, stackInfoUrl) {\n                // normalize the frames data\n                var normalized = {\n                  filename: frame.url,\n                  lineno: frame.line,\n                  colno: frame.column,\n                  function: frame.func || \"?\",\n                };\n\n                // Case when we don't have any information about the error\n                // E.g. throwing a string or raw object, instead of an `Error` in Firefox\n                // Generating synthetic error doesn't add any value here\n                //\n                // We should probably somehow let a user know that they should fix their code\n                if (!frame.url) {\n                  normalized.filename = stackInfoUrl; // fallback to whole stacks url from onerror handler\n                }\n\n                normalized.in_app = !(\n                  // determine if an exception came from outside of our app\n                  // first we check the global includePaths list.\n                  (\n                    (!!this._globalOptions.includePaths.test &&\n                      !this._globalOptions.includePaths.test(\n                        normalized.filename,\n                      )) ||\n                    // Now we check for fun, if the function name is Raven or TraceKit\n                    /(Raven|TraceKit)\\./.test(normalized[\"function\"]) ||\n                    // finally, we do a last ditch effort and check for raven.min.js\n                    /raven\\.(min\\.)?js$/.test(normalized.filename)\n                  )\n                );\n\n                return normalized;\n              },\n\n              _processException: function (\n                type,\n                message,\n                fileurl,\n                lineno,\n                frames,\n                options,\n              ) {\n                var prefixedMessage =\n                  (type ? type + \": \" : \"\") + (message || \"\");\n                if (\n                  !!this._globalOptions.ignoreErrors.test &&\n                  (this._globalOptions.ignoreErrors.test(message) ||\n                    this._globalOptions.ignoreErrors.test(prefixedMessage))\n                ) {\n                  return;\n                }\n\n                var stacktrace;\n\n                if (frames && frames.length) {\n                  fileurl = frames[0].filename || fileurl;\n                  // Sentry expects frames oldest to newest\n                  // and JS sends them as newest to oldest\n                  frames.reverse();\n                  stacktrace = { frames: frames };\n                } else if (fileurl) {\n                  stacktrace = {\n                    frames: [\n                      {\n                        filename: fileurl,\n                        lineno: lineno,\n                        in_app: true,\n                      },\n                    ],\n                  };\n                }\n\n                if (\n                  !!this._globalOptions.ignoreUrls.test &&\n                  this._globalOptions.ignoreUrls.test(fileurl)\n                ) {\n                  return;\n                }\n\n                if (\n                  !!this._globalOptions.whitelistUrls.test &&\n                  !this._globalOptions.whitelistUrls.test(fileurl)\n                ) {\n                  return;\n                }\n\n                var data = objectMerge(\n                  {\n                    // sentry.interfaces.Exception\n                    exception: {\n                      values: [\n                        {\n                          type: type,\n                          value: message,\n                          stacktrace: stacktrace,\n                        },\n                      ],\n                    },\n                    culprit: fileurl,\n                  },\n                  options,\n                );\n\n                // Fire away!\n                this._send(data);\n              },\n\n              _trimPacket: function (data) {\n                // For now, we only want to truncate the two different messages\n                // but this could/should be expanded to just trim everything\n                var max = this._globalOptions.maxMessageLength;\n                if (data.message) {\n                  data.message = truncate(data.message, max);\n                }\n                if (data.exception) {\n                  var exception = data.exception.values[0];\n                  exception.value = truncate(exception.value, max);\n                }\n\n                var request = data.request;\n                if (request) {\n                  if (request.url) {\n                    request.url = truncate(\n                      request.url,\n                      this._globalOptions.maxUrlLength,\n                    );\n                  }\n                  if (request.Referer) {\n                    request.Referer = truncate(\n                      request.Referer,\n                      this._globalOptions.maxUrlLength,\n                    );\n                  }\n                }\n\n                if (data.breadcrumbs && data.breadcrumbs.values)\n                  this._trimBreadcrumbs(data.breadcrumbs);\n\n                return data;\n              },\n\n              /**\n               * Truncate breadcrumb values (right now just URLs)\n               */\n              _trimBreadcrumbs: function (breadcrumbs) {\n                // known breadcrumb properties with urls\n                // TODO: also consider arbitrary prop values that start with (https?)?://\n                var urlProps = [\"to\", \"from\", \"url\"],\n                  urlProp,\n                  crumb,\n                  data;\n\n                for (var i = 0; i < breadcrumbs.values.length; ++i) {\n                  crumb = breadcrumbs.values[i];\n                  if (\n                    !crumb.hasOwnProperty(\"data\") ||\n                    !isObject(crumb.data) ||\n                    objectFrozen(crumb.data)\n                  )\n                    continue;\n\n                  data = objectMerge({}, crumb.data);\n                  for (var j = 0; j < urlProps.length; ++j) {\n                    urlProp = urlProps[j];\n                    if (data.hasOwnProperty(urlProp) && data[urlProp]) {\n                      data[urlProp] = truncate(\n                        data[urlProp],\n                        this._globalOptions.maxUrlLength,\n                      );\n                    }\n                  }\n                  breadcrumbs.values[i].data = data;\n                }\n              },\n\n              _getHttpData: function () {\n                if (!this._hasNavigator && !this._hasDocument) return;\n                var httpData = {};\n\n                if (this._hasNavigator && _navigator.userAgent) {\n                  httpData.headers = {\n                    \"User-Agent\": navigator.userAgent,\n                  };\n                }\n\n                if (this._hasDocument) {\n                  if (_document.location && _document.location.href) {\n                    httpData.url = _document.location.href;\n                  }\n                  if (_document.referrer) {\n                    if (!httpData.headers) httpData.headers = {};\n                    httpData.headers.Referer = _document.referrer;\n                  }\n                }\n\n                return httpData;\n              },\n\n              _resetBackoff: function () {\n                this._backoffDuration = 0;\n                this._backoffStart = null;\n              },\n\n              _shouldBackoff: function () {\n                return (\n                  this._backoffDuration &&\n                  now() - this._backoffStart < this._backoffDuration\n                );\n              },\n\n              /**\n               * Returns true if the in-process data payload matches the signature\n               * of the previously-sent data\n               *\n               * NOTE: This has to be done at this level because TraceKit can generate\n               *       data from window.onerror WITHOUT an exception object (IE8, IE9,\n               *       other old browsers). This can take the form of an \"exception\"\n               *       data object with a single frame (derived from the onerror args).\n               */\n              _isRepeatData: function (current) {\n                var last = this._lastData;\n\n                if (\n                  !last ||\n                  current.message !== last.message || // defined for captureMessage\n                  current.culprit !== last.culprit // defined for captureException/onerror\n                )\n                  return false;\n\n                // Stacktrace interface (i.e. from captureMessage)\n                if (current.stacktrace || last.stacktrace) {\n                  return isSameStacktrace(current.stacktrace, last.stacktrace);\n                } else if (current.exception || last.exception) {\n                  // Exception interface (i.e. from captureException/onerror)\n                  return isSameException(current.exception, last.exception);\n                }\n\n                return true;\n              },\n\n              _setBackoffState: function (request) {\n                // If we are already in a backoff state, don't change anything\n                if (this._shouldBackoff()) {\n                  return;\n                }\n\n                var status = request.status;\n\n                // 400 - project_id doesn't exist or some other fatal\n                // 401 - invalid/revoked dsn\n                // 429 - too many requests\n                if (!(status === 400 || status === 401 || status === 429))\n                  return;\n\n                var retry;\n                try {\n                  // If Retry-After is not in Access-Control-Expose-Headers, most\n                  // browsers will throw an exception trying to access it\n                  retry = request.getResponseHeader(\"Retry-After\");\n                  retry = parseInt(retry, 10) * 1000; // Retry-After is returned in seconds\n                } catch (e) {\n                  /* eslint no-empty:0 */\n                }\n\n                this._backoffDuration = retry\n                  ? // If Sentry server returned a Retry-After value, use it\n                    retry\n                  : // Otherwise, double the last backoff duration (starts at 1 sec)\n                    this._backoffDuration * 2 || 1000;\n\n                this._backoffStart = now();\n              },\n\n              _send: function (data) {\n                var globalOptions = this._globalOptions;\n\n                var baseData = {\n                    project: this._globalProject,\n                    logger: globalOptions.logger,\n                    platform: \"javascript\",\n                  },\n                  httpData = this._getHttpData();\n\n                if (httpData) {\n                  baseData.request = httpData;\n                }\n\n                // HACK: delete `trimHeadFrames` to prevent from appearing in outbound payload\n                if (data.trimHeadFrames) delete data.trimHeadFrames;\n\n                data = objectMerge(baseData, data);\n\n                // Merge in the tags and extra separately since objectMerge doesn't handle a deep merge\n                data.tags = objectMerge(\n                  objectMerge({}, this._globalContext.tags),\n                  data.tags,\n                );\n                data.extra = objectMerge(\n                  objectMerge({}, this._globalContext.extra),\n                  data.extra,\n                );\n\n                // Send along our own collected metadata with extra\n                data.extra[\"session:duration\"] = now() - this._startTime;\n\n                if (this._breadcrumbs && this._breadcrumbs.length > 0) {\n                  // intentionally make shallow copy so that additions\n                  // to breadcrumbs aren't accidentally sent in this request\n                  data.breadcrumbs = {\n                    values: [].slice.call(this._breadcrumbs, 0),\n                  };\n                }\n\n                // If there are no tags/extra, strip the key from the payload alltogther.\n                if (isEmptyObject(data.tags)) delete data.tags;\n\n                if (this._globalContext.user) {\n                  // sentry.interfaces.User\n                  data.user = this._globalContext.user;\n                }\n\n                // Include the environment if it's defined in globalOptions\n                if (globalOptions.environment)\n                  data.environment = globalOptions.environment;\n\n                // Include the release if it's defined in globalOptions\n                if (globalOptions.release) data.release = globalOptions.release;\n\n                // Include server_name if it's defined in globalOptions\n                if (globalOptions.serverName)\n                  data.server_name = globalOptions.serverName;\n\n                if (isFunction(globalOptions.dataCallback)) {\n                  data = globalOptions.dataCallback(data) || data;\n                }\n\n                // Why??????????\n                if (!data || isEmptyObject(data)) {\n                  return;\n                }\n\n                // Check if the request should be filtered or not\n                if (\n                  isFunction(globalOptions.shouldSendCallback) &&\n                  !globalOptions.shouldSendCallback(data)\n                ) {\n                  return;\n                }\n\n                // Backoff state: Sentry server previously responded w/ an error (e.g. 429 - too many requests),\n                // so drop requests until \"cool-off\" period has elapsed.\n                if (this._shouldBackoff()) {\n                  this._logDebug(\n                    \"warn\",\n                    \"Raven dropped error due to backoff: \",\n                    data,\n                  );\n                  return;\n                }\n\n                if (typeof globalOptions.sampleRate === \"number\") {\n                  if (Math.random() < globalOptions.sampleRate) {\n                    this._sendProcessedPayload(data);\n                  }\n                } else {\n                  this._sendProcessedPayload(data);\n                }\n              },\n\n              _getUuid: function () {\n                return uuid4();\n              },\n\n              _sendProcessedPayload: function (data, callback) {\n                var self = this;\n                var globalOptions = this._globalOptions;\n\n                if (!this.isSetup()) return;\n\n                // Try and clean up the packet before sending by truncating long values\n                data = this._trimPacket(data);\n\n                // ideally duplicate error testing should occur *before* dataCallback/shouldSendCallback,\n                // but this would require copying an un-truncated copy of the data packet, which can be\n                // arbitrarily deep (extra_data) -- could be worthwhile? will revisit\n                if (\n                  !this._globalOptions.allowDuplicates &&\n                  this._isRepeatData(data)\n                ) {\n                  this._logDebug(\"warn\", \"Raven dropped repeat event: \", data);\n                  return;\n                }\n\n                // Send along an event_id if not explicitly passed.\n                // This event_id can be used to reference the error within Sentry itself.\n                // Set lastEventId after we know the error should actually be sent\n                this._lastEventId =\n                  data.event_id || (data.event_id = this._getUuid());\n\n                // Store outbound payload after trim\n                this._lastData = data;\n\n                this._logDebug(\"debug\", \"Raven about to send:\", data);\n\n                var auth = {\n                  sentry_version: \"7\",\n                  sentry_client: \"raven-js/\" + this.VERSION,\n                  sentry_key: this._globalKey,\n                };\n\n                if (this._globalSecret) {\n                  auth.sentry_secret = this._globalSecret;\n                }\n\n                var exception = data.exception && data.exception.values[0];\n\n                // only capture 'sentry' breadcrumb is autoBreadcrumbs is truthy\n                if (\n                  this._globalOptions.autoBreadcrumbs &&\n                  this._globalOptions.autoBreadcrumbs.sentry\n                ) {\n                  this.captureBreadcrumb({\n                    category: \"sentry\",\n                    message: exception\n                      ? (exception.type ? exception.type + \": \" : \"\") +\n                        exception.value\n                      : data.message,\n                    event_id: data.event_id,\n                    level: data.level || \"error\", // presume error unless specified\n                  });\n                }\n\n                var url = this._globalEndpoint;\n                (globalOptions.transport || this._makeRequest).call(this, {\n                  url: url,\n                  auth: auth,\n                  data: data,\n                  options: globalOptions,\n                  onSuccess: function success() {\n                    self._resetBackoff();\n\n                    self._triggerEvent(\"success\", {\n                      data: data,\n                      src: url,\n                    });\n                    callback && callback();\n                  },\n                  onError: function failure(error) {\n                    self._logDebug(\n                      \"error\",\n                      \"Raven transport failed to send: \",\n                      error,\n                    );\n\n                    if (error.request) {\n                      self._setBackoffState(error.request);\n                    }\n\n                    self._triggerEvent(\"failure\", {\n                      data: data,\n                      src: url,\n                    });\n                    error =\n                      error ||\n                      new Error(\n                        \"Raven send failed (no additional details provided)\",\n                      );\n                    callback && callback(error);\n                  },\n                });\n              },\n\n              _makeRequest: function (opts) {\n                var request =\n                  _window.XMLHttpRequest && new _window.XMLHttpRequest();\n                if (!request) return;\n\n                // if browser doesn't support CORS (e.g. IE7), we are out of luck\n                var hasCORS =\n                  \"withCredentials\" in request ||\n                  typeof XDomainRequest !== \"undefined\";\n\n                if (!hasCORS) return;\n\n                var url = opts.url;\n\n                if (\"withCredentials\" in request) {\n                  request.onreadystatechange = function () {\n                    if (request.readyState !== 4) {\n                      return;\n                    } else if (request.status === 200) {\n                      opts.onSuccess && opts.onSuccess();\n                    } else if (opts.onError) {\n                      var err = new Error(\n                        \"Sentry error code: \" + request.status,\n                      );\n                      err.request = request;\n                      opts.onError(err);\n                    }\n                  };\n                } else {\n                  request = new XDomainRequest();\n                  // xdomainrequest cannot go http -> https (or vice versa),\n                  // so always use protocol relative\n                  url = url.replace(/^https?:/, \"\");\n\n                  // onreadystatechange not supported by XDomainRequest\n                  if (opts.onSuccess) {\n                    request.onload = opts.onSuccess;\n                  }\n                  if (opts.onError) {\n                    request.onerror = function () {\n                      var err = new Error(\"Sentry error code: XDomainRequest\");\n                      err.request = request;\n                      opts.onError(err);\n                    };\n                  }\n                }\n\n                // NOTE: auth is intentionally sent as part of query string (NOT as custom\n                //       HTTP header) so as to avoid preflight CORS requests\n                request.open(\"POST\", url + \"?\" + urlencode(opts.auth));\n                request.send(stringify(opts.data));\n              },\n\n              _logDebug: function (level) {\n                if (this._originalConsoleMethods[level] && this.debug) {\n                  // In IE<10 console methods do not have their own 'apply' method\n                  Function.prototype.apply.call(\n                    this._originalConsoleMethods[level],\n                    this._originalConsole,\n                    [].slice.call(arguments, 1),\n                  );\n                }\n              },\n\n              _mergeContext: function (key, context) {\n                if (isUndefined(context)) {\n                  delete this._globalContext[key];\n                } else {\n                  this._globalContext[key] = objectMerge(\n                    this._globalContext[key] || {},\n                    context,\n                  );\n                }\n              },\n            };\n\n            // Deprecations\n            Raven.prototype.setUser = Raven.prototype.setUserContext;\n            Raven.prototype.setReleaseContext = Raven.prototype.setRelease;\n\n            module.exports = Raven;\n          }).call(\n            this,\n            typeof global !== \"undefined\"\n              ? global\n              : typeof self !== \"undefined\"\n                ? self\n                : typeof window !== \"undefined\"\n                  ? window\n                  : {},\n          );\n        },\n        { 1: 1, 2: 2, 5: 5, 6: 6, 7: 7 },\n      ],\n      4: [\n        function (_dereq_, module, exports) {\n          (function (global) {\n            /**\n             * Enforces a single instance of the Raven client, and the\n             * main entry point for Raven. If you are a consumer of the\n             * Raven library, you SHOULD load this file (vs raven.js).\n             **/\n\n            var RavenConstructor = _dereq_(3);\n\n            // This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785)\n            var _window =\n              typeof window !== \"undefined\"\n                ? window\n                : typeof global !== \"undefined\"\n                  ? global\n                  : typeof self !== \"undefined\"\n                    ? self\n                    : {};\n            var _Raven = _window.Raven;\n\n            var Raven = new RavenConstructor();\n\n            /*\n             * Allow multiple versions of Raven to be installed.\n             * Strip Raven from the global context and returns the instance.\n             *\n             * @return {Raven}\n             */\n            Raven.noConflict = function () {\n              _window.Raven = _Raven;\n              return Raven;\n            };\n\n            Raven.afterLoad();\n\n            module.exports = Raven;\n          }).call(\n            this,\n            typeof global !== \"undefined\"\n              ? global\n              : typeof self !== \"undefined\"\n                ? self\n                : typeof window !== \"undefined\"\n                  ? window\n                  : {},\n          );\n        },\n        { 3: 3 },\n      ],\n      5: [\n        function (_dereq_, module, exports) {\n          (function (global) {\n            var _window =\n              typeof window !== \"undefined\"\n                ? window\n                : typeof global !== \"undefined\"\n                  ? global\n                  : typeof self !== \"undefined\"\n                    ? self\n                    : {};\n\n            function isObject(what) {\n              return typeof what === \"object\" && what !== null;\n            }\n\n            // Yanked from https://git.io/vS8DV re-used under CC0\n            // with some tiny modifications\n            function isError(value) {\n              switch ({}.toString.call(value)) {\n                case \"[object Error]\":\n                  return true;\n                case \"[object Exception]\":\n                  return true;\n                case \"[object DOMException]\":\n                  return true;\n                default:\n                  return value instanceof Error;\n              }\n            }\n\n            function isErrorEvent(value) {\n              return (\n                supportsErrorEvent() &&\n                {}.toString.call(value) === \"[object ErrorEvent]\"\n              );\n            }\n\n            function isUndefined(what) {\n              return what === void 0;\n            }\n\n            function isFunction(what) {\n              return typeof what === \"function\";\n            }\n\n            function isString(what) {\n              return Object.prototype.toString.call(what) === \"[object String]\";\n            }\n\n            function isArray(what) {\n              return Object.prototype.toString.call(what) === \"[object Array]\";\n            }\n\n            function isEmptyObject(what) {\n              for (var _ in what) {\n                if (what.hasOwnProperty(_)) {\n                  return false;\n                }\n              }\n              return true;\n            }\n\n            function supportsErrorEvent() {\n              try {\n                new ErrorEvent(\"\"); // eslint-disable-line no-new\n                return true;\n              } catch (e) {\n                return false;\n              }\n            }\n\n            function wrappedCallback(callback) {\n              function dataCallback(data, original) {\n                var normalizedData = callback(data) || data;\n                if (original) {\n                  return original(normalizedData) || normalizedData;\n                }\n                return normalizedData;\n              }\n\n              return dataCallback;\n            }\n\n            function each(obj, callback) {\n              var i, j;\n\n              if (isUndefined(obj.length)) {\n                for (i in obj) {\n                  if (hasKey(obj, i)) {\n                    callback.call(null, i, obj[i]);\n                  }\n                }\n              } else {\n                j = obj.length;\n                if (j) {\n                  for (i = 0; i < j; i++) {\n                    callback.call(null, i, obj[i]);\n                  }\n                }\n              }\n            }\n\n            function objectMerge(obj1, obj2) {\n              if (!obj2) {\n                return obj1;\n              }\n              each(obj2, function (key, value) {\n                obj1[key] = value;\n              });\n              return obj1;\n            }\n\n            /**\n             * This function is only used for react-native.\n             * react-native freezes object that have already been sent over the\n             * js bridge. We need this function in order to check if the object is frozen.\n             * So it's ok that objectFrozen returns false if Object.isFrozen is not\n             * supported because it's not relevant for other \"platforms\". See related issue:\n             * https://github.com/getsentry/react-native-sentry/issues/57\n             */\n            function objectFrozen(obj) {\n              if (!Object.isFrozen) {\n                return false;\n              }\n              return Object.isFrozen(obj);\n            }\n\n            function truncate(str, max) {\n              return !max || str.length <= max\n                ? str\n                : str.substr(0, max) + \"\\u2026\";\n            }\n\n            /**\n             * hasKey, a better form of hasOwnProperty\n             * Example: hasKey(MainHostObject, property) === true/false\n             *\n             * @param {Object} host object to check property\n             * @param {string} key to check\n             */\n            function hasKey(object, key) {\n              return Object.prototype.hasOwnProperty.call(object, key);\n            }\n\n            function joinRegExp(patterns) {\n              // Combine an array of regular expressions and strings into one large regexp\n              // Be mad.\n              var sources = [],\n                i = 0,\n                len = patterns.length,\n                pattern;\n\n              for (; i < len; i++) {\n                pattern = patterns[i];\n                if (isString(pattern)) {\n                  // If it's a string, we need to escape it\n                  // Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions\n                  sources.push(\n                    pattern.replace(/([.*+?^=!:${}()|\\[\\]\\/\\\\])/g, \"\\\\$1\"),\n                  );\n                } else if (pattern && pattern.source) {\n                  // If it's a regexp already, we want to extract the source\n                  sources.push(pattern.source);\n                }\n                // Intentionally skip other cases\n              }\n              return new RegExp(sources.join(\"|\"), \"i\");\n            }\n\n            function urlencode(o) {\n              var pairs = [];\n              each(o, function (key, value) {\n                pairs.push(\n                  encodeURIComponent(key) + \"=\" + encodeURIComponent(value),\n                );\n              });\n              return pairs.join(\"&\");\n            }\n\n            // borrowed from https://datatracker.ietf.org/doc/html/rfc3986#appendix-B\n            // intentionally using regex and not <a/> href parsing trick because React Native and other\n            // environments where DOM might not be available\n            function parseUrl(url) {\n              var match = url.match(\n                /^(([^:\\/?#]+):)?(\\/\\/([^\\/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?$/,\n              );\n              if (!match) return {};\n\n              // coerce to undefined values to empty string so we don't get 'undefined'\n              var query = match[6] || \"\";\n              var fragment = match[8] || \"\";\n              return {\n                protocol: match[2],\n                host: match[4],\n                path: match[5],\n                relative: match[5] + query + fragment, // everything minus origin\n              };\n            }\n            function uuid4() {\n              var crypto = _window.crypto || _window.msCrypto;\n\n              if (!isUndefined(crypto) && crypto.getRandomValues) {\n                // Use window.crypto API if available\n                // eslint-disable-next-line no-undef\n                var arr = new Uint16Array(8);\n                crypto.getRandomValues(arr);\n\n                // set 4 in byte 7\n                arr[3] = (arr[3] & 0xfff) | 0x4000;\n                // set 2 most significant bits of byte 9 to '10'\n                arr[4] = (arr[4] & 0x3fff) | 0x8000;\n\n                var pad = function (num) {\n                  var v = num.toString(16);\n                  while (v.length < 4) {\n                    v = \"0\" + v;\n                  }\n                  return v;\n                };\n\n                return (\n                  pad(arr[0]) +\n                  pad(arr[1]) +\n                  pad(arr[2]) +\n                  pad(arr[3]) +\n                  pad(arr[4]) +\n                  pad(arr[5]) +\n                  pad(arr[6]) +\n                  pad(arr[7])\n                );\n              } else {\n                // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523\n                return \"xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx\".replace(\n                  /[xy]/g,\n                  function (c) {\n                    var r = (Math.random() * 16) | 0,\n                      v = c === \"x\" ? r : (r & 0x3) | 0x8;\n                    return v.toString(16);\n                  },\n                );\n              }\n            }\n\n            /**\n             * Given a child DOM element, returns a query-selector statement describing that\n             * and its ancestors\n             * e.g. [HTMLElement] => body > div > input#foo.btn[name=baz]\n             * @param elem\n             * @returns {string}\n             */\n            function htmlTreeAsString(elem) {\n              /* eslint no-extra-parens:0*/\n              var MAX_TRAVERSE_HEIGHT = 5,\n                MAX_OUTPUT_LEN = 80,\n                out = [],\n                height = 0,\n                len = 0,\n                separator = \" > \",\n                sepLength = separator.length,\n                nextStr;\n\n              while (elem && height++ < MAX_TRAVERSE_HEIGHT) {\n                nextStr = htmlElementAsString(elem);\n                // bail out if\n                // - nextStr is the 'html' element\n                // - the length of the string that would be created exceeds MAX_OUTPUT_LEN\n                //   (ignore this limit if we are on the first iteration)\n                if (\n                  nextStr === \"html\" ||\n                  (height > 1 &&\n                    len + out.length * sepLength + nextStr.length >=\n                      MAX_OUTPUT_LEN)\n                ) {\n                  break;\n                }\n\n                out.push(nextStr);\n\n                len += nextStr.length;\n                elem = elem.parentNode;\n              }\n\n              return out.reverse().join(separator);\n            }\n\n            /**\n             * Returns a simple, query-selector representation of a DOM element\n             * e.g. [HTMLElement] => input#foo.btn[name=baz]\n             * @param HTMLElement\n             * @returns {string}\n             */\n            function htmlElementAsString(elem) {\n              var out = [],\n                className,\n                classes,\n                key,\n                attr,\n                i;\n\n              if (!elem || !elem.tagName) {\n                return \"\";\n              }\n\n              out.push(elem.tagName.toLowerCase());\n              if (elem.id) {\n                out.push(\"#\" + elem.id);\n              }\n\n              className = elem.className;\n              if (className && isString(className)) {\n                classes = className.split(/\\s+/);\n                for (i = 0; i < classes.length; i++) {\n                  out.push(\".\" + classes[i]);\n                }\n              }\n              var attrWhitelist = [\"type\", \"name\", \"title\", \"alt\"];\n              for (i = 0; i < attrWhitelist.length; i++) {\n                key = attrWhitelist[i];\n                attr = elem.getAttribute(key);\n                if (attr) {\n                  out.push(\"[\" + key + '=\"' + attr + '\"]');\n                }\n              }\n              return out.join(\"\");\n            }\n\n            /**\n             * Returns true if either a OR b is truthy, but not both\n             */\n            function isOnlyOneTruthy(a, b) {\n              return !!(!!a ^ !!b);\n            }\n\n            /**\n             * Returns true if the two input exception interfaces have the same content\n             */\n            function isSameException(ex1, ex2) {\n              if (isOnlyOneTruthy(ex1, ex2)) return false;\n\n              ex1 = ex1.values[0];\n              ex2 = ex2.values[0];\n\n              if (ex1.type !== ex2.type || ex1.value !== ex2.value)\n                return false;\n\n              return isSameStacktrace(ex1.stacktrace, ex2.stacktrace);\n            }\n\n            /**\n             * Returns true if the two input stack trace interfaces have the same content\n             */\n            function isSameStacktrace(stack1, stack2) {\n              if (isOnlyOneTruthy(stack1, stack2)) return false;\n\n              var frames1 = stack1.frames;\n              var frames2 = stack2.frames;\n\n              // Exit early if frame count differs\n              if (frames1.length !== frames2.length) return false;\n\n              // Iterate through every frame; bail out if anything differs\n              var a, b;\n              for (var i = 0; i < frames1.length; i++) {\n                a = frames1[i];\n                b = frames2[i];\n                if (\n                  a.filename !== b.filename ||\n                  a.lineno !== b.lineno ||\n                  a.colno !== b.colno ||\n                  a[\"function\"] !== b[\"function\"]\n                )\n                  return false;\n              }\n              return true;\n            }\n\n            /**\n             * Polyfill a method\n             * @param obj object e.g. `document`\n             * @param name method name present on object e.g. `addEventListener`\n             * @param replacement replacement function\n             * @param track {optional} record instrumentation to an array\n             */\n            function fill(obj, name, replacement, track) {\n              var orig = obj[name];\n              obj[name] = replacement(orig);\n              obj[name].__raven__ = true;\n              obj[name].__orig__ = orig;\n              if (track) {\n                track.push([obj, name, orig]);\n              }\n            }\n\n            module.exports = {\n              isObject: isObject,\n              isError: isError,\n              isErrorEvent: isErrorEvent,\n              isUndefined: isUndefined,\n              isFunction: isFunction,\n              isString: isString,\n              isArray: isArray,\n              isEmptyObject: isEmptyObject,\n              supportsErrorEvent: supportsErrorEvent,\n              wrappedCallback: wrappedCallback,\n              each: each,\n              objectMerge: objectMerge,\n              truncate: truncate,\n              objectFrozen: objectFrozen,\n              hasKey: hasKey,\n              joinRegExp: joinRegExp,\n              urlencode: urlencode,\n              uuid4: uuid4,\n              htmlTreeAsString: htmlTreeAsString,\n              htmlElementAsString: htmlElementAsString,\n              isSameException: isSameException,\n              isSameStacktrace: isSameStacktrace,\n              parseUrl: parseUrl,\n              fill: fill,\n            };\n          }).call(\n            this,\n            typeof global !== \"undefined\"\n              ? global\n              : typeof self !== \"undefined\"\n                ? self\n                : typeof window !== \"undefined\"\n                  ? window\n                  : {},\n          );\n        },\n        {},\n      ],\n      6: [\n        function (_dereq_, module, exports) {\n          (function (global) {\n            var utils = _dereq_(5);\n\n            /*\n TraceKit - Cross brower stack traces\n\n This was originally forked from github.com/occ/TraceKit, but has since been\n largely re-written and is now maintained as part of raven-js.  Tests for\n this are in test/vendor.\n\n MIT license\n*/\n\n            var TraceKit = {\n              collectWindowErrors: true,\n              debug: false,\n            };\n\n            // This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785)\n            var _window =\n              typeof window !== \"undefined\"\n                ? window\n                : typeof global !== \"undefined\"\n                  ? global\n                  : typeof self !== \"undefined\"\n                    ? self\n                    : {};\n\n            // global reference to slice\n            var _slice = [].slice;\n            var UNKNOWN_FUNCTION = \"?\";\n\n            // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types\n            var ERROR_TYPES_RE =\n              /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/;\n\n            function getLocationHref() {\n              if (typeof document === \"undefined\" || document.location == null)\n                return \"\";\n\n              return document.location.href;\n            }\n\n            /**\n             * TraceKit.report: cross-browser processing of unhandled exceptions\n             *\n             * Syntax:\n             *   TraceKit.report.subscribe(function(stackInfo) { ... })\n             *   TraceKit.report.unsubscribe(function(stackInfo) { ... })\n             *   TraceKit.report(exception)\n             *   try { ...code... } catch(ex) { TraceKit.report(ex); }\n             *\n             * Supports:\n             *   - Firefox: full stack trace with line numbers, plus column number\n             *              on top frame; column number is not guaranteed\n             *   - Opera:   full stack trace with line and column numbers\n             *   - Chrome:  full stack trace with line and column numbers\n             *   - Safari:  line and column number for the top frame only; some frames\n             *              may be missing, and column number is not guaranteed\n             *   - IE:      line and column number for the top frame only; some frames\n             *              may be missing, and column number is not guaranteed\n             *\n             * In theory, TraceKit should work on all of the following versions:\n             *   - IE5.5+ (only 8.0 tested)\n             *   - Firefox 0.9+ (only 3.5+ tested)\n             *   - Opera 7+ (only 10.50 tested; versions 9 and earlier may require\n             *     Exceptions Have Stacktrace to be enabled in opera:config)\n             *   - Safari 3+ (only 4+ tested)\n             *   - Chrome 1+ (only 5+ tested)\n             *   - Konqueror 3.5+ (untested)\n             *\n             * Requires TraceKit.computeStackTrace.\n             *\n             * Tries to catch all unhandled exceptions and report them to the\n             * subscribed handlers. Please note that TraceKit.report will rethrow the\n             * exception. This is REQUIRED in order to get a useful stack trace in IE.\n             * If the exception does not reach the top of the browser, you will only\n             * get a stack trace from the point where TraceKit.report was called.\n             *\n             * Handlers receive a stackInfo object as described in the\n             * TraceKit.computeStackTrace docs.\n             */\n            TraceKit.report = (function reportModuleWrapper() {\n              var handlers = [],\n                lastArgs = null,\n                lastException = null,\n                lastExceptionStack = null;\n\n              /**\n               * Add a crash handler.\n               * @param {Function} handler\n               */\n              function subscribe(handler) {\n                installGlobalHandler();\n                handlers.push(handler);\n              }\n\n              /**\n               * Remove a crash handler.\n               * @param {Function} handler\n               */\n              function unsubscribe(handler) {\n                for (var i = handlers.length - 1; i >= 0; --i) {\n                  if (handlers[i] === handler) {\n                    handlers.splice(i, 1);\n                  }\n                }\n              }\n\n              /**\n               * Remove all crash handlers.\n               */\n              function unsubscribeAll() {\n                uninstallGlobalHandler();\n                handlers = [];\n              }\n\n              /**\n               * Dispatch stack information to all handlers.\n               * @param {Object.<string, *>} stack\n               */\n              function notifyHandlers(stack, isWindowError) {\n                var exception = null;\n                if (isWindowError && !TraceKit.collectWindowErrors) {\n                  return;\n                }\n                for (var i in handlers) {\n                  if (handlers.hasOwnProperty(i)) {\n                    try {\n                      handlers[i].apply(\n                        null,\n                        [stack].concat(_slice.call(arguments, 2)),\n                      );\n                    } catch (inner) {\n                      exception = inner;\n                    }\n                  }\n                }\n\n                if (exception) {\n                  throw exception;\n                }\n              }\n\n              var _oldOnerrorHandler, _onErrorHandlerInstalled;\n\n              /**\n               * Ensures all global unhandled exceptions are recorded.\n               * Supported by Gecko and IE.\n               * @param {string} message Error message.\n               * @param {string} url URL of script that generated the exception.\n               * @param {(number|string)} lineNo The line number at which the error\n               * occurred.\n               * @param {?(number|string)} colNo The column number at which the error\n               * occurred.\n               * @param {?Error} ex The actual Error object.\n               */\n              function traceKitWindowOnError(message, url, lineNo, colNo, ex) {\n                var stack = null;\n\n                if (lastExceptionStack) {\n                  TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(\n                    lastExceptionStack,\n                    url,\n                    lineNo,\n                    message,\n                  );\n                  processLastException();\n                } else if (ex && utils.isError(ex)) {\n                  // non-string `ex` arg; attempt to extract stack trace\n\n                  // New chrome and blink send along a real error object\n                  // Let's just report that like a normal error.\n                  // See: https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror\n                  stack = TraceKit.computeStackTrace(ex);\n                  notifyHandlers(stack, true);\n                } else {\n                  var location = {\n                    url: url,\n                    line: lineNo,\n                    column: colNo,\n                  };\n\n                  var name = undefined;\n                  var msg = message; // must be new var or will modify original `arguments`\n                  var groups;\n                  if ({}.toString.call(message) === \"[object String]\") {\n                    var groups = message.match(ERROR_TYPES_RE);\n                    if (groups) {\n                      name = groups[1];\n                      msg = groups[2];\n                    }\n                  }\n\n                  location.func = UNKNOWN_FUNCTION;\n\n                  stack = {\n                    name: name,\n                    message: msg,\n                    url: getLocationHref(),\n                    stack: [location],\n                  };\n                  notifyHandlers(stack, true);\n                }\n\n                if (_oldOnerrorHandler) {\n                  return _oldOnerrorHandler.apply(this, arguments);\n                }\n\n                return false;\n              }\n\n              function installGlobalHandler() {\n                if (_onErrorHandlerInstalled) {\n                  return;\n                }\n                _oldOnerrorHandler = _window.onerror;\n                _window.onerror = traceKitWindowOnError;\n                _onErrorHandlerInstalled = true;\n              }\n\n              function uninstallGlobalHandler() {\n                if (!_onErrorHandlerInstalled) {\n                  return;\n                }\n                _window.onerror = _oldOnerrorHandler;\n                _onErrorHandlerInstalled = false;\n                _oldOnerrorHandler = undefined;\n              }\n\n              function processLastException() {\n                var _lastExceptionStack = lastExceptionStack,\n                  _lastArgs = lastArgs;\n                lastArgs = null;\n                lastExceptionStack = null;\n                lastException = null;\n                notifyHandlers.apply(\n                  null,\n                  [_lastExceptionStack, false].concat(_lastArgs),\n                );\n              }\n\n              /**\n               * Reports an unhandled Error to TraceKit.\n               * @param {Error} ex\n               * @param {?boolean} rethrow If false, do not re-throw the exception.\n               * Only used for window.onerror to not cause an infinite loop of\n               * rethrowing.\n               */\n              function report(ex, rethrow) {\n                var args = _slice.call(arguments, 1);\n                if (lastExceptionStack) {\n                  if (lastException === ex) {\n                    return; // already caught by an inner catch block, ignore\n                  } else {\n                    processLastException();\n                  }\n                }\n\n                var stack = TraceKit.computeStackTrace(ex);\n                lastExceptionStack = stack;\n                lastException = ex;\n                lastArgs = args;\n\n                // If the stack trace is incomplete, wait for 2 seconds for\n                // slow slow IE to see if onerror occurs or not before reporting\n                // this exception; otherwise, we will end up with an incomplete\n                // stack trace\n                setTimeout(\n                  function () {\n                    if (lastException === ex) {\n                      processLastException();\n                    }\n                  },\n                  stack.incomplete ? 2000 : 0,\n                );\n\n                if (rethrow !== false) {\n                  throw ex; // re-throw to propagate to the top level (and cause window.onerror)\n                }\n              }\n\n              report.subscribe = subscribe;\n              report.unsubscribe = unsubscribe;\n              report.uninstall = unsubscribeAll;\n              return report;\n            })();\n\n            /**\n             * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript\n             *\n             * Syntax:\n             *   s = TraceKit.computeStackTrace(exception) // consider using TraceKit.report instead (see below)\n             * Returns:\n             *   s.name              - exception name\n             *   s.message           - exception message\n             *   s.stack[i].url      - JavaScript or HTML file URL\n             *   s.stack[i].func     - function name, or empty for anonymous functions (if guessing did not work)\n             *   s.stack[i].args     - arguments passed to the function, if known\n             *   s.stack[i].line     - line number, if known\n             *   s.stack[i].column   - column number, if known\n             *\n             * Supports:\n             *   - Firefox:  full stack trace with line numbers and unreliable column\n             *               number on top frame\n             *   - Opera 10: full stack trace with line and column numbers\n             *   - Opera 9-: full stack trace with line numbers\n             *   - Chrome:   full stack trace with line and column numbers\n             *   - Safari:   line and column number for the topmost stacktrace element\n             *               only\n             *   - IE:       no line numbers whatsoever\n             *\n             * Tries to guess names of anonymous functions by looking for assignments\n             * in the source code. In IE and Safari, we have to guess source file names\n             * by searching for function bodies inside all page scripts. This will not\n             * work for scripts that are loaded cross-domain.\n             * Here be dragons: some function names may be guessed incorrectly, and\n             * duplicate functions may be mismatched.\n             *\n             * TraceKit.computeStackTrace should only be used for tracing purposes.\n             * Logging of unhandled exceptions should be done with TraceKit.report,\n             * which builds on top of TraceKit.computeStackTrace and provides better\n             * IE support by utilizing the window.onerror event to retrieve information\n             * about the top of the stack.\n             *\n             * Note: In IE and Safari, no stack trace is recorded on the Error object,\n             * so computeStackTrace instead walks its *own* chain of callers.\n             * This means that:\n             *  * in Safari, some methods may be missing from the stack trace;\n             *  * in IE, the topmost function in the stack trace will always be the\n             *    caller of computeStackTrace.\n             *\n             * This is okay for tracing (because you are likely to be calling\n             * computeStackTrace from the function you want to be the topmost element\n             * of the stack trace anyway), but not okay for logging unhandled\n             * exceptions (because your catch block will likely be far away from the\n             * inner function that actually caused the exception).\n             *\n             */\n            TraceKit.computeStackTrace = (function computeStackTraceWrapper() {\n              // Contents of Exception in various browsers.\n              //\n              // SAFARI:\n              // ex.message = Can't find variable: qq\n              // ex.line = 59\n              // ex.sourceId = 580238192\n              // ex.sourceURL = http://...\n              // ex.expressionBeginOffset = 96\n              // ex.expressionCaretOffset = 98\n              // ex.expressionEndOffset = 98\n              // ex.name = ReferenceError\n              //\n              // FIREFOX:\n              // ex.message = qq is not defined\n              // ex.fileName = http://...\n              // ex.lineNumber = 59\n              // ex.columnNumber = 69\n              // ex.stack = ...stack trace... (see the example below)\n              // ex.name = ReferenceError\n              //\n              // CHROME:\n              // ex.message = qq is not defined\n              // ex.name = ReferenceError\n              // ex.type = not_defined\n              // ex.arguments = ['aa']\n              // ex.stack = ...stack trace...\n              //\n              // INTERNET EXPLORER:\n              // ex.message = ...\n              // ex.name = ReferenceError\n              //\n              // OPERA:\n              // ex.message = ...message... (see the example below)\n              // ex.name = ReferenceError\n              // ex.opera#sourceloc = 11  (pretty much useless, duplicates the info in ex.message)\n              // ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace'\n\n              /**\n               * Computes stack trace information from the stack property.\n               * Chrome and Gecko use this property.\n               * @param {Error} ex\n               * @return {?Object.<string, *>} Stack trace information.\n               */\n              function computeStackTraceFromStackProp(ex) {\n                if (typeof ex.stack === \"undefined\" || !ex.stack) return;\n\n                var chrome =\n                    /^\\s*at (.*?) ?\\(((?:file|https?|blob|chrome-extension|native|eval|webpack|<anonymous>|[a-z]:|\\/).*?)(?::(\\d+))?(?::(\\d+))?\\)?\\s*$/i,\n                  gecko =\n                    /^\\s*(.*?)(?:\\((.*?)\\))?(?:^|@)((?:file|https?|blob|chrome|webpack|resource|\\[native).*?|[^@]*bundle)(?::(\\d+))?(?::(\\d+))?\\s*$/i,\n                  winjs =\n                    /^\\s*at (?:((?:\\[object object\\])?.+) )?\\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\\d+)(?::(\\d+))?\\)?\\s*$/i,\n                  // Used to additionally parse URL/line/column from eval frames\n                  geckoEval = /(\\S+) line (\\d+)(?: > eval line \\d+)* > eval/i,\n                  chromeEval = /\\((\\S*)(?::(\\d+))(?::(\\d+))\\)/,\n                  lines = ex.stack.split(\"\\n\"),\n                  stack = [],\n                  submatch,\n                  parts,\n                  element,\n                  reference = /^(.*) is undefined$/.exec(ex.message);\n\n                for (var i = 0, j = lines.length; i < j; ++i) {\n                  if ((parts = chrome.exec(lines[i]))) {\n                    var isNative = parts[2] && parts[2].indexOf(\"native\") === 0; // start of line\n                    var isEval = parts[2] && parts[2].indexOf(\"eval\") === 0; // start of line\n                    if (isEval && (submatch = chromeEval.exec(parts[2]))) {\n                      // throw out eval line/column and use top-most line/column number\n                      parts[2] = submatch[1]; // url\n                      parts[3] = submatch[2]; // line\n                      parts[4] = submatch[3]; // column\n                    }\n                    element = {\n                      url: !isNative ? parts[2] : null,\n                      func: parts[1] || UNKNOWN_FUNCTION,\n                      args: isNative ? [parts[2]] : [],\n                      line: parts[3] ? +parts[3] : null,\n                      column: parts[4] ? +parts[4] : null,\n                    };\n                  } else if ((parts = winjs.exec(lines[i]))) {\n                    element = {\n                      url: parts[2],\n                      func: parts[1] || UNKNOWN_FUNCTION,\n                      args: [],\n                      line: +parts[3],\n                      column: parts[4] ? +parts[4] : null,\n                    };\n                  } else if ((parts = gecko.exec(lines[i]))) {\n                    var isEval = parts[3] && parts[3].indexOf(\" > eval\") > -1;\n                    if (isEval && (submatch = geckoEval.exec(parts[3]))) {\n                      // throw out eval line/column and use top-most line number\n                      parts[3] = submatch[1];\n                      parts[4] = submatch[2];\n                      parts[5] = null; // no column when eval\n                    } else if (\n                      i === 0 &&\n                      !parts[5] &&\n                      typeof ex.columnNumber !== \"undefined\"\n                    ) {\n                      // FireFox uses this awesome columnNumber property for its top frame\n                      // Also note, Firefox's column number is 0-based and everything else expects 1-based,\n                      // so adding 1\n                      // NOTE: this hack doesn't work if top-most frame is eval\n                      stack[0].column = ex.columnNumber + 1;\n                    }\n                    element = {\n                      url: parts[3],\n                      func: parts[1] || UNKNOWN_FUNCTION,\n                      args: parts[2] ? parts[2].split(\",\") : [],\n                      line: parts[4] ? +parts[4] : null,\n                      column: parts[5] ? +parts[5] : null,\n                    };\n                  } else {\n                    continue;\n                  }\n\n                  if (!element.func && element.line) {\n                    element.func = UNKNOWN_FUNCTION;\n                  }\n\n                  stack.push(element);\n                }\n\n                if (!stack.length) {\n                  return null;\n                }\n\n                return {\n                  name: ex.name,\n                  message: ex.message,\n                  url: getLocationHref(),\n                  stack: stack,\n                };\n              }\n\n              /**\n               * Adds information about the first frame to incomplete stack traces.\n               * Safari and IE require this to get complete data on the first frame.\n               * @param {Object.<string, *>} stackInfo Stack trace information from\n               * one of the compute* methods.\n               * @param {string} url The URL of the script that caused an error.\n               * @param {(number|string)} lineNo The line number of the script that\n               * caused an error.\n               * @param {string=} message The error generated by the browser, which\n               * hopefully contains the name of the object that caused the error.\n               * @return {boolean} Whether or not the stack information was\n               * augmented.\n               */\n              function augmentStackTraceWithInitialElement(\n                stackInfo,\n                url,\n                lineNo,\n                message,\n              ) {\n                var initial = {\n                  url: url,\n                  line: lineNo,\n                };\n\n                if (initial.url && initial.line) {\n                  stackInfo.incomplete = false;\n\n                  if (!initial.func) {\n                    initial.func = UNKNOWN_FUNCTION;\n                  }\n\n                  if (stackInfo.stack.length > 0) {\n                    if (stackInfo.stack[0].url === initial.url) {\n                      if (stackInfo.stack[0].line === initial.line) {\n                        return false; // already in stack trace\n                      } else if (\n                        !stackInfo.stack[0].line &&\n                        stackInfo.stack[0].func === initial.func\n                      ) {\n                        stackInfo.stack[0].line = initial.line;\n                        return false;\n                      }\n                    }\n                  }\n\n                  stackInfo.stack.unshift(initial);\n                  stackInfo.partial = true;\n                  return true;\n                } else {\n                  stackInfo.incomplete = true;\n                }\n\n                return false;\n              }\n\n              /**\n               * Computes stack trace information by walking the arguments.caller\n               * chain at the time the exception occurred. This will cause earlier\n               * frames to be missed but is the only way to get any stack trace in\n               * Safari and IE. The top frame is restored by\n               * {@link augmentStackTraceWithInitialElement}.\n               * @param {Error} ex\n               * @return {?Object.<string, *>} Stack trace information.\n               */\n              function computeStackTraceByWalkingCallerChain(ex, depth) {\n                var functionName =\n                    /function\\s+([_$a-zA-Z\\xA0-\\uFFFF][_$a-zA-Z0-9\\xA0-\\uFFFF]*)?\\s*\\(/i,\n                  stack = [],\n                  funcs = {},\n                  recursion = false,\n                  parts,\n                  item,\n                  source;\n\n                for (\n                  var curr = computeStackTraceByWalkingCallerChain.caller;\n                  curr && !recursion;\n                  curr = curr.caller\n                ) {\n                  if (curr === computeStackTrace || curr === TraceKit.report) {\n                    // console.log('skipping internal function');\n                    continue;\n                  }\n\n                  item = {\n                    url: null,\n                    func: UNKNOWN_FUNCTION,\n                    line: null,\n                    column: null,\n                  };\n\n                  if (curr.name) {\n                    item.func = curr.name;\n                  } else if ((parts = functionName.exec(curr.toString()))) {\n                    item.func = parts[1];\n                  }\n\n                  if (typeof item.func === \"undefined\") {\n                    try {\n                      item.func = parts.input.substring(\n                        0,\n                        parts.input.indexOf(\"{\"),\n                      );\n                    } catch (e) {}\n                  }\n\n                  if (funcs[\"\" + curr]) {\n                    recursion = true;\n                  } else {\n                    funcs[\"\" + curr] = true;\n                  }\n\n                  stack.push(item);\n                }\n\n                if (depth) {\n                  // console.log('depth is ' + depth);\n                  // console.log('stack is ' + stack.length);\n                  stack.splice(0, depth);\n                }\n\n                var result = {\n                  name: ex.name,\n                  message: ex.message,\n                  url: getLocationHref(),\n                  stack: stack,\n                };\n                augmentStackTraceWithInitialElement(\n                  result,\n                  ex.sourceURL || ex.fileName,\n                  ex.line || ex.lineNumber,\n                  ex.message || ex.description,\n                );\n                return result;\n              }\n\n              /**\n               * Computes a stack trace for an exception.\n               * @param {Error} ex\n               * @param {(string|number)=} depth\n               */\n              function computeStackTrace(ex, depth) {\n                var stack = null;\n                depth = depth == null ? 0 : +depth;\n\n                try {\n                  stack = computeStackTraceFromStackProp(ex);\n                  if (stack) {\n                    return stack;\n                  }\n                } catch (e) {\n                  if (TraceKit.debug) {\n                    throw e;\n                  }\n                }\n\n                try {\n                  stack = computeStackTraceByWalkingCallerChain(ex, depth + 1);\n                  if (stack) {\n                    return stack;\n                  }\n                } catch (e) {\n                  if (TraceKit.debug) {\n                    throw e;\n                  }\n                }\n                return {\n                  name: ex.name,\n                  message: ex.message,\n                  url: getLocationHref(),\n                };\n              }\n\n              computeStackTrace.augmentStackTraceWithInitialElement =\n                augmentStackTraceWithInitialElement;\n              computeStackTrace.computeStackTraceFromStackProp =\n                computeStackTraceFromStackProp;\n\n              return computeStackTrace;\n            })();\n\n            module.exports = TraceKit;\n          }).call(\n            this,\n            typeof global !== \"undefined\"\n              ? global\n              : typeof self !== \"undefined\"\n                ? self\n                : typeof window !== \"undefined\"\n                  ? window\n                  : {},\n          );\n        },\n        { 5: 5 },\n      ],\n      7: [\n        function (_dereq_, module, exports) {\n          /*\n json-stringify-safe\n Like JSON.stringify, but doesn't throw on circular references.\n\n Originally forked from https://github.com/isaacs/json-stringify-safe\n version 5.0.1 on 3/8/2017 and modified to handle Errors serialization\n and IE8 compatibility. Tests for this are in test/vendor.\n\n ISC license: https://github.com/isaacs/json-stringify-safe/blob/master/LICENSE\n*/\n\n          exports = module.exports = stringify;\n          exports.getSerialize = serializer;\n\n          function indexOf(haystack, needle) {\n            for (var i = 0; i < haystack.length; ++i) {\n              if (haystack[i] === needle) return i;\n            }\n            return -1;\n          }\n\n          function stringify(obj, replacer, spaces, cycleReplacer) {\n            return JSON.stringify(\n              obj,\n              serializer(replacer, cycleReplacer),\n              spaces,\n            );\n          }\n\n          // https://github.com/ftlabs/js-abbreviate/blob/fa709e5f139e7770a71827b1893f22418097fbda/index.js#L95-L106\n          function stringifyError(value) {\n            var err = {\n              // These properties are implemented as magical getters and don't show up in for in\n              stack: value.stack,\n              message: value.message,\n              name: value.name,\n            };\n\n            for (var i in value) {\n              if (Object.prototype.hasOwnProperty.call(value, i)) {\n                err[i] = value[i];\n              }\n            }\n\n            return err;\n          }\n\n          function serializer(replacer, cycleReplacer) {\n            var stack = [];\n            var keys = [];\n\n            if (cycleReplacer == null) {\n              cycleReplacer = function (key, value) {\n                if (stack[0] === value) {\n                  return \"[Circular ~]\";\n                }\n                return (\n                  \"[Circular ~.\" +\n                  keys.slice(0, indexOf(stack, value)).join(\".\") +\n                  \"]\"\n                );\n              };\n            }\n\n            return function (key, value) {\n              if (stack.length > 0) {\n                var thisPos = indexOf(stack, this);\n                ~thisPos ? stack.splice(thisPos + 1) : stack.push(this);\n                ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key);\n\n                if (~indexOf(stack, value)) {\n                  value = cycleReplacer.call(this, key, value);\n                }\n              } else {\n                stack.push(value);\n              }\n\n              return replacer == null\n                ? value instanceof Error\n                  ? stringifyError(value)\n                  : value\n                : replacer.call(this, key, value);\n            };\n          }\n        },\n        {},\n      ],\n    },\n    {},\n    [4],\n  )(4);\n});\n"
  },
  {
    "path": "assets/javascripts/views/content/content.js",
    "content": "app.views.Content = class Content extends app.View {\n  static el = \"._content\";\n  static loadingClass = \"_content-loading\";\n\n  static events = { click: \"onClick\" };\n\n  static shortcuts = {\n    altUp: \"scrollStepUp\",\n    altDown: \"scrollStepDown\",\n    pageUp: \"scrollPageUp\",\n    pageDown: \"scrollPageDown\",\n    pageTop: \"scrollToTop\",\n    pageBottom: \"scrollToBottom\",\n    altF: \"onAltF\",\n  };\n\n  static routes = {\n    before: \"beforeRoute\",\n    after: \"afterRoute\",\n  };\n\n  init() {\n    this.scrollEl = app.isMobile()\n      ? document.scrollingElement || document.body\n      : this.el;\n    this.scrollMap = {};\n    this.scrollStack = [];\n\n    this.rootPage = new app.views.RootPage();\n    this.staticPage = new app.views.StaticPage();\n    this.settingsPage = new app.views.SettingsPage();\n    this.offlinePage = new app.views.OfflinePage();\n    this.typePage = new app.views.TypePage();\n    this.entryPage = new app.views.EntryPage();\n\n    this.entryPage\n      .on(\"loading\", () => this.onEntryLoading())\n      .on(\"loaded\", () => this.onEntryLoaded());\n\n    app\n      .on(\"ready\", () => this.onReady())\n      .on(\"bootError\", () => this.onBootError());\n  }\n\n  show(view) {\n    this.hideLoading();\n    if (view !== this.view) {\n      if (this.view != null) {\n        this.view.deactivate();\n      }\n      this.html((this.view = view));\n      this.view.activate();\n    }\n  }\n\n  showLoading() {\n    this.addClass(this.constructor.loadingClass);\n  }\n\n  isLoading() {\n    return this.el.classList.contains(this.constructor.loadingClass);\n  }\n\n  hideLoading() {\n    this.removeClass(this.constructor.loadingClass);\n  }\n\n  scrollTo(value) {\n    this.scrollEl.scrollTop = value || 0;\n  }\n\n  smoothScrollTo(value) {\n    if (app.settings.get(\"fastScroll\")) {\n      this.scrollTo(value);\n    } else {\n      $.smoothScroll(this.scrollEl, value || 0);\n    }\n  }\n\n  scrollBy(n) {\n    this.smoothScrollTo(this.scrollEl.scrollTop + n);\n  }\n\n  scrollToTop() {\n    this.smoothScrollTo(0);\n  }\n\n  scrollToBottom() {\n    this.smoothScrollTo(this.scrollEl.scrollHeight);\n  }\n\n  scrollStepUp() {\n    this.scrollBy(-80);\n  }\n\n  scrollStepDown() {\n    this.scrollBy(80);\n  }\n\n  scrollPageUp() {\n    this.scrollBy(40 - this.scrollEl.clientHeight);\n  }\n\n  scrollPageDown() {\n    this.scrollBy(this.scrollEl.clientHeight - 40);\n  }\n\n  scrollToTarget() {\n    let el;\n    if (\n      this.routeCtx.hash &&\n      (el = this.findTargetByHash(this.routeCtx.hash))\n    ) {\n      $.scrollToWithImageLock(el, this.scrollEl, \"top\", {\n        margin: this.scrollEl === this.el ? 0 : $.offset(this.el).top,\n      });\n      $.openDetailsAncestors(el);\n      $.highlight(el, { className: \"_highlight\" });\n    } else {\n      this.scrollTo(this.scrollMap[this.routeCtx.state.id]);\n    }\n  }\n\n  onReady() {\n    this.hideLoading();\n  }\n\n  onBootError() {\n    this.hideLoading();\n    this.html(this.tmpl(\"bootError\"));\n  }\n\n  onEntryLoading() {\n    this.showLoading();\n    if (this.scrollToTargetTimeout) {\n      clearTimeout(this.scrollToTargetTimeout);\n      this.scrollToTargetTimeout = null;\n    }\n  }\n\n  onEntryLoaded() {\n    this.hideLoading();\n    if (this.scrollToTargetTimeout) {\n      clearTimeout(this.scrollToTargetTimeout);\n      this.scrollToTargetTimeout = null;\n    }\n    this.scrollToTarget();\n  }\n\n  beforeRoute(context) {\n    this.cacheScrollPosition();\n    this.routeCtx = context;\n    this.scrollToTargetTimeout = this.delay(this.scrollToTarget);\n  }\n\n  cacheScrollPosition() {\n    if (!this.routeCtx || this.routeCtx.hash) {\n      return;\n    }\n    if (this.routeCtx.path === \"/\") {\n      return;\n    }\n\n    if (this.scrollMap[this.routeCtx.state.id] == null) {\n      this.scrollStack.push(this.routeCtx.state.id);\n      while (this.scrollStack.length > app.config.history_cache_size) {\n        delete this.scrollMap[this.scrollStack.shift()];\n      }\n    }\n\n    this.scrollMap[this.routeCtx.state.id] = this.scrollEl.scrollTop;\n  }\n\n  afterRoute(route, context) {\n    if (route !== \"entry\" && route !== \"type\") {\n      resetFavicon();\n    }\n\n    switch (route) {\n      case \"root\":\n        this.show(this.rootPage);\n        break;\n      case \"entry\":\n        this.show(this.entryPage);\n        break;\n      case \"type\":\n        this.show(this.typePage);\n        break;\n      case \"settings\":\n        this.show(this.settingsPage);\n        break;\n      case \"offline\":\n        this.show(this.offlinePage);\n        break;\n      default:\n        this.show(this.staticPage);\n    }\n\n    this.view.onRoute(context);\n    app.document.setTitle(\n      typeof this.view.getTitle === \"function\"\n        ? this.view.getTitle()\n        : undefined,\n    );\n  }\n\n  onClick(event) {\n    const link = $.closestLink($.eventTarget(event), this.el);\n    if (link && this.isExternalUrl(link.getAttribute(\"href\"))) {\n      $.stopEvent(event);\n      $.popup(link);\n    }\n  }\n\n  onAltF(event) {\n    if (\n      !document.activeElement ||\n      !$.hasChild(this.el, document.activeElement)\n    ) {\n      this.find(\"a:not(:empty)\")?.focus();\n      return $.stopEvent(event);\n    }\n  }\n\n  findTargetByHash(hash) {\n    let el = (() => {\n      try {\n        return $.id(decodeURIComponent(hash));\n      } catch (error) {}\n    })();\n    if (!el) {\n      el = (() => {\n        try {\n          return $.id(hash);\n        } catch (error1) {}\n      })();\n    }\n    return el;\n  }\n\n  isExternalUrl(url) {\n    return url?.startsWith(\"http:\") || url?.startsWith(\"https:\");\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/content/entry_page.js",
    "content": "app.views.EntryPage = class EntryPage extends app.View {\n  static className = \"_page\";\n  static errorClass = \"_page-error\";\n\n  static events = { click: \"onClick\" };\n\n  static shortcuts = {\n    altC: \"onAltC\",\n    altO: \"onAltO\",\n  };\n\n  static routes = { before: \"beforeRoute\" };\n\n  static LINKS = {\n    home: \"Homepage\",\n    code: \"Source code\",\n  };\n\n  init() {\n    this.cacheMap = {};\n    this.cacheStack = [];\n  }\n\n  deactivate() {\n    if (super.deactivate(...arguments)) {\n      this.empty();\n      this.entry = null;\n    }\n  }\n\n  loading() {\n    this.empty();\n    this.trigger(\"loading\");\n  }\n\n  render(content, fromCache) {\n    if (content == null) {\n      content = \"\";\n    }\n    if (fromCache == null) {\n      fromCache = false;\n    }\n    if (!this.activated) {\n      return;\n    }\n    this.empty();\n    this.subview = new (this.subViewClass())(this.el, this.entry);\n\n    $.batchUpdate(this.el, () => {\n      this.subview.render(content, fromCache);\n      if (!fromCache) {\n        this.addCopyButtons();\n      }\n    });\n\n    if (app.disabledDocs.findBy(\"slug\", this.entry.doc.slug)) {\n      this.hiddenView = new app.views.HiddenPage(this.el, this.entry);\n    }\n\n    setFaviconForDoc(this.entry.doc);\n    this.delay(this.polyfillMathML);\n    this.trigger(\"loaded\");\n  }\n\n  addCopyButtons() {\n    if (!this.copyButton) {\n      this.copyButton = document.createElement(\"button\");\n      this.copyButton.innerHTML = '<svg><use xlink:href=\"#icon-copy\"/></svg>';\n      this.copyButton.type = \"button\";\n      this.copyButton.className = \"_pre-clip\";\n      this.copyButton.title = \"Copy to clipboard\";\n      this.copyButton.setAttribute(\"aria-label\", \"Copy to clipboard\");\n    }\n    for (var el of this.findAllByTag(\"pre\")) {\n      el.appendChild(this.copyButton.cloneNode(true));\n    }\n  }\n\n  polyfillMathML() {\n    if (\n      window.supportsMathML !== false ||\n      !!this.polyfilledMathML ||\n      !this.findByTag(\"math\")\n    ) {\n      return;\n    }\n    this.polyfilledMathML = true;\n    $.append(\n      document.head,\n      `<link rel=\"stylesheet\" href=\"${app.config.mathml_stylesheet}\">`,\n    );\n  }\n\n  prepareContent(content) {\n    if (!this.entry.isIndex() || !this.entry.doc.links) {\n      return content;\n    }\n\n    const links = Object.entries(this.entry.doc.links).map(([link, url]) => {\n      return `<a href=\"${url}\" class=\"_links-link\">${EntryPage.LINKS[link]}</a>`;\n    });\n\n    return `<p class=\"_links\">${links.join(\"\")}</p>${content}`;\n  }\n\n  empty() {\n    if (this.subview != null) {\n      this.subview.deactivate();\n    }\n    this.subview = null;\n\n    if (this.hiddenView != null) {\n      this.hiddenView.deactivate();\n    }\n    this.hiddenView = null;\n\n    this.resetClass();\n    super.empty(...arguments);\n  }\n\n  subViewClass() {\n    return (\n      app.views[`${$.classify(this.entry.doc.type)}Page`] || app.views.BasePage\n    );\n  }\n\n  getTitle() {\n    return (\n      this.entry.doc.fullName +\n      (this.entry.isIndex() ? \" documentation\" : ` / ${this.entry.name}`)\n    );\n  }\n\n  beforeRoute() {\n    this.cache();\n    this.abort();\n  }\n\n  onRoute(context) {\n    const isSameFile = context.entry.filePath() === this.entry?.filePath?.();\n    this.entry = context.entry;\n    if (!isSameFile) {\n      this.restore() || this.load();\n    }\n  }\n\n  load() {\n    this.loading();\n    this.xhr = this.entry.loadFile(\n      (response) => this.onSuccess(response),\n      () => this.onError(),\n    );\n  }\n\n  abort() {\n    if (this.xhr) {\n      this.xhr.abort();\n      this.xhr = this.entry = null;\n    }\n  }\n\n  onSuccess(response) {\n    if (!this.activated) {\n      return;\n    }\n    this.xhr = null;\n    this.render(this.prepareContent(response));\n  }\n\n  onError() {\n    this.xhr = null;\n    this.render(this.tmpl(\"pageLoadError\"));\n    this.resetClass();\n    this.addClass(this.constructor.errorClass);\n    if (app.serviceWorker != null) {\n      app.serviceWorker.update();\n    }\n  }\n\n  cache() {\n    let path;\n    if (\n      this.xhr ||\n      !this.entry ||\n      this.cacheMap[(path = this.entry.filePath())]\n    ) {\n      return;\n    }\n\n    this.cacheMap[path] = this.el.innerHTML;\n    this.cacheStack.push(path);\n\n    while (this.cacheStack.length > app.config.history_cache_size) {\n      delete this.cacheMap[this.cacheStack.shift()];\n    }\n  }\n\n  restore() {\n    const path = this.entry.filePath();\n    if (this.cacheMap[[path]]) {\n      this.render(this.cacheMap[path], true);\n      return true;\n    }\n  }\n\n  onClick(event) {\n    const target = $.eventTarget(event);\n    if (target.hasAttribute(\"data-retry\")) {\n      $.stopEvent(event);\n      this.load();\n    } else if (target.classList.contains(\"_pre-clip\")) {\n      $.stopEvent(event);\n      navigator.clipboard.writeText(target.parentNode.textContent).then(\n        () => target.classList.add(\"_pre-clip-success\"),\n        () => target.classList.add(\"_pre-clip-error\"),\n      );\n      setTimeout(() => (target.className = \"_pre-clip\"), 2000);\n    }\n  }\n\n  onAltC() {\n    const link = this.find(\"._attribution:last-child ._attribution-link\");\n    if (!link) {\n      return;\n    }\n    console.log(link.href + location.hash);\n    navigator.clipboard.writeText(link.href + location.hash);\n  }\n\n  onAltO() {\n    const link = this.find(\"._attribution:last-child ._attribution-link\");\n    if (!link) {\n      return;\n    }\n    this.delay(() => $.popup(link.href + location.hash));\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/content/offline_page.js",
    "content": "app.views.OfflinePage = class OfflinePage extends app.View {\n  static className = \"_static\";\n\n  static events = {\n    click: \"onClick\",\n    change: \"onChange\",\n  };\n\n  deactivate() {\n    if (super.deactivate(...arguments)) {\n      this.empty();\n    }\n  }\n\n  render() {\n    if (app.cookieBlocked) {\n      this.html(this.tmpl(\"offlineError\", \"cookie_blocked\"));\n      return;\n    }\n\n    app.docs.getInstallStatuses((statuses) => {\n      if (!this.activated) {\n        return;\n      }\n      if (statuses === false) {\n        this.html(this.tmpl(\"offlineError\", app.db.reason, app.db.error));\n      } else {\n        let html = \"\";\n        for (var doc of app.docs.all()) {\n          html += this.renderDoc(doc, statuses[doc.slug]);\n        }\n        this.html(this.tmpl(\"offlinePage\", html));\n        this.refreshLinks();\n      }\n    });\n  }\n\n  renderDoc(doc, status) {\n    return app.templates.render(\"offlineDoc\", doc, status);\n  }\n\n  getTitle() {\n    return \"Offline\";\n  }\n\n  refreshLinks() {\n    for (var action of [\"install\", \"update\", \"uninstall\"]) {\n      this.find(`[data-action-all='${action}']`).classList[\n        this.find(`[data-action='${action}']`) ? \"add\" : \"remove\"\n      ](\"_show\");\n    }\n  }\n\n  docByEl(el) {\n    let slug;\n    while (!(slug = el.getAttribute(\"data-slug\"))) {\n      el = el.parentNode;\n    }\n    return app.docs.findBy(\"slug\", slug);\n  }\n\n  docEl(doc) {\n    return this.find(`[data-slug='${doc.slug}']`);\n  }\n\n  onRoute(context) {\n    this.render();\n  }\n\n  onClick(event) {\n    let el = $.eventTarget(event);\n    let action = el.getAttribute(\"data-action\");\n    if (action) {\n      const doc = this.docByEl(el);\n      if (action === \"update\") {\n        action = \"install\";\n      }\n      doc[action](\n        this.onInstallSuccess.bind(this, doc),\n        this.onInstallError.bind(this, doc),\n        this.onInstallProgress.bind(this, doc)\n      );\n      el.parentNode.innerHTML = `${el.textContent.replace(/e$/, \"\")}ing…`;\n    } else if (\n      (action =\n        el.getAttribute(\"data-action-all\") ||\n        el.parentElement.getAttribute(\"data-action-all\"))\n    ) {\n      if (action === \"uninstall\" && !window.confirm(\"Uninstall all docs?\")) {\n        return;\n      }\n      app.db.migrate();\n      for (el of Array.from(this.findAll(`[data-action='${action}']`))) {\n        $.click(el);\n      }\n    }\n  }\n\n  onInstallSuccess(doc) {\n    if (!this.activated) {\n      return;\n    }\n    doc.getInstallStatus((status) => {\n      if (!this.activated) {\n        return;\n      }\n      const el = this.docEl(doc);\n      if (el) {\n        el.outerHTML = this.renderDoc(doc, status);\n        $.highlight(el, { className: \"_highlight\" });\n        this.refreshLinks();\n      }\n    });\n  }\n\n  onInstallError(doc) {\n    if (!this.activated) {\n      return;\n    }\n    const el = this.docEl(doc);\n    if (el) {\n      el.lastElementChild.textContent = \"Error\";\n    }\n  }\n\n  onInstallProgress(doc, event) {\n    if (!this.activated || !event.lengthComputable) {\n      return;\n    }\n    const el = this.docEl(doc);\n    if (el) {\n      const percentage = Math.round((event.loaded * 100) / event.total);\n      el.lastElementChild.textContent = el.lastElementChild.textContent.replace(\n        /(\\s.+)?$/,\n        ` (${percentage}%)`\n      );\n    }\n  }\n\n  onChange(event) {\n    if (event.target.name === \"autoUpdate\") {\n      app.settings.set(\"manualUpdate\", !event.target.checked);\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/content/root_page.js",
    "content": "app.views.RootPage = class RootPage extends app.View {\n  static events = { click: \"onClick\" };\n\n  init() {\n    if (!this.isHidden()) {\n      this.setHidden(false);\n    } // reserve space in local storage\n    this.render();\n  }\n\n  render() {\n    this.empty();\n\n    const tmpl = app.isAndroidWebview()\n      ? \"androidWarning\"\n      : this.isHidden()\n        ? \"splash\"\n        : app.isMobile()\n          ? \"mobileIntro\"\n          : \"intro\";\n\n    this.append(this.tmpl(tmpl));\n  }\n\n  hideIntro() {\n    this.setHidden(true);\n    this.render();\n  }\n\n  setHidden(value) {\n    app.settings.set(\"hideIntro\", value);\n  }\n\n  isHidden() {\n    return app.isSingleDoc() || app.settings.get(\"hideIntro\");\n  }\n\n  onRoute() {}\n\n  onClick(event) {\n    if ($.eventTarget(event).hasAttribute(\"data-hide-intro\")) {\n      $.stopEvent(event);\n      this.hideIntro();\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/content/settings_page.js",
    "content": "app.views.SettingsPage = class SettingsPage extends app.View {\n  static className = \"_static\";\n\n  static events = {\n    click: \"onClick\",\n    change: \"onChange\",\n  };\n\n  render() {\n    this.html(this.tmpl(\"settingsPage\", this.currentSettings()));\n  }\n\n  currentSettings() {\n    const settings = {};\n    settings.theme = app.settings.get(\"theme\");\n    settings.smoothScroll = !app.settings.get(\"fastScroll\");\n    settings.arrowScroll = app.settings.get(\"arrowScroll\");\n    settings.noAutofocus = app.settings.get(\"noAutofocus\");\n    settings.autoInstall = app.settings.get(\"autoInstall\");\n    settings.analyticsConsent = app.settings.get(\"analyticsConsent\");\n    settings.spaceScroll = app.settings.get(\"spaceScroll\");\n    settings.spaceTimeout = app.settings.get(\"spaceTimeout\");\n    settings.noDocSpecificIcon = app.settings.get(\"noDocSpecificIcon\");\n    settings.autoSupported = app.settings.autoSupported;\n    for (var layout of app.Settings.LAYOUTS) {\n      settings[layout] = app.settings.hasLayout(layout);\n    }\n    return settings;\n  }\n\n  getTitle() {\n    return \"Preferences\";\n  }\n\n  setTheme(value) {\n    app.settings.set(\"theme\", value);\n  }\n\n  toggleLayout(layout, enable) {\n    app.settings.setLayout(layout, enable);\n  }\n\n  toggleSmoothScroll(enable) {\n    app.settings.set(\"fastScroll\", !enable);\n  }\n\n  toggleAnalyticsConsent(enable) {\n    app.settings.set(\"analyticsConsent\", enable ? \"1\" : \"0\");\n    if (!enable) {\n      resetAnalytics();\n    }\n  }\n\n  toggleSpaceScroll(enable) {\n    app.settings.set(\"spaceScroll\", enable ? 1 : 0);\n  }\n\n  setScrollTimeout(value) {\n    return app.settings.set(\"spaceTimeout\", value);\n  }\n\n  toggle(name, enable) {\n    app.settings.set(name, enable);\n  }\n\n  export() {\n    const data = new Blob([JSON.stringify(app.settings.export())], {\n      type: \"application/json\",\n    });\n    const link = document.createElement(\"a\");\n    link.href = URL.createObjectURL(data);\n    link.download = \"devdocs.json\";\n    link.style.display = \"none\";\n    document.body.appendChild(link);\n    link.click();\n    document.body.removeChild(link);\n  }\n\n  import(file, input) {\n    if (!file || file.type !== \"application/json\") {\n      new app.views.Notif(\"ImportInvalid\", { autoHide: false });\n      return;\n    }\n\n    const reader = new FileReader();\n    reader.onloadend = function () {\n      const data = (() => {\n        try {\n          return JSON.parse(reader.result);\n        } catch (error) {}\n      })();\n      if (!data || data.constructor !== Object) {\n        new app.views.Notif(\"ImportInvalid\", { autoHide: false });\n        return;\n      }\n      app.settings.import(data);\n      $.trigger(input.form, \"import\");\n    };\n    reader.readAsText(file);\n  }\n\n  onChange(event) {\n    const input = event.target;\n    switch (input.name) {\n      case \"theme\":\n        this.setTheme(input.value);\n        break;\n      case \"layout\":\n        this.toggleLayout(input.value, input.checked);\n        break;\n      case \"smoothScroll\":\n        this.toggleSmoothScroll(input.checked);\n        break;\n      case \"import\":\n        this.import(input.files[0], input);\n        break;\n      case \"analyticsConsent\":\n        this.toggleAnalyticsConsent(input.checked);\n        break;\n      case \"spaceScroll\":\n        this.toggleSpaceScroll(input.checked);\n        break;\n      case \"spaceTimeout\":\n        this.setScrollTimeout(input.value);\n        break;\n      default:\n        this.toggle(input.name, input.checked);\n    }\n  }\n\n  onClick(event) {\n    const target = $.eventTarget(event);\n    switch (target.getAttribute(\"data-action\")) {\n      case \"export\":\n        $.stopEvent(event);\n        this.export();\n        break;\n    }\n  }\n\n  onRoute(context) {\n    this.render();\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/content/static_page.js",
    "content": "app.views.StaticPage = class StaticPage extends app.View {\n  static className = \"_static\";\n\n  static titles = {\n    about: \"About\",\n    news: \"News\",\n    help: \"User Guide\",\n    notFound: \"404\",\n  };\n\n  deactivate() {\n    if (super.deactivate(...arguments)) {\n      this.empty();\n      this.page = null;\n    }\n  }\n\n  render(page) {\n    this.page = page;\n    this.html(this.tmpl(`${this.page}Page`));\n  }\n\n  getTitle() {\n    return this.constructor.titles[this.page];\n  }\n\n  onRoute(context) {\n    this.render(context.page || \"notFound\");\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/content/type_page.js",
    "content": "app.views.TypePage = class TypePage extends app.View {\n  static className = \"_page\";\n\n  deactivate() {\n    if (super.deactivate(...arguments)) {\n      this.empty();\n      this.type = null;\n    }\n  }\n\n  render(type) {\n    this.type = type;\n    this.html(this.tmpl(\"typePage\", this.type));\n    setFaviconForDoc(this.type.doc);\n  }\n\n  getTitle() {\n    return `${this.type.doc.fullName} / ${this.type.name}`;\n  }\n\n  onRoute(context) {\n    this.render(context.type);\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/layout/document.js",
    "content": "app.views.Document = class Document extends app.View {\n  static el = document;\n\n  static events = { visibilitychange: \"onVisibilityChange\" };\n\n  static shortcuts = {\n    help: \"onHelp\",\n    preferences: \"onPreferences\",\n    escape: \"onEscape\",\n    superLeft: \"onBack\",\n    superRight: \"onForward\",\n  };\n\n  static routes = { after: \"afterRoute\" };\n\n  init() {\n    this.menu = new app.views.Menu();\n    this.sidebar = new app.views.Sidebar();\n    this.addSubview(this.menu, this.addSubview(this.sidebar));\n    if (app.views.Resizer.isSupported()) {\n      this.resizer = new app.views.Resizer();\n      this.addSubview(this.resizer);\n    }\n    this.content = new app.views.Content();\n    this.addSubview(this.content);\n    if (!app.isSingleDoc() && !app.isMobile()) {\n      this.path = new app.views.Path();\n      this.addSubview(this.path);\n    }\n    if (!app.isSingleDoc()) {\n      this.settings = new app.views.Settings();\n    }\n\n    $.on(document.body, \"click\", this.onClick);\n\n    this.activate();\n  }\n\n  setTitle(title) {\n    return (this.el.title = title\n      ? `${title} — DevDocs`\n      : \"DevDocs API Documentation\");\n  }\n\n  afterRoute(route) {\n    if (route === \"settings\") {\n      if (this.settings != null) {\n        this.settings.activate();\n      }\n    } else {\n      if (this.settings != null) {\n        this.settings.deactivate();\n      }\n    }\n  }\n\n  onVisibilityChange() {\n    if (this.el.visibilityState !== \"visible\") {\n      return;\n    }\n    this.delay(() => {\n      if (app.isMobile() !== app.views.Mobile.detect()) {\n        location.reload();\n      }\n    }, 300);\n  }\n\n  onHelp() {\n    app.router.show(\"/help#shortcuts\");\n  }\n\n  onPreferences() {\n    app.router.show(\"/settings\");\n  }\n\n  onEscape() {\n    const path =\n      !app.isSingleDoc() || location.pathname === app.doc.fullPath()\n        ? \"/\"\n        : app.doc.fullPath();\n\n    app.router.show(path);\n  }\n\n  onBack() {\n    history.back();\n  }\n\n  onForward() {\n    history.forward();\n  }\n\n  onClick(event) {\n    const target = $.eventTarget(event);\n    if (!target.hasAttribute(\"data-behavior\")) {\n      return;\n    }\n    $.stopEvent(event);\n    switch (target.getAttribute(\"data-behavior\")) {\n      case \"back\":\n        history.back();\n        break;\n      case \"reload\":\n        window.location.reload();\n        break;\n      case \"reboot\":\n        app.reboot();\n        break;\n      case \"hard-reload\":\n        app.reload();\n        break;\n      case \"reset\":\n        if (confirm(\"Are you sure you want to reset DevDocs?\")) {\n          app.reset();\n        }\n        break;\n      case \"accept-analytics\":\n        Cookies.set(\"analyticsConsent\", \"1\", { expires: 1e8 }) && app.reboot();\n        break;\n      case \"decline-analytics\":\n        Cookies.set(\"analyticsConsent\", \"0\", { expires: 1e8 }) && app.reboot();\n        break;\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/layout/menu.js",
    "content": "app.views.Menu = class Menu extends app.View {\n  static el = \"._menu\";\n  static activeClass = \"active\";\n\n  static events = { click: \"onClick\" };\n\n  init() {\n    $.on(document.body, \"click\", (event) => this.onGlobalClick(event));\n  }\n\n  onClick(event) {\n    const target = $.eventTarget(event);\n    if (target.tagName === \"A\") {\n      target.blur();\n    }\n  }\n\n  onGlobalClick(event) {\n    if (event.which !== 1) {\n      return;\n    }\n    if (\n      typeof event.target.hasAttribute === \"function\"\n        ? event.target.hasAttribute(\"data-toggle-menu\")\n        : undefined\n    ) {\n      this.toggleClass(this.constructor.activeClass);\n    } else if (this.hasClass(this.constructor.activeClass)) {\n      this.removeClass(this.constructor.activeClass);\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/layout/mobile.js",
    "content": "app.views.Mobile = class Mobile extends app.View {\n  static className = \"_mobile\";\n\n  static elements = {\n    body: \"body\",\n    content: \"._container\",\n    sidebar: \"._sidebar\",\n    docPicker: \"._settings ._sidebar\",\n  };\n\n  static shortcuts = { escape: \"onEscape\" };\n\n  static routes = { after: \"afterRoute\" };\n\n  static detect() {\n    if (Cookies.get(\"override-mobile-detect\") != null) {\n      return JSON.parse(Cookies.get(\"override-mobile-detect\"));\n    }\n    try {\n      return (\n        window.matchMedia(\"(max-width: 480px)\").matches ||\n        window.matchMedia(\"(max-width: 767px)\").matches ||\n        window.matchMedia(\"(max-height: 767px) and (max-width: 1024px)\")\n          .matches ||\n        // Need to sniff the user agent because some Android and Windows Phone devices don't take\n        // resolution (dpi) into account when reporting device width/height.\n        (navigator.userAgent.includes(\"Android\") &&\n          navigator.userAgent.includes(\"Mobile\")) ||\n        navigator.userAgent.includes(\"IEMobile\")\n      );\n    } catch (error) {\n      return false;\n    }\n  }\n\n  static detectAndroidWebview() {\n    try {\n      return /(Android).*( Version\\/.\\.. ).*(Chrome)/.test(navigator.userAgent);\n    } catch (error) {\n      return false;\n    }\n  }\n\n  constructor() {\n    super(document.documentElement);\n  }\n\n  init() {\n    $.on($(\"._search\"), \"touchend\", () => this.onTapSearch());\n\n    this.toggleSidebar = $(\"button[data-toggle-sidebar]\");\n    this.toggleSidebar.removeAttribute(\"hidden\");\n    $.on(this.toggleSidebar, \"click\", () => this.onClickToggleSidebar());\n\n    this.back = $(\"button[data-back]\");\n    this.back.removeAttribute(\"hidden\");\n    $.on(this.back, \"click\", () => this.onClickBack());\n\n    this.forward = $(\"button[data-forward]\");\n    this.forward.removeAttribute(\"hidden\");\n    $.on(this.forward, \"click\", () => this.onClickForward());\n\n    this.docPickerTab = $('button[data-tab=\"doc-picker\"]');\n    this.docPickerTab.removeAttribute(\"hidden\");\n    $.on(this.docPickerTab, \"click\", (event) =>\n      this.onClickDocPickerTab(event),\n    );\n\n    this.settingsTab = $('button[data-tab=\"settings\"]');\n    this.settingsTab.removeAttribute(\"hidden\");\n    $.on(this.settingsTab, \"click\", (event) => this.onClickSettingsTab(event));\n\n    app.document.sidebar.search.on(\"searching\", () => this.showSidebar());\n\n    this.activate();\n  }\n\n  showSidebar() {\n    if (this.isSidebarShown()) {\n      window.scrollTo(0, 0);\n      return;\n    }\n\n    this.contentTop = window.scrollY;\n    this.content.style.display = \"none\";\n    this.sidebar.style.display = \"block\";\n\n    const selection = this.findByClass(app.views.ListSelect.activeClass);\n    if (selection) {\n      const scrollContainer =\n        window.scrollY === this.body.scrollTop\n          ? this.body\n          : document.documentElement;\n      $.scrollTo(selection, scrollContainer, \"center\");\n    } else {\n      window.scrollTo(\n        0,\n        (this.findByClass(app.views.ListFold.activeClass) && this.sidebarTop) ||\n          0,\n      );\n    }\n  }\n\n  hideSidebar() {\n    if (!this.isSidebarShown()) {\n      return;\n    }\n    this.sidebarTop = window.scrollY;\n    this.sidebar.style.display = \"none\";\n    this.content.style.display = \"block\";\n    window.scrollTo(0, this.contentTop || 0);\n  }\n\n  isSidebarShown() {\n    return this.sidebar.style.display !== \"none\";\n  }\n\n  onClickBack() {\n    return history.back();\n  }\n\n  onClickForward() {\n    return history.forward();\n  }\n\n  onClickToggleSidebar() {\n    if (this.isSidebarShown()) {\n      this.hideSidebar();\n    } else {\n      this.showSidebar();\n    }\n  }\n\n  onClickDocPickerTab(event) {\n    $.stopEvent(event);\n    this.showDocPicker();\n  }\n\n  onClickSettingsTab(event) {\n    $.stopEvent(event);\n    this.showSettings();\n  }\n\n  showDocPicker() {\n    window.scrollTo(0, 0);\n    this.docPickerTab.classList.add(\"active\");\n    this.settingsTab.classList.remove(\"active\");\n    this.docPicker.style.display = \"block\";\n    this.content.style.display = \"none\";\n  }\n\n  showSettings() {\n    window.scrollTo(0, 0);\n    this.docPickerTab.classList.remove(\"active\");\n    this.settingsTab.classList.add(\"active\");\n    this.docPicker.style.display = \"none\";\n    this.content.style.display = \"block\";\n  }\n\n  onTapSearch() {\n    return window.scrollTo(0, 0);\n  }\n\n  onEscape() {\n    return this.hideSidebar();\n  }\n\n  afterRoute(route) {\n    this.hideSidebar();\n\n    if (route === \"settings\") {\n      this.showDocPicker();\n    } else {\n      this.content.style.display = \"block\";\n    }\n\n    if (page.canGoBack()) {\n      this.back.removeAttribute(\"disabled\");\n    } else {\n      this.back.setAttribute(\"disabled\", \"disabled\");\n    }\n\n    if (page.canGoForward()) {\n      this.forward.removeAttribute(\"disabled\");\n    } else {\n      this.forward.setAttribute(\"disabled\", \"disabled\");\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/layout/path.js",
    "content": "app.views.Path = class Path extends app.View {\n  static className = \"_path\";\n  static attributes = { role: \"complementary\" };\n\n  static events = { click: \"onClick\" };\n\n  static routes = { after: \"afterRoute\" };\n\n  render(...args) {\n    this.html(this.tmpl(\"path\", ...args));\n    this.show();\n  }\n\n  show() {\n    if (!this.el.parentNode) {\n      this.prependTo(app.el);\n    }\n  }\n\n  hide() {\n    if (this.el.parentNode) {\n      $.remove(this.el);\n    }\n  }\n\n  onClick(event) {\n    const link = $.closestLink(event.target, this.el);\n    if (link) {\n      this.clicked = true;\n    }\n  }\n\n  afterRoute(route, context) {\n    if (context.type) {\n      this.render(context.doc, context.type);\n    } else if (context.entry) {\n      if (context.entry.isIndex()) {\n        this.render(context.doc);\n      } else {\n        this.render(context.doc, context.entry.getType(), context.entry);\n      }\n    } else {\n      this.hide();\n    }\n\n    if (this.clicked) {\n      this.clicked = null;\n      app.document.sidebar.reset();\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/layout/resizer.js",
    "content": "app.views.Resizer = class Resizer extends app.View {\n  static className = \"_resizer\";\n\n  static events = {\n    dragstart: \"onDragStart\",\n    dragend: \"onDragEnd\",\n  };\n\n  static MIN = 260;\n  static MAX = 600;\n\n  static isSupported() {\n    return \"ondragstart\" in document.createElement(\"div\") && !app.isMobile();\n  }\n\n  init() {\n    this.el.setAttribute(\"draggable\", \"true\");\n    this.appendTo($(\"._app\"));\n  }\n\n  resize(value, save) {\n    value -= app.el.offsetLeft;\n    if (!(value > 0)) {\n      return;\n    }\n    value = Math.min(Math.max(Math.round(value), Resizer.MIN), Resizer.MAX);\n    const newSize = `${value}px`;\n    document.documentElement.style.setProperty(\"--sidebarWidth\", newSize);\n    if (save) {\n      app.settings.setSize(value);\n    }\n  }\n\n  onDragStart(event) {\n    event.dataTransfer.effectAllowed = \"link\";\n    event.dataTransfer.setData(\"Text\", \"\");\n    this.onDrag = this.onDrag.bind(this);\n    $.on(window, \"dragover\", this.onDrag);\n  }\n\n  onDrag(event) {\n    const value = event.pageX;\n    if (!(value > 0)) {\n      return;\n    }\n    this.lastDragValue = value;\n    if (this.lastDrag && this.lastDrag > Date.now() - 50) {\n      return;\n    }\n    this.lastDrag = Date.now();\n    this.resize(value, false);\n  }\n\n  onDragEnd(event) {\n    $.off(window, \"dragover\", this.onDrag);\n    let value = event.pageX || event.screenX - window.screenX;\n    if (\n      this.lastDragValue &&\n      !(this.lastDragValue - 5 < value && value < this.lastDragValue + 5)\n    ) {\n      // https://github.com/freeCodeCamp/devdocs/issues/265\n      value = this.lastDragValue;\n    }\n    this.resize(value, true);\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/layout/settings.js",
    "content": "app.views.Settings = class Settings extends app.View {\n  static SIDEBAR_HIDDEN_LAYOUT = \"_sidebar-hidden\";\n\n  static el = \"._settings\";\n\n  static elements = {\n    sidebar: \"._sidebar\",\n    saveBtn: 'button[type=\"submit\"]',\n    backBtn: \"button[data-back]\",\n  };\n\n  static events = {\n    import: \"onImport\",\n    change: \"onChange\",\n    submit: \"onSubmit\",\n    click: \"onClick\",\n  };\n\n  static shortcuts = { enter: \"onEnter\" };\n\n  init() {\n    this.addSubview((this.docPicker = new app.views.DocPicker()));\n  }\n\n  activate() {\n    if (super.activate(...arguments)) {\n      this.render();\n      document.body.classList.remove(Settings.SIDEBAR_HIDDEN_LAYOUT);\n    }\n  }\n\n  deactivate() {\n    if (super.deactivate(...arguments)) {\n      this.resetClass();\n      this.docPicker.detach();\n      if (app.settings.hasLayout(Settings.SIDEBAR_HIDDEN_LAYOUT)) {\n        document.body.classList.add(Settings.SIDEBAR_HIDDEN_LAYOUT);\n      }\n    }\n  }\n\n  render() {\n    this.docPicker.appendTo(this.sidebar);\n    this.refreshElements();\n    this.addClass(\"_in\");\n  }\n\n  save(options) {\n    if (options == null) {\n      options = {};\n    }\n    if (!this.saving) {\n      let docs;\n      this.saving = true;\n\n      if (options.import) {\n        docs = app.settings.getDocs();\n      } else {\n        docs = this.docPicker.getSelectedDocs();\n        app.settings.setDocs(docs);\n      }\n\n      this.saveBtn.textContent = \"Saving\\u2026\";\n      const disabledDocs = new app.collections.Docs(\n        (() => {\n          const result = [];\n          for (var doc of app.docs.all()) {\n            if (!docs.includes(doc.slug)) {\n              result.push(doc);\n            }\n          }\n          return result;\n        })(),\n      );\n      disabledDocs.uninstall(() => {\n        app.db.migrate();\n        return app.reload();\n      });\n    }\n  }\n\n  onChange() {\n    this.addClass(\"_dirty\");\n  }\n\n  onEnter() {\n    this.save();\n  }\n\n  onSubmit(event) {\n    event.preventDefault();\n    this.save();\n  }\n\n  onImport() {\n    this.addClass(\"_dirty\");\n    this.save({ import: true });\n  }\n\n  onClick(event) {\n    if (event.which !== 1) {\n      return;\n    }\n    if (event.target === this.backBtn) {\n      $.stopEvent(event);\n      app.router.show(\"/\");\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/list/list_focus.js",
    "content": "app.views.ListFocus = class ListFocus extends app.View {\n  static activeClass = \"focus\";\n\n  static events = { click: \"onClick\" };\n\n  static shortcuts = {\n    up: \"onUp\",\n    down: \"onDown\",\n    left: \"onLeft\",\n    enter: \"onEnter\",\n    superEnter: \"onSuperEnter\",\n    escape: \"blur\",\n  };\n\n  constructor(el) {\n    super(el);\n    this.focusOnNextFrame = (el) => requestAnimationFrame(() => this.focus(el));\n  }\n\n  focus(el, options) {\n    if (options == null) {\n      options = {};\n    }\n    if (el && !el.classList.contains(this.constructor.activeClass)) {\n      this.blur();\n      el.classList.add(this.constructor.activeClass);\n      if (options.silent !== true) {\n        $.trigger(el, \"focus\");\n      }\n    }\n  }\n\n  blur() {\n    const cursor = this.getCursor();\n    if (cursor) {\n      cursor.classList.remove(this.constructor.activeClass);\n      $.trigger(cursor, \"blur\");\n    }\n  }\n\n  getCursor() {\n    return (\n      this.findByClass(this.constructor.activeClass) ||\n      this.findByClass(app.views.ListSelect.activeClass)\n    );\n  }\n\n  findNext(cursor) {\n    const next = cursor.nextSibling;\n    if (next) {\n      if (next.tagName === \"A\") {\n        return next;\n      } else if (next.tagName === \"SPAN\") {\n        // pagination link\n        $.click(next);\n        return this.findNext(cursor);\n      } else if (next.tagName === \"DIV\") {\n        // sub-list\n        if (cursor.className.includes(\" open\")) {\n          return this.findFirst(next) || this.findNext(next);\n        } else {\n          return this.findNext(next);\n        }\n      } else if (next.tagName === \"H6\") {\n        // title\n        return this.findNext(next);\n      }\n    } else if (cursor.parentNode !== this.el) {\n      return this.findNext(cursor.parentNode);\n    }\n  }\n\n  findFirst(cursor) {\n    const first = cursor.firstChild;\n    if (!first) {\n      return;\n    }\n\n    if (first.tagName === \"A\") {\n      return first;\n    } else if (first.tagName === \"SPAN\") {\n      // pagination link\n      $.click(first);\n      return this.findFirst(cursor);\n    }\n  }\n\n  findPrev(cursor) {\n    const prev = cursor.previousSibling;\n    if (prev) {\n      if (prev.tagName === \"A\") {\n        return prev;\n      } else if (prev.tagName === \"SPAN\") {\n        // pagination link\n        $.click(prev);\n        return this.findPrev(cursor);\n      } else if (prev.tagName === \"DIV\") {\n        // sub-list\n        if (prev.previousSibling.className.includes(\"open\")) {\n          return this.findLast(prev) || this.findPrev(prev);\n        } else {\n          return this.findPrev(prev);\n        }\n      } else if (prev.tagName === \"H6\") {\n        // title\n        return this.findPrev(prev);\n      }\n    } else if (cursor.parentNode !== this.el) {\n      return this.findPrev(cursor.parentNode);\n    }\n  }\n\n  findLast(cursor) {\n    const last = cursor.lastChild;\n    if (!last) {\n      return;\n    }\n\n    if (last.tagName === \"A\") {\n      return last;\n    } else if (last.tagName === \"SPAN\" || last.tagName === \"H6\") {\n      // pagination link or title\n      return this.findPrev(last);\n    } else if (last.tagName === \"DIV\") {\n      // sub-list\n      return this.findLast(last);\n    }\n  }\n\n  onDown() {\n    const cursor = this.getCursor();\n    if (cursor) {\n      this.focusOnNextFrame(this.findNext(cursor));\n    } else {\n      this.focusOnNextFrame(this.findByTag(\"a\"));\n    }\n  }\n\n  onUp() {\n    const cursor = this.getCursor();\n    if (cursor) {\n      this.focusOnNextFrame(this.findPrev(cursor));\n    } else {\n      this.focusOnNextFrame(this.findLastByTag(\"a\"));\n    }\n  }\n\n  onLeft() {\n    const cursor = this.getCursor();\n    if (\n      cursor &&\n      !cursor.classList.contains(app.views.ListFold.activeClass) &&\n      cursor.parentNode !== this.el\n    ) {\n      const prev = cursor.parentNode.previousSibling;\n      if (prev && prev.classList.contains(app.views.ListFold.targetClass)) {\n        this.focusOnNextFrame(cursor.parentNode.previousSibling);\n      }\n    }\n  }\n\n  onEnter() {\n    const cursor = this.getCursor();\n    if (cursor) {\n      $.click(cursor);\n    }\n  }\n\n  onSuperEnter() {\n    const cursor = this.getCursor();\n    if (cursor) {\n      $.popup(cursor);\n    }\n  }\n\n  onClick(event) {\n    if (event.which !== 1 || event.metaKey || event.ctrlKey) {\n      return;\n    }\n    const target = $.eventTarget(event);\n    if (target.tagName === \"A\") {\n      this.focus(target, { silent: true });\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/list/list_fold.js",
    "content": "app.views.ListFold = class ListFold extends app.View {\n  static targetClass = \"_list-dir\";\n  static handleClass = \"_list-arrow\";\n  static activeClass = \"open\";\n\n  static events = { click: \"onClick\" };\n\n  static shortcuts = {\n    left: \"onLeft\",\n    right: \"onRight\",\n  };\n\n  open(el) {\n    if (el && !el.classList.contains(this.constructor.activeClass)) {\n      el.classList.add(this.constructor.activeClass);\n      $.trigger(el, \"open\");\n    }\n  }\n\n  close(el) {\n    if (el && el.classList.contains(this.constructor.activeClass)) {\n      el.classList.remove(this.constructor.activeClass);\n      $.trigger(el, \"close\");\n    }\n  }\n\n  toggle(el) {\n    if (el.classList.contains(this.constructor.activeClass)) {\n      this.close(el);\n    } else {\n      this.open(el);\n    }\n  }\n\n  reset() {\n    let el;\n    while ((el = this.findByClass(this.constructor.activeClass))) {\n      this.close(el);\n    }\n  }\n\n  getCursor() {\n    return (\n      this.findByClass(app.views.ListFocus.activeClass) ||\n      this.findByClass(app.views.ListSelect.activeClass)\n    );\n  }\n\n  onLeft() {\n    const cursor = this.getCursor();\n    if (cursor?.classList?.contains(this.constructor.activeClass)) {\n      this.close(cursor);\n    }\n  }\n\n  onRight() {\n    const cursor = this.getCursor();\n    if (\n      cursor != null\n        ? cursor.classList.contains(this.constructor.targetClass)\n        : undefined\n    ) {\n      this.open(cursor);\n    }\n  }\n\n  onClick(event) {\n    if (event.which !== 1 || event.metaKey || event.ctrlKey) {\n      return;\n    }\n    if (!event.pageY) {\n      return;\n    } // ignore fabricated clicks\n    let el = $.eventTarget(event);\n    if (el.parentNode.tagName.toUpperCase() === \"SVG\") {\n      el = el.parentNode;\n    }\n\n    if (el.classList.contains(this.constructor.handleClass)) {\n      $.stopEvent(event);\n      this.toggle(el.parentNode);\n    } else if (el.classList.contains(this.constructor.targetClass)) {\n      if (el.hasAttribute(\"href\")) {\n        if (el.classList.contains(this.constructor.activeClass)) {\n          if (el.classList.contains(app.views.ListSelect.activeClass)) {\n            this.close(el);\n          }\n        } else {\n          this.open(el);\n        }\n      } else {\n        this.toggle(el);\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/list/list_select.js",
    "content": "app.views.ListSelect = class ListSelect extends app.View {\n  static activeClass = \"active\";\n\n  static events = { click: \"onClick\" };\n\n  deactivate() {\n    if (super.deactivate(...arguments)) {\n      this.deselect();\n    }\n  }\n\n  select(el) {\n    this.deselect();\n    if (el) {\n      el.classList.add(this.constructor.activeClass);\n      $.trigger(el, \"select\");\n    }\n  }\n\n  deselect() {\n    const selection = this.getSelection();\n    if (selection) {\n      selection.classList.remove(this.constructor.activeClass);\n      $.trigger(selection, \"deselect\");\n    }\n  }\n\n  selectByHref(href) {\n    if (this.getSelection()?.getAttribute(\"href\") !== href) {\n      this.select(this.find(`a[href='${href}']`));\n    }\n  }\n\n  selectCurrent() {\n    this.selectByHref(location.pathname + location.hash);\n  }\n\n  getSelection() {\n    return this.findByClass(this.constructor.activeClass);\n  }\n\n  onClick(event) {\n    if (event.which !== 1 || event.metaKey || event.ctrlKey) {\n      return;\n    }\n    const target = $.eventTarget(event);\n    if (target.tagName === \"A\") {\n      this.select(target);\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/list/paginated_list.js",
    "content": "app.views.PaginatedList = class PaginatedList extends app.View {\n  static PER_PAGE = app.config.max_results;\n\n  constructor(data) {\n    super();\n    this.data = data;\n    this.constructor.events = this.constructor.events || {};\n    if (this.constructor.events.click == null) {\n      this.constructor.events.click = \"onClick\";\n    }\n  }\n\n  renderPaginated() {\n    this.page = 0;\n\n    if (this.totalPages() > 1) {\n      this.paginateNext();\n    } else {\n      this.html(this.renderAll());\n    }\n  }\n\n  // render: (dataSlice) -> implemented by subclass\n\n  renderAll() {\n    return this.render(this.data);\n  }\n\n  renderPage(page) {\n    return this.render(\n      this.data.slice(\n        (page - 1) * PaginatedList.PER_PAGE,\n        page * PaginatedList.PER_PAGE,\n      ),\n    );\n  }\n\n  renderPageLink(count) {\n    return this.tmpl(\"sidebarPageLink\", count);\n  }\n\n  renderPrevLink(page) {\n    return this.renderPageLink((page - 1) * PaginatedList.PER_PAGE);\n  }\n\n  renderNextLink(page) {\n    return this.renderPageLink(\n      this.data.length - page * PaginatedList.PER_PAGE,\n    );\n  }\n\n  totalPages() {\n    return Math.ceil(this.data.length / PaginatedList.PER_PAGE);\n  }\n\n  paginate(link) {\n    $.lockScroll(link.nextSibling || link.previousSibling, () => {\n      $.batchUpdate(this.el, () => {\n        if (link.nextSibling) {\n          this.paginatePrev(link);\n        } else {\n          this.paginateNext(link);\n        }\n      });\n    });\n  }\n\n  paginateNext() {\n    if (this.el.lastChild) {\n      this.remove(this.el.lastChild);\n    } // remove link\n    if (this.page >= 2) {\n      this.hideTopPage();\n    } // keep previous page into view\n    this.page++;\n    this.append(this.renderPage(this.page));\n    if (this.page < this.totalPages()) {\n      this.append(this.renderNextLink(this.page));\n    }\n  }\n\n  paginatePrev() {\n    this.remove(this.el.firstChild); // remove link\n    this.hideBottomPage();\n    this.page--;\n    this.prepend(this.renderPage(this.page - 1)); // previous page is offset by one\n    if (this.page >= 3) {\n      this.prepend(this.renderPrevLink(this.page - 1));\n    }\n  }\n\n  paginateTo(object) {\n    const index = this.data.indexOf(object);\n    if (index >= PaginatedList.PER_PAGE) {\n      for (\n        let i = 0, end = Math.floor(index / PaginatedList.PER_PAGE);\n        i < end;\n        i++\n      ) {\n        this.paginateNext();\n      }\n    }\n  }\n\n  hideTopPage() {\n    const n =\n      this.page <= 2 ? PaginatedList.PER_PAGE : PaginatedList.PER_PAGE + 1; // remove link\n    for (let i = 0, end = n; i < end; i++) {\n      this.remove(this.el.firstChild);\n    }\n    this.prepend(this.renderPrevLink(this.page));\n  }\n\n  hideBottomPage() {\n    const n =\n      this.page === this.totalPages()\n        ? this.data.length % PaginatedList.PER_PAGE || PaginatedList.PER_PAGE\n        : PaginatedList.PER_PAGE + 1; // remove link\n    for (let i = 0, end = n; i < end; i++) {\n      this.remove(this.el.lastChild);\n    }\n    this.append(this.renderNextLink(this.page - 1));\n  }\n\n  onClick(event) {\n    const target = $.eventTarget(event);\n    if (target.tagName === \"SPAN\") {\n      // link\n      $.stopEvent(event);\n      this.paginate(target);\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/misc/news.js",
    "content": "//= require views/misc/notif\n\napp.views.News = class News extends app.views.Notif {\n  static className = \"_notif _notif-news\";\n\n  static defaultOptions = { autoHide: 30000 };\n\n  init0() {\n    this.unreadNews = this.getUnreadNews();\n    if (this.unreadNews.length) {\n      this.show();\n    }\n    this.markAllAsRead();\n  }\n\n  render() {\n    this.html(app.templates.notifNews(this.unreadNews));\n  }\n\n  getUnreadNews() {\n    const time = this.getLastReadTime();\n    if (!time) {\n      return [];\n    }\n\n    const result = [];\n    for (var news of app.news) {\n      if (new Date(news[0]).getTime() <= time) {\n        break;\n      }\n      result.push(news);\n    }\n    return result;\n  }\n\n  getLastNewsTime() {\n    return new Date(app.news[0][0]).getTime();\n  }\n\n  getLastReadTime() {\n    return app.settings.get(\"news\");\n  }\n\n  markAllAsRead() {\n    app.settings.set(\"news\", this.getLastNewsTime());\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/misc/notice.js",
    "content": "app.views.Notice = class Notice extends app.View {\n  static className = \"_notice\";\n  static attributes = { role: \"alert\" };\n\n  constructor(type, ...args) {\n    super();\n    this.type = type;\n    this.args = args || [];\n    this.init0(); // needs this.args\n    this.refreshElements();\n  }\n\n  init0() {\n    this.activate();\n  }\n\n  activate() {\n    if (super.activate(...arguments)) {\n      this.show();\n    }\n  }\n\n  deactivate() {\n    if (super.deactivate(...arguments)) {\n      this.hide();\n    }\n  }\n\n  show() {\n    this.html(this.tmpl(`${this.type}Notice`, ...this.args));\n    this.prependTo(app.el);\n  }\n\n  hide() {\n    $.remove(this.el);\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/misc/notif.js",
    "content": "app.views.Notif = class Notif extends app.View {\n  static className = \"_notif\";\n  static activeClass = \"_in\";\n  static attributes = { role: \"alert\" };\n\n  static defaultOptions = { autoHide: 15000 };\n\n  static events = { click: \"onClick\" };\n\n  constructor(type, options) {\n    super();\n    this.type = type;\n    this.options = { ...this.constructor.defaultOptions, ...(options || {}) };\n    this.init0(); // needs this.options\n    this.refreshElements();\n  }\n\n  init0() {\n    this.show();\n  }\n\n  show() {\n    if (this.timeout) {\n      clearTimeout(this.timeout);\n      this.timeout = this.delay(this.hide, this.options.autoHide);\n    } else {\n      this.render();\n      this.position();\n      this.activate();\n      this.appendTo(document.body);\n      this.el.offsetWidth; // force reflow\n      this.addClass(this.constructor.activeClass);\n      if (this.options.autoHide) {\n        this.timeout = this.delay(this.hide, this.options.autoHide);\n      }\n    }\n  }\n\n  hide() {\n    clearTimeout(this.timeout);\n    this.timeout = null;\n    this.detach();\n  }\n\n  render() {\n    this.html(this.tmpl(`notif${this.type}`));\n  }\n\n  position() {\n    const notifications = $$(`.${Notif.className}`);\n    if (notifications.length) {\n      const lastNotif = notifications[notifications.length - 1];\n      this.el.style.top =\n        lastNotif.offsetTop + lastNotif.offsetHeight + 16 + \"px\";\n    }\n  }\n\n  onClick(event) {\n    if (event.which !== 1) {\n      return;\n    }\n    const target = $.eventTarget(event);\n    if (target.hasAttribute(\"data-behavior\")) {\n      return;\n    }\n    if (target.tagName !== \"A\" || target.classList.contains(\"_notif-close\")) {\n      $.stopEvent(event);\n      this.hide();\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/misc/tip.js",
    "content": "//= require views/misc/notif\n\napp.views.Tip = class Tip extends app.views.Notif {\n  static className = \"_notif _notif-tip\";\n\n  static defautOptions = { autoHide: false };\n\n  render() {\n    this.html(this.tmpl(`tip${this.type}`));\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/misc/updates.js",
    "content": "//= require views/misc/notif\n\napp.views.Updates = class Updates extends app.views.Notif {\n  static className = \"_notif _notif-news\";\n\n  static defautOptions = { autoHide: 30000 };\n\n  init0() {\n    this.lastUpdateTime = this.getLastUpdateTime();\n    this.updatedDocs = this.getUpdatedDocs();\n    this.updatedDisabledDocs = this.getUpdatedDisabledDocs();\n    if (this.updatedDocs.length > 0 || this.updatedDisabledDocs.length > 0) {\n      this.show();\n    }\n    this.markAllAsRead();\n  }\n\n  render() {\n    this.html(\n      app.templates.notifUpdates(this.updatedDocs, this.updatedDisabledDocs),\n    );\n  }\n\n  getUpdatedDocs() {\n    if (!this.lastUpdateTime) {\n      return [];\n    }\n    return Array.from(app.docs.all()).filter(\n      (doc) => doc.mtime > this.lastUpdateTime,\n    );\n  }\n\n  getUpdatedDisabledDocs() {\n    if (!this.lastUpdateTime) {\n      return [];\n    }\n    const result = [];\n    for (var doc of Array.from(app.disabledDocs.all())) {\n      if (\n        doc.mtime > this.lastUpdateTime &&\n        app.docs.findBy(\"slug_without_version\", doc.slug_without_version)\n      ) {\n        result.push(doc);\n      }\n    }\n    return result;\n  }\n\n  getLastUpdateTime() {\n    return app.settings.get(\"version\");\n  }\n\n  markAllAsRead() {\n    app.settings.set(\n      \"version\",\n      app.config.env === \"production\"\n        ? app.config.version\n        : Math.floor(Date.now() / 1000),\n    );\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/pages/base.js",
    "content": "app.views.BasePage = class BasePage extends app.View {\n  constructor(el, entry) {\n    super(el);\n    this.entry = entry;\n  }\n\n  deactivate() {\n    if (super.deactivate(...arguments)) {\n      return (this.highlightNodes = []);\n    }\n  }\n\n  render(content, fromCache) {\n    if (fromCache == null) {\n      fromCache = false;\n    }\n    this.highlightNodes = [];\n    this.previousTiming = null;\n    if (!this.constructor.className) {\n      this.addClass(`_${this.entry.doc.type}`);\n    }\n    this.html(content);\n    if (!fromCache) {\n      this.highlightCode();\n    }\n    this.activate();\n    if (this.afterRender) {\n      this.delay(this.afterRender);\n    }\n    if (this.highlightNodes.length > 0) {\n      requestAnimationFrame(() => this.paintCode());\n    }\n  }\n\n  highlightCode() {\n    for (var el of this.findAll(\"pre[data-language]\")) {\n      var language = el.getAttribute(\"data-language\");\n      el.classList.add(`language-${language}`);\n      this.highlightNodes.push(el);\n    }\n  }\n\n  paintCode(timing) {\n    if (this.previousTiming) {\n      if (Math.round(1000 / (timing - this.previousTiming)) > 50) {\n        // fps\n        this.nodesPerFrame = Math.round(\n          Math.min(this.nodesPerFrame * 1.25, 50),\n        );\n      } else {\n        this.nodesPerFrame = Math.round(Math.max(this.nodesPerFrame * 0.8, 10));\n      }\n    } else {\n      this.nodesPerFrame = 10;\n    }\n\n    for (var el of this.highlightNodes.splice(0, this.nodesPerFrame)) {\n      const clipEl = el.lastElementChild;\n      if (clipEl) {\n        $.remove(clipEl);\n      }\n      Prism.highlightElement(el);\n      if (clipEl) {\n        $.append(el, clipEl);\n      }\n    }\n\n    if (this.highlightNodes.length > 0) {\n      requestAnimationFrame(() => this.paintCode());\n    }\n    this.previousTiming = timing;\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/pages/hidden.js",
    "content": "app.views.HiddenPage = class HiddenPage extends app.View {\n  static events = { click: \"onClick\" };\n\n  constructor(el, entry) {\n    super(el);\n    this.entry = entry;\n  }\n\n  init() {\n    this.notice = new app.views.Notice(\"disabledDoc\");\n    this.addSubview(this.notice);\n    this.activate();\n  }\n\n  onClick(event) {\n    const link = $.closestLink(event.target, this.el);\n    if (link) {\n      $.stopEvent(event);\n      $.popup(link);\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/pages/jquery.js",
    "content": "//= require views/pages/base\n\napp.views.JqueryPage = class JqueryPage extends app.views.BasePage {\n  static demoClassName = \"_jquery-demo\";\n\n  afterRender() {\n    // Prevent jQuery Mobile's demo iframes from scrolling the page\n    for (var iframe of this.findAllByTag(\"iframe\")) {\n      iframe.style.display = \"none\";\n      this.onIframeLoaded = this.onIframeLoaded.bind(this);\n      $.on(iframe, \"load\", this.onIframeLoaded);\n    }\n\n    return this.runExamples();\n  }\n\n  onIframeLoaded(event) {\n    event.target.style.display = \"\";\n    $.off(event.target, \"load\", this.onIframeLoaded);\n  }\n\n  runExamples() {\n    for (var el of this.findAllByClass(\"entry-example\")) {\n      try {\n        this.runExample(el);\n      } catch (error) {}\n    }\n  }\n\n  runExample(el) {\n    const source = el.getElementsByClassName(\"syntaxhighlighter\")[0];\n    if (!source || source.innerHTML.indexOf(\"!doctype\") === -1) {\n      return;\n    }\n\n    let iframe = el.getElementsByClassName(JqueryPage.demoClassName)[0];\n    if (!iframe) {\n      iframe = document.createElement(\"iframe\");\n      iframe.className = JqueryPage.demoClassName;\n      iframe.width = \"100%\";\n      iframe.height = 200;\n      el.appendChild(iframe);\n    }\n\n    const doc = iframe.contentDocument;\n    doc.write(this.fixIframeSource(source.textContent));\n    doc.close();\n  }\n\n  fixIframeSource(source) {\n    source = source.replace(\n      '\"/resources/',\n      '\"https://api.jquery.com/resources/',\n    ); // attr(), keydown()\n    source = source.replace(\n      \"</head>\",\n      `\\\n<style>\n  html, body { border: 0; margin: 0; padding: 0; }\n  body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; }\n</style>\n<script>\n  $.ajaxPrefilter(function(opt, opt2, xhr) {\n    if (opt.url.indexOf('http') !== 0) {\n      xhr.abort();\n      document.body.innerHTML = \"<p><strong>This demo cannot run inside DevDocs.</strong></p>\";\n    }\n  });\n</script>\n</head>\\\n`,\n    );\n    return source.replace(/<script>/gi, '<script nonce=\"devdocs\">');\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/pages/rdoc.js",
    "content": "//= require views/pages/base\n\napp.views.RdocPage = class RdocPage extends app.views.BasePage {\n  static events = { click: \"onClick\" };\n\n  onClick(event) {\n    if (!event.target.classList.contains(\"method-click-advice\")) {\n      return;\n    }\n    $.stopEvent(event);\n\n    const source = $(\n      \".method-source-code\",\n      event.target.closest(\".method-detail\"),\n    );\n    const isShown = source.style.display === \"block\";\n\n    source.style.display = isShown ? \"none\" : \"block\";\n    return (event.target.textContent = isShown ? \"Show source\" : \"Hide source\");\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/pages/sqlite.js",
    "content": "//= require views/pages/base\n\napp.views.SqlitePage = class SqlitePage extends app.views.BasePage {\n  static events = { click: \"onClick\" };\n\n  onClick(event) {\n    const id = event.target.getAttribute(\"data-toggle\");\n    if (!id) {\n      return;\n    }\n    const el = this.find(`#${id}`);\n    if (!el) {\n      return;\n    }\n    $.stopEvent(event);\n    if (el.style.display === \"none\") {\n      el.style.display = \"block\";\n      event.target.textContent = \"hide\";\n    } else {\n      el.style.display = \"none\";\n      event.target.textContent = \"show\";\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/pages/support_tables.js",
    "content": "//= require views/pages/base\n\napp.views.SupportTablesPage = class SupportTablesPage extends (\n  app.views.BasePage\n) {\n  static events = { click: \"onClick\" };\n\n  onClick(event) {\n    if (!event.target.classList.contains(\"show-all\")) {\n      return;\n    }\n    $.stopEvent(event);\n\n    let el = event.target;\n    while (el.tagName !== \"TABLE\") {\n      el = el.parentNode;\n    }\n    el.classList.add(\"show-all\");\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/search/search.js",
    "content": "app.views.Search = class Search extends app.View {\n  static SEARCH_PARAM = app.config.search_param;\n\n  static el = \"._search\";\n  static activeClass = \"_search-active\";\n\n  static elements = {\n    input: \"._search-input\",\n    resetLink: \"._search-clear\",\n  };\n\n  static events = {\n    input: \"onInput\",\n    click: \"onClick\",\n    submit: \"onSubmit\",\n  };\n\n  static shortcuts = {\n    typing: \"focus\",\n    altG: \"google\",\n    altS: \"stackoverflow\",\n    altD: \"duckduckgo\",\n  };\n\n  static routes = { after: \"afterRoute\" };\n\n  static HASH_RGX = new RegExp(`^#${Search.SEARCH_PARAM}=(.*)`);\n\n  init() {\n    this.addSubview((this.scope = new app.views.SearchScope(this.el)));\n\n    this.searcher = new app.Searcher();\n    this.searcher\n      .on(\"results\", (results) => this.onResults(results))\n      .on(\"end\", () => this.onEnd());\n\n    this.scope.on(\"change\", () => this.onScopeChange());\n\n    app.on(\"ready\", () => this.onReady());\n    $.on(window, \"hashchange\", () => this.searchUrl());\n    $.on(window, \"focus\", (event) => this.onWindowFocus(event));\n  }\n\n  focus() {\n    if (document.activeElement === this.input) {\n      return;\n    }\n    if (app.settings.get(\"noAutofocus\")) {\n      return;\n    }\n    this.input.focus();\n  }\n\n  autoFocus() {\n    if (app.isMobile() || $.isAndroid() || $.isIOS()) {\n      return;\n    }\n    if (document.activeElement?.tagName === \"INPUT\") {\n      return;\n    }\n    if (app.settings.get(\"noAutofocus\")) {\n      return;\n    }\n    this.input.focus();\n  }\n\n  onWindowFocus(event) {\n    if (event.target === window) {\n      return this.autoFocus();\n    }\n  }\n\n  getScopeDoc() {\n    if (this.scope.isActive()) {\n      return this.scope.getScope();\n    }\n  }\n\n  reset(force) {\n    if (force || !this.input.value) {\n      this.scope.reset();\n    }\n    this.el.reset();\n    this.onInput();\n    this.autoFocus();\n  }\n\n  onReady() {\n    this.value = \"\";\n    this.delay(this.onInput);\n  }\n\n  onInput() {\n    if (\n      this.value == null || // ignore events pre-\"ready\"\n      this.value === this.input.value\n    ) {\n      return;\n    }\n    this.value = this.input.value;\n\n    if (this.value.length) {\n      this.search();\n    } else {\n      this.clear();\n    }\n  }\n\n  search(url) {\n    if (url == null) {\n      url = false;\n    }\n    this.addClass(this.constructor.activeClass);\n    this.trigger(\"searching\");\n\n    this.hasResults = null;\n    this.flags = { urlSearch: url, initialResults: true };\n    this.searcher.find(this.scope.getScope().entries.all(), \"text\", this.value);\n  }\n\n  searchUrl() {\n    if (location.pathname === \"/\") {\n      this.scope.searchUrl();\n    } else if (!app.router.isIndex()) {\n      return;\n    }\n\n    const value = this.extractHashValue();\n    if (!value) {\n      return;\n    }\n    this.input.value = this.value = value;\n    this.input.setSelectionRange(value.length, value.length);\n    this.search(true);\n    return true;\n  }\n\n  clear() {\n    this.removeClass(this.constructor.activeClass);\n    this.trigger(\"clear\");\n  }\n\n  externalSearch(url) {\n    const value = this.value;\n    if (value) {\n      if (this.scope.name()) {\n        value = `${this.scope.name()} ${value}`;\n      }\n      $.popup(`${url}${encodeURIComponent(value)}`);\n      this.reset();\n    }\n  }\n\n  google() {\n    this.externalSearch(\"https://www.google.com/search?q=\");\n  }\n\n  stackoverflow() {\n    this.externalSearch(\"https://stackoverflow.com/search?q=\");\n  }\n\n  duckduckgo() {\n    this.externalSearch(\"https://duckduckgo.com/?t=devdocs&q=\");\n  }\n\n  onResults(results) {\n    if (results.length) {\n      this.hasResults = true;\n    }\n    this.trigger(\"results\", results, this.flags);\n    this.flags.initialResults = false;\n  }\n\n  onEnd() {\n    if (!this.hasResults) {\n      this.trigger(\"noresults\");\n    }\n  }\n\n  onClick(event) {\n    if (event.target === this.resetLink) {\n      $.stopEvent(event);\n      this.reset();\n    }\n  }\n\n  onSubmit(event) {\n    $.stopEvent(event);\n  }\n\n  onScopeChange() {\n    this.value = \"\";\n    this.onInput();\n  }\n\n  afterRoute(name, context) {\n    if (app.shortcuts.eventInProgress?.name === \"escape\") {\n      return;\n    }\n    if (!context.init && app.router.isIndex()) {\n      this.reset(true);\n    }\n    if (context.hash) {\n      this.delay(this.searchUrl);\n    }\n    requestAnimationFrame(() => this.autoFocus());\n  }\n\n  extractHashValue() {\n    const value = this.getHashValue();\n    if (value != null) {\n      app.router.replaceHash();\n      return value;\n    }\n  }\n\n  getHashValue() {\n    try {\n      return Search.HASH_RGX.exec($.urlDecode(location.hash))?.[1];\n    } catch (error) {}\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/search/search_scope.js",
    "content": "app.views.SearchScope = class SearchScope extends app.View {\n  static SEARCH_PARAM = app.config.search_param;\n\n  static elements = {\n    input: \"._search-input\",\n    tag: \"._search-tag\",\n  };\n\n  static events = {\n    click: \"onClick\",\n    keydown: \"onKeydown\",\n    textInput: \"onTextInput\",\n  };\n\n  static routes = { after: \"afterRoute\" };\n\n  static HASH_RGX = new RegExp(`^#${SearchScope.SEARCH_PARAM}=(.+?) .`);\n\n  init() {\n    this.placeholder = this.input.getAttribute(\"placeholder\");\n\n    this.searcher = new app.SynchronousSearcher({\n      fuzzy_min_length: 2,\n      max_results: 1,\n    });\n    this.searcher.on(\"results\", (results) => this.onResults(results));\n  }\n\n  getScope() {\n    return this.doc || app;\n  }\n\n  isActive() {\n    return !!this.doc;\n  }\n\n  name() {\n    return this.doc?.name;\n  }\n\n  search(value, searchDisabled) {\n    if (searchDisabled == null) {\n      searchDisabled = false;\n    }\n    if (this.doc) {\n      return;\n    }\n    this.searcher.find(app.docs.all(), \"text\", value);\n    if (!this.doc && searchDisabled) {\n      this.searcher.find(app.disabledDocs.all(), \"text\", value);\n    }\n  }\n\n  searchUrl() {\n    const value = this.extractHashValue();\n    if (value) {\n      this.search(value, true);\n    }\n  }\n\n  onResults(results) {\n    const doc = results[0];\n    if (!doc) {\n      return;\n    }\n    if (app.docs.contains(doc)) {\n      this.selectDoc(doc);\n    } else {\n      this.redirectToDoc(doc);\n    }\n  }\n\n  selectDoc(doc) {\n    const previousDoc = this.doc;\n    if (doc === previousDoc) {\n      return;\n    }\n    this.doc = doc;\n\n    this.tag.textContent = doc.fullName;\n    this.tag.style.display = \"block\";\n\n    this.input.removeAttribute(\"placeholder\");\n    this.input.value = this.input.value.slice(this.input.selectionStart);\n    this.input.style.paddingLeft = this.tag.offsetWidth + 10 + \"px\";\n\n    $.trigger(this.input, \"input\");\n    this.trigger(\"change\", this.doc, previousDoc);\n  }\n\n  redirectToDoc(doc) {\n    const { hash } = location;\n    app.router.replaceHash(\"\");\n    location.assign(doc.fullPath() + hash);\n  }\n\n  reset() {\n    if (!this.doc) {\n      return;\n    }\n    const previousDoc = this.doc;\n    this.doc = null;\n\n    this.tag.textContent = \"\";\n    this.tag.style.display = \"none\";\n\n    this.input.setAttribute(\"placeholder\", this.placeholder);\n    this.input.style.paddingLeft = \"\";\n\n    this.trigger(\"change\", null, previousDoc);\n  }\n\n  doScopeSearch(event) {\n    this.search(this.input.value.slice(0, this.input.selectionStart));\n    if (this.doc) {\n      $.stopEvent(event);\n    }\n  }\n\n  onClick(event) {\n    if (event.target === this.tag) {\n      this.reset();\n      $.stopEvent(event);\n    }\n  }\n\n  onKeydown(event) {\n    if (event.which === 8) {\n      // backspace\n      if (this.doc && this.input.selectionEnd === 0) {\n        this.reset();\n        $.stopEvent(event);\n      }\n    } else if (!this.doc && this.input.value && !$.isChromeForAndroid()) {\n      if (event.ctrlKey || event.metaKey || event.altKey || event.shiftKey) {\n        return;\n      }\n      if (\n        event.which === 9 || // tab\n        (event.which === 32 && app.isMobile())\n      ) {\n        // space\n        this.doScopeSearch(event);\n      }\n    }\n  }\n\n  onTextInput(event) {\n    if (!$.isChromeForAndroid()) {\n      return;\n    }\n    if (!this.doc && this.input.value && event.data === \" \") {\n      this.doScopeSearch(event);\n    }\n  }\n\n  extractHashValue() {\n    const value = this.getHashValue();\n    if (value) {\n      const newHash = $.urlDecode(location.hash).replace(\n        `#${SearchScope.SEARCH_PARAM}=${value} `,\n        `#${SearchScope.SEARCH_PARAM}=`\n      );\n      app.router.replaceHash(newHash);\n      return value;\n    }\n  }\n\n  getHashValue() {\n    try {\n      return SearchScope.HASH_RGX.exec($.urlDecode(location.hash))?.[1];\n    } catch (error) {}\n  }\n\n  afterRoute(name, context) {\n    if (!app.isSingleDoc() && context.init && context.doc) {\n      this.selectDoc(context.doc);\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/sidebar/doc_list.js",
    "content": "app.views.DocList = class DocList extends app.View {\n  static className = \"_list\";\n  static attributes = { role: \"navigation\" };\n\n  static events = {\n    open: \"onOpen\",\n    close: \"onClose\",\n    click: \"onClick\",\n  };\n\n  static routes = { after: \"afterRoute\" };\n\n  static elements = {\n    disabledTitle: \"._list-title\",\n    disabledList: \"._disabled-list\",\n  };\n\n  init() {\n    this.lists = {};\n\n    this.addSubview((this.listFocus = new app.views.ListFocus(this.el)));\n    this.addSubview((this.listFold = new app.views.ListFold(this.el)));\n    this.addSubview((this.listSelect = new app.views.ListSelect(this.el)));\n\n    app.on(\"ready\", () => this.render());\n  }\n\n  activate() {\n    if (super.activate(...arguments)) {\n      for (var slug in this.lists) {\n        var list = this.lists[slug];\n        list.activate();\n      }\n      this.listSelect.selectCurrent();\n    }\n  }\n\n  deactivate() {\n    if (super.deactivate(...arguments)) {\n      for (var slug in this.lists) {\n        var list = this.lists[slug];\n        list.deactivate();\n      }\n    }\n  }\n\n  render() {\n    let html = \"\";\n    for (var doc of app.docs.all()) {\n      html += this.tmpl(\"sidebarDoc\", doc, {\n        fullName: app.docs.countAllBy(\"name\", doc.name) > 1,\n      });\n    }\n    this.html(html);\n    if (!app.isSingleDoc() && app.disabledDocs.size() !== 0) {\n      this.renderDisabled();\n    }\n  }\n\n  renderDisabled() {\n    this.append(\n      this.tmpl(\"sidebarDisabled\", { count: app.disabledDocs.size() })\n    );\n    this.refreshElements();\n    this.renderDisabledList();\n  }\n\n  renderDisabledList() {\n    if (app.settings.get(\"hideDisabled\")) {\n      this.removeDisabledList();\n    } else {\n      this.appendDisabledList();\n    }\n  }\n\n  appendDisabledList() {\n    let doc;\n    let html = \"\";\n    const docs = [].concat(...(app.disabledDocs.all() || []));\n\n    while ((doc = docs.shift())) {\n      if (doc.version != null) {\n        var versions = \"\";\n        while (true) {\n          versions += this.tmpl(\"sidebarDoc\", doc, { disabled: true });\n          if (docs[0]?.name !== doc.name) {\n            break;\n          }\n          doc = docs.shift();\n        }\n        html += this.tmpl(\"sidebarDisabledVersionedDoc\", doc, versions);\n      } else {\n        html += this.tmpl(\"sidebarDoc\", doc, { disabled: true });\n      }\n    }\n\n    this.append(this.tmpl(\"sidebarDisabledList\", html));\n    this.disabledTitle.classList.add(\"open-title\");\n    this.refreshElements();\n  }\n\n  removeDisabledList() {\n    if (this.disabledList) {\n      $.remove(this.disabledList);\n    }\n    this.disabledTitle.classList.remove(\"open-title\");\n    this.refreshElements();\n  }\n\n  reset(options) {\n    if (options == null) {\n      options = {};\n    }\n    this.listSelect.deselect();\n    if (this.listFocus != null) {\n      this.listFocus.blur();\n    }\n    this.listFold.reset();\n    if (options.revealCurrent || app.isSingleDoc()) {\n      this.revealCurrent();\n    }\n  }\n\n  onOpen(event) {\n    $.stopEvent(event);\n    const doc = app.docs.findBy(\"slug\", event.target.getAttribute(\"data-slug\"));\n\n    if (doc && !this.lists[doc.slug]) {\n      this.lists[doc.slug] = doc.types.isEmpty()\n        ? new app.views.EntryList(doc.entries.all())\n        : new app.views.TypeList(doc);\n      $.after(event.target, this.lists[doc.slug].el);\n    }\n  }\n\n  onClose(event) {\n    $.stopEvent(event);\n    const doc = app.docs.findBy(\"slug\", event.target.getAttribute(\"data-slug\"));\n\n    if (doc && this.lists[doc.slug]) {\n      this.lists[doc.slug].detach();\n      delete this.lists[doc.slug];\n    }\n  }\n\n  select(model) {\n    this.listSelect.selectByHref(model?.fullPath());\n  }\n\n  reveal(model) {\n    this.openDoc(model.doc);\n    if (model.type) {\n      this.openType(model.getType());\n    }\n    this.focus(model);\n    this.paginateTo(model);\n    this.scrollTo(model);\n  }\n\n  focus(model) {\n    if (this.listFocus != null) {\n      this.listFocus.focus(this.find(`a[href='${model.fullPath()}']`));\n    }\n  }\n\n  revealCurrent() {\n    const model = app.router.context.type || app.router.context.entry;\n    if (model) {\n      this.reveal(model);\n      this.select(model);\n    }\n  }\n\n  openDoc(doc) {\n    if (app.disabledDocs.contains(doc) && doc.version) {\n      this.listFold.open(\n        this.find(`[data-slug='${doc.slug_without_version}']`),\n      );\n    }\n    this.listFold.open(this.find(`[data-slug='${doc.slug}']`));\n  }\n\n  closeDoc(doc) {\n    this.listFold.close(this.find(`[data-slug='${doc.slug}']`));\n  }\n\n  openType(type) {\n    this.listFold.open(\n      this.lists[type.doc.slug].find(`[data-slug='${type.slug}']`),\n    );\n  }\n\n  paginateTo(model) {\n    if (this.lists[model.doc.slug] != null) {\n      this.lists[model.doc.slug].paginateTo(model);\n    }\n  }\n\n  scrollTo(model) {\n    $.scrollTo(this.find(`a[href='${model.fullPath()}']`), null, \"top\", {\n      margin: app.isMobile() ? 48 : 0,\n    });\n  }\n\n  toggleDisabled() {\n    if (this.disabledTitle.classList.contains(\"open-title\")) {\n      this.removeDisabledList();\n      app.settings.set(\"hideDisabled\", true);\n    } else {\n      this.appendDisabledList();\n      app.settings.set(\"hideDisabled\", false);\n    }\n  }\n\n  onClick(event) {\n    const target = $.eventTarget(event);\n    if (\n      this.disabledTitle &&\n      $.hasChild(this.disabledTitle, target) &&\n      target.tagName !== \"A\"\n    ) {\n      $.stopEvent(event);\n      this.toggleDisabled();\n      return;\n    }\n    const slug = target.getAttribute(\"data-enable\");\n    if (slug) {\n      $.stopEvent(event);\n      const doc = app.disabledDocs.findBy(\"slug\", slug);\n      if (doc) {\n        this.onEnabled = this.onEnabled.bind(this);\n        app.enableDoc(doc, this.onEnabled, this.onEnabled);\n      }\n    }\n  }\n\n  onEnabled() {\n    this.reset();\n    this.render();\n  }\n\n  afterRoute(route, context) {\n    if (context.init) {\n      if (this.activated) {\n        this.reset({ revealCurrent: true });\n      }\n    } else {\n      this.select(context.type || context.entry);\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/sidebar/doc_picker.js",
    "content": "app.views.DocPicker = class DocPicker extends app.View {\n  static className = \"_list _list-picker\";\n\n  static events = {\n    mousedown: \"onMouseDown\",\n    mouseup: \"onMouseUp\",\n  };\n\n  init() {\n    this.addSubview((this.listFold = new app.views.ListFold(this.el)));\n  }\n\n  activate() {\n    if (super.activate(...arguments)) {\n      this.render();\n      this.onDOMFocus = this.onDOMFocus.bind(this);\n      $.on(this.el, \"focus\", this.onDOMFocus, true);\n    }\n  }\n\n  deactivate() {\n    if (super.deactivate(...arguments)) {\n      this.empty();\n      $.off(this.el, \"focus\", this.onDOMFocus, true);\n      this.focusEl = null;\n    }\n  }\n\n  render() {\n    let doc;\n    let html = this.tmpl(\"docPickerHeader\");\n    let docs = app.docs.all().concat(...(app.disabledDocs.all() || []));\n\n    while ((doc = docs.shift())) {\n      if (doc.version != null) {\n        var versions;\n        [docs, versions] = this.extractVersions(docs, doc);\n        html += this.tmpl(\n          \"sidebarVersionedDoc\",\n          doc,\n          this.renderVersions(versions),\n          { open: app.docs.contains(doc) },\n        );\n      } else {\n        html += this.tmpl(\"sidebarLabel\", doc, {\n          checked: app.docs.contains(doc),\n        });\n      }\n    }\n\n    this.html(html + this.tmpl(\"docPickerNote\"));\n\n    requestAnimationFrame(() => this.findByTag(\"input\")?.focus());\n  }\n\n  renderVersions(docs) {\n    let html = \"\";\n    for (var doc of docs) {\n      html += this.tmpl(\"sidebarLabel\", doc, {\n        checked: app.docs.contains(doc),\n      });\n    }\n    return html;\n  }\n\n  extractVersions(originalDocs, version) {\n    const docs = [];\n    const versions = [version];\n    for (var doc of originalDocs) {\n      (doc.name === version.name ? versions : docs).push(doc);\n    }\n    return [docs, versions];\n  }\n\n  empty() {\n    this.resetClass();\n    super.empty(...arguments);\n  }\n\n  getSelectedDocs() {\n    return [...this.findAllByTag(\"input\")]\n      .filter((input) => input?.checked)\n      .map((input) => input.name);\n  }\n\n  onMouseDown() {\n    this.mouseDown = Date.now();\n  }\n\n  onMouseUp() {\n    this.mouseUp = Date.now();\n  }\n\n  onDOMFocus(event) {\n    const { target } = event;\n    if (target.tagName === \"INPUT\") {\n      if (\n        (!this.mouseDown || !(Date.now() < this.mouseDown + 100)) &&\n        (!this.mouseUp || !(Date.now() < this.mouseUp + 100))\n      ) {\n        $.scrollTo(target.parentNode, null, \"continuous\");\n      }\n    } else if (target.classList.contains(app.views.ListFold.targetClass)) {\n      target.blur();\n      if (!this.mouseDown || !(Date.now() < this.mouseDown + 100)) {\n        if (this.focusEl === $(\"input\", target.nextElementSibling)) {\n          if (target.classList.contains(app.views.ListFold.activeClass)) {\n            this.listFold.close(target);\n          }\n          let prev = target.previousElementSibling;\n          while (\n            prev.tagName !== \"LABEL\" &&\n            !prev.classList.contains(app.views.ListFold.targetClass)\n          ) {\n            prev = prev.previousElementSibling;\n          }\n          if (prev.classList.contains(app.views.ListFold.activeClass)) {\n            prev = $.makeArray($$(\"input\", prev.nextElementSibling)).pop();\n          }\n          this.delay(() => prev.focus());\n        } else {\n          if (!target.classList.contains(app.views.ListFold.activeClass)) {\n            this.listFold.open(target);\n          }\n          this.delay(() => $(\"input\", target.nextElementSibling).focus());\n        }\n      }\n    }\n    this.focusEl = target;\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/sidebar/entry_list.js",
    "content": "//= require views/list/paginated_list\n\napp.views.EntryList = class EntryList extends app.views.PaginatedList {\n  static tagName = \"div\";\n  static className = \"_list _list-sub\";\n\n  constructor(entries) {\n    super(...arguments);\n    this.entries = entries;\n    this.init0(); // needs this.data from PaginatedList\n    this.refreshElements();\n  }\n\n  init0() {\n    this.renderPaginated();\n    this.activate();\n  }\n\n  render(entries) {\n    return this.tmpl(\"sidebarEntry\", entries);\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/sidebar/results.js",
    "content": "app.views.Results = class Results extends app.View {\n  static className = \"_list\";\n\n  static events = { click: \"onClick\" };\n\n  static routes = { after: \"afterRoute\" };\n\n  constructor(sidebar, search) {\n    super();\n    this.sidebar = sidebar;\n    this.search = search;\n    this.init0(); // needs this.search\n    this.refreshElements();\n  }\n\n  deactivate() {\n    if (super.deactivate(...arguments)) {\n      this.empty();\n    }\n  }\n\n  init0() {\n    this.addSubview((this.listFocus = new app.views.ListFocus(this.el)));\n    this.addSubview((this.listSelect = new app.views.ListSelect(this.el)));\n\n    this.search\n      .on(\"results\", (entries, flags) => this.onResults(entries, flags))\n      .on(\"noresults\", () => this.onNoResults())\n      .on(\"clear\", () => this.onClear());\n  }\n\n  onResults(entries, flags) {\n    if (flags.initialResults) {\n      this.listFocus?.blur();\n    }\n    if (flags.initialResults) {\n      this.empty();\n    }\n    this.append(this.tmpl(\"sidebarResult\", entries));\n\n    if (flags.initialResults) {\n      if (flags.urlSearch) {\n        this.openFirst();\n      } else {\n        this.focusFirst();\n      }\n    }\n  }\n\n  onNoResults() {\n    this.html(this.tmpl(\"sidebarNoResults\"));\n  }\n\n  onClear() {\n    this.empty();\n  }\n\n  focusFirst() {\n    if (!app.isMobile()) {\n      this.listFocus?.focusOnNextFrame(this.el.firstElementChild);\n    }\n  }\n\n  openFirst() {\n    this.el.firstElementChild?.click();\n  }\n\n  onDocEnabled(doc) {\n    app.router.show(doc.fullPath());\n    return this.sidebar.onDocEnabled();\n  }\n\n  afterRoute(route, context) {\n    if (route === \"entry\") {\n      this.listSelect.selectByHref(context.entry.fullPath());\n    } else {\n      this.listSelect.deselect();\n    }\n  }\n\n  onClick(event) {\n    if (event.which !== 1) {\n      return;\n    }\n    const slug = $.eventTarget(event).getAttribute(\"data-enable\");\n    if (slug) {\n      $.stopEvent(event);\n      const doc = app.disabledDocs.findBy(\"slug\", slug);\n      if (doc) {\n        return app.enableDoc(doc, this.onDocEnabled.bind(this, doc), $.noop);\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/sidebar/sidebar.js",
    "content": "app.views.Sidebar = class Sidebar extends app.View {\n  static el = \"._sidebar\";\n\n  static events = {\n    focus: \"onFocus\",\n    select: \"onSelect\",\n    click: \"onClick\",\n  };\n\n  static routes = { after: \"afterRoute\" };\n\n  static shortcuts = {\n    altR: \"onAltR\",\n    escape: \"onEscape\",\n  };\n\n  init() {\n    if (!app.isMobile()) {\n      this.addSubview((this.hover = new app.views.SidebarHover(this.el)));\n    }\n    this.addSubview((this.search = new app.views.Search()));\n\n    this.search\n      .on(\"searching\", () => this.onSearching())\n      .on(\"clear\", () => this.onSearchClear())\n      .scope.on(\"change\", (newDoc, previousDoc) =>\n        this.onScopeChange((newDoc, previousDoc)),\n      );\n\n    this.results = new app.views.Results(this, this.search);\n    this.docList = new app.views.DocList();\n\n    app.on(\"ready\", () => this.onReady());\n\n    $.on(document.documentElement, \"mouseleave\", () => this.hide());\n    $.on(document.documentElement, \"mouseenter\", () =>\n      this.resetDisplay({ forceNoHover: false }),\n    );\n  }\n\n  hide() {\n    this.removeClass(\"show\");\n  }\n\n  display() {\n    this.addClass(\"show\");\n  }\n\n  resetDisplay(options) {\n    if (options == null) {\n      options = {};\n    }\n    if (!this.hasClass(\"show\")) {\n      return;\n    }\n    this.removeClass(\"show\");\n\n    if (options.forceNoHover !== false && !this.hasClass(\"no-hover\")) {\n      this.addClass(\"no-hover\");\n      this.resetHoverOnMouseMove = this.resetHoverOnMouseMove.bind(this);\n      $.on(window, \"mousemove\", this.resetHoverOnMouseMove);\n    }\n  }\n\n  resetHoverOnMouseMove() {\n    $.off(window, \"mousemove\", this.resetHoverOnMouseMove);\n    return requestAnimationFrame(() => this.resetHover());\n  }\n\n  resetHover() {\n    return this.removeClass(\"no-hover\");\n  }\n\n  showView(view) {\n    if (this.view !== view) {\n      if (this.hover != null) {\n        this.hover.hide();\n      }\n      this.saveScrollPosition();\n      if (this.view != null) {\n        this.view.deactivate();\n      }\n      this.view = view;\n      this.render();\n      this.view.activate();\n      this.restoreScrollPosition();\n    }\n  }\n\n  render() {\n    this.html(this.view);\n  }\n\n  showDocList() {\n    this.showView(this.docList);\n  }\n\n  showResults() {\n    this.display();\n    this.showView(this.results);\n  }\n\n  reset() {\n    this.display();\n    this.showDocList();\n    this.docList.reset();\n    this.search.reset();\n  }\n\n  onReady() {\n    this.view = this.docList;\n    this.render();\n    this.view.activate();\n  }\n\n  onScopeChange(newDoc, previousDoc) {\n    if (previousDoc) {\n      this.docList.closeDoc(previousDoc);\n    }\n    if (newDoc) {\n      this.docList.reveal(newDoc.toEntry());\n    } else {\n      this.scrollToTop();\n    }\n  }\n\n  saveScrollPosition() {\n    if (this.view === this.docList) {\n      this.scrollTop = this.el.scrollTop;\n    }\n  }\n\n  restoreScrollPosition() {\n    if (this.view === this.docList && this.scrollTop) {\n      this.el.scrollTop = this.scrollTop;\n      this.scrollTop = null;\n    } else {\n      this.scrollToTop();\n    }\n  }\n\n  scrollToTop() {\n    this.el.scrollTop = 0;\n  }\n\n  onSearching() {\n    this.showResults();\n  }\n\n  onSearchClear() {\n    this.resetDisplay();\n    this.showDocList();\n  }\n\n  onFocus(event) {\n    this.display();\n    if (event.target !== this.el) {\n      $.scrollTo(event.target, this.el, \"continuous\", { bottomGap: 2 });\n    }\n  }\n\n  onSelect() {\n    this.resetDisplay();\n  }\n\n  onClick(event) {\n    if (event.which !== 1) {\n      return;\n    }\n    if ($.eventTarget(event).hasAttribute?.(\"data-reset-list\")) {\n      $.stopEvent(event);\n      this.onAltR();\n    }\n  }\n\n  onAltR() {\n    this.reset();\n    this.docList.reset({ revealCurrent: true });\n    this.display();\n  }\n\n  onEscape() {\n    const doc = this.search.getScopeDoc();\n    this.reset();\n    this.resetDisplay();\n    if (doc) {\n      this.docList.reveal(doc.toEntry());\n    } else {\n      this.scrollToTop();\n    }\n  }\n\n  onDocEnabled() {\n    this.docList.onEnabled();\n    this.reset();\n  }\n\n  afterRoute(name, context) {\n    if (\n      (app.shortcuts.eventInProgress != null\n        ? app.shortcuts.eventInProgress.name\n        : undefined) === \"escape\"\n    ) {\n      return;\n    }\n    if (!context.init && app.router.isIndex()) {\n      this.reset();\n    }\n    this.resetDisplay();\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/sidebar/sidebar_hover.js",
    "content": "app.views.SidebarHover = class SidebarHover extends app.View {\n  static itemClass = \"_list-hover\";\n\n  static events = {\n    focus: \"onFocus\",\n    blur: \"onBlur\",\n    mouseover: \"onMouseover\",\n    mouseout: \"onMouseout\",\n    scroll: \"onScroll\",\n    click: \"onClick\",\n  };\n\n  static routes = { after: \"onRoute\" };\n\n  show(el) {\n    if (el !== this.cursor) {\n      this.hide();\n      if (this.isTarget(el) && this.isTruncated(el.lastElementChild || el)) {\n        this.cursor = el;\n        this.clone = this.makeClone(this.cursor);\n        $.append(document.body, this.clone);\n        if (this.offsetTop == null) {\n          this.offsetTop = this.el.offsetTop;\n        }\n        this.position();\n      }\n    }\n  }\n\n  hide() {\n    if (this.cursor) {\n      $.remove(this.clone);\n      this.cursor = this.clone = null;\n    }\n  }\n\n  position() {\n    if (this.cursor) {\n      const rect = $.rect(this.cursor);\n      if (rect.top >= this.offsetTop) {\n        this.clone.style.top = rect.top + \"px\";\n        this.clone.style.left = rect.left + \"px\";\n      } else {\n        this.hide();\n      }\n    }\n  }\n\n  makeClone(el) {\n    const clone = el.cloneNode(true);\n    clone.classList.add(\"clone\");\n    return clone;\n  }\n\n  isTarget(el) {\n    return el.classList?.contains(this.constructor.itemClass);\n  }\n\n  isSelected(el) {\n    return el.classList.contains(\"active\");\n  }\n\n  isTruncated(el) {\n    return el.scrollWidth > el.offsetWidth;\n  }\n\n  onFocus(event) {\n    this.focusTime = Date.now();\n    this.show(event.target);\n  }\n\n  onBlur() {\n    this.hide();\n  }\n\n  onMouseover(event) {\n    if (\n      this.isTarget(event.target) &&\n      !this.isSelected(event.target) &&\n      this.mouseActivated()\n    ) {\n      this.show(event.target);\n    }\n  }\n\n  onMouseout(event) {\n    if (this.isTarget(event.target) && this.mouseActivated()) {\n      this.hide();\n    }\n  }\n\n  mouseActivated() {\n    // Skip mouse events caused by focus events scrolling the sidebar.\n    return !this.focusTime || Date.now() - this.focusTime > 500;\n  }\n\n  onScroll() {\n    this.position();\n  }\n\n  onClick(event) {\n    if (event.target === this.clone) {\n      $.click(this.cursor);\n    }\n  }\n\n  onRoute() {\n    this.hide();\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/sidebar/type_list.js",
    "content": "app.views.TypeList = class TypeList extends app.View {\n  static tagName = \"div\";\n  static className = \"_list _list-sub\";\n\n  static events = {\n    open: \"onOpen\",\n    close: \"onClose\",\n  };\n\n  constructor(doc) {\n    super();\n    this.doc = doc;\n    this.init0(); // needs this.doc\n    this.refreshElements();\n  }\n\n  init0() {\n    this.lists = {};\n    this.render();\n    this.activate();\n  }\n\n  activate() {\n    if (super.activate(...arguments)) {\n      for (var slug in this.lists) {\n        var list = this.lists[slug];\n        list.activate();\n      }\n    }\n  }\n\n  deactivate() {\n    if (super.deactivate(...arguments)) {\n      for (var slug in this.lists) {\n        var list = this.lists[slug];\n        list.deactivate();\n      }\n    }\n  }\n\n  render() {\n    let html = \"\";\n    for (var group of this.doc.types.groups()) {\n      html += this.tmpl(\"sidebarType\", group);\n    }\n    return this.html(html);\n  }\n\n  onOpen(event) {\n    $.stopEvent(event);\n    const type = this.doc.types.findBy(\n      \"slug\",\n      event.target.getAttribute(\"data-slug\"),\n    );\n\n    if (type && !this.lists[type.slug]) {\n      this.lists[type.slug] = new app.views.EntryList(type.entries());\n      $.after(event.target, this.lists[type.slug].el);\n    }\n  }\n\n  onClose(event) {\n    $.stopEvent(event);\n    const type = this.doc.types.findBy(\n      \"slug\",\n      event.target.getAttribute(\"data-slug\"),\n    );\n\n    if (type && this.lists[type.slug]) {\n      this.lists[type.slug].detach();\n      delete this.lists[type.slug];\n    }\n  }\n\n  paginateTo(model) {\n    if (model.type) {\n      this.lists[model.getType().slug]?.paginateTo(model);\n    }\n  }\n};\n"
  },
  {
    "path": "assets/javascripts/views/view.js",
    "content": "app.View = class View extends Events {\n  constructor(el) {\n    super();\n    if (el instanceof HTMLElement) {\n      this.el = el;\n    }\n    this.setupElement();\n    if (this.el.className) {\n      this.originalClassName = this.el.className;\n    }\n    if (this.constructor.className) {\n      this.resetClass();\n    }\n    this.refreshElements();\n    if (typeof this.init === \"function\") {\n      this.init();\n      this.refreshElements();\n    }\n  }\n\n  setupElement() {\n    if (this.el == null) {\n      this.el =\n        typeof this.constructor.el === \"string\"\n          ? $(this.constructor.el)\n          : this.constructor.el\n            ? this.constructor.el\n            : document.createElement(this.constructor.tagName || \"div\");\n    }\n\n    if (this.constructor.attributes) {\n      for (var key in this.constructor.attributes) {\n        var value = this.constructor.attributes[key];\n        this.el.setAttribute(key, value);\n      }\n    }\n  }\n\n  refreshElements() {\n    if (this.constructor.elements) {\n      for (var name in this.constructor.elements) {\n        var selector = this.constructor.elements[name];\n        this[name] = this.find(selector);\n      }\n    }\n  }\n\n  addClass(name) {\n    this.el.classList.add(name);\n  }\n\n  removeClass(name) {\n    this.el.classList.remove(name);\n  }\n\n  toggleClass(name) {\n    this.el.classList.toggle(name);\n  }\n\n  hasClass(name) {\n    return this.el.classList.contains(name);\n  }\n\n  resetClass() {\n    this.el.className = this.originalClassName || \"\";\n    if (this.constructor.className) {\n      for (var name of Array.from(this.constructor.className.split(\" \"))) {\n        this.addClass(name);\n      }\n    }\n  }\n\n  find(selector) {\n    return $(selector, this.el);\n  }\n\n  findAll(selector) {\n    return $$(selector, this.el);\n  }\n\n  findByClass(name) {\n    return this.findAllByClass(name)[0];\n  }\n\n  findLastByClass(name) {\n    const all = this.findAllByClass(name)[0];\n    return all[all.length - 1];\n  }\n\n  findAllByClass(name) {\n    return this.el.getElementsByClassName(name);\n  }\n\n  findByTag(tag) {\n    return this.findAllByTag(tag)[0];\n  }\n\n  findLastByTag(tag) {\n    const all = this.findAllByTag(tag);\n    return all[all.length - 1];\n  }\n\n  findAllByTag(tag) {\n    return this.el.getElementsByTagName(tag);\n  }\n\n  append(value) {\n    $.append(this.el, value.el || value);\n  }\n\n  appendTo(value) {\n    $.append(value.el || value, this.el);\n  }\n\n  prepend(value) {\n    $.prepend(this.el, value.el || value);\n  }\n\n  prependTo(value) {\n    $.prepend(value.el || value, this.el);\n  }\n\n  before(value) {\n    $.before(this.el, value.el || value);\n  }\n\n  after(value) {\n    $.after(this.el, value.el || value);\n  }\n\n  remove(value) {\n    $.remove(value.el || value);\n  }\n\n  empty() {\n    $.empty(this.el);\n    this.refreshElements();\n  }\n\n  html(value) {\n    this.empty();\n    this.append(value);\n  }\n\n  tmpl(...args) {\n    return app.templates.render(...args);\n  }\n\n  delay(fn, ...args) {\n    const delay = typeof args[args.length - 1] === \"number\" ? args.pop() : 0;\n    return setTimeout(fn.bind(this, ...args), delay);\n  }\n\n  onDOM(event, callback) {\n    $.on(this.el, event, callback);\n  }\n\n  offDOM(event, callback) {\n    $.off(this.el, event, callback);\n  }\n\n  bindEvents() {\n    let method, name;\n    if (this.constructor.events) {\n      for (name in this.constructor.events) {\n        method = this.constructor.events[name];\n        this[method] = this[method].bind(this);\n        this.onDOM(name, this[method]);\n      }\n    }\n\n    if (this.constructor.routes) {\n      for (name in this.constructor.routes) {\n        method = this.constructor.routes[name];\n        this[method] = this[method].bind(this);\n        app.router.on(name, this[method]);\n      }\n    }\n\n    if (this.constructor.shortcuts) {\n      for (name in this.constructor.shortcuts) {\n        method = this.constructor.shortcuts[name];\n        this[method] = this[method].bind(this);\n        app.shortcuts.on(name, this[method]);\n      }\n    }\n  }\n\n  unbindEvents() {\n    let method, name;\n    if (this.constructor.events) {\n      for (name in this.constructor.events) {\n        method = this.constructor.events[name];\n        this.offDOM(name, this[method]);\n      }\n    }\n\n    if (this.constructor.routes) {\n      for (name in this.constructor.routes) {\n        method = this.constructor.routes[name];\n        app.router.off(name, this[method]);\n      }\n    }\n\n    if (this.constructor.shortcuts) {\n      for (name in this.constructor.shortcuts) {\n        method = this.constructor.shortcuts[name];\n        app.shortcuts.off(name, this[method]);\n      }\n    }\n  }\n\n  addSubview(view) {\n    return (this.subviews || (this.subviews = [])).push(view);\n  }\n\n  activate() {\n    if (this.activated) {\n      return;\n    }\n    this.bindEvents();\n    if (this.subviews) {\n      for (var view of Array.from(this.subviews)) {\n        view.activate();\n      }\n    }\n    this.activated = true;\n    return true;\n  }\n\n  deactivate() {\n    if (!this.activated) {\n      return;\n    }\n    this.unbindEvents();\n    if (this.subviews) {\n      for (var view of Array.from(this.subviews)) {\n        view.deactivate();\n      }\n    }\n    this.activated = false;\n    return true;\n  }\n\n  detach() {\n    this.deactivate();\n    $.remove(this.el);\n  }\n};\n"
  },
  {
    "path": "assets/stylesheets/application.css.scss",
    "content": "/*!\n * Copyright 2013-2026 Thibaut Courouble and other contributors\n *\n * This source code is licensed under the terms of the Mozilla\n * Public License, v. 2.0, a copy of which may be obtained at:\n * http://mozilla.org/MPL/2.0/\n */\n\n // SCSS partials that were formerly imported here are now invoked on an as-needed, per-file basis.\n\n@use 'components/app';\n@use 'components/header';\n@use 'components/notif';\n@use 'components/sidebar';\n@use 'components/settings';\n@use 'components/content';\n@use 'components/page';\n@use 'components/fail';\n@use 'components/path';\n@use 'components/notice';\n@use 'components/prism';\n@use 'components/mobile';\n@use 'components/environment';\n\n@use 'pages/angular';\n@use 'pages/angularjs';\n@use 'pages/apache';\n@use 'pages/async';\n@use 'pages/bash';\n@use 'pages/bootstrap';\n@use 'pages/cppref';\n@use 'pages/cakephp';\n@use 'pages/clojure';\n@use 'pages/codeception';\n@use 'pages/coffeescript';\n@use 'pages/cordova';\n@use 'pages/crystal';\n@use 'pages/cypress';\n@use 'pages/d';\n@use 'pages/d3';\n@use 'pages/dart';\n@use 'pages/dojo';\n@use 'pages/drupal';\n@use 'pages/eigen3';\n@use 'pages/elixir';\n@use 'pages/elisp';\n@use 'pages/ember';\n@use 'pages/erlang';\n@use 'pages/express';\n@use 'pages/fastapi';\n@use 'pages/fluture';\n@use 'pages/git';\n@use 'pages/github';\n@use 'pages/gnuplot';\n@use 'pages/go';\n@use 'pages/graphite';\n@use 'pages/groovy';\n@use 'pages/gtk';\n@use 'pages/hapi';\n@use 'pages/haproxy';\n@use 'pages/haskell';\n@use 'pages/jasmine';\n@use 'pages/jekyll';\n@use 'pages/joi';\n@use 'pages/jq';\n@use 'pages/jquery';\n@use 'pages/julia';\n@use 'pages/knockout';\n@use 'pages/kotlin';\n@use 'pages/kubectl';\n@use 'pages/kubernetes';\n@use 'pages/laravel';\n@use 'pages/liquid';\n@use 'pages/lit';\n@use 'pages/love';\n@use 'pages/lua';\n@use 'pages/gnu_make';\n@use 'pages/mariadb';\n@use 'pages/mdn';\n@use 'pages/meteor';\n@use 'pages/mkdocs';\n@use 'pages/modernizr';\n@use 'pages/moment';\n@use 'pages/nginx';\n@use 'pages/node';\n@use 'pages/npm';\n@use 'pages/nushell';\n@use 'pages/octave';\n@use 'pages/openjdk';\n@use 'pages/openlayers';\n@use 'pages/perl';\n@use 'pages/phalcon';\n@use 'pages/phaser';\n@use 'pages/php';\n@use 'pages/phpunit';\n@use 'pages/postgres';\n@use 'pages/pug';\n@use 'pages/pygame';\n@use 'pages/python';\n@use 'pages/qt';\n@use 'pages/ramda';\n@use 'pages/rdoc';\n@use 'pages/react';\n@use 'pages/react_native';\n@use 'pages/reactivex';\n@use 'pages/redis';\n@use 'pages/rethinkdb';\n@use 'pages/rfc';\n@use 'pages/rubydoc';\n@use 'pages/rust';\n@use 'pages/rxjs';\n@use 'pages/sanctuary';\n@use 'pages/sanctuary_def';\n@use 'pages/sanctuary_type_classes';\n@use 'pages/scala';\n@use 'pages/sinon';\n@use 'pages/sphinx';\n@use 'pages/sphinx_simple';\n@use 'pages/sqlite';\n@use 'pages/support_tables';\n@use 'pages/tailwindcss';\n@use 'pages/tcl_tk';\n@use 'pages/tensorflow';\n@use 'pages/terraform';\n@use 'pages/typescript';\n@use 'pages/underscore';\n@use 'pages/vue';\n@use 'pages/webpack';\n@use 'pages/wordpress';\n@use 'pages/yard';\n@use 'pages/yii';\n"
  },
  {
    "path": "assets/stylesheets/components/_app.scss",
    "content": "@use 'global/classes';\n\nhtml._booting { background: var(--contentBackground); }\nbody._max-width { background: none; }\nhtml._booting body._max-width { background: var(--documentBackground); }\n\n._app {\n  position: relative;\n  z-index: 1;\n  height: 100%;\n  overflow: hidden;\n  -webkit-transition: opacity .2s;\n          transition: opacity .2s;\n  @extend %border-box;\n\n  ._booting & { opacity: 0; }\n\n  ._max-width & {\n    margin: 0 auto;\n    max-width: var(--maxWidth);\n    background: var(--contentBackground);\n    box-shadow: 1px 0 var(--headerBorder), -1px 0 var(--headerBorder);\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/components/_content.scss",
    "content": "//\n// Content\n//\n\n@use 'global/base';\n@use 'global/icons';\n@use 'global/mixins' as m;\n@use 'global/classes';\n\n._container {\n  position: relative;\n  z-index: var(--contentZ);\n  height: 100%;\n  margin-left: var(--sidebarWidth);\n  pointer-events: none;\n  @extend %border-box;\n\n  @include m.mobile { margin-left: var(--sidebarMediumWidth); }\n\n  ._sidebar-hidden & { margin-left: 0; }\n  body:not(._native-scrollbars) & { -webkit-margin-end: -1px; }\n}\n\n._content {\n  position: relative;\n  height: 100%;\n  overflow-y: scroll;\n  margin-left: .875rem;\n  padding: 1.125rem 1.5rem 0;\n  font-size: .875rem;\n  pointer-events: auto;\n  -webkit-overflow-scrolling: touch;\n  @extend %border-box;\n\n  ._sidebar-hidden &:before {\n    content: '';\n    display: block;\n    margin-top: var(--headerHeight);\n  }\n\n  ._text-justify-hyphenate & {\n    text-align: justify;\n    hyphens: auto;\n  }\n\n  ._overlay-scrollbars & { padding-left: .625rem; }\n  @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { margin-left: 0; }\n  @supports (-ms-overflow-style: none) { margin-left: 0; }\n\n  body:not(._native-scrollbars) & {\n    -webkit-padding-start: .625rem;\n    -webkit-padding-end: .75rem;\n  }\n}\n\n%loading {\n  content: 'Loading\\2026';\n  position: absolute;\n  top: 50%;\n  left: 0;\n  right: 0;\n  line-height: 1;\n  margin-top: -.6em;\n  font-size: 4rem;\n  font-weight: 300;\n  letter-spacing: -.125rem;\n  color: var(--loadingText);\n  text-align: center;\n  cursor: default;\n}\n\n._content-loading:before {\n  @extend %loading;\n}\n\n//\n// Splash screen\n//\n\n._splash-title {\n  color: var(--splashText);\n  @extend %loading, %user-select-none;\n}\n\n//\n// Intro\n//\n\n._intro {\n  display: -ms-flexbox;\n  display: flex;\n  -ms-flex-direction: column;\n      flex-direction: column;\n    -ms-flex-pack: center;\n  justify-content: center;\n   -ms-flex-align: center;\n      align-items: center;\n  min-height: calc(100vh - 2.375rem);\n\n  ._sidebar-hidden & {\n    min-height: calc(100vh - 2.375rem - var(--headerHeight));\n  }\n}\n\n._intro-message {\n  max-width: 37rem;\n  margin: .5rem 0;\n  padding: 1rem 1.25rem;\n  @extend %note, %note-green;\n}\n\n._intro-hide {\n  float: right;\n  line-height: 1.5rem;\n  cursor: pointer;\n}\n\n._intro-title {\n  margin: 0 0 1rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n}\n\n._intro-list {\n  margin: 1rem 0;\n  padding-left: 2.25rem;\n}\n\n._intro-link { cursor: pointer; }\n\n//\n// Error\n//\n\n._error {\n  position: absolute;\n  top: 50%;\n  left: 0;\n  right: 0;\n  padding: 0 2rem;\n  line-height: 1.5rem;\n  text-align: center;\n}\n\n._error-title {\n  margin: -5.5rem 0 1rem;\n  line-height: 2rem;\n  font-size: 1.5rem;\n}\n\n._error-text {\n  margin: 0 0 1rem;\n  color: var(--textColorLight);\n}\n\n._error-links {\n  font-size: 1rem;\n  font-weight: var(--boldFontWeight);\n}\n\n._error-link { padding: 0 .5rem; }\n\n//\n// Heading\n//\n\n._lined-heading,\n%lined-heading {\n  display: flex;\n  align-items: center;\n\n  > * { margin: 0 .3125rem; }\n\n  &:after {\n    content: '';\n    flex-grow: 1;\n    height: 1px;\n    margin-top: .25rem;\n    margin-left: 1rem;\n    background: var(--boxBorderLight);\n  }\n}\n\n._block-heading { @extend %block-heading; }\n\n._heading-links {\n  float: right;\n  font-weight: normal;\n\n  > a + a { margin-left: .25rem; }\n}\n\n//\n// Table of contents\n//\n\n._toc {\n  float: right;\n  max-width: 15em;\n  margin: .25rem 0 1.5rem 1.5rem;\n  padding: .625rem 1rem;\n  @extend %box;\n\n  + h1, + ._lined-heading { margin-top: 0; }\n}\n\n._toc-title {\n  margin: 0 0 .5rem;\n  font-size: inherit;\n  font-weight: var(--boldFontWeight);\n}\n\n._toc-list {\n  margin: 0;\n  padding: 0 1em 0 0;\n  list-style: none;\n}\n\n._toc-link { @extend %internal-link; }\n\n//\n// Static page\n//\n\n._static {\n  padding-bottom: 2em;\n\n  > ._lined-heading:first-child { margin-top: 0; }\n}\n\n//\n// Credits table\n//\n\n._credits {\n  max-width: 100%;\n}\n\n//\n// Doc table\n//\n\n._docs {\n  width: 100%;\n  margin-top: .25rem;\n  line-height: 1.5rem;\n\n  th, td {\n    width: 1%;\n\n    &:first-child { width: auto; }\n    &:last-child { width: 12rem; }\n  }\n}\n\n._docs-name:before {\n  float: left;\n  margin: .25rem .5rem .25rem 0;\n  @extend %doc-icon;\n}\n\n._docs-size {\n  text-align: right;\n\n  > small { color: var(--textColorLight); }\n}\n\n._docs-tools {\n  display: -ms-flexbox;\n  display: flex;\n  -ms-flex-wrap: wrap;\n      flex-wrap: wrap;\n    -ms-flex-pack: justify;\n  justify-content: space-between;\n  -ms-flex-align: center;\n     align-items: center;\n  line-height: 1.5rem;\n  margin-top: -.5rem;\n\n  input[type=checkbox] {\n    vertical-align: top;\n    margin: .25rem;\n  }\n}\n\n._docs-links {\n  -ms-flex: 0 0 auto;\n      flex: 0 0 auto;\n  margin: .5rem 0;\n  padding: .25rem 0;\n  @extend %box;\n\n  ._btn-link {\n    vertical-align: top;\n    padding: 0 .75rem;\n  }\n  ._btn-link:not(._show) { display: none; }\n  ._btn-link._show ~ ._btn-link._show { border-left: 1px solid var(--boxBorder); }\n}\n\n//\n// News\n//\n\n._content {\n  ._news-row {\n    position: relative;\n    padding-left: 10em;\n    font-size: .8125rem;\n    color: var(--textColorLight);\n\n    + ._news-row { margin-top: 1em; }\n  }\n\n  ._news-title {\n    display: block;\n    font-size: .875rem;\n    color: var(--textColor);\n  }\n\n  ._news-date {\n    position: absolute;\n    top: 0;\n    left: 0;\n    font-size: .875rem;\n  }\n}\n\n//\n// Keyboard shortcuts\n//\n\n._shortcuts-title {\n  width: 16rem;\n  max-width: 40%;\n  margin: 2rem 0 1rem;\n  font-size: 1rem;\n  text-align: right;\n}\n\n._shortcuts-dl { margin: 1rem 0; }\n\n._shortcuts-dt {\n  float: left;\n  clear: left;\n  margin: 0 0 .75rem;\n  width: 16rem;\n  max-width: 40%;\n  font-weight: normal;\n  text-align: right;\n}\n\n._shortcuts-dd {\n  display: block;\n  margin: 0 0 .75rem;\n  padding: 1px 0 1px .75rem;\n  overflow: hidden;\n}\n\n._shortcut-code {\n  display: inline-block;\n  vertical-align: top;\n  padding: 0 .5em;\n  @extend %label;\n}\n\n//\n// Search aliases\n//\n\n._aliases {\n  display: flex;\n  justify-content: space-between;\n\n  > table {\n    margin-top: 0;\n    width: calc(50% - 0.5rem);\n  }\n}\n\n//\n// Utilities\n//\n\n._bold { font-weight: var(--boldFontWeight); }\n._note { @extend %note; }\n._note-green { @extend %note-green; }\n._label { @extend %label; }\n._code { @extend %code; }\n._highlight, ._highlight > td { background: var(--highlightBackground) !important; }\n\n._table { width: 100%; }\n._mobile ._table { overflow-x: auto; }\n\n._pre-heading { @extend %pre-heading; }\n\n._pre-clip {\n  display: none;\n  position: absolute;\n  top: 0;\n  right: 0;\n  opacity: .5;\n  padding: .375rem;\n  cursor: pointer;\n\n  pre:hover > & {\n    display: block;\n    top: 0.1875rem;\n    padding: 0;\n  }\n\n  &:hover { opacity: 1; }\n\n  > svg {\n    @extend %svg-icon;\n    fill: var(--absolute);\n  }\n\n  &._pre-clip-success > svg,\n  &._pre-clip-error > svg {\n    display: none;\n  }\n\n  &._pre-clip-success:before { content: 'Copied'; }\n  &._pre-clip-error:before { content: 'Error'; }\n}\n\n._btn {\n  display: inline-block;\n  vertical-align: top;\n  line-height: normal;\n  white-space: nowrap;\n  padding: .375rem .675rem;\n  background-color: var(--boxBackground);\n  border: 1px solid var(--boxBorder);\n  border-radius: 3px;\n  cursor: pointer;\n\n  &:active {\n    box-shadow: inset 0 1px 1px rgba(black, .05), inset 0 1px 4px var(--boxBorder);\n  }\n}\n\n._file-btn {\n  position: relative;\n  overflow: hidden;\n\n  > input {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    visibility: hidden;\n  }\n}\n\n._btn-link {\n  line-height: inherit;\n  color: var(--linkColor);\n  text-decoration: var(--linkTextDecoration);\n\n  &:hover {\n    color: var(--linkColorHover);\n    text-decoration: underline;\n  }\n}\n\n._reset-btn,\n._reset-btn:hover {\n  color: var(--textColorRed);\n}\n\n._github-btn {\n  display: inline-block;\n  vertical-align: text-top;\n  margin-left: .25rem;\n  background: inherit;\n}\n"
  },
  {
    "path": "assets/stylesheets/components/_environment.scss.erb",
    "content": "._hide-in-development {\n  <%= App.environment != :production ? 'display: none;' : '' %>\n}\n"
  },
  {
    "path": "assets/stylesheets/components/_fail.scss",
    "content": "@use 'global/classes';\n\n._fail { // Don't use CSS variables, in case the browser doesn't support them.\n  display: block;\n  position: relative;\n  top: 1.5rem;\n  width: 24rem;\n  max-width: 90%;\n  margin: 0 auto;\n  padding: 1rem 1.5rem;\n  background: #eaefef;\n  border-radius: 5px;\n  @extend %border-box;\n\n  &:after { // margin\n    content: '';\n    position: relative;\n    top: 3rem;\n    float: left;\n    width: 1px;\n    height: 1px;\n  }\n}\n\n._fail-title {\n  margin: 0 0 1rem;\n  font-size: 1rem;\n  font-weight: bold;\n}\n\n._fail-text, ._fail-list {\n  margin: 0 0 1rem;\n  font-size: .875rem;\n}\n\n._fail-text:last-child { margin: 0; }\n"
  },
  {
    "path": "assets/stylesheets/components/_header.scss",
    "content": "//\n// Header\n//\n\n@use 'global/icons';\n@use 'global/mixins' as m;\n@use 'global/classes';\n\n._header {\n  position: absolute;\n  z-index: var(--headerZ);\n  top: 0;\n  left: 0;\n  display: -ms-flexbox;\n  display: flex;\n  width: var(--sidebarWidth);\n  height: var(--headerHeight);\n  background: var(--headerBackground);\n  border-bottom: 1px solid var(--headerBorder);\n  border-right: 1px solid var(--headerBorder);\n  @extend %border-box;\n  @extend %user-select-none;\n\n  @include m.mobile { width: var(--sidebarMediumWidth); }\n}\n\n._header-left {\n  float: left;\n  height: 100%;\n}\n\n._header-right {\n  float: right;\n  height: 100%;\n}\n\n._header-btn {\n  position: relative;\n  flex: 0 0 auto;\n  width: 2.25rem;\n  height: 100%;\n  color: var(--textColorLight);\n  text-align: center;\n\n  &[hidden] { display: none; }\n\n  &[disabled] {\n    opacity: .3;\n    cursor: not-allowed;\n  }\n\n  > svg {\n    width: 1.5rem;\n    height: 1.5rem;\n    @extend %svg-icon;\n  }\n}\n\n//\n// Menu\n//\n\n._menu {\n  position: absolute;\n  z-index: 1;\n  top: .25rem;\n  right: .25rem;\n  width: 8.5rem;\n  height: calc(2.25rem  * 6 + 2.5rem + 1px); // (height of each menu element * total menu elements + menu title element total height + menu title border size)\n  white-space: nowrap;\n  word-wrap: normal;\n  overflow-wrap: normal;\n  font-size: .875rem;\n  background: var(--contentBackground);\n  border: 1px solid var(--headerBorder);\n  border-radius: 3px;\n  box-shadow: -1px 1px 1px rgba(black, .05);\n  transition: all 0ms cubic-bezier(0.23, 1, 0.32, 1) 1ms;\n  opacity: 0;\n  -webkit-transform: scale(0, 0);\n          transform: scale(0, 0);\n  -webkit-transform-origin: 100% 0;\n          transform-origin: 100% 0;\n\n  &.active {\n    transition-duration: 250ms;\n    opacity: 1;\n    -webkit-transform: scale(1, 1);\n            transform: scale(1, 1);\n  }\n\n  &:focus-within,\n  ._menu-btn:focus + & {\n    transition-duration: 250ms;\n    opacity: 1;\n    -webkit-transform: scale(1, 1);\n            transform: scale(1, 1);\n  }\n}\n\n._menu-title {\n  margin: 0;\n  line-height: 1.5rem;\n  font-size: 1rem;\n  font-weight: var(--boldFontWeight);\n  letter-spacing: -.5px;\n  background: var(--sidebarBackground);\n  border-bottom: 1px solid var(--sidebarBorder);\n  border-radius: 2px 2px 0 0;\n}\n\n._menu-title-link,\n._menu-title-link:hover {\n  display: block;\n  padding: .5rem 1rem;\n  color: var(--focusText);\n  text-decoration: none;\n}\n\n._menu-link {\n  display: block;\n  padding: 0 1rem;\n  line-height: 2.25rem;\n  color: inherit;\n  text-decoration: none;\n\n  &:hover {\n    color: var(--focusText);\n    text-decoration: none;\n    background: var(--sidebarBackground);\n  }\n\n  &:last-child { border-radius: 0 0 2px 2px; }\n}\n\n//\n// Search form\n//\n\n._search {\n  -ms-flex: 1 1 auto;\n      flex: 1 1 auto;\n  position: relative;\n  height: 100%;\n  padding: .5rem 0 .5rem .5rem;\n  @extend %border-box;\n\n  > svg {\n    position: absolute;\n    z-index: 1;\n    top: .875rem;\n    left: .875rem;\n    width: 1.25rem;\n    height: 1.25rem;\n    opacity: .42;\n    @extend %svg-icon;\n\n    fill: var(--absolute);\n  }\n}\n\n._search-input {\n  position: relative;\n  display: block;\n  width: 100%;\n  height: 100%;\n  padding: 0 .75rem 1px 1.75rem;\n  font-size: .875rem;\n  background: var(--contentBackground);\n  border: 1px solid;\n  border-color: var(--searchBorder);\n  border-radius: 3px;\n\n  &:focus {\n    outline: 0;\n    border-color: var(--inputFocusBorder);\n    box-shadow: 0 0 1px var(--inputFocusBorder);\n  }\n\n  &[disabled] {\n    background: var(--sidebarBackground);\n    cursor: not-allowed;\n  }\n}\n\n._search-clear {\n  display: none;\n  position: absolute;\n  top: .5em;\n  right: 0;\n  width: 1.75rem;\n  height: 2rem;\n  opacity: .42;\n  @extend %hide-text;\n\n  &:hover { opacity: .7; }\n\n  > svg {\n    position: absolute;\n    top: .5rem;\n    left: .375rem;\n    @extend %svg-icon;\n    fill: var(--absolute);\n  }\n\n  ._search-active > & { display: block; }\n}\n\n._search-tag {\n  display: none;\n  position: absolute;\n  z-index: 2;\n  top: .875rem;\n  left: .875rem;\n  padding: 0 .375rem;\n  line-height: 1.25rem;\n  max-width: 50%;\n  font-size: .8125rem;\n  color: var(--textColorLight);\n  background: var(--searchTagBackground);\n  border-radius: 2px;\n  cursor: pointer;\n  @extend %truncate-text;\n}\n"
  },
  {
    "path": "assets/stylesheets/components/_mobile.scss",
    "content": "//\n// Mobile overrides\n//\n\n._mobile {\n  font-size: 100%;\n  background: var(--contentBackground);\n\n  ._hide-on-mobile { display: none; }\n\n  // Layout\n\n  body { -ms-overflow-style: -ms-autohiding-scrollbar; }\n\n  &:not(._booting) {\n    ._app, ._content { overflow: visible; }\n  }\n\n  ._container {\n    margin: 0;\n    padding-top: var(--headerHeight);\n  }\n\n  ._content {\n    position: static;\n    height: auto;\n    margin: 0;\n    padding: .75rem 1rem 1px;\n\n    &:before { content: none; }\n  }\n\n  ._content-loading:before, ._splash-title { font-size: 3rem; }\n\n  ._header { position: fixed; }\n\n  ._header, ._list {\n    width: 100%;\n    border-right: 0;\n    box-shadow: none;\n  }\n\n  // Settings\n\n  ._settings { position: relative; }\n  ._settings-tabs { display: block; }\n  ._header > ._settings-btn-back { width: auto; }\n\n  // Header\n\n  ._header-btn[hidden] { display: block; }\n\n  ._search {\n    padding-right: .125rem;\n    padding-left: .125rem;\n\n    > svg { left: .5rem; }\n  }\n\n  ._search-tag { left: .5rem; }\n  ._search-clear > svg { left: .25rem; }\n\n  // Sidebar\n\n  ._sidebar {\n    position: relative;\n    min-height: 100%;\n    overflow: visible;\n  }\n\n  ._resizer { display: none; }\n\n  ._list-item {\n    white-space: normal;\n    word-wrap: break-word;\n    overflow-wrap: break-word;\n    box-shadow: none;\n  }\n\n  ._list-result {\n    padding-left: 2.375rem;\n\n    &:before {\n      position: absolute;\n      top: .25rem;\n      left: .75rem;\n    }\n  }\n\n  // Notice\n\n  ._notice {\n    position: fixed;\n    left: 0;\n    padding: 0 .5rem;\n  }\n\n  ._notice-text { font-size: .75em; }\n\n  // Notification\n\n  ._notif { position: fixed; }\n\n  // Table of contents\n\n  ._toc {\n    float: none;\n    max-width: none;\n    margin-left: 0;\n  }\n\n  // Search Aliases\n\n  ._aliases {\n    display: block;\n\n    > table { width: 100%; }\n  }\n}\n\n//\n// Fix viewport on Windows Phone\n//\n\n@-ms-viewport { width: device-width; }\n@media (orientation: portrait) and (min-device-width: 720px) and (max-device-width: 768px),\n       (orientation: landscape) and (device-width: 1280px) and (max-device-height: 768px) {\n  @-ms-viewport { width: 50%; }\n}\n\n//\n// Header buttons\n//\n\n._forward-btn {\n  margin-right: -.5rem;\n\n  > svg { margin-left: -.375rem; }\n}\n\n//\n// Intro\n//\n\n._mobile-intro {\n  > ._intro-list { padding-left: 1.5rem; }\n\n  ._intro-hide {\n    position: static;\n    float: none;\n    display: block;\n    margin-top: .75rem;\n    text-align: center;\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/components/_notice.scss",
    "content": "@use 'global/mixins' as m;\n\n._notice {\n  position: absolute;\n  z-index: var(--noticeZ);\n  bottom: 0;\n  left: var(--sidebarWidth);\n  right: 0;\n  height: 2.5rem;\n  padding: 0 1.25rem;\n  background: var(--noticeBackground);\n  box-shadow: inset 0 1px var(--noticeBorder);\n\n  @include m.mobile { left: var(--sidebarMediumWidth); }\n\n  ._sidebar-hidden & { left: 0; }\n\n  ~ ._container { padding-bottom: 2.5rem; }\n}\n\n._notice-text {\n  display: table-cell;\n  vertical-align: middle;\n  margin: 0;\n  height: 2.5rem;\n  line-height: 1rem;\n  font-size: .875rem;\n}\n\n._notice-link { cursor: pointer; }\n"
  },
  {
    "path": "assets/stylesheets/components/_notif.scss",
    "content": "@use 'global/classes';\n@use 'global/icons';\n\n._notif, %notif {\n  position: absolute;\n  z-index: 2;\n  top: 1rem;\n  right: 1rem;\n  width: 25rem;\n  max-width: 90%;\n  padding: .625rem 1rem;\n  font-size: .75rem;\n  color: var(--notifColor);\n  background: var(--notifBackground);\n  border: var(--notifBorder);\n  border-radius: .25rem;\n  transition: opacity .2s;\n  opacity: 0;\n  cursor: default;\n  @extend %border-box, %user-select-none;\n\n  &._in { opacity: 1; }\n}\n\n._notif-title {\n  margin: 0 0 .5rem;\n  line-height: 1rem;\n  font-size: inherit;\n}\n\n._notif-text { margin-bottom: 0; }\n._notif-text + ._notif-text { margin-top: .25rem; }\n\n._notif-info {\n  float: right;\n  color: var(--notifColorLight);\n}\n\n._notif-link,\n._notif-link:hover {\n  color: inherit;\n  text-decoration: underline;\n  cursor: pointer;\n}\n\n._notif-close {\n  position: absolute;\n  top: 0;\n  right: 0;\n  width: 2.25rem;\n  height: 2.25rem;\n  opacity: .9;\n  @extend %hide-text;\n\n  > svg {\n    position: absolute;\n    top: .625rem;\n    left: .625rem;\n    fill: white;\n    @extend %svg-icon;\n  }\n\n  &:hover { opacity: 1; }\n}\n\n._notif-content {\n  max-height: calc(50vh - 4.5rem);\n  margin: 0 -.25rem 0 0;\n  padding-right: .75rem;\n  overflow-y: auto;\n\n  &::-webkit-scrollbar { width: 10px !important; }\n\n  &::-webkit-scrollbar-track {\n    background: var(--notifBackground) !important;\n    border: 0 !important;\n    border-radius: 5px !important;\n  }\n\n  &::-webkit-scrollbar-thumb {\n    border: 3px solid var(--notifBackground) !important;\n\n    &:hover, &:active { border-width: 2px !important; }\n  }\n\n  > ._notif-title {\n    margin-bottom: .5rem;\n    text-align: center;\n  }\n}\n\n._notif-news {\n  > ._news-row {\n    line-height: 1.125rem;\n    font-size: .6875rem;\n    color: var(--notifColorLight);\n    margin-bottom: .25rem;\n\n    + ._news-row { margin-top: .625rem; }\n  }\n\n  ._news-title {\n    display: block;\n    margin-bottom: .25rem;\n    font-size: .75rem;\n    font-weight: normal;\n    color: white;\n  }\n\n  ._news-date {\n    float: right;\n    margin-left: 1rem;\n    font-weight: var(--boldFontWeight);\n  }\n\n  code {\n    display: inline-block;\n    vertical-align: baseline;\n    line-height: 0;\n    margin: 0 .25rem;\n    padding: 0;\n    color: inherit;\n    background: none;\n    border: 0;\n  }\n}\n\n._notif-list {\n  margin: 0;\n  padding-left: 1rem;\n}\n\n._notif-tip {\n  color: var(--textColor);\n  background: var(--tipBackground);\n  border: var(--tipBorder);\n\n  ._notif-info { color: var(--textColorLight); }\n}\n\n._notif-right {\n  float: right;\n}\n"
  },
  {
    "path": "assets/stylesheets/components/_page.scss",
    "content": "@use 'global/classes';\n@use 'components/content';\n\n//\n// Page\n//\n\n._page {\n  position: relative;\n  min-height: calc(100% - 1.25rem);\n\n  &._page-error { position: static; }\n\n  > h1,\n  > article > h1,\n  > header > h1,\n  > section > h1 {\n    @extend ._lined-heading;\n  }\n  > h1:first-child,\n  > article:first-of-type > h1,\n  > header:first-of-type > h1,\n  > section:first-of-type > h1 {\n    margin-top: 0;\n  }\n\n  a[href^=\"http:\"], a[href^=\"https:\"] { @extend %external-link; }\n\n  a:not([href]) {\n    color: inherit;\n    text-decoration: none;\n  }\n\n  iframe {\n    display: block;\n    max-width: 100%;\n    margin-bottom: 1em;\n    padding: 1px;\n    border: 1px dotted var(--boxBorder);\n    border-radius: 3px;\n    @extend %border-box;\n  }\n}\n\n//\n// Links\n//\n\n._links {\n  position: absolute;\n  top: 0;\n  right: 0;\n  margin: 0;\n  line-height: 2em;\n  text-align: right;\n\n  + h1 { margin-top: 0; }\n\n  @media (max-width: 1023px) { display: none; }\n}\n\n._links-link {\n  display: inline-block;\n  vertical-align: top;\n  padding: 0 .5rem;\n  background: var(--contentBackground);\n  @extend %internal-link;\n\n  & + & { margin-left: .75rem; }\n  &:first-child { padding-left: 1rem; }\n  &:last-child { padding-right: 0; }\n}\n\n//\n// Attribution box\n//\n\n._attribution {\n  clear: both;\n  margin: 2rem 0 1.5rem;\n  font-size: .75rem;\n  color: var(--textColorLight);\n  text-align: center;\n  -webkit-font-smoothing: subpixel-antialiased;\n\n  & + & { margin-top: 1.5rem; }\n  & + & > ._attribution-link { display: none; }\n}\n\n._attribution-p {\n  display: inline-block;\n  margin: 0;\n  padding: .25rem .75rem;\n  background: var(--labelBackground);\n  border-radius: 3px;\n}\n\n._attribution-link { @extend %internal-link; }\n\n//\n// Entry list\n//\n\n._entry-list {\n  padding-left: 1em;\n  list-style: none;\n}\n"
  },
  {
    "path": "assets/stylesheets/components/_path.scss",
    "content": "@use 'global/mixins' as m;\n@use 'global/icons';\n\n._path {\n  position: absolute;\n  z-index: var(--headerZ);\n  bottom: 0;\n  left: var(--sidebarWidth);\n  right: 0;\n  height: 2rem;\n  line-height: 2rem;\n  padding: 0 .375rem;\n  font-size: .875rem;\n  background: var(--pathBackground);\n  box-shadow: inset 0 1px var(--pathBorder);\n\n  @include m.mobile { left: var(--sidebarMediumWidth); }\n\n  ._sidebar-hidden & { left: 0; }\n\n  ~ ._container { padding-bottom: 2rem; }\n  a:focus { outline: 0; }\n}\n\n._path-item {\n  position: relative;\n  display: inline-block;\n  vertical-align: top;\n  padding: 0 .375rem;\n  color: var(--textColor);\n  text-decoration: none;\n\n  &:first-child:before {\n    content: '';\n    float: left;\n    width: 1rem;\n    height: 1rem;\n    margin: .5rem .375rem 0 0;\n    @extend %doc-icon;\n  }\n}\n\n._path-arrow {\n  display: inline-block;\n  vertical-align: top;\n  width: .75rem;\n  height: .75rem;\n  margin: .625rem .25rem;\n  fill: #888;\n}\n"
  },
  {
    "path": "assets/stylesheets/components/_prism.scss",
    "content": "html {\n  --prismValue: #905;\n  --prismText: #5e8e01;\n  --prismOperator: #a67f59;\n  --prismKeyword: #0070a3;\n  --prismFunction: #dd4a68;\n  --prismVariable: #e90;\n}\nhtml._theme-dark {\n  --prismValue: #eb8160;\n  --prismText: #ddcf88;\n  --prismOperator: #b1c676;\n  --prismKeyword: #91b3ed;\n  --prismFunction: #c79e6b;\n  --prismVariable: #e9c062;\n}\n\n.token.comment,\n.token.prolog,\n.token.doctype,\n.token.cdata,\n.token.punctuation {\n  color: var(--textColorLight);\n}\n\n.namespace {\n  opacity: .7;\n}\n\n.token.property,\n.token.tag,\n.token.boolean,\n.token.number,\n.token.constant,\n.token.symbol,\n.token.deleted {\n  color: var(--prismValue);\n}\n\n.token.selector,\n.token.attr-name,\n.token.string,\n.token.char,\n.token.builtin,\n.token.inserted {\n  color: var(--prismText);\n}\n\n.token.operator,\n.token.entity,\n.token.url,\n.language-css .token.string,\n.style .token.string {\n  color: var(--prismOperator);\n}\n\n.token.atrule,\n.token.attr-value,\n.token.keyword {\n  color: var(--prismKeyword);\n}\n\n.token.function {\n  color: var(--prismFunction);\n}\n\n.token.regex,\n.token.important,\n.token.variable {\n  color: var(--prismVariable);\n}\n\n.token.important,\n.token.bold {\n  font-weight: var(--boldFontWeight);\n}\n\n.token.italic {\n  font-style: italic;\n}\n"
  },
  {
    "path": "assets/stylesheets/components/_settings.scss",
    "content": "//\n// Settings\n//\n\n@use 'global/classes';\n@use 'global/icons';\n\n._settings {\n  display: none;\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  z-index: var(--headerZ);\n\n  &._in { display: block; }\n\n  > ._header { justify-content: space-between; }\n\n  &._dirty > ._header {\n    background: var(--noteGreenBackground);\n    border-color: var(--noteGreenBorder);\n  }\n}\n\n//\n// Settings page\n//\n\n._settings-fieldset {\n  display: -ms-flexbox;\n  display: flex;\n  margin: 1.5rem 0;\n  line-height: 1.5rem;\n}\n\n._settings-legend {\n  -ms-flex: 0 1 10rem;\n      flex: 0 1 10rem;\n  margin: 0;\n  padding-right: .5rem;\n  line-height: inherit;\n  font-size: inherit;\n  font-weight: var(--boldFontWeight);\n  text-align: right;\n  @extend %border-box;\n}\n\n._settings-inputs {\n  -ms-flex: 1 1 20rem;\n      flex: 1 1 20rem;\n}\n\n._settings-label {\n  &:not(._theme-label) {\n    margin: 0 0 .375rem;\n  }\n\n  > small {\n    display: block;\n    color: var(--textColorLight);\n    margin-left: 1.75rem;\n  }\n  &._theme-label > small {\n    display: inline-block;\n    margin-left: 0.75rem;\n  }\n\n  input[type=checkbox], input[type=radio] {\n    vertical-align: top;\n    margin: .25rem .375rem;\n  }\n}\n\n@media (max-width: 80rem) {\n  ._setting-max-width { display: none; }\n}\n\n._setting-native-scrollbar { display: none; }\n@supports (-webkit-margin-end: 1px) { ._setting-native-scrollbar { display: block; } }\n\n//\n// Settings buttons\n//\n\n._settings-btn {\n  display: block;\n  width: 100%;\n  height: 100%;\n  line-height: 1.5rem;\n  padding: 0 .75rem;\n  font-size: .875rem;\n  font-weight: var(--boldFontWeight);\n  color: inherit;\n  text-align: left;\n  cursor: pointer;\n  @extend %border-box;\n\n  > svg {\n    width: 1.5rem;\n    height: 1.5rem;\n    margin-right: .125rem;\n    @extend %svg-icon;\n  }\n}\n\n._settings-btn-back {\n  ._dirty & { display: none; }\n}\n\n._settings-btn-save {\n  display: none;\n\n  ._dirty & { display: block; }\n}\n\n//\n// Header tabs\n//\n\n._settings-tabs {\n  display: none; // mobile only\n  margin-right: .5rem;\n\n  ._dirty & { display: none !important; }\n}\n\n._settings-tab {\n  position: relative;\n  vertical-align: top;\n  padding: 0 .75rem;\n  line-height: var(--headerHeight);\n  color: var(--textColorLight);\n\n  &.active {\n    color: var(--textColor);\n    font-weight: var(--boldFontWeight);\n    box-shadow: inset 0 -2px var(--linkColor);\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/components/_sidebar.scss",
    "content": "//\n// Sidebar\n//\n\n@use 'global/classes';\n@use 'global/icons';\n@use 'global/mixins' as m;\n\n._sidebar {\n  position: absolute;\n  z-index: var(--sidebarZ);\n  top: 0;\n  bottom: 0;\n  left: 0;\n  overflow-x: hidden;\n  overflow-y: scroll;\n  padding-top: var(--headerHeight);\n  background: var(--sidebarBackground);\n  background-clip: content-box;\n  -webkit-overflow-scrolling: touch;\n  -ms-overflow-style: none; // IE 10 doesn't support pointer-events\n  @extend %border-box;\n  @extend %user-select-none;\n\n  &:focus { outline: none; }\n\n  ._overlay-scrollbars & {\n    padding-top: 0;\n    top: var(--headerHeight);\n  }\n\n  body:not(._native-scrollbars) & {\n    &::-webkit-scrollbar { width: 10px; }\n    &::-webkit-scrollbar-track {\n      background: var(--contentBackground);\n      border: 0;\n    }\n    &::-webkit-scrollbar-thumb {\n      border-width: 3px;\n      &:hover, &:active { border-width: 2px; }\n    }\n  }\n\n  ._sidebar-hidden & {\n    transform: translateX(-95%);\n    transform: translateX(calc(.5rem - 100%));\n    opacity: 0;\n  }\n\n  &:hover:not(.no-hover),\n  &.show {\n    transform: none;\n    opacity: 1;\n  }\n}\n\n._resizer {\n  position: absolute;\n  z-index: var(--headerZ);\n  top: var(--headerHeight);\n  bottom: var(--headerHeight);\n  left: var(--sidebarWidth);\n  margin-left: -2px;\n  width: 3px;\n  cursor: col-resize;\n\n  ._sidebar-hidden & { display: none; }\n  ._sidebar-hidden ._sidebar.show ~ & { display: block; }\n}\n\n//\n// List\n//\n\n._list {\n  margin: 0;\n  padding: 0;\n  list-style: none;\n  width: var(--sidebarWidth);\n  box-shadow: inset -1px 0 var(--sidebarBorder);\n  @extend %border-box;\n\n  @include m.mobile { width: var(--sidebarMediumWidth); }\n\n  ._sidebar > & { min-height: 100%; }\n\n  a:focus { outline: 0; }\n}\n\n._list-title {\n  position: relative;\n  margin: .5rem 0 0;\n  padding: 0 .75rem 0 2.125rem;\n  line-height: 2rem;\n  font-size: .75rem;\n  color: var(--textColorLight);\n  text-transform: uppercase;\n  cursor: default;\n}\n\n._list-title-link {\n  display: none;\n  float: right;\n  font-weight: normal;\n  text-transform: none;\n\n  ._list-title:hover > & { display: block; }\n}\n\n._list-item {\n  display: block;\n  position: relative;\n  padding: .25rem .75rem;\n  line-height: 1.5rem;\n  font-size: .875rem;\n  cursor: default;\n  background: var(--sidebarBackground);\n  box-shadow: inset -1px 0 var(--sidebarBorder);\n  @extend %truncate-text;\n\n  &, &:hover {\n    color: inherit;\n    text-decoration: none;\n  }\n\n  &.focus,\n  &.focus:hover,\n  &.active,\n  &.active:hover {\n    color: var(--focusText);\n    background: var(--focusBackground);\n    box-shadow: inset -1px 0 var(--focusBorder);\n  }\n\n  &.active,\n  &.active:hover {\n    color: var(--selectionText);\n    background: var(--selectionBackground);\n    box-shadow: inset -1px 0 var(--selectionBorder);\n  }\n\n  &:before {\n    float: left;\n    margin: .25rem .625rem 0 0;\n    @extend %doc-icon;\n  }\n}\n\n._list-text {\n  display: block;\n  pointer-events: none;\n  @extend %truncate-text;\n}\n\n._list-count, ._list-enable {\n  float: right;\n  font-size: .75rem;\n  margin-left: .375rem;\n\n  .focus > &,\n  .active > & {\n    color: inherit;\n  }\n}\n\n._list-count {\n  color: var(--textColorLighter);\n  pointer-events: none;\n\n  ._list-disabled:hover > & { display: none; }\n}\n\n._list-enable {\n  display: none;\n  color: var(--linkColor);\n  cursor: pointer;\n\n  &:hover { text-decoration: underline; }\n  ._list-disabled:hover > &, ._list-result > & { display: block; }\n  ._list-result.active > & { margin-right: -1rem; }\n}\n\n//\n// List hierarchy\n//\n\n._list-dir:not(._list-rdir),\n%_list-dir {\n  padding-left: 2.125rem;\n}\n\n._list-disabled {\n  @extend %_list-dir;\n\n  &, &:hover { color: var(--textColorLight); }\n  &:before { opacity: .7; }\n}\n\n._list-arrow {\n  position: absolute;\n  top: 0;\n  left: .25rem;\n  padding: .5rem .375rem .5rem .5rem;\n  width: 1rem;\n  height: 1rem;\n  cursor: pointer;\n  fill: var(--absolute);\n  opacity: .4;\n\n  &:hover { opacity: .65; }\n\n  ._list-rdir > & {\n    left: auto;\n    right: .25rem;\n  }\n\n  .open > &, .open-title > & {\n     -webkit-transform: rotate(90deg);\n             transform: rotate(90deg);\n   }\n}\n\n._list-sub {\n  display: none;\n\n  .open + & { display: block; }\n  > ._list-item { padding-left: 2.375rem; }\n  > ._list-dir, > ._list-sub > ._list-item { padding-left: 2.75rem; }\n  > ._list-disabled { padding-left: 3.75rem; }\n  > ._list-item:before { content: none; }\n  > ._list-dir { line-height: 1.375rem; }\n\n  ._list-arrow {\n    left: 1rem;\n    padding: .4375rem;\n  }\n}\n\n//\n// List pagination\n//\n\n._list-pagelink {\n  color: var(--linkColor);\n  cursor: pointer;\n\n  &:hover {\n    color: var(--linkColorHover);\n    text-decoration: underline;\n  }\n}\n\n//\n// Search results\n//\n\n._list-result.active {\n  padding-right: 1.75rem;\n\n  > ._list-reveal { display: block; }\n  > ._list-count { display: none; }\n}\n\n._list-reveal {\n  display: none;\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  right: 0;\n  width: 2rem;\n  cursor: pointer;\n\n  &:before {\n    content: '';\n    position: absolute;\n    bottom: 50%;\n    left: .75rem;\n    width: .75rem;\n    height: 1px;\n    background: var(--transparentSelectionText);\n    box-shadow: 0 -3px var(--transparentSelectionText), // top line\n                0 3px var(--transparentSelectionText);  // bottom line\n  }\n}\n\n//\n// List note\n//\n\n._list-note {\n  padding: .5rem .75rem;\n  line-height: 1.25rem;\n  font-size: .8125rem;\n  color: var(--textColorLight);\n\n  & + & { padding-top: 0; }\n}\n\n._list-note-link { cursor: pointer; }\n\n//\n// List hover clone\n//\n\n._list-hover.clone {\n  position: fixed;\n  overflow: visible;\n  z-index: var(--hoverZ);\n  left: 0;\n  min-width: var(--sidebarWidth);\n  padding: .25rem .75rem;\n  pointer-events: none;\n  -webkit-font-smoothing: subpixel-antialiased;\n  -webkit-transform: translate3d(0, 0, 0);\n          transform: translate3d(0, 0, 0);\n  @extend %border-box;\n\n  @include m.mobile { min-width: var(--sidebarMediumWidth); }\n\n  > ._list-text { display: inline; }\n\n  &:not(._list-result) {\n    padding-left: 2.75rem;\n\n    &:before { content: none; }\n  }\n\n  ._list-reveal, ._list-enable { display: none; }\n}\n\n//\n// List picker\n//\n\n._list-picker {\n  ._list-item { cursor: pointer; }\n  ._list-sub > ._list-item { padding-left: 2.375rem; }\n}\n\n._list-picker-head {\n  display: flex;\n  justify-content: space-between;\n  position: -webkit-sticky;\n  position: sticky;\n  top: 0;\n  z-index: 1;\n  margin-right: 1px;\n  padding: .5rem .75rem .25rem .75rem;\n  line-height: 1.5rem;\n  font-size: .75rem;\n  font-weight: var(--bolderFontWeight);\n  color: var(--textColorLight);\n  text-transform: uppercase;\n  background: linear-gradient(to bottom, var(--sidebarBackground), var(--sidebarBackground) 75%, var(--transparentSidebarBackground));\n  cursor: default;\n}\n\n._list-checkbox {\n  position: absolute;\n  top: .5rem;\n  right: .75rem;\n}\n\n._list-link {\n  display: block;\n  padding: .75rem 0;\n  font-size: .8125rem;\n  text-align: center;\n  @extend %external-link;\n\n  &:after { visibility: hidden; }\n  &:hover:after { visibility: visible; }\n}\n"
  },
  {
    "path": "assets/stylesheets/global/_base.scss",
    "content": "@use 'global/classes';\n@use 'global/mixins' as m;\n@use 'global/variables' as *;\n@use 'global/print';\n\nhtml {\n  height: 100%;\n  font-size: 100%;\n  background: #fff; // fallback to show the error message to browsers that don't support CSS variables.\n  background: var(--documentBackground);\n\n  @include m.mobile { font-size: 93.75%; }\n\n  @include m.print { background: none; }\n}\n\nhtml._theme-default {\n  color-scheme: light only;\n}\nhtml._theme-dark {\n  color-scheme: dark only;\n}\n\nbody {\n  height: 100%;\n  margin: 0;\n  overflow: auto;\n  font-size: 1em;\n  font-weight: normal;\n  font-family: $baseFont;\n  line-height: 1.7;\n  color: $textColor; // fallback to show the error message to browsers that don't support CSS variables.\n  color: var(--textColor);\n  word-wrap: break-word;\n  overflow-wrap: break-word;\n  background: var(--contentBackground);\n  touch-action: manipulation;\n  -webkit-tap-highlight-color: rgba(black, 0);\n  -webkit-touch-callout: none;\n  -webkit-text-size-adjust: 100%;\n      -ms-text-size-adjust: 100%;\n}\n\na {\n  color: var(--linkColor);\n  text-decoration: var(--linkTextDecoration);\n\n  &:hover {\n    color: var(--linkColorHover);\n    text-decoration: underline;\n  }\n}\n\nimg {\n  max-width: 100%;\n  height: auto;\n  border: 0;\n}\n\nh1, h2, h3, h4, h5, h6 {\n  margin: 1.5em 0 1em;\n  line-height: 1.3;\n  font-weight: var(--bolderFontWeight);\n}\n\nh1 { font-size: 1.5em; }\nh2 { font-size: 1.375em; }\nh3 { font-size: 1.25em; }\nh4 { font-size: 1.125em; }\nh5, h6 { font-size: 1em; }\n\np { margin: 0 0 1em; }\np:last-child { margin-bottom: 0; }\n\nb, strong { font-weight: var(--boldFontWeight); }\n\nsmall { font-size: .9em; }\n\nul, ol {\n  margin: 1.5em 0;\n  padding: 0 0 0 2em;\n  list-style: disc outside;\n}\n\nul ul { list-style-type: circle; }\nol { list-style-type: decimal; }\nol ol { list-style-type: lower-alpha; }\nol ol ol { list-style-type: lower-roman; }\n\nli + li { margin-top: .25em; }\nli > ul, li > ol, dd > ul, dd > ol { margin: .5em 0; }\nli > p { margin-bottom: .25em; }\n\ndl { margin: 1.5em 0; }\ndt { font-weight: var(--boldFontWeight); }\ndd {\n  margin: .375em;\n  padding-left: 1em;\n\n  + dt { margin-top: 1em; }\n}\n\nabbr, acronym, dfn {\n  cursor: help;\n  border-bottom: 1px dotted var(--textColor);\n}\n\npre, code, samp, %pre, %code {\n  font-family: var(--monoFont);\n  font-weight: normal;\n  font-style: normal;\n  font-size: .9em;\n  color: var(--textColor);\n  white-space: pre-wrap;\n  direction: ltr;\n  -moz-tab-size: 2;\n    -o-tab-size: 2;\n       tab-size: 2;\n}\n\npre, %pre {\n  position: relative;\n  margin: 1.5em 0;\n  padding: .375rem .625rem;\n  line-height: 1.5;\n  overflow: auto;\n  @extend %box;\n}\n\ndetails:has(> pre) {\n  margin: 1.5em 0;\n\n  > pre {\n    margin: 0;\n  }\n}\n\na > code { color: inherit; }\n\ntable {\n  margin: 1.5em 0;\n  background: none;\n  border: 1px solid var(--boxBorder);\n  border-collapse: separate;\n  border-spacing: 0;\n  border-radius: 3px;\n  display: inline-block;\n  overflow-x: auto;\n  max-width: 100%;\n}\n\ncaption {\n  font-weight: var(--boldFontWeight);\n  padding: 0 .7em .3em;\n}\n\nth, td {\n  vertical-align: top;\n  padding: .3em .7em;\n  padding-bottom: -webkit-calc(.3em + 1px);\n  padding-bottom:         calc(.3em + 1px);\n  text-align: left;\n  white-space: normal !important;\n}\n\nth {\n  font-weight: var(--boldFontWeight);\n  border: 0;\n  border-bottom: 1px solid var(--boxBorder);\n  border-radius: 0;\n  @extend %heading-box;\n\n  &:empty { background: none; }\n\n  + th, + td { border-left: 1px solid var(--boxBorder); }\n\n  tr:first-child > &:first-child { border-top-left-radius: 3px; }\n  tr:first-child > &:last-child { border-top-right-radius: 3px; }\n  tr:last-child > &:first-child { border-bottom-left-radius: 3px; }\n  thead > tr:last-child > &:first-child { border-bottom-left-radius: 0; }\n  tr:last-child > & { border-bottom-width: 0; }\n  thead > tr:last-child > & { border-bottom-width: 1px; }\n}\n\ntd {\n  background: var(--contentBackground);\n  border-bottom: 1px solid var(--boxBorderLight);\n\n  + td { border-left: 1px solid var(--boxBorderLight); }\n  tr:last-child > & { border-bottom: 0; }\n\n  > pre:only-child, > p:only-child, > ul:only-child, > ol:only-child {\n    margin-top: 0;\n    margin-bottom: 0;\n  }\n  > pre:first-child, > p:first-child, > ul:first-child, > ol:first-child { margin-top: 0; }\n  > pre:last-child, > p:last-child, > ul:last-child, > ol:last-child { margin-bottom: 0; }\n}\n\nsection, main {\n  display: block;\n  outline: 0;\n}\n\nlabel {\n  display: block;\n  @extend %user-select-none;\n}\n\ninput, button {\n  display: inline-block;\n  margin: 0;\n  font-family: inherit;\n  font-size: 100%;\n  color: var(--textColor);\n  line-height: normal;\n  @extend %border-box;\n}\n\ninput[type=checkbox] {\n  width: 1rem;\n  height: 1rem;\n  cursor: pointer;\n}\n\nbutton {\n  padding: 0;\n  background: none;\n  border: 0;\n  cursor: pointer;\n}\n\nbutton, input[type=\"search\"] {\n  -webkit-appearance: none;\n          appearance: none;\n}\n\nbutton:focus {\n  outline: 1px dotted;\n  outline: -webkit-focus-ring-color auto 5px;\n}\n\nimg, iframe {\n  background: var(--externalsBackground);\n}\n\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\n\n::-ms-clear { display: none; }\n\n::-moz-focus-inner {\n  padding: 0 !important;\n  border: 0 !important;\n}\n\n::-webkit-input-placeholder { color: var(--textColorLighter); }\n::-moz-placeholder          { color: var(--textColorLighter); opacity: 1; }\n:-ms-input-placeholder      { color: var(--textColorLighter); }\n\nbody:not(._native-scrollbars) {\n  *::-webkit-scrollbar { -webkit-appearance: none; }\n  *::-webkit-scrollbar:vertical { width: 16px; }\n  *::-webkit-scrollbar:horizontal { height: 16px; }\n\n  *::-webkit-scrollbar-button,\n  *::-webkit-scrollbar-corner { display: none; }\n\n  *::-webkit-scrollbar-track {\n    background: var(--contentBackground);\n    border: 1px solid var(--contentBackground);\n\n    &:hover {\n      background: var(--sidebarBackground);\n      border-color: var(--sidebarBorder);\n    }\n\n    &:vertical { border-width: 0 0 0 1px; }\n\n    &:vertical:corner-present {\n      border-width: 0 0 1px 1px;\n      border-radius: 0 0 0 2px;\n    }\n\n    &:horizontal {\n      border-width: 1px 1px 0 1px;\n      border-radius: 2px 2px 0 0;\n    }\n  }\n\n  *::-webkit-scrollbar-thumb {\n    min-height: 2rem;\n    background: var(--scrollbarColor);\n    background-clip: padding-box;\n    border: 5px solid rgba(black, 0);\n    border-radius: 10px;\n\n    &:hover,\n    &:active {\n      background-color: var(--scrollbarColorHover);\n      border-width: 4px;\n    }\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/global/_classes.scss",
    "content": "//\n// Utilities\n//\n\n%border-box {\n  -moz-box-sizing: border-box;\n       box-sizing: border-box;\n}\n\n%user-select-none {\n  -webkit-user-select: none;\n     -moz-user-select: -moz-none;\n      -ms-user-select: none;\n          user-select: none;\n}\n\n%hide-text {\n  white-space: nowrap;\n  overflow: hidden;\n  text-indent: 100%;\n  word-wrap: normal;\n  overflow-wrap: normal;\n  @extend %user-select-none;\n}\n\n%truncate-text {\n  overflow: hidden;\n  white-space: nowrap;\n  word-wrap: normal;\n  overflow-wrap: normal;\n  text-overflow: ellipsis;\n}\n\n//\n// Boxes\n//\n\n%box {\n  background: var(--boxBackground);\n  border: 1px solid var(--boxBorder);\n  border-radius: 3px;\n}\n\n%heading-box {\n  color: var(--boxHeaderColor);\n  background: var(--boxHeaderBackground);\n  border: 1px solid var(--boxBorder);\n  border-radius: 3px;\n}\n\n%block-heading {\n  line-height: 1.25rem;\n  margin: 2em 0 1em;\n  padding: .5em .75em;\n  font-size: 1rem;\n  overflow: hidden;\n  @extend %heading-box;\n}\n\n%pre-heading {\n  margin: 0;\n  padding: .375rem .625rem;\n  font-size: inherit;\n  font-weight: normal;\n  line-height: 1.5;\n  border-bottom-left-radius: 0;\n  border-bottom-right-radius: 0;\n  @extend %heading-box;\n\n  + pre {\n    border-top-left-radius: 0;\n    border-top-right-radius: 0;\n    border-top: 0;\n    margin-top: 0;\n  }\n}\n\n//\n// Notes\n//\n\n%note {\n  margin: 1.5rem 0;\n  padding: .5rem .875rem;\n  background: var(--noteBackground);\n  border: 1px solid var(--noteBorder);\n  border-radius: 3px;\n}\n\n%label {\n  margin: 0 1px;\n  padding: 1px 4px 2px;\n  background: var(--labelBackground);\n  border-radius: 3px;\n}\n\n%block-label {\n  display: block;\n  line-height: 1.375rem;\n  margin: 2em 0 1em;\n  padding-left: .5em;\n  padding-right: .5em;\n  overflow: hidden;\n  font-size: inherit;\n  color: var(--boxHeaderColor);\n  border: 1px solid var(--boxBorder);\n  border-radius: 2px;\n  @extend %label;\n}\n\n%label-yellow {\n  background: var(--noteBackground);\n  border-color: var(--noteBorder);\n}\n\n%note-green, %label-green {\n  background: var(--noteGreenBackground);\n  border-color: var(--noteGreenBorder);\n}\n\n%note-blue, %label-blue {\n  background: var(--noteBlueBackground);\n  border-color: var(--noteBlueBorder);\n}\n\n%note-orange, %label-orange {\n  background: var(--noteOrangeBackground);\n  border-color: var(--noteOrangeBorder);\n}\n\n%note-red, %label-red {\n  background: var(--noteRedBackground);\n  border-color: var(--noteRedBorder);\n}\n\n%note-gray, %label-gray {\n  background: var(--boxBackground);\n  border: 1px solid var(--boxBorder);\n}\n\n//\n// External links\n//\n\n%external-link {\n  &:after {\n    content: '';\n    display: inline-block;\n    vertical-align: top;\n    width: .5rem;\n    height: .5rem;\n    margin: .125rem 0 0 .125rem;\n    background-size: .5rem .5rem;\n    pointer-events: none;\n\n    // <svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M15,15H2V6h2.595c0,0,0.689-0.896,2.17-2H1C0.447,4,0,4.449,0,5v11c0,0.553,0.447,1,1,1h15c0.553,0,1-0.447,1-1v-3.746 l-2,1.645V15z M13.361,8.05v3.551L20,6.4l-6.639-4.999v3.131C5.3,4.532,5.3,12.5,5.3,12.5C7.582,8.752,8.986,8.05,13.361,8.05z\"/></svg>\n    background-image: url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjAgMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iIzMzNzdjMCIgZD0iTTE1LDE1SDJWNmgyLjU5NWMwLDAsMC42ODktMC44OTYsMi4xNy0ySDFDMC40NDcsNCwwLDQuNDQ5LDAsNXYxMWMwLDAuNTUzLDAuNDQ3LDEsMSwxaDE1YzAuNTUzLDAsMS0wLjQ0NywxLTF2LTMuNzQ2IGwtMiwxLjY0NVYxNXogTTEzLjM2MSw4LjA1djMuNTUxTDIwLDYuNGwtNi42MzktNC45OTl2My4xMzFDNS4zLDQuNTMyLDUuMywxMi41LDUuMywxMi41QzcuNTgyLDguNzUyLDguOTg2LDguMDUsMTMuMzYxLDguMDV6Ii8+PC9zdmc+);\n  }\n}\nhtml._theme-dark %external-link:after {\n  background-image: url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjAgMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2NiZDBkMCIgZD0iTTE1LDE1SDJWNmgyLjU5NWMwLDAsMC42ODktMC44OTYsMi4xNy0ySDFDMC40NDcsNCwwLDQuNDQ5LDAsNXYxMWMwLDAuNTUzLDAuNDQ3LDEsMSwxaDE1YzAuNTUzLDAsMS0wLjQ0NywxLTF2LTMuNzQ2IGwtMiwxLjY0NVYxNXogTTEzLjM2MSw4LjA1djMuNTUxTDIwLDYuNGwtNi42MzktNC45OTl2My4xMzFDNS4zLDQuNTMyLDUuMywxMi41LDUuMywxMi41QzcuNTgyLDguNzUyLDguOTg2LDguMDUsMTMuMzYxLDguMDV6Ii8+PC9zdmc+);\n}\n\n%internal-link:after { content: none !important; }\n"
  },
  {
    "path": "assets/stylesheets/global/_icons.scss.erb",
    "content": "<% manifest = JSON.parse(File.read('assets/images/sprites/docs.json')) %>\n\n%svg-icon {\n  display: inline-block;\n  vertical-align: top;\n  width: 1rem;\n  height: 1rem;\n  pointer-events: none;\n  fill: currentColor;\n}\n\n%doc-icon {\n  content: '';\n  display: block;\n  width: 1rem;\n  height: 1rem;\n  background-image: image-url('sprites/docs.png');\n  background-size: <%= manifest['icons_per_row'] %>rem <%= manifest['icons_per_row'] %>rem;\n}\n\n@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) {\n  %doc-icon { background-image: image-url('sprites/docs@2x.png'); }\n}\n\nhtml._theme-dark {\n  %darkIconFix {\n    filter: invert(100%) grayscale(100%);\n    -webkit-filter: invert(100%) grayscale(100%);\n  }\n}\n\n<%=\n  items = []\n\n  manifest['items'].each do |item|\n    rules = []\n    rules << \"background-position: -#{item['col']}rem -#{item['row']}rem;\"\n    rules << \"@extend %darkIconFix !optional;\" if item['dark_icon_fix']\n    items << \"._icon-#{item['type']}:before { #{rules.join(' ')} }\"\n  end\n\n  items.join('')\n %>\n"
  },
  {
    "path": "assets/stylesheets/global/_mixins.scss",
    "content": "//\n// Mixins\n//\n\n@mixin print {\n  @media print {\n    @content;\n  }\n}\n\n@mixin mobile {\n  @media (max-width: 800px) {\n    @content;\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/global/_print.scss",
    "content": "@use 'global/mixins' as m;\n\n@include m.print {\n  ._header, ._sidebar, ._path, ._notif, ._toc, ._pre-clip, ._notice, ._links {\n    display: none !important;\n  }\n\n  body, ._app, ._container, ._content {\n    margin: 0;\n    padding: 0;\n    height: initial;\n    background: none;\n\n    &:after {\n      content: '';\n      clear: both;\n    }\n  }\n\n  ::-webkit-scrollbar {\n    display: none;\n  }\n\n  %external-link:after {\n    display: none;\n  }\n\n  ._attribution-p {\n    background: none;\n    border: 2px solid var(--boxBorder);\n  }\n\n  ._attribution:last-child:after {\n    content: 'Exported from DevDocs \\2014  https://devdocs.io';\n    display: block;\n    margin-top: 1rem;\n    font-weight: var(--bolderFontWeight);\n  }\n\n  ._attribution {\n    page-break-inside: avoid;\n  }\n\n  h1, h2, h3, h4, h5, h6 {\n    page-break-inside: avoid;\n    page-break-after: avoid;\n  }\n\n  pre {\n    page-break-before: avoid;\n    orphans: 5;\n    widows: 5;\n  }\n\n  p {\n    orphans: 2;\n    widows: 2;\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/global/_variables-dark.scss",
    "content": "@use 'sass:color';\n\nhtml._theme-dark {\n  --absolute: white;\n\n  --documentBackground: #222;\n  --contentBackground: #33373a;\n\n  --textColor: #cbd0d0;\n  --textColorLight: #9da5ad;\n  --textColorLighter: #77787a;\n\n  --externalsBackground: #fff;\n\n  --inputFocusBorder: transparent;\n\n  --focusBackground: #3f4042;\n  --focusBorder: #000;\n  --focusText: #f7f2f2;\n\n  --loadingText: #5d6164;\n\n  --selectionBackground: #007acc;\n  --selectionBorder: #000;\n\n  --highlightBackground: #64675f;\n\n  --linkColor: var(--textColor);\n  --linkColorHover: white;\n  --linkTextDecoration: underline;\n\n  --headerBackground: #1c1c1c;\n  --headerBorder: #000;\n  --searchTagBackground: #{color.adjust(#1c1c1c, $lightness: -5%)};\n  --searchBorder: #{color.adjust(#000, $lightness: -2%)};\n\n  --sidebarBackground: #24282a;\n  --transparentSidebarBackground: #{rgba(#24282a, 0)};\n  --sidebarBorder: #000;\n\n  --scrollbarColor: #6c6c6f;\n  --scrollbarColorHover: #949697;\n\n  --pathBackground: var(--headerBackground);\n  --pathBorder: var(--headerBorder);\n\n  --noticeBackground: var(--sidebarBackground);\n  --noticeBorder: var(--sidebarBorder);\n\n  --boxBackground: var(--sidebarBackground);\n  --boxBorder: var(--headerBorder);\n  --boxBorderLight: var(--headerBorder);\n  --boxHeaderColor: #dbe4e4;\n  --boxHeaderBackground: var(--sidebarBackground);\n\n  --noteBackground: #45474b;\n  --noteBorder: #000;\n\n  --noteGreenBackground: #284a2a;\n  --noteGreenBorder: #000;\n\n  --noteBlueBackground: #2a4151;\n  --noteBlueBorder: #000;\n\n  --noteOrangeBackground: #563322;\n  --noteOrangeBorder: #000;\n\n  --noteRedBackground: #603033;\n  --noteRedBorder: #000;\n\n  --labelBackground: var(--boxBackground);\n\n  --notifBackground: #{rgba(#555, .95)};\n  --notifBorder: 1px solid #000;\n\n  --tipBackground: var(--notifBackground);\n  --tipBorder: var(--notifBorder);\n}\n"
  },
  {
    "path": "assets/stylesheets/global/_variables-light.scss",
    "content": "@use 'sass:color';\n\nhtml._theme-default {\n  --absolute: black;\n\n  --documentBackground: #f3f3f3;\n  --contentBackground: #fff;\n\n  --textColor: #333;\n  --textColorLight: #666;\n  --textColorLighter: #888;\n\n  --externalsBackground: #fff;\n\n  --inputFocusBorder: #35b5f4;\n\n  --focusBackground: #e5e5e5;\n  --focusBorder: #d4d4d4;\n  --focusText: #000;\n\n  --loadingText: #ccc;\n\n  --selectionBackground: #398df0;\n  --selectionBorder: #196fc2;\n\n  --highlightBackground: #fffdcd;\n\n  --linkColor: #3377c0;\n  --linkColorHover: #2f6cb6;\n  --linkTextDecoration: none;\n\n  --headerBackground: #eee;\n  --headerBorder: #d7d7d7;\n  --searchTagBackground: #{color.adjust(#eee, $lightness: -5%)};\n  --searchBorder: #{color.adjust(#d7d7d7, $lightness: -2%)};\n\n  --sidebarBackground: #f9f9f9;\n  --transparentSidebarBackground: #{rgba(#f9f9f9, 0)};\n  --sidebarBorder: #e1e1e1;\n\n  --scrollbarColor: #ccc;\n  --scrollbarColorHover: #999;\n\n  --pathBackground: var(--sidebarBackground);\n  --pathBorder: var(--sidebarBorder);\n\n  --noticeBackground: #faf9e2;\n  --noticeBorder: #e2e2c1;\n\n  --boxBackground: #fafafa;\n  --boxBorder: #d8d8d8;\n  --boxBorderLight: #e5e5e5;\n  --boxHeaderColor: var(--textColor);\n  --boxHeaderBackground: #f5f5f5;\n\n  --noteBackground: #f8f8dd;\n  --noteBorder: #d3d952;\n\n  --noteGreenBackground: #e7f8e1;\n  --noteGreenBorder: #89da70;\n\n  --noteBlueBackground: #d4f3fd;\n  --noteBlueBorder: #94bbeb;\n\n  --noteOrangeBackground: #fbe6d1;\n  --noteOrangeBorder: #ec8b01;\n\n  --noteRedBackground: #fed5d3;\n  --noteRedBorder: #dc7874;\n\n  --labelBackground: #f4f4f4;\n\n  --notifBackground: #{rgba(#333, .85)};\n  --notifBorder: none;\n\n  --tipBackground: #{rgba(#fffdcd, .95)};\n  --tipBorder: 1px solid #e7dca9;\n}\n"
  },
  {
    "path": "assets/stylesheets/global/_variables.scss",
    "content": "@use 'global/variables-light';\n@use 'global/variables-dark';\n\n// Variables needed to style the error message for browsers that don't support CSS variables.\n$baseFont: -apple-system, BlinkMacSystemFont, 'San Francisco', 'Segoe UI', Roboto, Ubuntu, 'Helvetica Neue', Arial, sans-serif;\n$textColor: #333;\n\nhtml {\n  --baseFont: #{$baseFont};\n  --monoFont: 'SF Mono', 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;\n  --boldFontWeight: 500;\n  --bolderFontWeight: 600;\n\n  --textColorRed: #f44336;\n\n  --splashText: var(--loadingText);\n\n  --selectionText: #fff;\n  --transparentSelectionText: rgba(255, 255, 255, 0.9);\n\n  --notifColor: #fff;\n  --notifColorLight: #ccc;\n\n  --maxWidth: 80rem;\n  --headerHeight: 3rem;\n  --sidebarWidth: 20rem;\n  --sidebarMediumWidth: 16rem;\n\n  --focusBackground: #e5e5e5;\n  --focusBorder: #d4d4d4;\n  --focusText: #000;\n\n  --contentZ: 1;\n  --sidebarZ: 2;\n  --headerZ: 3;\n  --noticeZ: 4;\n  --hoverZ: 5;\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_angular.scss",
    "content": "@use 'global/base';\n@use 'pages/simple';\n\n._angular {\n  @extend %simple;\n\n  .pre-title { @extend %pre-heading; }\n\n  .breadcrumbs { @extend %note; }\n  .banner { @extend %note-green; }\n  code.stable { @extend %label-green; }\n  code.experimental { @extend %label-orange; }\n  code.deprecated { @extend %label-red; }\n  .alert.is-important { @extend %note-red; }\n  .alert.is-helpful, .breadcrumbs { @extend %note-blue; }\n\n  .breadcrumbs { padding-left: 2em; }\n\n  img { margin: 1em 0; }\n\n  .location-badge {\n    font-style: italic;\n    text-align: right;\n  }\n\n  td h3 { margin: 0 !important; }\n\n  .docs-reference-member-card .docs-reference-card-item {\n    &:has(~ .docs-reference-card-item),\n    &:last-child:not(:first-of-type) {\n      margin: .25rem 0 1.5rem 1.5rem;\n      padding: .625rem 1rem;\n      @extend %box;\n    }\n    span {\n      display: inline-block;\n    }\n    .docs-param-group {\n      margin-block-start: 1rem;\n      &:not(:has(~ .docs-param-group)) {\n        margin-block: 1rem;\n      }\n      .docs-param-name {\n        @extend %code;\n        margin-inline-end: 0.25rem;\n      }\n      .docs-param-name:after {\n        content: \":\";\n      }\n      .docs-parameter-description p:first-child {\n        margin-block-start: 0;\n      }\n    }\n    .docs-param-keyword {\n      color: var(--focusText);\n      @extend %code;\n      margin-inline-end: 0.5rem;\n    }\n    .docs-return-type {\n      padding-block: 1rem;\n    }\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_angularjs.scss",
    "content": "@use 'global/classes';\n\n._angularjs {\n  h2 { @extend %block-heading; }\n\n  //\n  // Index\n  //\n\n  .nav-index-section {\n    margin: 1.5em 0 1em -2em;\n    list-style: none;\n    font-weight: var(--boldFontWeight);\n    text-transform: capitalize;\n  }\n\n  //\n  // Other\n  //\n\n  h3, h4 { font-size: 1rem; }\n\n  .alert { @extend %note; }\n  .alert-success { @extend %note-green; }\n  .alert-error { @extend %note-red; }\n\n  p > code, li > code, td > code { @extend %label; }\n\n  .view-source, .improve-docs {\n    order: 1;\n    display: block;\n    vertical-align: top;\n    padding-left: 1em;\n    font-size: .875rem;\n  }\n\n  .defs {\n    padding-left: 1rem;\n    list-style: none;\n\n    > li > h3:first-child {\n      margin: 0 0 1em -1rem;\n      @extend %block-label, %label-blue;\n    }\n\n    > li + li { margin-top: 2em; }\n\n    h4 {\n      margin: 1em 0 .5em;\n      font-size: 1em;\n    }\n\n    ul { list-style-type: disc; }\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_apache.scss",
    "content": "@use 'pages/simple';\n\n._apache {\n  @extend %simple;\n\n  .note, .warning { @extend %note; }\n  .warning { @extend %note-red; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_async.scss",
    "content": "@use 'pages/simple';\n\n._async {\n  @extend %simple;\n\n  h3 > .type-signature {\n    float: right;\n    color: var(--textColorLight);\n  }\n\n  h3 > .signature-attributes {\n    font-size: .75rem;\n    font-weight: normal;\n    font-style: italic;\n    color: var(--textColorLighter);\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_bash.scss",
    "content": "@use 'global/classes';\n\n._bash {\n  dl > dt > code,\n  dl > dt > kbd {\n    @extend %block-label, %label-blue;\n  }\n\n  th[align=left] {\n    border-left: 1px solid var(--boxBorder);\n  }\n\n  code { @extend %label; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_bootstrap.scss",
    "content": "@use 'pages/simple';\n\n._bootstrap {\n  @extend %simple;\n\n  h4 > code, h5 > code, strong > code { @extend %label; }\n\n  h2 > small {\n    color: var(--textColorLight);\n    float: right;\n  }\n\n  .h5 { font-size: 1.25rem; }\n\n  .bs-callout { @extend %note; }\n  .bs-callout-info { @extend %note-blue; }\n  .bs-callout-danger { @extend %note-red; }\n  .bs-callout > h4, .bs-callout > h5 { margin-top: .25rem; }\n\n  .text-danger { @extend %label, %label-red; }\n\n  p.bs-example {\n    padding: .375rem .625rem;\n    line-height: 1.5;\n    @extend %heading-box;\n  }\n\n  div.bs-example {\n    @extend %pre-heading;\n  }\n\n  a.thumbnail {\n    display: block;\n    padding: .25em;\n    @extend %box;\n\n    &:after { content: none; }\n    + h4 { margin: .75em 0 .5em; }\n    > img { display: block; }\n  }\n\n  .col { margin-bottom: 1.5em; }\n  .d-block { display: block; }\n\n  @media (min-width: 800px) {\n    .row {\n      display: flex;\n      flex-wrap: wrap;\n    }\n\n    .col {\n      flex: 0 1 auto;\n      padding: 0 1em;\n      width: 33.33%;\n      -moz-box-sizing: border-box;\n           box-sizing: border-box;\n    }\n    .col-md-6 { width: 50%; }\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_cakephp.scss",
    "content": "@use 'pages/simple';\n\n._cakephp {\n  @extend %simple;\n\n  h3 > .source { float: right; }\n  h3 > a, span.label, span.php-keyword1 { font-weight: normal; }\n\n  h4 {\n    margin: 1.5em 0;\n    @extend %block-label;\n  }\n\n  dl { margin: 1em 0; }\n\n  .info { @extend %note; }\n  code { @extend %label; }\n  p > .label, dt > .label, div > .label { @extend %label, %label-yellow; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_chef.scss",
    "content": "@use 'pages/simple';\n\n._chef {\n  @extend %simple;\n\n  .note, .warning { @extend %note; }\n  .warning { @extend %note, %note-red; }\n\n  code { @extend %label; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_clojure.scss",
    "content": "@use 'global/classes';\n\n._clojure {\n  h2:not([id]) { @extend %block-heading; }\n  h2[id], h3 { @extend %block-label, %label-blue; }\n\n  .type {\n    float: right;\n    font-size: .9em;\n    color: var(--textColorLight);\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_codeception.scss",
    "content": "@use 'pages/simple';\n\n._codeception {\n  @extend %simple;\n\n  h4 { @extend %block-label; }\n  .warning, .alert { @extend %note; }\n  .alert-danger { @extend %note-red; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_coffeescript.scss",
    "content": "@use 'pages/simple';\n\n\n._coffeescript {\n  @extend %simple;\n\n  // CoffeeScript / JavaScript code blocks\n  > .code {\n    margin: 1.5em 0;\n    overflow: hidden;\n\n    > pre {\n      float: left;\n      width: 49%;\n      margin: 0;\n      @extend %border-box;\n\n      &:last-child { float: right; }\n    }\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_cordova.scss",
    "content": "@use 'pages/simple';\n\n._cordova {\n  @extend %simple;\n\n  .alert { @extend %note; }\n\n  .compat .n { background: pink; }\n  .compat .y { background: lightgreen; }\n  .compat .p { background: khaki; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_cppref.scss",
    "content": "@use 'global/base';\n\n._cppref {\n  > h2, > h3 { @extend %block-heading; }\n  > h4 { @extend %block-label, %label-blue; }\n  .fmbox { @extend %note; }\n  code, .t-mark, .t-mark-rev { @extend %label; }\n  .t-cc { @extend %code; }\n\n  .t-li1 { margin: 0 0 1em; }\n\n  .t-mark, .t-mark-rev {\n    white-space: nowrap;\n    @extend %label-green;\n  }\n\n  .t-dcl-begin pre {\n    margin: 0;\n    padding: 0;\n    line-height: inherit;\n    background: none;\n    border: 0;\n  }\n\n  .t-lines > span { display: block; } // numeric/fenv, string/byte, etc.\n\n  .t-spar { // language/switch, language/for, etc.\n    font-style: italic;\n    color: var(--textColorLight);\n  }\n  .t-sdsc-nopad dl, .t-sdsc-nopad dd { margin: 0; }\n\n  td {\n    > h3, > h5 {\n      margin: 0 0 .5em;\n      line-height: inherit;\n\n      &:only-child { margin: 0; }\n    }\n\n    > ul, > dl {\n      margin: .5em 0;\n\n      &:only-child { margin: 0; }\n    }\n\n    > .t-dsc-member-div > div { // utility/functional\n      float: left;\n\n      + div { margin-left: .5em; }\n    }\n\n    > table { margin: 0; }\n  }\n\n  .t-dcl-rev-aux > td:empty { padding: 0; }\n\n  .t-inheritance-diagram {\n    display: table;\n    margin: 1rem 0;\n    padding: .375rem;\n    font-size: .75rem;\n    border: 1px solid var(--boxBorder);\n    border-radius: 2px;\n  }\n\n  ul > ul { margin: 0 0 .5em; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_crystal.scss",
    "content": "@use 'global/base';\n@use 'pages/simple';\n\n._crystal {\n  @extend %simple;\n\n  .signature { @extend %code; }\n  a.signature, .superclass > a { @extend %label; }\n\n  .entry-detail { margin-top: 1em; }\n  .view-source { float: right; }\n\n  .superclass-hierarchy {\n    list-style: none;\n    padding: 0;\n    overflow: hidden;\n  }\n\n  li.superclass {\n    float: left;\n    margin: 0 .5em 0 0;\n    padding: 0;\n  }\n\n  li.superclass + li.superclass:before {\n    content: '<';\n    margin-right: .5em;\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_cypress.scss",
    "content": "@use 'pages/simple';\n\n._cypress {\n  @extend %simple;\n\n  .note {\n    h1 {\n      margin-left: inherit\n    }\n\n    &.danger {\n      @extend %note-red\n    }\n\n    &.info {\n      @extend %note-blue\n    }\n\n    &.success {\n      @extend %note-green\n    }\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_d.scss",
    "content": "@use 'global/base';\n\n._d {\n  h2 { @extend %block-heading; }\n  h3, .d_decl { @extend %block-label, %label-blue; }\n  .d_decl { @extend %code; }\n  .d_decl > small { color: var(--textColorLight); }\n  .d_decl > strong { font-weight: var(--bolderFontWeight); }\n\n  p > code, li > code, td > code, dd > code { @extend %label; }\n\n  span.red { color: var(--textColorRed); }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_d3.scss",
    "content": "@use 'global/classes';\n\n._d3 {\n  > h2 { @extend %block-heading; }\n  > h3 { @extend %block-label; }\n  > h4 { font-size: 1rem; }\n  > h6 { @extend %block-label, %label-blue; }\n\n  > h6 > .source {\n    float: right;\n    font-weight: normal;\n  }\n\n  code { @extend %label; }\n  blockquote { @extend %note, %note-blue; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_dart.scss",
    "content": "@use 'pages/simple';\n\n._dart {\n  @extend %simple;\n\n  dl:not(.dl-horizontal) dt, .multi-line-signature {\n    @extend %block-label;\n\n    .features {\n      float: right;\n      color: var(--textColorLight);\n    }\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_dojo.scss",
    "content": "@use 'pages/simple';\n\n._dojo {\n  @extend %simple;\n\n  .jsdoc-inheritance { color: var(--textColorLight); }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_drupal.scss",
    "content": "@use 'global/classes';\n\n._drupal {\n  h3 { @extend %block-heading; }\n  .signature { @extend %note, %note-blue; }\n\n  span.api-deprecated { @extend %label, %label-red; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_eigen3.scss",
    "content": ""
  },
  {
    "path": "assets/stylesheets/pages/_elisp.scss",
    "content": "@use 'global/classes';\n\n._elisp {\n    dl > dt {\n        @extend %block-label, %label-blue;\n    }\n\n    dl[compact] > dt {\n        background: none;\n        border-color: none;\n        line-height: normal;\n        margin: auto;\n        border: none;\n    }\n\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_elixir.scss",
    "content": "@use 'pages/simple';\n\n._elixir {\n  @extend %simple;\n\n  .type-detail { margin-bottom: 2em; }\n  .type-detail pre { margin-left: -1rem; }\n  ._mobile & .type-detail pre { margin-left: 0; }\n\n  a.source {\n    float: right;\n    font-size: .9em;\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_ember.scss",
    "content": "@use 'pages/simple';\n\n._ember {\n  @extend %simple;\n\n  .pre-title { @extend %pre-heading; }\n\n  h3 > .access {\n    float: right;\n    color: var(--textColorLight);\n    font-weight: normal;\n  }\n\n  h3 > .args,\n  h3 > .return-type {\n    font-weight: normal;\n  }\n\n  p.github-link {\n    color: var(--textColorLight);\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_erlang.scss",
    "content": "@use 'pages/simple';\n\n._erlang {\n  @extend %simple;\n\n  h3 > code { display: block; }\n  code.code { @extend %label; }\n  .note { @extend %note; }\n  .warning { @extend %note, %note-red; }\n  .note .label, .warning .label { font-weight: var(--boldFontWeight); }\n\n  .since {\n\t  color: var(--textColorLight);\n\t  font-weight: normal;\n\t  font-size: small;\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_express.scss",
    "content": "@use 'pages/simple';\n\n._express {\n  @extend %simple;\n\n  .doc-box { @extend %note; }\n  .doc-warn { @extend %note-red; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_fastapi.scss",
    "content": "@use 'global/classes';\n\n._fastapi {\n  > h2 { @extend %block-heading; }\n  > h3 { @extend %block-label, %label-blue; }\n\n  code { @extend %label; }\n\n  .tabbed-block {\n    border: 1px dashed black;\n    padding: 0.5rem 1rem 0;\n    margin-bottom: 1rem;\n  }\n  .tabbed-block label {\n    font-weight: var(--bolderFontWeight);\n  }\n\n  .admonition { @extend %note; }\n  .admonition.tip { @extend %note-green; }\n  .admonition.note { @extend %note-blue; }\n  .admonition-title {\n    font-weight: var(--bolderFontWeight);\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_fluture.scss",
    "content": "@use 'pages/simple';\n\n._fluture {\n  @extend %simple;\n\n  pre > code {\n    font-size: inherit;\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_git.scss",
    "content": "@use 'global/classes';\n\n._git {\n  padding-left: 1rem;\n\n  > h1,\n  > h2,\n  > .reference-menu,\n  > .callout,\n  > h1 + .sectionbody {\n    margin-left: -1rem;\n  }\n\n  > h2 { @extend %block-heading; }\n  h3 { font-size: 1rem; }\n\n  > .callout,\n  > h1 + .sectionbody {\n    @extend %note, %note-green;\n  }\n\n  code { @extend %label; }\n  em { font-style: normal; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_github.scss",
    "content": "@use 'pages/simple';\n\n._github {\n  @extend %simple;\n\n  h4 { @extend %block-label; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_gnu_make.scss",
    "content": "@use 'global/classes';\n\n._gnu_make {\n    dl dt {\n        @extend %block-label, %label-blue;\n    }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_gnuplot.scss",
    "content": "@use 'pages/simple';\n\n._gnuplot {\n  .CENTER {\n    text-align: center;\n  }\n  @extend %simple;\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_go.scss",
    "content": "@use 'pages/simple';\n\n._go {\n  @extend %simple;\n\n  #short-nav, table.dir { margin-left: -1rem; }\n\n  a.source, span[title^=\"Added in Go\"] {\n    float: right;\n    font-size: .9em;\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_graphite.scss",
    "content": "@use 'pages/simple';\n\n._graphite {\n  @extend %simple;\n\n  dl > dt {\n    @extend %block-label, %label-blue;\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_groovy.scss",
    "content": "@use 'global/classes';\n\n._groovy {\n  padding-left: 1rem;\n\n  h1, h2 { margin-left: -1rem; }\n  h2 { @extend %block-heading; }\n  h3 { @extend %block-label; }\n\n  .constructor { @extend %label-blue; }\n  .method { @extend %label-blue; }\n  .element { @extend %label-green; }\n  .field { @extend %label-green; }\n  .enum_constant { @extend %label-orange; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_gtk.scss",
    "content": "@use 'global/classes';\n\n._gtk {\n  padding-left: 1rem;\n\n  h1, h2, h3 { margin-left: -1rem; }\n  h2 { @extend %block-heading; }\n  h3 { @extend %block-label, %label-blue; }\n\n  div.toc { margin-top: 1.5em; }\n  .toc dl { margin-top: 0; margin-bottom: 0; }\n\n  .note { @extend %note, %note-green; }\n  .warning { @extend %note, %note-orange; }\n\n  .gallery-float { float:left; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_hapi.scss",
    "content": "@use 'pages/simple';\n\n._hapi {\n  @extend %simple;\n\n  pre > code {\n    font-size: inherit;\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_haproxy.scss",
    "content": "@use 'global/classes';\n\n._haproxy {\n  padding-left: 1rem;\n\n  h1, h2, h3 { margin-left: -1rem; }\n  h2 { @extend %block-heading; }\n  h3 { @extend %block-label; }\n  h4 { @extend %block-label; }\n\n  ._mobile & {\n    padding-left: 0;\n\n    h1, h2, h3 { margin-left: 0; }\n  }\n\n  .pagination-centered { text-align: center; }\n\n  .pull-right { float: right !important; }\n\n  .keyword { @extend %block-label, %label-blue;}\n\n  pre.text {\n    background: var(--contentBackground);\n    border-width: 0px;\n  }\n\n  td.alert-success { background: var(--noteGreenBackground); }\n  td.alert-error { background: var(--noteRedBackground); }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_haskell.scss",
    "content": "@use 'global/base';\n\n._haskell-api {\n  > h2 { @extend %block-heading; }\n  > h3 { @extend %block-label; }\n  h4 { font-size: 1em; }\n\n  .module + .package, p.src > .link { float: right; }\n\n  .src {\n    white-space: normal;\n    @extend %code;\n  }\n  p.src {\n    font-size: .8125rem;\n    @extend %block-label, %label-blue;\n  }\n  dt.src { white-space: normal; }\n\n  > .subs { margin-left: 2em; }\n  .subs p.src { margin-top: 1em; }\n\n  dt > code, .complexity, .version { @extend %label; }\n  .complexity, .version { @extend %label-green; }\n\n  table { margin: 1em 0; }\n  td > pre { margin: 0; }\n\n  .warning { @extend %note; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_jasmine.scss",
    "content": "@use 'global/classes';\n\n._jasmine {\n    .subsection-title, h2 { @extend %block-heading; }\n    h4 { @extend %block-label, %label-blue; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_jekyll.scss",
    "content": "@use 'pages/simple';\n\n._jekyll {\n  @extend %simple;\n\n  .note.info { @extend %note-blue; }\n  .note.warning { @extend %note-red; }\n  .note.unreleased { @extend %note-orange; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_joi.scss",
    "content": "@use 'pages/simple';\n\n._joi {\n  @extend %simple;\n\n  pre > code {\n    font-size: inherit;\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_jq.scss",
    "content": "@use 'global/base';\n@use 'pages/simple';\n\n._jq {\n  @extend %simple;\n\n  .manual-example table {\n\t  border: none;\n\n\t  & td {\n\t\t  @extend %pre;\n\t\t  &.jqprogram { font-weight: bold; }\n\t\t  border: none;\n\t  }\n\n\t  & th {\n\t\t  color: var(--textColor);\n\t\t  background: var(--contentBackground);\n\t\t  text-align: right;\n\t\t  border: none;\n\t  }\n\n\t  & tr:not(:first-child) th:not(:empty) {\n\t\t  &, & + td {\n\t\t\t  border-top: 1px solid var(--boxBorder);\n\t\t  }\n\t  }\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_jquery.scss",
    "content": "@use 'global/classes';\n\n._jquery {\n  //\n  // Index page\n  //\n\n  h2.entry-title {\n    margin: 0 0 1.5rem;\n    font-size: 1rem;\n    font-weight: normal;\n  }\n\n  .entry-summary {\n    margin: -1rem 0 1.5rem;\n    padding-right: 1.5rem;\n  }\n\n  .post:not(:only-of-type),\n  .page:not(:only-of-type) {\n    width: 50%;\n    float: left;\n\n    &:nth-of-type(2n+1) { clear: both; }\n  }\n\n  //\n  // Article page\n  //\n\n  // Table of contents\n\n  .toc > h4 { font-size: inherit; }\n\n  .toc-list {\n    margin-top: 0;\n    font-weight: var(--boldFontWeight);\n\n    > li + li { margin-top: 1em; }\n    > li > ul { font-weight: normal; }\n    > li > ul > li + li { margin-top: 0; }\n  }\n\n  // Headings\n\n  .section-title, .entry-wrapper > h3, .underline { @extend %block-heading; }\n\n  .name > .version-details,\n  .section-title > .version-details,\n  .returns,\n  .option-type {\n    float: right;\n    font-weight: var(--boldFontWeight);\n    margin-left: 1em;\n  }\n\n  // Method signatures\n\n  .signatures {\n    padding: 0;\n    list-style: none;\n  }\n\n  .signature {\n    + .signature { margin-top: 1em; }\n\n    > .name {\n      margin-top: 1em;\n      @extend %block-label, %label-blue;\n    }\n\n    > ul {\n      padding-left: 1em;\n      list-style: none;\n\n      > li + li { margin-top: 1em; }\n      > li > ul { list-style-type: disc; }\n    }\n  }\n\n  // Examples\n\n  .entry-example {\n    > h4 {\n      margin: 2em 0 1.5em;\n      line-height: inherit;\n      font-size: inherit;\n      font-weight: normal;\n    }\n  }\n\n  // Quick nav (jQuery UI)\n\n  #quick-nav {\n    margin-bottom: 2em;\n    max-width: 38em;\n    overflow: hidden;\n    @extend %note, %note-blue;\n\n    > h2 {\n      margin: .25rem 0 1rem;\n      font-size: 1rem;\n\n      > a { float: right; }\n    }\n  }\n\n  .quick-nav-section {\n    width: 33%;\n    float: left;\n\n    > h3 {\n      margin: 0 0 .5em;\n      font-size: inherit;\n    }\n  }\n\n  // Options (jQuery UI)\n\n  .api-item {\n    padding-left: 1rem;\n\n    > h3 {\n      margin-left: -1rem;\n      @extend %block-label, %label-blue;\n    }\n  }\n\n  // Misc\n\n  p > code, li > code { @extend %label; }\n  .warning { @extend %note; }\n\n  .name > a,\n  .version-details > a {\n    color: inherit;\n    @extend %internal-link;\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_julia.scss",
    "content": "@use 'pages/simple';\n\n._julia {\n  @extend %simple;\n\n  .footnote { @extend %note; }\n  .note { @extend %note; }\n  .docstring-category { float: right; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_knockout.scss",
    "content": "@use 'global/classes';\n\n._knockout {\n  > h2 { @extend %block-heading; }\n  > h3 { @extend %block-label, %label-blue; }\n  p > code { @extend %label; }\n  .liveExample, blockquote { @extend %note; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_kotlin.scss",
    "content": "@use 'global/classes';\n\n._kotlin {\n  h2 { @extend %block-heading; }\n  h3 { @extend %block-label, %label-blue; }\n  code { @extend %label; }\n\n  td > pre { margin: .5em 0; }\n\n  .api-docs-breadcrumbs { @extend %note; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_kubectl.scss",
    "content": "@use 'pages/simple';\n\n._kubectl {\n  @extend %simple;\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_kubernetes.scss",
    "content": "@use 'pages/simple';\n\n._kubernetes {\n  @extend %simple;\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_laravel.scss",
    "content": "@use 'global/classes';\n\n._laravel {\n  h2 { @extend %block-heading; }\n  h3 { @extend %block-label, %label-blue; }\n  h4 { font-size: 1em; }\n\n  blockquote { @extend %note; }\n  blockquote.tip { @extend %note-blue; }\n  p > code, h4 > code { @extend %label; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_liquid.scss",
    "content": "@use 'pages/simple';\n\n._liquid {\n  @extend %simple;\n\n  p.code-label { @extend %pre-heading; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_lit.scss",
    "content": "@use 'pages/simple';\n\n._lit {\n  @extend %simple;\n\n  h4 {\n    @extend %block-label, %label-blue;\n  }\n\n  .view-source {\n    float: right;\n  }\n  .propertyDetails {\n    padding-left: 1.5em;\n  }\n  .heading.property {\n    margin-top: 2em;\n  }\n  .heading.property > h4 {\n    font-weight: 400;\n  }\n  .newKeyword,\n  .readonlyKeyword,\n  .staticKeyword {\n    font-style: italic;\n  }\n  .functionName,\n  .propertyName {\n    font-weight: 700;\n  }\n  aside.litdev-aside {\n    display: flex;\n    border-style: solid;\n    border-width: 1px;\n    padding: 1em 1em 1em 0em;\n    margin: 1em 0;\n    svg {\n      width: 1.5em;\n      margin-inline: 1em;\n    }\n  }\n  litdev-switchable-sample {\n    pre[data-language] {\n      position: relative;\n    }\n    pre[data-language]::before {\n      position: absolute;\n      top: 0;\n      right: 16px;\n      opacity: 0.5;\n    }\n    pre[data-language=\"js\"]::before {\n      content: \"JavaScript\";\n    }\n    pre[data-language=\"ts\"]::before {\n      content: \"TypeScript\";\n    }\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_love.scss",
    "content": "@use 'pages/simple';\n\n._love {\n  @extend %simple;\n\n  .note { @extend %note; }\n  .note-green { @extend %note-green; }\n  .note-red { @extend %note-red; }\n\n  .label, dt > code { @extend %label; }\n  .label-green { @extend %label-green; }\n  .label-red { @extend %label-red; }\n\n  .smwtable { width: 100%; }\n  .smwtable td:nth-last-child(2), .smwtable td:last-child { width: 2.5em; }\n\n  .cell-orange { background: var(--noteOrangeBackground); }\n  .cell-green { background: var(--noteGreenBackground); }\n  .cell-red { background: var(--noteRedBackground); }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_lua.scss",
    "content": "@use 'pages/simple';\n\n._lua {\n  @extend %simple;\n\n  .apii { float: right; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_mariadb.scss",
    "content": "@use 'pages/simple';\n\n._mariadb {\n  @extend %simple;\n\n  .graybox, .product {\n    @extend %note;\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_mdn.scss",
    "content": "@use 'global/classes';\n\n._mdn {\n  container-type: inline-size;\n\n  .index { // HTML, CSS\n    -webkit-columns: 16em;\n       -moz-columns: 16em;\n            columns: 16em;\n\n    > span {\n      display: block;\n      font-size: 1rem;\n      font-weight: var(--boldFontWeight);\n    }\n\n    ul, ol {\n      margin: 0 0 1em;\n      padding: 0;\n      line-height: 1.5;\n      list-style: none;\n    }\n\n    li { padding-left: 1em; }\n  }\n\n  > h2 { @extend %block-heading; }\n  > h3 { @extend %block-label, %label-blue; }\n  > h4 { font-size: 1em; }\n\n  p > code, li > code { @extend %label; }\n\n  details { @extend %note; @extend %box; }\n  details.baseline-indicator.not { @extend %note; @extend %note-gray; }\n  details.baseline-indicator.low { @extend %note; @extend %note-blue; }\n  details.baseline-indicator.high { @extend %note; @extend %note-green; }\n  summary > div { display: inline; }\n  summary .status-title { font-weight: bold; }\n\n  > .note,\n  .notecard, // MDN 2021\n  .notice {\n    @extend %note; @extend %note-blue;\n  }\n  .warning,\n  .overheadIndicator,\n  .blockIndicator,\n  .syntaxbox,           // CSS, JavaScript\n  .twopartsyntaxbox,    // CSS\n  .inheritsbox,         // JavaScript\n  .eval:first-of-type { // JavaScript\n    @extend %note;\n  }\n\n  .warning { @extend %note-red; }\n\n  > .note {\n    em {\n      font-style: normal;\n      font-weight: var(--boldFontWeight);\n    }\n\n    > ul { margin: 1em 0; }\n    > p:last-child, > ul:last-child { margin-bottom: 0; }\n  }\n\n  .inlineIndicator {\n    white-space: nowrap;\n    @extend %label;\n  }\n\n  .syntaxbox a,        // CSS\n  .twopartsyntaxbox a, // CSS\n  .inlineIndicator > a {\n    color: inherit;\n    @extend %internal-link;\n  }\n\n  .deprecated, .obsolete { @extend %label-red; }\n  .nonStandard, .projectSpecific, .experimental { @extend %label-orange; }\n\n  .htmlelt,\n  .cssprop {\n    display: table;\n    @extend %note, %note-blue;\n\n    > li {\n      display: table-row;\n      margin: 0;\n\n      > dfn {\n        display: table-cell;\n        padding: .125rem 1.5rem .125rem 0;\n        white-space: pre;\n        border: 0;\n        cursor: inherit;\n\n        &:after { content: ':'; }\n      }\n    }\n\n    th, td {\n      background: none;\n      border: 0;\n    }\n  }\n\n  dt > strong > code, // HTML element attribute\n  dl > dt > code {    // CSS property value, Javascript function argument\n    font-family: inherit;\n    font-weight: var(--boldFontWeight);\n    font-size: inherit;\n  }\n\n  .eventinfo { // DOM event\n    > dd + dt { margin-top: 0; }\n  }\n\n  .cleared { clear: both; } // CSS/box-shadow\n\n  code > strong { font-weight: normal; }\n\n  // Compatibility tables\n\n  .bc-github-link {\n    float: right;\n    font-size: .75rem;\n  }\n\n  .bc-supports-yes, .bc-supports-yes + dd, .bc-supports-yes + dd + dd { background: var(--noteGreenBackground); }\n  .bc-supports-preview, .bc-supports-preview + dd, .bc-supports-preview + dd + dd { background: var(--noteBlueBackground); }\n  .bc-supports-unknown, .bc-supports-unknown + dd, .bc-supports-unknown + dd + dd { background: var(--noteBackground); }\n  .bc-supports-partial, .bc-supports-partial + dd, .bc-supports-partial + dd + dd { background: var(--noteOrangeBackground); }\n  .bc-supports-no, .bc-supports-no + dd, .bc-supports-no + dd + dd { background: var(--noteRedBackground); }\n\n  .bc-table {\n    min-width: 100%;\n\n    dl {\n      margin: .25rem 0 0;\n      padding: .25rem 0 0;\n      font-size: .75rem;\n      border-top: 1px solid var(--boxBorder);\n    }\n\n    dd { margin: 0; }\n  }\n\n  // based on https://github.com/mdn/yari/blob/63936bc42c/client/src/document/interactive-examples.scss\n  .interactive {\n      width: 100%;\n      height: 680px;\n      &.is-js-height {\n        height: 520px;\n      }\n      &.is-shorter-height {\n        height: 440px;\n      }\n      &.is-taller-height {\n        height: 730px;\n      }\n      &.is-tabbed-shorter-height {\n        height: 490px;\n      }\n      &.is-tabbed-standard-height {\n        height: 550px;\n      }\n      &.is-tabbed-taller-height {\n        height: 780px;\n      }\n    }\n  @container (min-width: 594px) {\n    .interactive {\n      height: 380px;\n\n      &.is-js-height {\n        height: 450px;\n      }\n      &.is-shorter-height {\n        height: 370px;\n      }\n      &.is-taller-height {\n        height: 660px;\n      }\n      &.is-tabbed-shorter-height {\n        height: 360px;\n      }\n      &.is-tabbed-standard-height {\n        height: 430px;\n      }\n      &.is-tabbed-taller-height {\n        height: 640px;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_meteor.scss",
    "content": "@use 'pages/simple';\n\n._meteor {\n  @extend %simple;\n\n  .note, .warning, .subtitle-page { @extend %note; }\n  .subtitle-page { @extend %note-blue; }\n  .warning { @extend %note-red; }\n\n  dl.args { margin-left: 1rem; }\n  dt > code { @extend %label; }\n\n  .api-heading { overflow: hidden; }\n  .api-heading > code { font-weight: var(--boldFontWeight); }\n  .locus, .src-code { float: right; }\n  .locus, .type, .src-code { margin-left: .5em; }\n  h2 .subtext-api { margin-top: .25rem; }\n  .locus, .subtext-api, .subtext-api > code { font-size: .75rem; }\n  .locus, .type { color: var(--textColorLight); }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_mkdocs.scss",
    "content": "@use 'global/classes';\n\n._mkdocs {\n  h2 { @extend %block-heading; }\n  h3 { @extend %block-label, %label-blue; }\n  h4 { @extend %block-label; }\n\n  blockquote { @extend %note; }\n\n  strong { font-weight: var(--bolderFontWeight); }\n\n  p > code, li > code { @extend %label; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_modernizr.scss",
    "content": "@use 'global/classes';\n\n._modernizr {\n  h2 { @extend %block-heading; }\n  h3 { @extend %block-label, %label-blue; }\n  h4 { font-size: 1em; }\n\n  code { @extend %label; }\n  blockquote { @extend %note; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_moment.scss",
    "content": "@use 'global/classes';\n\n._moment {\n  > h2 { @extend %block-heading; }\n  > h3 { @extend %block-label, %label-blue; }\n  > h3 > span { float: right;}\n  h4 { font-size: 1em; }\n  code { @extend %label; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_nginx.scss",
    "content": "@use 'global/classes';\n\n._nginx {\n  h4 { @extend %block-heading; }\n  .note { @extend %note; }\n  .directive { margin: 2.5em 0 1em; }\n  td > pre { margin: 0; }\n  dt > code { @extend %label; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_node.scss",
    "content": "@use 'global/classes';\n\n._node {\n  // https://nodejs.org/api/documentation.html#stability-index\n  .api_stability { clear: both; }\n  .api_stability_0 { @extend %note, %note-red; }\n  .api_stability_1 { @extend %note, %note-orange; }\n  .api_stability_1 { @extend %note; }\n  .api_stability_2 { @extend %note, %note-green; }\n  .api_stability_3 { @extend %note, %note-blue; }\n\n  > h2 { @extend %block-heading; }\n  > h3 { @extend %block-label, %label-blue; }\n  > h4 { @extend %block-label; }\n  > h2 + h2, > h3 + h3 { margin-top: 0; }\n\n  h3 { @extend %block-label, %label-blue}\n  h4 { @extend %block-label }\n\n  p > code, li > code, .type {\n    white-space: normal;\n    @extend %label;\n  }\n\n  .api_metadata {\n    float: right;\n    margin: 0 0 1em 1em;\n    @extend %label;\n  }\n\n  .srclink { float: right; }\n  details > table { margin: 0; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_npm.scss",
    "content": "@use 'pages/simple';\n\n._npm {\n  @extend %simple;\n\n  .pageColumns {\n    padding-left: 0;\n    list-style: none;\n  }\n\n  .faint.heading {\n    font-size: .9em;\n    color: var(--textColorLight);\n  }\n\n  .youtube-video iframe { width: 420px; height: 315px; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_nushell.scss",
    "content": "@use 'pages/simple';\n\n._nushell {\n  @extend %simple;\n\n  pre > code {\n    font-size: inherit;\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_octave.scss",
    "content": "@use 'pages/simple';\n\n._octave {\n  @extend %simple;\n\n  dl:not([compact]) > dt { @extend %block-label, %label-blue; }\n\n  dl[compact] > dt { @extend %block-label; }\n\n  .footnotes-heading { @extend %block-heading; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_openjdk.scss",
    "content": "@use 'pages/simple';\n\n._openjdk {\n  @extend %simple;\n  > .inheritance { @extend %note; }\n\n  ul.inheritance { list-style: none; }\n  > ul.inheritance { @extend %note, %note-blue; }\n  > ul.inheritance ul.inheritance { margin: 0; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_openlayers.scss",
    "content": "@use 'global/base';\n@use 'pages/simple';\n\n._openlayers {\n  @extend %simple;\n  .nameContainer {\n    @extend %block-label;\n    > * { display: inline-block; margin: 0; }\n    > .tag-source { float: right; }\n  }\n  .card { @extend %box; margin-bottom: 1rem; padding: 1rem; }\n  .signature, .type-signature { @extend %code; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_perl.scss",
    "content": "@use 'pages/simple';\n\n._perl {\n  @extend %simple;\n\n  dt + dt { margin-top: 1em; }\n\n  > dl > dt  { @extend %block-label; }\n  > dl > dt.function { @extend %label-blue; }\n  > dl > dt.variable { @extend %label-green; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_phalcon.scss",
    "content": "@use 'pages/simple';\n\n._phalcon {\n  @extend %simple;\n\n  h3 > small {\n    float: right;\n    color: var(--textColorLight);\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_phaser.scss",
    "content": "@use 'pages/simple';\n\n._phaser {\n  @extend %simple;\n\n  .type-signature, dt.tag-source {\n    color: #666;\n    font-weight: normal;\n  }\n\n  .deprecated-notice { @extend %note; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_php.scss",
    "content": "@use 'global/classes';\n@use 'components/content';\n\n._php {\n  h1 {\n    margin-top: 0;\n    @extend %lined-heading;\n  }\n\n  h2 { @extend %block-heading; }\n  h3.title { @extend %block-heading; }\n\n  .manualnavbar {\n    margin-top: 1rem;\n  }\n\n  .verinfo {\n    float: right;\n    font-weight: var(--boldFontWeight);\n  }\n\n  .classsynopsis,\n  .description > .constructorsynopsis,\n  .description > .methodsynopsis,\n  .description > .fieldsynopsis { @extend %note, %note-blue; }\n\n  .classsynopsisinfo_comment { color: var(--textColorLight); }\n\n  .classsynopsisinfo_comment,\n  .classsynopsis > .constructorsynopsis,\n  .classsynopsis > .methodsynopsis,\n  .classsynopsis > .fieldsynopsis { margin-left: 1em; }\n\n  blockquote.note { @extend %note; }\n  blockquote.note > p { margin-bottom: 0; }\n\n  div.warning { @extend %note, %note-red; }\n  div.caution { @extend %note, %note-orange; }\n  div.tip { @extend %note, %note-green; }\n\n  strong > code, dt > code { @extend %label; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_phpunit.scss",
    "content": "@use 'pages/simple';\n\n._phpunit {\n  @extend %simple;\n\n  .warning, .alert {\n    @extend %note;\n\n    > h3 {\n      margin: 0 0 .5em;\n      font-size: 1em;\n    }\n  }\n\n  .alert-danger { @extend %note-red; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_postgres.scss",
    "content": "@use 'global/classes';\n\n._postgres {\n  padding-left: 1rem;\n\n  h1, h1 ~ p, h1 ~ pre, h1 ~ ul, h1 ~ blockquote, h2, .navfooter { margin-left: -1rem; }\n  h2 { @extend %block-heading; }\n\n  .variablelist dt { @extend %block-label, %label-blue; }\n\n  blockquote.note, blockquote.important, blockquote.tip, blockquote.caution { @extend %note; }\n  blockquote.tip { @extend %note-green; }\n  blockquote.caution { @extend %note-orange; }\n\n  blockquote > h3 {\n    font-size: .875rem;\n    margin: 0 0 .25rem;\n  }\n\n  p > code { @extend %label; }\n  p.c2 { font-weight: var(--boldFontWeight); }\n\n  .navfooter > table { width: 100%; }\n  td[align=center] { text-align: center; }\n  td[align=right] { text-align: right; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_pug.scss",
    "content": "@use 'pages/simple';\n\n._pug {\n  @extend %simple;\n\n  .alert { @extend %note; }\n  .alert-danger { @extend %note-orange; }\n  .alert h6 { margin-top: .25rem; }\n\n  h4 > code { @extend %label; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_pygame.scss",
    "content": "@use 'pages/simple';\n\n._pygame {\n  @extend %simple;\n\n  dl.class > dt { @extend %block-label, %label-orange; }\n  dl.function > dt, dl.method > dt { @extend %block-label, %label-blue; }\n  dl.attribute > dt, dl.exception > dt , dl.data > dt { @extend %block-label, %label-green; }\n\n  .line-block { @extend %note; }\n  .line-block > .line:first-child { margin-bottom: 1em; }\n  .line-block > .line:only-child { margin-bottom: 0em; }\n  span.signature { font-family: monospace; }\n\n  .warning { @extend %note, %note-red }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_python.scss",
    "content": "@use 'pages/sphinx';\n\n._python {\n    @extend %sphinx;\n  \n    h2 > a, h3 > a, dt[id] > a.external, dt[id] > a.internal { float: none !important; }\n  }\n  \n"
  },
  {
    "path": "assets/stylesheets/pages/_qt.scss",
    "content": "@use 'pages/simple';\n\n._qt {\n  @extend %simple;\n\n  // Function headers\n  h3.fn > code {\n    float: right;\n    color: var(--textColorLight);\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_ramda.scss",
    "content": "@use 'global/base';\n@use 'pages/simple';\n\n._ramda {\n  @extend %simple;\n\n  code { @extend %code; }\n  h3 > small { float: right; }\n  ul { margin-top: 1em; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_rdoc.scss",
    "content": "@use 'global/classes';\n@use 'global/mixins' as m;\n\n._rdoc {\n  > .description, > .documentation-section { padding-left: 1rem; }\n  > .description > h2, header > h3, > h2 { @extend %block-heading; }\n  > .description > h2, header > h3, .method-heading { margin-left: -1rem; }\n  .description > h1 { font-size: 1rem; }\n  .method-description > h2, h3, h4, h5, h6 { font-size: 1em; }\n\n  .method-heading {\n    font-weight: var(--boldFontWeight);\n    @extend %block-label, %label-blue;\n\n    + .method-heading { margin-top: -.5em; }\n  }\n\n  > .meta {\n    @extend %note, %note-blue;\n\n    > dd { margin: 0; }\n    > dd + dt { margin-top: .5em; }\n  }\n\n  a.method-click-advice {\n    float: right;\n    font-size: .75rem;\n    color: var(--linkColor);\n    cursor: pointer;\n    @extend %user-select-none;\n\n    &:hover { text-decoration: underline; }\n\n    @include m.print {\n      display: none;\n    }\n  }\n\n  .method-source-code {\n    display: none;\n  }\n\n  // Rails guides\n  .note { @extend %note; }\n  .info { @extend %note, %note-blue; }\n  .warning { @extend %note, %note-red; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_react.scss",
    "content": "@use 'pages/simple';\n\n._react {\n  @extend %simple;\n\n  .note {\n    @extend %note;\n    h4 {\n      margin-top: .25rem;\n      margin-bottom: .5rem;\n    }\n  }\n\n  .note-orange {\n    @extend %note-orange;\n  }\n\n  .note-blue {\n    @extend %note-blue;\n  }\n\n  .note-green {\n    @extend %note-green;\n  }\n\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_react_native.scss",
    "content": "@use 'pages/simple';\n\n._react_native {\n  @extend %simple;\n\n  .deprecated { @extend %note, %note-orange; }\n  .deprecatedTitle { font-weight: var(--boldFontWeight); }\n\n  span.platform { float: right; }\n  span.propType, span.platform { font-weight: normal; }\n\n  .label {\n    display:inline-block;\n    font-size:.85rem;\n   }\n   .label::before {\n     content: \"[\";\n   }\n   .label::after {\n     content: \"]\";\n   }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_reactivex.scss",
    "content": "@use 'pages/simple';\n\n._reactivex {\n  @extend %simple;\n\n  img {\n    max-width: 50%;\n  }\n\n  figure {\n    margin: 0;\n  }\n\n  dfn {\n    cursor: text;\n    font-style: italic;\n    text-decoration: none;\n    border-bottom: none;\n  }\n\n  #tree {\n    dt, dd {\n      font-weight: normal;\n    }\n\n    dt {\n      float: left;\n      clear: left;\n\n      margin-top: 0;\n\n      &:before {\n        content: \"\\2026\";\n      }\n    }\n\n    dd:not(.sub) {\n      float: left;\n\n      margin: 0 0 0 5px;\n      padding: 0;\n    }\n\n    dl#outer > dt {\n      font-weight: bold;\n      margin-top: 5px;\n\n      & + dd {\n        margin-top: 5px;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_redis.scss",
    "content": "@use 'global/base';\n@use 'global/classes';\n\n._redis {\n  padding-left: 1rem;\n\n  // Index\n\n  > .commands {\n    padding-left: 0;\n    list-style: none;\n\n    > li { margin-bottom: 1em; }\n  }\n\n  .command, .summary { display: block; }\n\n  .args {\n    font-size: .75rem;\n    color: var(--textColorLight);\n  }\n\n  // Others\n\n  > h1, > h2, > .metadata, > h1 ~ p, > h1 + pre { margin-left: -1rem; }\n  > h2 ~ p { margin-left: 0; }\n\n  > h2 { @extend %block-heading; }\n  p > code { @extend %label; }\n\n  > .metadata { @extend %note, %note-green; }\n  > .metadata > p { margin: 0; }\n\n  > .example {\n    white-space: normal;\n    @extend %pre;\n\n    > .prompt {\n      float: left;\n      margin-right: .5em;\n      color: var(--textColorLight);\n    }\n\n    > code {\n      display: block;\n      clear: left;\n      margin-bottom: .5em;\n\n      &:last-child { margin-bottom: 0; }\n    }\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_rethinkdb.scss",
    "content": "@use 'pages/simple';\n\n._rethinkdb {\n  @extend %simple;\n\n  .infobox-alert { @extend %note-orange; }\n\n  .api_command_illustration {\n    float: right;\n    margin: 0 0 1em 1em;\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_rfc.scss",
    "content": "@use 'global/base';\n\n._rfc-pre {\n  font-size: .8125rem;\n  min-width: 38rem;\n  @extend %code;\n\n  > h2 { @extend %block-heading; }\n  > h3 { @extend %block-label, %label-blue; }\n  > h4 { @extend %block-label; }\n  > h3, > h4 { font-size: .875rem; }\n\n  > h1, > h2, > h3, > h4, > h5 {\n    margin: 0;\n    font-family: var(--baseFont);\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_rubydoc.scss",
    "content": "@use 'pages/simple';\n\n._rubydoc {\n  @extend %simple;\n\n  p.note { @extend %note; }\n  span.note { @extend %label; }\n  span.note.private { @extend %label-red; }\n\n  h4 + ul { margin-top: 1em; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_rust.scss",
    "content": "@use 'global/base';\n@use 'pages/simple';\n\n._rust {\n  @extend %simple;\n\n  h4 { @extend %block-label; }\n  .code-header { @extend %code; }\n  .docblock { margin-left: 1em; }\n  div.information, div.important-traits {\n    @extend %note;\n\n    > pre { margin: .5rem 0; }\n  }\n\n  div.stability { margin-bottom: 1em; }\n  em.stab, span.stab { @extend %label; }\n  em.stab.unstable, span.stab.unstable { @extend %label-orange; }\n  .out-of-band { float: right; }\n  .since, .src, .rightside {\n    float: right;\n    margin-left: .5rem;\n  }\n\n  .grammar-container { @extend %note, %note-gray; }\n\n  /* Railroad styles from:\n   * https://github.com/rust-lang/reference/blob/f82156b8c3a784158ce609bebfa3a77b5ae8a5ed/theme/reference.css#L683-L734\n   * Plus CSS variables inheriting from DevDocs variables\n   */\n\n  svg.railroad {\n    --railroad-background-color: var(--boxBackground);\n    --railroad-background-image:\n    linear-gradient(to right, rgb(from currentColor r g b / 0.1) 1px, transparent 1px),\n    linear-gradient(to bottom, rgb(from currentColor r g b / 0.1) 1px, transparent 1px);\n    --railroad-path-stroke: currentColor;\n    --railroad-rect-stroke: currentColor;\n    --railroad-rect-fill: var(--noteBackground);\n    --railroad-text-fill: currentColor;\n\n    background-color: var(--railroad-background-color);\n    background-size: 15px 15px;\n    background-image: var(--railroad-background-image);\n  }\n\n  svg.railroad rect.railroad_canvas {\n    stroke-width: 0px;\n    fill: none;\n  }\n\n  svg.railroad path {\n    stroke-width: 3px;\n    stroke: var(--railroad-path-stroke);\n    fill: none;\n  }\n\n  svg.railroad .debug {\n    stroke-width: 1px;\n    stroke: red;\n  }\n\n  svg.railroad text {\n    font: 14px monospace;\n    text-anchor: middle;\n    fill: var(--railroad-text-fill);\n  }\n\n  svg.railroad .nonterminal text {\n    font-weight: bold;\n  }\n\n  svg.railroad text.comment {\n    font: italic 12px monospace;\n  }\n\n  svg.railroad rect {\n    stroke-width: 3px;\n    stroke: var(--railroad-rect-stroke);\n    fill: var(--railroad-rect-fill);\n  }\n\n  svg.railroad g.labeledbox>rect {\n    stroke-width: 1px;\n    stroke: grey;\n    stroke-dasharray: 5px;\n    fill: rgba(90, 90, 150, .1);\n  }\n\n  svg.railroad g.exceptbox > rect {\n    fill:rgba(245, 160, 125, .1);\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_rxjs.scss",
    "content": "@use 'pages/simple';\n\n._rxjs {\n  @extend %simple;\n\n  .pre-title { @extend %pre-heading; }\n\n  .breadcrumbs { @extend %note; }\n  .banner { @extend %note-green; }\n  code.stable { @extend %label-green; }\n  code.experimental { @extend %label-orange; }\n  code.deprecated { @extend %label-red; }\n  .alert.is-important { @extend %note-red; }\n  .alert.is-helpful, .breadcrumbs { @extend %note-blue; }\n\n  .breadcrumbs { padding-left: 2em; }\n\n  img { margin: 1em 0; }\n\n  .location-badge {\n    font-style: italic;\n    text-align: right;\n  }\n\n  td h3 { margin: 0 !important; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_sanctuary.scss",
    "content": "@use 'pages/simple';\n\n._sanctuary {\n  @extend %simple;\n\n  pre > code {\n    font-size: inherit;\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_sanctuary_def.scss",
    "content": "@use 'pages/simple';\n\n._sanctuary_def {\n  @extend %simple;\n\n  pre > code {\n    font-size: inherit;\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_sanctuary_type_classes.scss",
    "content": "@use 'pages/simple';\n\n._sanctuary_type_classes {\n  @extend %simple;\n\n  pre > code {\n    font-size: inherit;\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_scala.scss",
    "content": "@use 'global/base';\n@use 'global/mixins' as m;\n@use 'pages/simple';\n\n._scala {\n  @extend %simple;\n  \n  .deprecated { @extend %label-red; }\n\n  .attributes dl,\n  .attributes pre { \n    margin: 0;\n  }\n  \n  .related-types {\n    @extend %pre;\n    margin: 0;\n    white-space: normal;\n  }\n\n  .links {\n    @extend %box;\n    margin-left: -1rem;\n    text-align: center;\n    padding: .5em;\n\n    a { padding: .4em }\n\n    @include m.print {\n      display: none;\n    }\n  }\n\n  .source-link {\n    float: right;\n    font-size: .75rem;\n    color: var(--linkColor);\n    cursor: pointer;\n    @extend %user-select-none;\n\n    &:hover { text-decoration: underline; }\n\n    @include m.print {\n      display: none;\n    }\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_simple.scss",
    "content": "@use 'global/classes';\n\n%simple {\n  padding-left: 1rem;\n\n  h1, h2, h3 { margin-left: -1rem; }\n  h2 { @extend %block-heading; }\n  h3 { @extend %block-label, %label-blue; }\n  h4 { font-size: inherit; }\n\n  ._mobile & {\n    padding-left: 0;\n\n    h1, h2, h3 { margin-left: 0; }\n  }\n\n  p > code, li > code, td > code, blockquote > code, dd > code { @extend %label; }\n  blockquote { @extend %note; }\n  blockquote > h4, blockquote > h5 { margin-top: .25rem; }\n}\n\n._simple { @extend %simple; }\n"
  },
  {
    "path": "assets/stylesheets/pages/_sinon.scss",
    "content": "@use 'pages/simple';\n\n._sinon {\n  @extend %simple;\n\n  h4 { @extend %block-label;}\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_sphinx.scss",
    "content": "@use 'global/classes';\n\n%sphinx {\n  h2 { @extend %block-heading; }\n  h3 { @extend %block-label; }\n  h4 { font-size: 1em; }\n  > dl:not(.docutils) > dt { @extend %block-label, %label-blue; }\n  dd > dl:not(.docutils) > dt { @extend %block-label; }\n  .class > dt, #main-interface .function > dt { @extend %block-label, %label-blue; }\n  dt + dt { margin-top: -.5em; }\n\n  .note, .admonition, div.versionadded, div.versionchanged, .deprecated-removed, .deprecated, .topic { @extend %note; }\n\n  .important { @extend %note-orange; }\n  .warning, .deprecated-removed, .deprecated { @extend %note-red; }\n  .hint { @extend %note-green; }\n\n  .versionmodified, span.title, .topic-title {\n    display: block;\n    font-weight: var(--boldFontWeight);\n  }\n\n  p > code, li > code, dd > code, .docutils > dt > code { @extend %label; }\n\n  ul.simple { margin: 1em 0; }\n\n  h2 > a, h3 > a, dt[id] > a.external, dt[id] > a.internal { float: right; }\n\n  .admonition-title {\n    float: left;\n    margin: 0 .5em 0 0;\n    font-weight: var(--boldFontWeight);\n\n    &:after { content: ':'; }\n  }\n\n  .admonition > dl, .admonition > ul {\n    clear: left;\n    margin: 0;\n  }\n  .admonition-title + dl { padding-top: .5em; }\n\n  td > div { margin: 0 !important; }\n\n  .classifier:before { content:\": \" }\n\n  .property::after { content:\" \" }\n\n  span.descclassname, span.descname { font-family: var(--monoFont) }\n}\n\nnav[aria-label=\"Page navigation\"]{\n  display: flex !important;\n  justify-content: space-between !important;\n}\n\n._sphinx {\n  @extend %sphinx;\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_sphinx_simple.scss",
    "content": "@use 'pages/simple';\n\n._sphinx_simple {\n  @extend %simple;\n\n  .admonition { @extend %note; }\n  .admonition.warning { @extend %note-orange; }\n  .admonition.tip { @extend %note-green; }\n  .admonition-title {\n    margin: 0 0 .25rem;\n    font-weight: var(--boldFontWeight);\n  }\n\n  code { @extend %label; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_sqlite.scss",
    "content": "@use 'pages/simple';\n\n._sqlite {\n  @extend %simple;\n\n  dt { @extend %block-label, %label-blue; }\n  .todo { @extend %note, %note-red; }\n}\n\nsvg {\n  text.fill { fill: var(--textColor); }\n  .fill { fill: var(--textColorLighter); }\n  .stroke { fill: none; stroke-width: 2.16; stroke: var(--textColorLighter); }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_support_tables.scss",
    "content": "@use 'global/classes';\n\n._support_tables {\n  h2 { @extend %block-heading; }\n  code { @extend %label; }\n\n  > .stats {\n    tr.show-all ~ tr { display: none; }\n\n    &.show-all {\n      tr.show-all { display: none; }\n      tr.show-all ~ tr { display: table-row; }\n    }\n\n    td, th {\n      position: relative;\n      text-align: center;\n      min-width: 5rem;\n    }\n\n    sup {\n      position: absolute;\n      top: 0;\n      right: 2px;\n      font-size: .625rem;\n    }\n\n    tr.current {\n      font-weight: var(--boldFontWeight);\n      font-size: 1rem;\n    }\n\n    td {\n      padding: .125rem .25rem;\n      cursor: default;\n    }\n\n    td.y {\n      color: white;\n      background: #39b54a;\n    }\n\n    td.n, td.p {\n      color: white;\n      background: #c44230;\n    }\n\n    td.a {\n      color: white;\n      background: #a8bd04;\n    }\n\n    td.u {\n      color: white;\n      background: #838383;\n    }\n\n    th.show-all {\n      background: none;\n      border-bottom: 0;\n    }\n\n    a.show-all { display: block; }\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_tailwindcss.scss",
    "content": "._tailwindcss {\n  // Styling for customizing-colors page - color swatches (based on original tailwind display design)\n  .colors {\n    display: flex;\n    gap: 1.2rem;\n\n    // Text offset\n    margin-bottom: 1rem;\n  }\n\n  // Color swatch title\n  .color > div:first-child {\n    font-weight: bold;\n  }\n\n  .color-swatch-group {\n    display: flex;\n    gap: 1rem;\n    flex-wrap: wrap;\n  }\n\n  .color-swatch-container {\n    display: inline-block;\n  }\n\n  // Tiny box with the color set as background\n  .color-swatch {\n    width: 120px;\n    height: 40px;\n    border-radius: 4px;\n  }\n\n  .color-tone-information {\n    display: flex;\n    justify-content: space-between;\n  }\n\n  // Styling for large quick-reference lookup tables\n  .long-quick-reference {\n    max-height: 40vh;\n    width: fit-content;\n    overflow-y: auto;\n    padding: .3rem;\n    border-top: 1px solid var(--textColor);\n    border-bottom: 1px solid var(--textColor);\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_tcl_tk.scss",
    "content": "@use 'pages/simple';\n\n._tcl_tk {\n  @extend %simple;\n\n  dl { margin: .5em 0; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_tensorflow.scss",
    "content": "@use 'pages/simple';\n\n._tensorflow {\n  @extend %simple;\n\n  h4 { @extend %block-label; }\n  h3 + h3 { margin-top: .25rem; }\n  > .toc ul ul { margin: .25rem 0; }\n  table { float: inherit }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_terraform.scss",
    "content": "@use 'pages/simple';\n\n._terraform {\n  @extend %simple;\n  .note, .alert { @extend %note; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_typescript.scss",
    "content": "@use 'pages/simple';\n\n._typescript {\n  @extend %simple;\n  .deprecated { @extend %label-red; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_underscore.scss",
    "content": "@use 'global/classes';\n\n._underscore {\n  padding-left: 1rem;\n\n  > h1, > h2, .header { margin-left: -1rem; }\n  > h2 { @extend %block-heading; }\n\n  .header {\n    display: inline-block;\n    vertical-align: top;\n    margin-bottom: 1em;\n  }\n\n  > p[id] { margin-top: 2rem; }\n  > pre { margin-top: 1em; }\n\n  .header + code {\n    margin-left: 1em;\n    @extend %label;\n  }\n\n  .alias {\n    margin-left: 1em;\n    font-style: italic;\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_vue.scss",
    "content": "@use 'pages/simple';\n\n._vue {\n  @extend %simple;\n\n  p.tip { @extend %note; }\n  .custom-block {\n    @extend %note;\n    &.tip { @extend %note-green; }\n    &.info { @extend %note-blue; }\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_webpack.scss",
    "content": "@use 'pages/simple';\n\n._webpack {\n  @extend %simple;\n\n  blockquote.tip { @extend %note-blue; }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_wordpress.scss",
    "content": "@use 'pages/simple';\n\n._wordpress {\n  @extend %simple;\n\n  .breadcrumbs {\n    display: none;\n  }\n\n  .callout-warning {\n    @extend %note, %note-red;\n  }\n\n  .callout-alert {\n    @extend %note, %note-orange;\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_yard.scss",
    "content": "@use 'global/classes';\n@use 'pages/simple';\n\n._yard {\n  @extend %simple;\n\n  .tag_title {\n    font-weight: var(--boldFontWeight);\n  }\n\n  .sl-c-description-list--horizontal * {\n    display: inline-block;\n    padding: 0;\n    margin: 0;\n    dt {\n      margin-left: 1rem;\n    }\n  }\n\n  .sl-c-callout--warning {\n    @extend %note, %note-orange;\n  }\n}\n"
  },
  {
    "path": "assets/stylesheets/pages/_yii.scss",
    "content": "@use 'pages/simple';\n\n._yii {\n  @extend %simple;\n\n  .detail-header-tag, .detailHeaderTag {\n    float: right;\n    color: var(--textColorLight);\n  }\n}\n"
  },
  {
    "path": "config.ru",
    "content": "require 'bundler/setup'\n\n$LOAD_PATH.unshift 'lib'\n\nrequire 'app'\n\nmap '/' do\n  run App\nend\n\nif App.development?\n  map '/assets' do\n    run App.sprockets\n  end\nend\n"
  },
  {
    "path": "docs/adding-docs.md",
    "content": "Adding a documentation may look like a daunting task but once you get the hang of it, it's actually quite simple. Don't hesitate to ask for help [in Discord](https://discord.gg/PRyKn3Vbay) if you ever get stuck.\n\n**Note:** please read the [contributing guidelines](../.github/CONTRIBUTING.md) before submitting a new documentation.\n\n1. Create a subclass of `Docs::UrlScraper` or `Docs::FileScraper` in the `lib/docs/scrapers/` directory. Its name should be the [PascalCase](http://api.rubyonrails.org/classes/String.html#method-i-camelize) equivalent of the filename (e.g. `my_doc` → `MyDoc`)\n2. Add the appropriate class attributes and filter options (see the [Scraper Reference](./scraper-reference.md) page).\n3. Check that the scraper is listed in `thor docs:list`.\n4. Create filters specific to the scraper in the `lib/docs/filters/[my_doc]/` directory and add them to the class's [filter stacks](./scraper-reference.md#filter-stacks). You may create any number of filters but will need at least the following two:\n   * A [`CleanHtml`](./filter-reference.md#cleanhtmlfilter) filter whose task is to clean the HTML markup (e.g. adding `id` attributes to headings) and remove everything superfluous and/or nonessential.\n   * An [`Entries`](./filter-reference.md#entriesfilter) filter whose task is to determine the pages' metadata (the list of entries, each with a name, type and path).\n   The [Filter Reference](./filter-reference.md) page has all the details about filters.\n5. Using the `thor docs:page [my_doc] [path]` command, check that the scraper works properly. Files will appear in the `public/docs/[my_doc]/` directory (but not inside the app as the command doesn't touch the index). `path` in this case refers to either the remote path (if using `UrlScraper`) or the local path (if using `FileScraper`).\n6. Generate the full documentation using the `thor docs:generate [my_doc] --force` command. Additionally, you can use the `--verbose` option to see which files are being created/updated/deleted (useful to see what changed since the last run), and the `--debug` option to see which URLs are being requested and added to the queue (useful to pin down which page adds unwanted URLs to the queue).\n7. Start the server, open the app, enable the documentation, and see how everything plays out.\n8. Tweak the scraper/filters and repeat 5) and 6) until the pages and metadata are ok.\n9. To customize the pages' styling, create an SCSS file in the `assets/stylesheets/pages/` directory and import it in `application.css.scss`. Both the file and CSS class should be named `_[type]` where [type] is equal to the scraper's `type` attribute (documentations with the same type share the same custom CSS and JS). Setting the type to `simple` will apply the general styling rules in `assets/stylesheets/pages/_simple.scss`, which can be used for documentations where little to no CSS changes are needed.\n10. To add syntax highlighting or execute custom JavaScript on the pages, create a file in the `assets/javascripts/views/pages/` directory (take a look at the other files to see how it works).\n11. Add the documentation's icon in the `public/icons/docs/[my_doc]/` directory, in both 16x16 and 32x32-pixels formats. The icon spritesheet is automatically generated when you (re)start your local DevDocs instance.\n12. Add the documentation's copyright details to `options[:attribution]`. This is the data shown in the table on the [about](https://devdocs.io/about) page, and is ordered alphabetically. Please see an existing scraper for the typesetting.\n13. Ensure `thor updates:check [my_doc]` shows the correct latest version.\n\nIf the documentation includes more than a few hundreds pages and is available for download, try to scrape it locally (e.g. using `FileScraper`). It'll make the development process much faster and avoids putting too much load on the source site. (It's not a problem if your scraper is coupled to your local setup, just explain how it works in your pull request.)\n\nFinally, try to document your scraper and filters' behavior as much as possible using comments (e.g. why some URLs are ignored, HTML markup removed, metadata that way, etc.). It'll make updating the documentation much easier.\n"
  },
  {
    "path": "docs/file-scrapers.md",
    "content": "# File Scraper Reference\n\nThis lists the docs that use `FileScraper` and instructions for building some of them.\n\nIf you open a PR to update one of these docs, please add/fix the instructions.\n\n## Dart\n\nClick the “API docs” link under the “Stable channel” header on\nhttps://www.dartlang.org/tools/sdk/archive. Rename the expanded ZIP to `dart~2`\nand put it in `docs/`\n\nOr run the following commands in your terminal:\n\n```sh\ncurl https://storage.googleapis.com/dart-archive/channels/stable/release/$RELEASE/api-docs/dartdocs-gen-api.zip > dartApi.zip; \\\nunzip dartApi.zip; mv gen-dartdocs docs/dart~$VERSION\n```\n\n## date-fns\n\n```sh\ngit clone https://github.com/date-fns/date-fns docs/date_fns\ncd docs/date_fns\ngit checkout v2.29.2\nyarn install\nnode scripts/build/docs.js\nls tmp/docs.json\n```\n\n## Django\n\nGo to https://docs.djangoproject.com/, select the version from the\nbubble in the bottom-right corner, then download the HTML version from the sidebar.\n\n```sh\nmkdir --parent docs/django\\~$VERSION/; \\\ncurl https://media.djangoproject.com/docs/django-docs-$VERSION-en.zip | \\\nbsdtar --extract --file - --directory=docs/django\\~$VERSION/\n```\n\n## Elisp\n\nGo to https://www.gnu.org/software/emacs/manual/elisp.html, download the HTML tarball and extract its content in `docs/elisp` or run the following command:\n\n```sh\nmkdir docs/elisp \\\n&& curl curl https://www.gnu.org/software/emacs/manual/elisp.html_node.tar.gz | \\\ntar --extract --gzip --strip-components=1 --directory=docs/elisp\n```\n\n## Erlang\n\nGo to https://www.erlang.org/downloads and download the HTML documentation file.\n\n```ah\nmkdir --parent docs/erlang\\~$VERSION/; \\\ncurl -L https://github.com/erlang/otp/releases/download/OTP-$RELEASE/otp_doc_html_$RELEASE.tar.gz | \\\nbsdtar --extract --file - --directory=docs/erlang\\~$VERSION/\n```\n\n## es-toolkit\n\n```sh\ngit clone https://github.com/toss/es-toolkit docs/es_toolkit\n```\n\n## Gnu\n\n### Bash\nGo to https://www.gnu.org/software/bash/manual/, download the HTML tar file (with one web page per node) and extract its content in `docs/bash` or run the following command:\n\n```sh\nmkdir docs/bash \\\n&& curl https://www.gnu.org/software/bash/manual/bash.html_node.tar.gz | \\\ntar --extract --gzip --directory=docs/bash\n```\n\n### GCC\nGo to https://gcc.gnu.org/onlinedocs/ and download the HTML tarball of GCC Manual and GCC CPP manual or run the following commands to download the tarballs:\n\n```sh\n# GCC manual\nmkdir docs/gcc~${VERSION}; \\\ncurl https://gcc.gnu.org/onlinedocs/gcc-$RELEASE/gcc-html.tar.gz | \\\ntar --extract --gzip --strip-components=1 --directory=docs/gcc~${VERSION}\n\n# GCC CPP manual\nmkdir docs/gcc~${VERSION}_cpp; \\\ncurl https://gcc.gnu.org/onlinedocs/gcc-$RELEASE/cpp-html.tar.gz | \\\ntar --extract --gzip --strip-components=1 --directory=docs/gcc~${VERSION}_cpp\n```\n\n### GNU Fortran\nGo to https://gcc.gnu.org/onlinedocs/ and download the HTML tarball of Fortran manual or run the following commands to download the tarball:\n\n```sh\nmkdir docs/gnu_fortran~$VERSION; \\\ncurl https://gcc.gnu.org/onlinedocs/gcc-$RELEASE/gfortran-html.tar.gz | \\\ntar --extract --gzip --strip-components=1 --directory=docs/gnu_fortran~$VERSION\n```\n\n## GNU Make\nGo to https://www.gnu.org/software/make/manual/, download the HTML tarball and extract its content in `docs/gnu_make` or run the following command:\n\n```sh\nmkdir docs/gnu_make \\\n&& curl https://www.gnu.org/software/make/manual/make.html_node.tar.gz | \\\ntar --extract --gzip --strip-components=1 --directory=docs/gnu_make\n```\n\n## Gnuplot\n\nThe most recent release can be found near the bottom of\nhttps://sourceforge.net/p/gnuplot/gnuplot-main/ref/master/tags/\n\n```sh\nDEVDOCS_ROOT=/path/to/devdocs\nmkdir gnuplot-src $DEVDOCS_ROOT/docs/gnuplot\ngit clone -b $RELEASE --depth 1 https://git.code.sf.net/p/gnuplot/gnuplot-main ./gnuplot-src\ncd gnuplot-src/\n./prepare\n./configure\ncd docs/\nmake nofigures.tex\nlatex2html -html 5.0,math -split 4 -link 8 -long_titles 5 -dir $DEVDOCS_ROOT/docs/gnuplot -ascii_mode -no_auto_link nofigures.tex\n```\n\nTo install `latex2html` on macOS: `brew install basictex latex2html`, then edit\n`/usr/local/Cellar/latex2html/2019.2/l2hconf.pm` to include the path to LaTeX:\n\n<details>\n\nOn line 21 (approximately):\n\n```\n#  Give the paths to latex and dvips on your system:\n#\n$LATEX = '/Library/TeX/texbin/latex';\t# LaTeX\n$PDFLATEX = '/Library/TeX/texbin/pdflatex';\t# pdfLaTeX\n$LUALATEX = '/Library/TeX/texbin/lualatex';\t# LuaLaTeX\n$DVILUALATEX = '/Library/TeX/texbin/dvilualatex';\t# dviLuaLaTeX\n$DVIPS = '/Library/TeX/texbin/dvips';\t# dvips\n$DVIPNG = '';\t# dvipng\n$PDFTOCAIRO = '/usr/local/bin/pdf2svg';\t# pdf to svg converter\n$PDFCROP = '';\t# pdfcrop\n$GS = '/usr/local/opt/ghostscript/bin/gs';\t# GhostScript\n```\n</details>\n\n## Man\n\n```sh\nwget --recursive --no-parent https://man7.org/linux/man-pages/\nmv man7.org/linux/man-pages/ docs/man/\n```\n\n## NumPy\n\n```sh\nmkdir --parent docs/numpy~$VERSION/; \\\ncurl https://numpy.org/doc/$VERSION/numpy-html.zip | \\\nbsdtar --extract --file=- --directory=docs/numpy~$VERSION/\n```\n\n## OpenGL\n\n```sh\ncd docs/\ngit clone https://github.com/KhronosGroup/OpenGL-Refpages.git\nln -s OpenGL-Refpages/gl4/html/ opengl~4\nln -s OpenGL-Refpages/gl2.1/xhtml/ opengl~2.1\n```\n\n## OpenJDK\nSearch 'Openjdk' in https://www.debian.org/distrib/packages, find the `openjdk-$VERSION-doc` package,\ndownload it, extract it with `dpkg -x $PACKAGE ./` and move `./usr/share/doc/openjdk-16-jre-headless/api/`\nto `path/to/devdocs/docs/openjdk~$VERSION`\n\n```sh\ncurl -O http://ftp.at.debian.org/debian/pool/main/o/openjdk-25/openjdk-25-doc_25+36-1_all.deb\ntar xf openjdk-25-doc_25+36-1_all.deb\ntar xf data.tar.xz\nmv ./usr/share/doc/openjdk-25-jre-headless/api/ docs/openjdk~25\n```\n\nIf you use or have access to a Debian-based GNU/Linux distribution you can run the following command:\n```sh\napt download openjdk-$VERSION-doc\ndpkg -x $PACKAGE ./\n# previous command makes a directory called 'usr' in the current directory\nmv ./usr/share/doc/openjdk-16-jre-headless/api/ docs/openjdk~$VERSION\n```\n\n## Pandas\n\nFrom the home directory; `devdocs`, execute below:\n\n```sh\ncurl https://pandas.pydata.org/docs/pandas.zip -o tmp.zip && unzip tmp.zip -d docs/pandas~2 && rm tmp.zip\n```\n\n\n## PHP\nClick the link under the \"Many HTML files\" column on https://www.php.net/download-docs.php, extract the tarball, change its name to `php` and put it in `docs/`.\n\nOr run the following commands in your terminal:\n\n```sh\ncurl https://www.php.net/distributions/manual/php_manual_en.tar.gz | tar xz; mv php-chunked-xhtml/ docs/php/\n```\n## Python 3.6+\n\n```sh\nmkdir docs/python~$VERSION\ncd docs/python~$VERSION\ncurl -L https://docs.python.org/$VERSION/archives/python-$RELEASE-docs-html.tar.bz2 | \\\ntar xj --strip-components=1\n```\n\n## Python < 3.6\n\n```sh\nmkdir docs/python~$VERSION\ncd docs/python~$VERSION\ncurl -L https://docs.python.org/ftp/python/doc/$RELEASE/python-$RELEASE-docs-html.tar.bz2 | \\\ntar xj --strip-components=1\n```\n\n## R\n\n```bash\nsudo dnf install bzip2-devel\nsudo dnf install gcc-gfortran\nsudo dnf install libcurl-devel\nsudo dnf install texinfo\nsudo dnf install xz-devel\n\nDEVDOCSROOT=docs/r\nRLATEST=https://cran.r-project.org/src/base/R-latest.tar.gz # or /R-${VERSION::1}/R-$VERSION.tar.gz\n\nRSOURCEDIR=${TMPDIR:-/tmp}/R/latest\nRBUILDDIR=${TMPDIR:-/tmp}/R/build\nmkdir -p \"$RSOURCEDIR\" \"$RBUILDDIR\" \"$DEVDOCSROOT\"\n\n# Download, configure, and build with static HTML pages\ncurl \"$RLATEST\" | tar -C \"$RSOURCEDIR\" -xzf - --strip-components=1\n(cd \"$RBUILDDIR\" && \"$RSOURCEDIR/configure\" --enable-prebuilt-html --with-recommended-packages --disable-byte-compiled-packages --disable-shared --disable-java --with-readline=no --with-x=no)\nmake _R_HELP_LINKS_TO_TOPICS_=FALSE -C \"$RBUILDDIR\"\n\n# Export all html documentation built − global, and per-package\ncp -r \"$RBUILDDIR/doc\" \"$DEVDOCSROOT/\"\nls -d \"$RBUILDDIR\"/library/*/html | while read orig; do\n    dest=\"$DEVDOCSROOT${orig#$RBUILDDIR}\"\n    mkdir -p \"$dest\" && cp -r \"$orig\"/* \"$dest/\"\ndone\n```\n\n## RDoc\n\n### Nokogiri\n### Ruby / Minitest\n\n```sh\ngit clone https://github.com/minitest/minitest\ncd minitest/\necho -e \"source 'https://rubygems.org'\\n\\ngem 'hoe'\\ngem 'rdoc', '< 7'\" > Gemfile\nbundle install\nbundle exec rake docs\ncp -r docs $DEVDOCS/docs/minitest\n```\n\n### Ruby on Rails\n* Run `git clone --branch v$RELEASE --depth 7 https://github.com/rails/rails.git && cd rails`\n* Open `railties/lib/rails/api/task.rb` and comment out any code related to sdoc (`configure_sdoc`)\n* Run `bundle config set --local without 'db job'` (in the Rails directory)\n* Run `bundle install && bundle exec rake rdoc` (in the Rails directory)\n* Run `cd guides && bundle exec rake guides:generate:html && cd ..`\n* Run `cp -r guides/output html/guides`\n* Run `cp -r html $DEVDOCS/docs/rails~$VERSION`\n\n### Ruby\nDownload the tarball of Ruby from https://www.ruby-lang.org/en/downloads/, extract it, run\n`./configure && make html` in your terminal (while your are in the ruby directory) and move\n`.ext/html` to `path/to/devdocs/docs/ruby~$VERSION/`.\n\nOr run the following commands in your terminal:\n```sh\ncurl https://cache.ruby-lang.org/pub/ruby/$VERSION/ruby-$RELEASE.tar.gz > ruby.tar; \\\ntar -xf ruby.tar; cd ruby-$RELEASE; ./configure && make html; mv .ext/html path/to/devdocs/docs/ruby~$VERSION\n```\n\nTo generate the htmls file you have to run `make` command but it does not install Ruby in your system, only generates html files so you have not\nto worry about cleaning or removing a new Ruby installation.\n\n## Scala\n\nSee `lib/docs/scrapers/scala.rb`\n\n## SQLite\n\nDownload the docs from https://sqlite.org/download.html, unzip it, and rename\nit to `docs/sqlite`\n\n```sh\ncurl https://sqlite.org/2022/sqlite-doc-3400000.zip | bsdtar --extract --file - --directory=docs/sqlite/ --strip-components=1\n```\n\n## Three.js\nDownload the docs from https://github.com/mrdoob/three.js/tree/dev/files or run the following commands in your terminal:\nMake sure to set the version per the release tag (e.g. r160). Note that the r prefix is already included, only the version number is needed.\n\n```sh\ncurl https://codeload.github.com/mrdoob/three.js/tar.gz/refs/tags/r${VERSION} > threejs.tar.gz\ntar -xzf threejs.tar.gz\nmkdir -p docs/threejs~${VERSION}\nmv three.js-r${VERSION}/list.json tmp/list.json\nmv three.js-r${VERSION}/docs/* docs/threejs~${VERSION}/\n\nrm -rf three.js-r${VERSION}/\nrm threejs.tar.gz\n```\n"
  },
  {
    "path": "docs/filter-reference.md",
    "content": "**Table of contents:**\n\n* [Overview](#overview)\n* [Instance methods](#instance-methods)\n* [Core filters](#core-filters)\n* [Custom filters](#custom-filters)\n  - [CleanHtmlFilter](#cleanhtmlfilter)\n  - [EntriesFilter](#entriesfilter)\n\n## Overview\n\nFilters use the [HTML::Pipeline](https://github.com/jch/html-pipeline) library. They take an HTML string or [Nokogiri](http://nokogiri.org/) node as input, optionally perform modifications and/or extract information from it, and then outputs the result. Together they form a pipeline where each filter hands its output to the next filter's input. Every documentation page passes through this pipeline before being copied on the local filesystem.\n\nFilters are subclasses of the [`Docs::Filter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/core/filter.rb) class and require a `call` method. A basic implementation looks like this:\n\n```ruby\nmodule Docs\n  class CustomFilter < Filter\n    def call\n      doc\n    end\n  end\nend\n```\n\nFilters which manipulate the Nokogiri node object (`doc` and related methods) are _HTML filters_ and must not manipulate the HTML string (`html`). Vice-versa, filters which manipulate the string representation of the document are _text filters_ and must not manipulate the Nokogiri node object. The two types are divided into two stacks within the scrapers. These stacks are then combined into a pipeline that calls the HTML filters before the text filters (more details [here](./scraper-reference.md#filter-stacks)). This is to avoid parsing the document multiple times.\n\nThe `call` method must return either `doc` or `html`, depending on the type of filter.\n\n## Instance methods\n\n* `doc` [Nokogiri::XML::Node]\n  The Nokogiri representation of the container element.\n  See [Nokogiri's API docs](http://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/Node) for the list of available methods.\n\n* `html` [String]\n  The string representation of the container element.\n\n* `context` [Hash] **(frozen)**\n  The scraper's `options` along with a few additional keys: `:base_url`, `:root_url`, `:root_page` and `:url`.\n\n* `result` [Hash]\n  Used to store the page's metadata and pass back information to the scraper.\n  Possible keys:\n\n  - `:path` — the page's normalized path\n  - `:store_path` — the path where the page will be stored (equal to `:path` with `.html` at the end)\n  - `:internal_urls` — the list of distinct internal URLs found within the page\n  - `:entries` — the [`Entry`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/core/models/entry.rb) objects to add to the index\n\n* `css`, `at_css`, `xpath`, `at_xpath`\n  Shortcuts for `doc.css`, `doc.xpath`, etc.\n\n* `base_url`, `current_url`, `root_url` [Docs::URL]\n  Shortcuts for `context[:base_url]`, `context[:url]`, and `context[:root_url]` respectively.\n\n* `root_path` [String]\n  Shortcut for `context[:root_path]`.\n\n* `subpath` [String]\n  The sub-path from the base URL of the current URL.\n  _Example: if `base_url` equals `example.com/docs` and `current_url` equals `example.com/docs/file?raw`, the returned value is `/file`._\n\n* `slug` [String]\n  The `subpath` removed of any leading slash or `.html` extension.\n  _Example: if `subpath` equals `/dir/file.html`, the returned value is `dir/file`._\n\n* `root_page?` [Boolean]\n  Returns `true` if the current page is the root page.\n\n* `initial_page?` [Boolean]\n  Returns `true` if the current page is the root page or its subpath is one of the scraper's `initial_paths`.\n\n## Core filters\n\n* [`ContainerFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/container.rb) — changes the root node of the document (remove everything outside)\n* [`CleanHtmlFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/clean_html.rb) — removes HTML comments, `<script>`, `<style>`, etc.\n* [`NormalizeUrlsFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/normalize_urls.rb) — replaces all URLs with their fully qualified counterpart\n* [`InternalUrlsFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/internal_urls.rb) — detects internal URLs (the ones to scrape) and replaces them with their unqualified, relative counterpart\n* [`NormalizePathsFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/normalize_paths.rb) — makes the internal paths consistent (e.g. always end with `.html`)\n* [`CleanLocalUrlsFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/clean_local_urls.rb) — removes links, iframes and images pointing to localhost (`FileScraper` only)\n* [`InnerHtmlFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/inner_html.rb) — converts the document to a string\n* [`CleanTextFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/clean_text.rb) — removes empty nodes\n* [`AttributionFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/attribution.rb) — appends the license info and link to the original document\n* [`TitleFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/title.rb) — prepends the document with a title (disabled by default)\n* [`EntriesFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/entries.rb) — abstract filter for extracting the page's metadata\n\n## Custom filters\n\nScrapers can have any number of custom filters but require at least the two described below.\n\n**Note:** filters are located in the [`lib/docs/filters`](https://github.com/freeCodeCamp/devdocs/tree/main/lib/docs/filters/) directory. The class's name must be the [CamelCase](http://api.rubyonrails.org/classes/String.html#method-i-camelize) equivalent of the filename.\n\n### `CleanHtmlFilter`\n\nThe `CleanHtml` filter is tasked with cleaning the HTML markup where necessary and removing anything superfluous or nonessential. Only the core documentation should remain at the end.\n\nNokogiri's many jQuery-like methods make it easy to search and modify elements — see the [API docs](http://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/Node).\n\nHere's an example implementation that covers the most common use-cases:\n\n```ruby\nmodule Docs\n  class MyScraper\n    class CleanHtmlFilter < Filter\n      def call\n        css('hr').remove\n        css('#changelog').remove if root_page?\n\n        # Set id attributes on <h3> instead of an empty <a>\n        css('h3').each do |node|\n          node['id'] = node.at_css('a')['id']\n        end\n\n        # Make proper table headers\n        css('td.header').each do |node|\n          node.name = 'th'\n        end\n\n        # Remove code highlighting\n        css('pre').each do |node|\n          node.content = node.content\n        end\n\n        doc\n      end\n    end\n  end\nend\n```\n\n**Notes:**\n\n* Empty elements will be automatically removed by the core `CleanTextFilter` later in the pipeline's execution.\n* Although the goal is to end up with a clean version of the page, try to keep the number of modifications to a minimum, so as to make the code easier to maintain. Custom CSS is the preferred way of normalizing the pages (except for hiding stuff which should always be done by removing the markup).\n* Try to document your filter's behavior as much as possible, particularly modifications that apply only to a subset of pages. It'll make updating the documentation easier.\n\n### `EntriesFilter`\n\nThe `Entries` filter is responsible for extracting the page's metadata, represented by a set of _entries_, each with a name, type and path.\n\nThe following two models are used under the hood to represent the metadata:\n\n* [`Entry(name, type, path)`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/core/models/entry.rb)\n* [`Type(name, slug, count)`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/core/models/type.rb)\n\nEach scraper must implement its own `EntriesFilter` by subclassing the [`Docs::EntriesFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/entries.rb) class. The base class already implements the `call` method and includes four methods which the subclasses can override:\n\n* `get_name` [String]\n  The name of the default entry (aka. the page's name).\n  It is usually guessed from the `slug` (documented above) or by searching the HTML markup.\n  **Default:** modified version of `slug` (underscores are replaced with spaces and forward slashes with dots)\n\n* `get_type` [String]\n  The type of the default entry (aka. the page's type).\n  Entries without a type can be searched for but won't be listed in the app's sidebar (unless no other entries have a type).\n  **Default:** `nil`\n\n* `include_default_entry?` [Boolean]\n  Whether to include the default entry.\n  Used when a page consists of multiple entries (returned by `additional_entries`) but doesn't have a name/type of its own, or to remove a page from the index (if it has no additional entries), in which case it won't be copied on the local filesystem and any link to it in the other pages will be broken (as explained on the [Scraper Reference](./scraper-reference.md) page, this is used to keep the `:skip` / `:skip_patterns` options to a maintainable size, or if the page includes links that can't reached from anywhere else).\n  **Default:** `true`\n\n* `additional_entries` [Array]\n  The list of additional entries.\n  Each entry is represented by an Array of three attributes: its name, fragment identifier, and type. The fragment identifier refers to the `id` attribute of the HTML element (usually a heading) that the entry relates to. It is combined with the page's path to become the entry's path. If absent or `nil`, the page's path is used. If the type is absent or `nil`, the default `type` is used.\n  Example: `[ ['One'], ['Two', 'id'], ['Three', nil, 'type'] ]` adds three additional entries, the first one named \"One\" with the default path and type, the second one named \"Two\" with the URL fragment \"#id\" and the default type, and the third one named \"Three\" with the default path and the type \"type\".\n  The list is usually constructed by running through the markup. Exceptions can also be hard-coded for specific pages.\n  **Default:** `[]`\n\nThe following accessors are also available, but must not be overridden:\n\n* `name` [String]\n  Memoized version of `get_name` (`nil` for the root page).\n\n* `type` [String]\n  Memoized version of `get_type` (`nil` for the root page).\n\n**Notes:**\n\n* Leading and trailing whitespace is automatically removed from names and types.\n* Names must be unique across the documentation and as short as possible (ideally less than 30 characters). Whenever possible, methods should be differentiated from properties by appending `()`, and instance methods should be differentiated from class methods using the `Class#method` or `object.method` conventions.\n* You can call `name` from `get_type` or `type` from `get_name` but doing both will cause a stack overflow (i.e. you can infer the name from the type or the type from the name, but you can't do both at the same time). Don't call `get_name` or `get_type` directly as their value isn't memoized.\n* The root page has no name and no type (both are `nil`). `get_name` and `get_type` won't get called with the page (but `additional_entries` will).\n* `Docs::EntriesFilter` is an _HTML filter_. It must be added to the scraper's `html_filters` stack.\n* Try to document the code as much as possible, particularly the special cases. It'll make updating the documentation easier.\n\n**Example:**\n\n```ruby\nmodule Docs\n  class MyScraper\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        node = at_css('h1')\n        result = node.content.strip\n        result << ' event' if type == 'Events'\n        result << '()' if node['class'].try(:include?, 'function')\n        result\n      end\n\n      def get_type\n        object, method = *slug.split('/')\n        method ? object : 'Miscellaneous'\n      end\n\n      def additional_entries\n        return [] if root_page?\n\n        css('h2').map do |node|\n          [node.content, node['id']]\n        end\n      end\n\n      def include_default_entry?\n        !at_css('.obsolete')\n      end\n    end\n  end\nend\n```\n\n\nreturn [[Home]]\n"
  },
  {
    "path": "docs/maintainers.md",
    "content": "# Maintainer's Guide\n\nThis document is intended for [DevDocs maintainers](#list-of-maintainers).\n\n## Merging pull requests\n\n- PRs should be approved by at least one maintainer before being merged.\n\n- PRs that add or update documentations should always be built and tested locally, and the doc files uploaded by the `thor docs:upload` command, before the PR is merged on GitHub.\n\n  This workflow is required because there is a dependency between the local and production environments. The `thor docs:download` command downloads documentations from production files uploaded by the `thor docs:upload` command. If a PR adding a new documentation is merged and pushed to GitHub before the files have been uploaded to production, the `thor docs:download` will fail for the new documentation and the docker container will not build properly until the new documentation is deployed to production.\n\n## Updating docs\n\nThe process for updating docs is as follow:\n\n- Follow the checklist in [CONTRIBUTING.md#updating-existing-documentations](../.github/CONTRIBUTING.md#updating-existing-documentations).\n- Commit the changes (protip: use the `thor docs:commit` command documented below).\n- Optional: do more updates.\n- Run `thor docs:upload` (documented below).\n- Push to GitHub to [deploy the app](#deploying-devdocs) and verify that everything works in production.\n- Run `thor docs:clean` (documented below).\n\nNote: changes to `public/docs/docs.json` should never be committed. This file reflects which documentations have been downloaded or generated locally, which is always none on a fresh `git clone`.\n\n## Setup requirements\n\nIn order to deploy DevDocs, you must:\n\n- be given access to Heroku, [configure the Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) on your computer, and familiarize yourself with Heroku's UI and CLI, as well as that of New Relic (accessible through [the Heroku dashboard](https://dashboard.heroku.com/apps/devdocs)).\n\n- be given access to DevDocs's [Sentry instance](https://sentry.io/devdocs/devdocs-js/) (for JS error tracking) and familiarize yourself with its UI.\n\n- be provided with DevDocs's S3 credentials, and install (`brew install awscli` on macOS) and [configure](https://docs.aws.amazon.com/cli/latest/reference/configure/) the AWS CLI on your computer. Alternatively, [rclone](https://rclone.org/) [s3](https://rclone.org/s3/) may be used (add `--rclone` to the commands). The configuration must add a named profile called \"devdocs\":\n\n  ```\n  aws configure --profile devdocs\n  ```\n\n## Thor commands\n\nIn addition to the [publicly-documented commands](https://github.com/freeCodeCamp/devdocs#available-commands), the following commands are aimed at DevDocs maintainers:\n\n- `thor docs:package`\n\n  Generates packages for one or more documentations. Those packages are intended to be uploaded to DevDocs's S3 bundle zone by maintainers via the `thor docs:upload` command, and downloaded by users via the `thor docs:download` command.\n\n  Versions can be specified as such: `thor docs:package rails@5.2 node@10\\ LTS`.\n\n  Packages can also be automatically generated during the scraping process by passing the `--package` option to `thor docs:generate`.\n\n- `thor docs:upload`\n\n  This command does two operations:\n\n  1. sync the files for the specified documentations with S3 (used by the Heroku app);\n  2. upload the documentations' packages to DevDocs's S3 bundle zone (used by the `thor docs:download` command).\n\n  For the command to work, you must have the AWS CLI configured as indicated above.\n\n  **Important:** the app should always be deployed immediately after this command has finished running. Do not run this command unless you are able and ready to deploy DevDocs.\n\n  To upload all documentations that are packaged on your computer, run `thor docs:upload --packaged`.\n  To test your configuration and the effect of this command without uploading anything, pass the `--dryrun` option.\n\n- `thor docs:commit`\n\n  Shortcut command to create a Git commit for a given documentation once it has been updated. Scraper and `assets/` file changes will be committed. The commit message will include the most recent version that the documentation was updated to. If some files were missed by the commit, use `git commit --amend` to add them to the commit. The command may be run before `thor docs:upload` is run, but the commit should not be pushed to GitHub before the files have been uploaded and the app deployed.\n\n- `thor docs:clean`\n\n  Shortcut command to delete all package files (once uploaded via `thor docs:upload`, they are not needed anymore).\n\n## Deploying DevDocs\n\nOnce docs have been uploaded via `thor docs:upload` (if applicable), you can push to the DevDocs main branch (or merge the PR containing the updates). This triggers a GitHub action which starts by running the tests. If they succeed, the Heroku application will be deployed automatically.\n\n- If you're deploying documentation updates, verify that the documentations work properly once the deploy is done. Keep in mind that you'll need to wait a few seconds for the service worker to finish caching the new assets. You should see a \"DevDocs has been updated\" notification appear when the caching is done, after which you need to refresh the page to see the changes.\n- If you're deploying frontend changes, monitor [Sentry](https://sentry.io/devdocs/devdocs-js/) for new JS errors once the deploy is done.\n- If you're deploying server changes, monitor New Relic (accessible through [the Heroku dashboard](https://dashboard.heroku.com/apps/devdocs)) for Ruby exceptions and throughput or response time changes once the deploy is done.\n\nIf any issue arises, run `heroku rollback` to rollback to the previous version of the app (this can also be done via Heroku's UI). Note that this will not revert changes made to documentation files that were uploaded via `thor docs:upload`. Try and fix the issue as quickly as possible, then re-deploy the app. Reach out to other maintainers if you need help.\n\nIf this is your first deploy, make sure another maintainer is around to assist.\n\n## Infrastructure\n\nThe bundled documents are available at downloads.devdocs.io and the documents themselves at documents.devdocs.io. Download and document requests are proxied to S3 buckets devdocs-downloads.s3.amazonaws.com and devdocs-documents.s3.amazonaws.com respectively.\n\nNew proxy VMs should be created from the `devdocs-proxy` snapshot. Before adding them to the load-balancer, it's necessary to add their IP addresses to the aws:SourceIp lists for both buckets, or their requests will be rejected.\n\nWhen creating a new proxy VM and the `devdocs-proxy` snapshot is not available, then the new vm should be provisioned as follows:\n\n```bash\n# we need at least nginx 1.19.x\nwget https://nginx.org/keys/nginx_signing.key\napt-key add nginx_signing.key\necho 'deb https://nginx.org/packages/mainline/ubuntu/ focal nginx' >> /etc/apt/sources.list\necho 'deb-src https://nginx.org/packages/mainline/ubuntu/ focal nginx' >> /etc/apt/sources.list\napt-get -y remove nginx-common\napt-get -y update\napt-get -y install nginx\n\n# the config is on github\nrm -rf /etc/nginx/*\nrm -rf /etc/nginx/.* 2> /dev/null\ngit clone https://github.com/freeCodeCamp/devdocs-nginx-config.git /etc/nginx\n\n# at this point we need to add the certs from Cloudflare and test the config\nnginx -t\n\n# if nginx is already running, just\n# ps aux | grep nginx\n# find the number and kill it\n\nnginx\n```\n\n## List of maintainers in alphabetical order\n\nThe following people (used to) maintain DevDocs:\n\n- [Ahmad Abdolsaheb](https://github.com/ahmadabdolsaheb)\n- [Bryan Hernández](https://github.com/MasterEnoc)\n- [Jasper van Merle](https://github.com/jmerle)\n- [Jed Fox](https://github.com/j-f1)\n- [Mrugesh Mohapatra](https://github.com/raisedadead)\n- [Oliver Eyton-Williams](https://github.com/ojeytonwilliams)\n- [Simon Legner](https://github.com/simon04)\n- [Thibaut Courouble](https://github.com/thibaut)\n\nTo reach out, please ping [@freeCodeCamp/devdocs](https://github.com/orgs/freeCodeCamp/teams/devdocs).\n\nInterested in helping maintain DevDocs? Come talk to us on [Discord](https://discord.gg/PRyKn3Vbay) :)\n\nIn addition, we appreciate the major contributions made by [these great people](https://github.com/freeCodeCamp/devdocs/graphs/contributors).\n"
  },
  {
    "path": "docs/scraper-reference.md",
    "content": "**Table of contents:**\n\n* [Overview](#overview)\n* [Configuration](#configuration)\n  - [Attributes](#attributes)\n  - [Filter stacks](#filter-stacks)\n  - [Filter options](#filter-options)\n\n## Overview\n\nStarting from a root URL, scrapers recursively follow links that match a set of rules, passing each valid response through a chain of filters before writing the file on the local filesystem. They also create an index of the pages' metadata (determined by one filter), which is dumped into a JSON file at the end.\n\nScrapers rely on the following libraries:\n\n* [Typhoeus](https://github.com/typhoeus/typhoeus) for making HTTP requests\n* [HTML::Pipeline](https://github.com/jch/html-pipeline) for applying filters\n* [Nokogiri](http://nokogiri.org/) for parsing HTML\n\nThere are currently two kinds of scrapers: [`UrlScraper`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/core/scrapers/url_scraper.rb) which downloads files via HTTP and [`FileScraper`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/core/scrapers/file_scraper.rb) which reads them from the local filesystem. They function almost identically (both use URLs), except that `FileScraper` substitutes the base URL with a local path before reading a file. `FileScraper` uses the placeholder `localhost` base URL by default and includes a filter to remove any URL pointing to it at the end.\n\nTo be processed, a response must meet the following requirements:\n\n* 200 status code\n* HTML content type\n* effective URL (after redirection) contained in the base URL (explained below)\n\n(`FileScraper` only checks if the file exists and is not empty.)\n\nEach URL is requested only once (case-insensitive).\n\n## Configuration\n\nConfiguration is done via class attributes and divided into three main categories:\n\n* [Attributes](#attributes) — essential information such as name, version, URL, etc.\n* [Filter stacks](#filter-stacks) — the list of filters that will be applied to each page.\n* [Filter options](#filter-options) — the options passed to said filters.\n\n**Note:** scrapers are located in the [`lib/docs/scrapers`](https://github.com/freeCodeCamp/devdocs/tree/main/lib/docs/scrapers/) directory. The class's name must be the [CamelCase](http://api.rubyonrails.org/classes/String.html#method-i-camelize) equivalent of the filename.\n\n### Attributes\n\n* `name` [String]\n  Must be unique.\n  Defaults to the class's name.\n\n* `slug` [String]\n  Must be unique, lowercase, and not include dashes (underscores are ok).\n  Defaults to `name` lowercased.\n\n* `type` [String] **(required, inherited)**\n  Defines the CSS class name (`_[type]`) and custom JavaScript class (`app.views.[Type]Page`) that will be added/loaded on each page. Documentations sharing a similar structure (e.g. generated with the same tool or originating from the same website) should use the same `type` to avoid duplicating the CSS and JS.\n  Must include lowercase letters only.\n\n* `release` [String] **(required)**\n  The version of the software at the time the scraper was last run. This is only informational and doesn't affect the scraper's behavior.\n\n* `base_url` [String] **(required in `UrlScraper`)**\n  The documents' location. Only URLs _inside_ the `base_url` will be scraped. \"inside\" more or less means \"starting with\" except that `/docs` is outside `/doc` (but `/doc/` is inside).\n   Defaults to `localhost` in `FileScraper`. _(Note: any iframe, image, or skipped link pointing to localhost will be removed by the `CleanLocalUrls` filter; the value should be overridden if the documents are available online.)_\n  Unless `root_path` is set, the root/initial URL is equal to `base_url`.\n\n* `base_urls` [Array] **(the `MultipleBaseUrls` module must be included)** Documentation's locations. Almost the same as `base_url` but in this case more than one URL can be added, should be used when a documentation is split in different URLs or needs more URLs to be completed. See [`typescript.rb`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/scrapers/typescript.rb).\n\n* `root_path` [String] **(inherited)**\n  The path from the `base_url` of the root URL.\n\n* `initial_paths` [Array] **(inherited)**\n  A list of paths (from the `base_url`) to add to the initial queue. Useful for scraping isolated documents.\n  Defaults to `[]`. _(Note: the `root_path` is added to the array at runtime.)_\n\n* `dir` [String] **(required, `FileScraper` only)**\n  The absolute path where the files are located on the local filesystem.\n  _Note: `FileScraper` works exactly like `UrlScraper` (manipulating the same kind of URLs) except that it substitutes `base_url` with `dir` in order to read files instead of making HTTP requests._\n\n* `params` [Hash] **(inherited, `UrlScraper` only)**\n  Query string parameters to append to every URL. (e.g. `{ format: 'raw' }` → `?format=raw`)\n  Defaults to `{}`.\n\n* `abstract` [Boolean]\n  Make the scraper abstract / not runnable. Used for sharing behavior with other scraper classes (e.g. all MDN scrapers inherit from the abstract [`Mdn`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/scrapers/mdn/mdn.rb) class).\n  Defaults to `false`.\n\n### Filter stacks\n\nEach scraper has two [filter](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/core/filter.rb) [stacks](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/core/filter_stack.rb): `html_filters` and `text_filters`. They are combined into a pipeline (using the [HTML::Pipeline](https://github.com/jch/html-pipeline) library) which causes each filter to hand its output to the next filter's input.\n\nHTML filters are executed first and manipulate a parsed version of the document (a [Nokogiri](http://nokogiri.org/Nokogiri/XML/Node.html) node object), whereas text filters manipulate the document as a string. This separation avoids parsing the document multiple times.\n\nFilter stacks are like sorted sets. They can modified using the following methods:\n\n```ruby\npush(*names)                 # append one or more filters at the end\ninsert_before(index, *names) # insert one filter before another (index can be a name)\ninsert_after(index, *names)  # insert one filter after another (index can be a name)\nreplace(index, name)         # replace one filter with another (index can be a name)\n```\n\n\"names\" are `require` paths relative to `Docs` (e.g. `jquery/clean_html` → `Docs::Jquery::CleanHtml`).\n\nDefault `html_filters`:\n\n* [`ContainerFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/container.rb) — changes the root node of the document (remove everything outside)\n* [`CleanHtmlFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/clean_html.rb) — removes HTML comments, `<script>`, `<style>`, etc.\n* [`NormalizeUrlsFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/normalize_urls.rb) — replaces all URLs with their fully qualified counterpart\n* [`InternalUrlsFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/internal_urls.rb) — detects internal URLs (the ones to scrape) and replaces them with their unqualified, relative counterpart\n* [`NormalizePathsFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/normalize_paths.rb) — makes the internal paths consistent (e.g. always end with `.html`)\n* [`CleanLocalUrlsFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/clean_local_urls.rb) — removes links, iframes and images pointing to localhost (`FileScraper` only)\n\nDefault `text_filters`:\n\n* [`InnerHtmlFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/inner_html.rb) — converts the document to a string\n* [`CleanTextFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/clean_text.rb) — removes empty nodes\n* [`AttributionFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/attribution.rb) — appends the license info and link to the original document\n\nAdditionally:\n\n* [`TitleFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/title.rb) is a core HTML filter, disabled by default, which prepends the document with a title (`<h1>`).\n* [`EntriesFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/entries.rb) is an abstract HTML filter that each scraper must implement and responsible for extracting the page's metadata.\n\n### Filter options\n\nThe filter options are stored in the `options` Hash. The Hash is inheritable (a recursive copy) and empty by default.\n\nMore information about how filters work is available on the [Filter Reference](./filter-reference.md) page.\n\n* [`ContainerFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/container.rb)\n\n  - `:container` [String or Proc]\n    A CSS selector of the container element. Everything outside of it will be removed and become unavailable to the other filters. If more than one element match the selector, the first one inside the DOM is used. If no elements match the selector, an error is raised.\n    If the value is a Proc, it is called for each page with the filter instance as argument, and should return a selector or `nil`.\n    The default container is the `<body>` element.\n    _Note: links outside of the container element will not be followed by the scraper. To remove links that should be followed, use a [`CleanHtml`](./filter-reference.md#cleanhtmlfilter) filter later in the stack._\n\n* [`NormalizeUrlsFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/normalize_urls.rb)\n  The following options are used to modify URLs in the pages. They are useful to remove duplicates (when the same page is accessible from multiple URLs) and fix websites that have a bunch of redirections in place (when URLs that should be scraped, aren't, because they are behind a redirection which is outside of the `base_url` — see the MDN scrapers for examples of this).\n\n  - `:replace_urls` [Hash]\n    Replaces all instances of a URL with another.\n    Format: `{ 'original_url' => 'new_url' }`\n  - `:replace_paths` [Hash]\n    Replaces all instances of a sub-path (path from the `base_url`) with another.\n    Format: `{ 'original_path' => 'new_path' }`\n  - `:fix_urls` [Proc]\n    Called with each URL. If the returned value is `nil`, the URL isn't modified. Otherwise the returned value is used as replacement.\n\n  _Note: before these rules are applied, all URLs are converted to their fully qualified counterpart (http://...)._\n\n* [`InternalUrlsFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/internal_urls.rb)\n\n  Internal URLs are the ones _inside_ the scraper's `base_url` (\"inside\" more or less means \"starting with\", except that `/docs` is outside `/doc`). They will be scraped unless excluded by one of the following rules. All internal URLs are converted to relative URLs inside the pages.\n\n  - `:skip_links` [Boolean or Proc]\n    If `false`, does not convert or follow any internal URL (creating a single-page documentation).\n    If the value is a Proc, it is called for each page with the filter instance as argument.\n  - `:follow_links` [Proc]\n    Called for page with the filter instance as argument. If the returned value is `false`, does not add internal URLs to the queue.\n  - `:trailing_slash` [Boolean]\n    If `true`, adds a trailing slash to all internal URLs. If `false`, removes it.\n    This is another option used to remove duplicate pages.\n  - `:skip` [Array]\n    Ignores internal URLs whose sub-paths (path from the `base_url`) are in the Array (case-insensitive).\n  - `:skip_patterns` [Array]\n    Ignores internal URLs whose sub-paths match any Regexp in the Array.\n  - `:only` [Array]\n    Ignores internal URLs whose sub-paths aren't in the Array (case-insensitive) and don't match any Regexp in `:only_patterns`.\n  - `:only_patterns` [Array]\n    Ignores internal URLs whose sub-paths don't match any Regexp in the Array and aren't in `:only`.\n\n  If the scraper has a `root_path`, the empty and `/` paths are automatically skipped.\n  If `:only` or `:only_patterns` is set, the root path is automatically added to `:only`.\n\n  _Note: pages can be excluded from the index based on their content using the [`Entries`](./filter-reference.md#entriesfilter) filter. However, their URLs will still be converted to relative in the other pages and trying to open them will return a 404 error. Although not ideal, this is often better than having to maintain a long list of `:skip` URLs._\n\n* [`AttributionFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/attribution.rb)\n\n  - `:attribution` [String] **(required)**\n    An HTML string with the copyright and license information. See the other scrapers for examples.\n\n* [`TitleFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/title.rb)\n\n  - `:title` [String or Boolean or Proc]\n    Unless the value is `false`, adds a title to every page.\n    If the value is `nil`, the title is the name of the page as determined by the [`Entries`](./filter-reference.md#entriesfilter) filter. Otherwise the title is the String or the value returned by the Proc (called for each page, with the filter instance as argument). If the Proc returns `nil` or `false`, no title is added.\n  - `:root_title` [String or Boolean]\n    Overrides the `:title` option for the root page only.\n\n  _Note: this filter is disabled by default._\n\n### Processing responses before filters\n\nThese methods are runned before filter stacks, and can directly process responses.\n\n* `process_response?(response)`\n\n  Determine whether a response should be processed. A response will be dropped if this method returns `false`.\n\n  It is useful to filter pages, such as empty, invalid, or redirecting pages, depending on the content.\n\n  Example: [lib/docs/scrapers/kotlin.rb](../lib/docs/scrapers/kotlin.rb)\n\n\n* `parse(response)`\n\n  Parse HTTP/File response, and convert to a Nokogiri document by default.\n\n  Overrides this method if you want to modified HTML source code before Nokogiri.\nIt is useful to preserve whitespaces of code segments within non-pre blocks, because Nokogiri may delete them.\n\n  Example: [lib/docs/scrapers/go.rb](../lib/docs/scrapers/go.rb)\n\n\n\n## Keeping scrapers up-to-date\n\nIn order to keep scrapers up-to-date the `get_latest_version(opts)` method should be overridden. If `self.release` is defined, this should return the latest version of the documentation. If `self.release` is not defined, it should return the Epoch time when the documentation was last modified. If the documentation will never change, simply return `1.0.0`. The result of this method is periodically reported in a \"Documentation versions report\" issue which helps maintainers keep track of outdated documentations.\n\nTo make life easier, there are a few utility methods that you can use in `get_latest_version`:\n\n### General HTTP methods\n* `fetch(url, opts)`\n\n  Makes a GET request to the url and returns the response body.\n\n  Example: [lib/docs/scrapers/bash.rb](../lib/docs/scrapers/bash.rb)\n* `fetch_doc(url, opts)`\n\n  Makes a GET request to the url and returns the HTML body converted to a Nokogiri document.\n\n  Example: [lib/docs/scrapers/git.rb](../lib/docs/scrapers/git.rb)\n* `fetch_json(url, opts)`\n\n  Makes a GET request to the url and returns the JSON body converted to a dictionary.\n\n  Example: [lib/docs/scrapers/mdn/mdn.rb](../lib/docs/scrapers/mdn/mdn.rb)\n\n### Package repository methods\n* `get_npm_version(package, opts)`\n\n  Returns the latest version of the given npm package.\n\n  Example: [lib/docs/scrapers/bower.rb](../lib/docs/scrapers/bower.rb)\n\n### GitHub methods\n* `get_latest_github_release(owner, repo, opts)`\n\n  Returns the tag name of the latest GitHub release of the given repository. If the tag name is preceded by a \"v\", the \"v\" will be removed.\n\n  Example: [lib/docs/scrapers/jsdoc.rb](../lib/docs/scrapers/jsdoc.rb)\n* `get_github_tags(owner, repo, opts)`\n\n  Returns the list of tags on the given repository ([format](https://developer.github.com/v3/repos/#list-tags)).\n\n  Example: [lib/docs/scrapers/liquid.rb](../lib/docs/scrapers/liquid.rb)\n* `get_github_file_contents(owner, repo, path, opts)`\n\n  Returns the contents of the requested file in the default branch of the given repository.\n\n  Example: [lib/docs/scrapers/minitest.rb](../lib/docs/scrapers/minitest.rb)\n* `get_latest_github_commit_date(owner, repo, opts)`\n\n    Returns the date of the most recent commit in the default branch of the given repository.\n\n    Example: [lib/docs/scrapers/reactivex.rb](../lib/docs/scrapers/reactivex.rb)\n\n### GitLab methods\n* `get_gitlab_tags(hostname, group, project, opts)`\n\n  Returns the list of tags on the given repository ([format](https://docs.gitlab.com/ee/api/tags.html)).\n\n  Example: [lib/docs/scrapers/gtk.rb](../lib/docs/scrapers/gtk.rb)\n"
  },
  {
    "path": "lib/app.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'bundler/setup'\nBundler.require :app\n\nclass App < Sinatra::Application\n  Bundler.require environment\n  require 'sinatra/cookies'\n  require 'tilt/erubi'\n  require 'active_support/notifications'\n\n  Rack::Mime::MIME_TYPES['.webapp'] = 'application/x-web-app-manifest+json'\n\n  configure do\n    use Rack::SslEnforcer, only_environments: ['production', 'test'], hsts: true, force_secure_cookies: false\n\n    set :sentry_dsn, ENV['SENTRY_DSN']\n    set :protection, except: [:frame_options, :xss_header]\n\n    set :root, Pathname.new(File.expand_path('../..', __FILE__))\n    set :sprockets, Sprockets::Environment.new(root)\n\n    set :assets_prefix, 'assets'\n    set :assets_path, File.join(public_folder, assets_prefix)\n    set :assets_manifest_path, File.join(assets_path, 'manifest.json')\n    set :assets_compile, %w(*.png docs.js docs.json application.js application.css application-dark.css)\n\n    require 'yajl/json_gem'\n    set :docs_prefix, 'docs'\n    set :docs_origin, File.join('', docs_prefix)\n    set :docs_path, File.join(public_folder, docs_prefix)\n    set :docs_manifest_path, File.join(docs_path, 'docs.json')\n    set :default_docs, %w(css dom html http javascript)\n    set :news_path, File.join(root, assets_prefix, 'javascripts', 'news.json')\n\n    set :csp, false\n\n    require 'docs'\n    Docs.generate_manifest\n    set :docs_aliases, Docs.aliases\n\n    Dir[docs_path, root.join(assets_prefix, '*/')].each do |path|\n      sprockets.append_path(path)\n    end\n\n    Sprockets::Helpers.configure do |config|\n      config.environment = sprockets\n      config.prefix = \"/#{assets_prefix}\"\n      config.public_path = public_folder\n      config.protocol = :relative\n    end\n  end\n\n  configure :test, :development do\n    require 'thor'\n    load 'tasks/sprites.thor'\n\n    SpritesCLI.new.invoke(:generate, [], :disable_optimization => true)\n\n    require 'active_support/cache'\n    sprockets.cache = ActiveSupport::Cache.lookup_store :file_store, root.join('tmp', 'cache', 'assets', environment.to_s)\n  end\n\n  configure :development do\n    register Sinatra::Reloader\n\n    use BetterErrors::Middleware\n    BetterErrors.application_root = File.expand_path('..', __FILE__)\n    BetterErrors.editor = :sublime\n\n    set :csp, \"default-src 'self' *; script-src 'self' 'nonce-devdocs' *; font-src 'none'; style-src 'self' 'unsafe-inline' *; img-src 'self' * data:;\"\n  end\n\n  configure :production do\n    set :static, false\n    set :docs_origin, '//documents.devdocs.io'\n    set :csp, \"default-src 'self' *; script-src 'self' 'nonce-devdocs' https://www.google-analytics.com https://secure.gaug.es https://*.jquery.com; font-src 'none'; style-src 'self' 'unsafe-inline' *; img-src 'self' * data:;\"\n\n    use Rack::ConditionalGet\n    use Rack::ETag\n    use Rack::Deflater\n    use Rack::Static,\n      root: 'public',\n      urls: %w(/assets /docs/ /images /favicon.ico /robots.txt /opensearch.xml /mathml.css /manifest.json),\n      header_rules: [\n        [:all,              { 'Cache-Control' => 'no-cache, max-age=0'    }],\n        ['/assets',         { 'Cache-Control' => 'public, max-age=604800' }],\n        ['/docs',           { 'Cache-Control' => 'public, max-age=86400'  }],\n        ['/images',         { 'Cache-Control' => 'public, max-age=86400'  }],\n        ['/favicon.ico',    { 'Cache-Control' => 'public, max-age=86400'  }],\n        ['/robots.txt',     { 'Cache-Control' => 'public, max-age=86400'  }],\n        ['/opensearch.xml', { 'Cache-Control' => 'public, max-age=86400'  }],\n        ['/mathml.css',     { 'Cache-Control' => 'public, max-age=86400'  }],\n        ['/manifest.json',  { 'Cache-Control' => 'public, max-age=86400'  }]\n      ]\n\n    sprockets.js_compressor = Terser.new\n    sprockets.css_compressor = :sass\n\n    Sprockets::Helpers.configure do |config|\n      config.digest = true\n      config.manifest = Sprockets::Manifest.new(sprockets, assets_manifest_path)\n    end\n  end\n\n  configure :test do\n    set :docs_manifest_path, File.join(root, 'test', 'files', 'docs.json')\n  end\n\n  def self.parse_docs\n    Hash[JSON.parse(File.read(docs_manifest_path)).map! { |doc|\n      doc['full_name'] = doc['name'].dup\n      doc['full_name'] << \" #{doc['version']}\" if doc['version'] && !doc['version'].empty?\n      doc['slug_without_version'] = doc['slug'].split('~').first\n      [doc['slug'], doc]\n    }]\n  end\n\n  def self.parse_news\n    JSON.parse(File.read(news_path))\n  end\n\n  configure :development, :test do\n    set :docs, -> { parse_docs }\n    set :news, -> { parse_news }\n  end\n\n  configure :production do\n    set :docs, parse_docs\n    set :news, parse_news\n  end\n\n  helpers do\n    include Sinatra::Cookies\n    include Sprockets::Helpers\n\n    def memoized_cookies\n      @memoized_cookies ||= cookies.to_hash\n    end\n\n    def canonical_origin\n      \"https://#{request.host_with_port}\"\n    end\n\n    def browser\n      @browser ||= Browser.new(request.user_agent)\n    end\n\n    def unsupported_browser?\n      browser.ie?\n    end\n\n    def docs\n      @docs ||= begin\n        cookie = memoized_cookies['docs']\n\n        if cookie.nil?\n          settings.default_docs\n        else\n          cookie.split('/')\n        end\n      end\n    end\n\n    def find_doc(slug)\n      settings.docs[slug] || begin\n        settings.docs.each do |_, doc|\n          return doc if doc['slug_without_version'] == slug\n        end\n        nil\n      end\n    end\n\n    def user_has_docs?(slug)\n      docs.include?(slug) || begin\n        slug = \"#{slug}~\"\n        docs.any? { |_slug| _slug.start_with?(slug) }\n      end\n    end\n\n    def doc_index_urls\n      docs.each_with_object [] do |slug, result|\n        if doc = settings.docs[slug]\n          result << File.join('', settings.docs_prefix, slug, 'index.json') + \"?#{doc['mtime']}\"\n        end\n      end\n    end\n\n    def doc_index_page?\n      @doc && (request.path == \"/#{@doc['slug']}/\" || request.path == \"/#{@doc['slug_without_version']}/\")\n    end\n\n    def query_string_for_redirection\n      request.query_string.empty? ? nil : \"?#{request.query_string}\"\n    end\n\n    def service_worker_asset_urls\n      @@service_worker_asset_urls ||= [\n        javascript_path('application'),\n        stylesheet_path('application'),\n        image_path('sprites/docs.png'),\n        image_path('sprites/docs@2x.png'),\n        asset_path('docs.js'),\n        App.production? ? nil : javascript_path('debug'),\n      ].compact\n    end\n\n    # Returns a cache name for the service worker to use which changes if any of the assets changes\n    # When a manifest exist, this name is only created once based on the asset manifest because it never changes without a server restart\n    # If a manifest does not exist, it is created every time this method is called because the assets can change while the server is running\n    def service_worker_cache_name\n      if File.exist?(App.assets_manifest_path)\n        if defined?(@@service_worker_cache_name)\n          return @@service_worker_cache_name\n        end\n\n        digest = Sprockets::Manifest\n                   .new(nil, App.assets_manifest_path)\n                   .files\n                   .values\n                   .map {|file| file[\"digest\"]}\n                   .join\n\n        return @@service_worker_cache_name ||= Digest::MD5.hexdigest(digest)\n      else\n        paths = App.sprockets\n                  .each_file\n                  .to_a\n                  .reject {|file| file.start_with?(App.docs_path)}\n\n        return App.sprockets.pack_hexdigest(App.sprockets.files_digest(paths))\n      end\n    end\n\n    def redirect_via_js(path)\n      response.set_cookie :initial_path, value: path, expires: Time.now + 15, path: '/'\n      redirect '/', 302\n    end\n\n    def supports_js_redirection?\n      modern_browser?(browser) && !memoized_cookies.empty?\n    end\n\n    # https://github.com/fnando/browser#detecting-modern-browsers\n    # https://github.com/fnando/browser/blob/v2.6.1/lib/browser/browser.rb\n    # This restores the old browser gem `#modern?` functionality as it was in 2.6.1\n    # It's possible this isn't even really needed any longer, these versions are quite old now\n    def modern_browser?(browser)\n      [\n        browser.webkit?,\n        browser.firefox? && browser.version.to_i >= 17,\n        browser.ie? && browser.version.to_i >= 9 && !browser.compatibility_view?,\n        browser.edge? && !browser.compatibility_view?,\n        browser.opera? && browser.version.to_i >= 12,\n        browser.firefox? && browser.device.tablet? && browser.platform.android? && b.version.to_i >= 14\n      ].any?\n    end\n  end\n\n  before do\n    halt erb :unsupported if unsupported_browser?\n  end\n\n  OUT_HOST = 'out.devdocs.io'.freeze\n\n  before do\n    if request.host == OUT_HOST && !request.path.start_with?('/s/')\n      query_string = \"?#{request.query_string}\" unless request.query_string.empty?\n      redirect \"https://devdocs.io#{request.path}#{query_string}\", 302\n    end\n  end\n\n  get '/service-worker.js' do\n    content_type 'application/javascript'\n    expires 0, :'no-cache'\n    erb :'service-worker.js'\n  end\n\n  get '/' do\n    return redirect \"/#q=#{params[:q]}\" if params[:q]\n    return redirect '/' unless request.query_string.empty?\n    response.headers['Content-Security-Policy'] = settings.csp if settings.csp\n    erb :index\n  end\n\n  %w(settings offline about news help).each do |page|\n    get \"/#{page}\" do\n      if supports_js_redirection?\n        redirect_via_js \"/#{page}\"\n      else\n        redirect \"/#/#{page}\", 302\n      end\n    end\n  end\n\n  get '/search' do\n    redirect \"/#q=#{params[:q]}\"\n  end\n\n  get '/ping' do\n    200\n  end\n\n  %w(docs.json application.js application.css).each do |asset|\n    class_eval <<-CODE, __FILE__, __LINE__ + 1\n      get '/#{asset}' do\n        redirect asset_path('#{asset}', protocol: 'http')\n      end\n    CODE\n  end\n\n  {\n    '/s/maxcdn'           => 'https://www.maxcdn.com/?utm_source=devdocs&utm_medium=banner&utm_campaign=devdocs',\n    '/s/shopify'          => 'https://www.shopify.com/careers?utm_source=devdocs&utm_medium=banner&utm_campaign=devdocs',\n    '/s/jetbrains'        => 'https://www.jetbrains.com/?utm_source=devdocs&utm_medium=sponsorship&utm_campaign=devdocs',\n    '/s/jetbrains/ruby'   => 'https://www.jetbrains.com/ruby/?utm_source=devdocs&utm_medium=sponsorship&utm_campaign=devdocs',\n    '/s/jetbrains/python' => 'https://www.jetbrains.com/pycharm/?utm_source=devdocs&utm_medium=sponsorship&utm_campaign=devdocs',\n    '/s/jetbrains/c'      => 'https://www.jetbrains.com/clion/?utm_source=devdocs&utm_medium=sponsorship&utm_campaign=devdocs',\n    '/s/jetbrains/web'    => 'https://www.jetbrains.com/webstorm/?utm_source=devdocs&utm_medium=sponsorship&utm_campaign=devdocs',\n    '/s/code-school'      => 'https://www.codeschool.com/?utm_campaign=devdocs&utm_content=homepage&utm_source=devdocs&utm_medium=sponsorship',\n    '/s/tw'               => 'https://twitter.com/intent/tweet?url=http%3A%2F%2Fdevdocs.io&via=DevDocs&text=All-in-one%20API%20documentation%20browser%20with%20offline%20mode%20and%20instant%20search%3A',\n    '/s/fb'               => 'https://www.facebook.com/sharer/sharer.php?u=http%3A%2F%2Fdevdocs.io',\n    '/s/re'               => 'https://www.reddit.com/submit?url=http%3A%2F%2Fdevdocs.io&title=All-in-one%20API%20documentation%20browser%20with%20offline%20mode%20and%20instant%20search&resubmit=true'\n  }.each do |path, url|\n    class_eval <<-CODE, __FILE__, __LINE__ + 1\n      get '#{path}' do\n        redirect '#{url}'\n      end\n    CODE\n  end\n\n  %w(/maxcdn /maxcdn/).each do |path|\n    class_eval <<-CODE, __FILE__, __LINE__ + 1\n      get '#{path}' do\n        410\n      end\n    CODE\n  end\n\n  {\n    '/tips'                   => '/help',\n    '/css-data-types/'        => '/css-values-units/',\n    '/css-at-rules/'          => '/?q=css%20%40',\n    '/dom/window/setinterval' => '/dom/windoworworkerglobalscope/setinterval',\n    '/html/article'           => '/html/element/article',\n    '/html-html5/'            => 'html-elements/',\n    '/html-standard/'         => 'html-elements/',\n    '/http-status-codes/'     => '/http-status/',\n    '/ruby/bignum'            => '/ruby~2.3/bignum',\n    '/ruby/fixnum'            => '/ruby~2.3/fixnum',\n  }.each do |path, url|\n    class_eval <<-CODE, __FILE__, __LINE__ + 1\n      get '#{path}' do\n        redirect '#{url}', 301\n      end\n    CODE\n  end\n\n  get %r{/feed(?:\\.atom)?} do\n    content_type 'application/atom+xml'\n    settings.news_feed\n  end\n\n  DOC_REDIRECTS = {\n    'iojs' => 'node',\n    'node_lts' => 'node~6_lts',\n    'node~4.2_lts' => 'node~4_lts',\n    'yii1' => 'yii~1.1',\n    'python2' => 'python~2.7',\n    'xpath' => 'xslt_xpath',\n    'angular~4_typescript' => 'angular',\n    'angular~2_typescript' => 'angular~2',\n    'angular~2.0_typescript' => 'angular~2',\n    'angular~1.5' => 'angularjs~1.5',\n    'angular~1.4' => 'angularjs~1.4',\n    'angular~1.3' => 'angularjs~1.3',\n    'angular~1.2' => 'angularjs~1.2',\n    'codeigniter~3.0' => 'codeigniter~3',\n    'pytorch~1' => 'pytorch~1.13',\n    'pytorch~2' => 'pytorch',\n    'webpack~2' => 'webpack'\n  }\n\n  get %r{/([\\w~\\.%]+)(\\-[\\w\\-]+)?(/.*)?} do |doc, type, rest|\n    doc.sub! '%7E', '~'\n\n    if DOC_REDIRECTS.key?(doc)\n      return redirect \"/#{DOC_REDIRECTS[doc]}#{type}#{rest}\", 301\n    end\n\n    if rest && doc == 'angular' && rest.start_with?('/ng')\n      return redirect \"/angularjs/api#{rest}\", 301\n    end\n\n    if rest && doc == 'dom'\n      if rest.start_with?('/windowtimers')\n        return redirect \"/dom#{rest.sub('windowtimers', 'windoworworkerglobalscope')}\", 301\n      end\n\n      if rest.start_with?('/window/url.')\n        return redirect \"/dom#{rest.sub('window/url.', 'url/')}\", 301\n      end\n\n      if rest.start_with?('/window.')\n        return redirect \"/dom#{rest.sub('window.', 'window/')}\", 301\n      end\n\n      if rest.start_with?('/element.')\n        return redirect \"/dom#{rest.sub('element.', 'element/')}\", 301\n      end\n\n      if rest.start_with?('/event.')\n        return redirect \"/dom#{rest.sub('event.', 'event/')}\", 301\n      end\n\n      if rest.start_with?('/document.')\n        return redirect \"/dom#{rest.sub('document.', 'document/')}\", 301\n      end\n    end\n\n    return 404 unless @doc = find_doc(doc)\n\n    if rest.nil?\n      redirect \"/#{doc}#{type}/#{query_string_for_redirection}\"\n    elsif rest.length > 1 && rest.end_with?('/')\n      redirect \"/#{doc}#{type}#{rest[0...-1]}#{query_string_for_redirection}\"\n    elsif user_has_docs?(doc) && supports_js_redirection?\n      redirect_via_js(request.path)\n    else\n      response.headers['Content-Security-Policy'] = settings.csp if settings.csp\n      erb :other\n    end\n  end\n\n  not_found do\n    send_file File.join(settings.public_folder, '404.html'), status: status\n  end\n\n  error do\n    send_file File.join(settings.public_folder, '500.html'), status: status\n  end\n\n  configure do\n    require 'rss'\n    feed = RSS::Maker.make('atom') do |maker|\n      maker.channel.id = 'tag:devdocs.io,2014:/feed'\n      maker.channel.title = 'DevDocs'\n      maker.channel.author = 'DevDocs'\n      maker.channel.updated = \"#{settings.news.first.first}T14:00:00Z\"\n\n      maker.channel.links.new_link do |link|\n        link.rel = 'self'\n        link.href = 'https://devdocs.io/feed.atom'\n        link.type = 'application/atom+xml'\n      end\n\n      maker.channel.links.new_link do |link|\n        link.rel = 'alternate'\n        link.href = 'https://devdocs.io/'\n        link.type = 'text/html'\n      end\n\n      news.each_with_index do |news, i|\n        maker.items.new_item do |item|\n          item.id = \"tag:devdocs.io,2014:News/#{settings.news.length - i}\"\n          item.title = news[1].split(\"\\n\").first.gsub(/<\\/?[^>]*>/, '')\n          item.description do |desc|\n            desc.content = news[1..-1].join.gsub(\"\\n\", '<br>').gsub('href=\"/', 'href=\"https://devdocs.io/')\n            desc.type = 'html'\n          end\n          item.updated = \"#{news.first}T14:00:00Z\"\n          item.published = \"#{news.first}T14:00:00Z\"\n          item.links.new_link do |link|\n            link.rel = 'alternate'\n            link.href = 'https://devdocs.io/'\n            link.type = 'text/html'\n          end\n        end\n      end\n    end\n\n    set :news_feed, feed.to_s\n  end\nend\n"
  },
  {
    "path": "lib/docs/core/autoload_helper.rb",
    "content": "module Docs\n  module AutoloadHelper\n    def autoload_all(path, suffix = '')\n      Dir[\"#{Docs.root_path}/#{path}/**/*.rb\"].each do |file|\n        name = File.basename(file, '.rb') + (suffix ? \"_#{suffix}\" : '')\n        autoload name.camelize, file\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/core/doc.rb",
    "content": "module Docs\n  class Doc\n    INDEX_FILENAME = 'index.json'\n    DB_FILENAME = 'db.json'\n    META_FILENAME = 'meta.json'\n\n    class << self\n      include Instrumentable\n\n      attr_accessor :name, :slug, :type, :release, :abstract, :links\n\n      def inherited(subclass)\n        subclass.type = type\n      end\n\n      def version(version = nil, &block)\n        return @version unless block_given?\n\n        klass = Class.new(self)\n        klass.name = name\n        klass.slug = slug\n        klass.version = version\n        klass.release = release\n        klass.links = links\n        klass.class_exec(&block)\n        @versions ||= []\n        @versions << klass\n        klass\n      end\n\n      def version=(value)\n        @version = value.to_s\n      end\n\n      def versions\n        @versions.presence || [self]\n      end\n\n      def version?\n        version.present?\n      end\n\n      def versioned?\n        @versions.presence\n      end\n\n      def name\n        @name || super.demodulize\n      end\n\n      def slug\n        slug = @slug || default_slug || raise('slug is required')\n        version? ? \"#{slug}~#{version_slug}\" : slug\n      end\n\n      def version_slug\n        return if version.blank?\n        slug = version.downcase\n        slug.gsub! '+', 'p'\n        slug.gsub! '#', 's'\n        slug.gsub! %r{[^a-z0-9\\_\\.]}, '_'\n        slug\n      end\n\n      def path\n        slug\n      end\n\n      def index_path\n        File.join path, INDEX_FILENAME\n      end\n\n      def db_path\n        File.join path, DB_FILENAME\n      end\n\n      def meta_path\n        File.join path, META_FILENAME\n      end\n\n      def as_json\n        json = { name: name, slug: slug, type: type }\n        json[:links] = links if links.present?\n        json[:version] = version if version.present? || defined?(@version)\n        json[:release] = release if release.present?\n        json\n      end\n\n      def store_page(store, id)\n        index = EntryIndex.new\n        pages = PageDb.new\n\n        store.open(path) do\n          if page = new.build_page(id) and store_page?(page)\n            index.add page[:entries]\n            pages.add page[:path], page[:output]\n            store_index(store, INDEX_FILENAME, index, false)\n            store_index(store, DB_FILENAME, pages, false)\n            store.write page[:store_path], page[:output]\n            true\n          else\n            false\n          end\n        end\n      rescue Docs::SetupError => error\n        puts \"ERROR: #{error.message}\"\n        false\n      end\n\n      def store_pages(store)\n        index = EntryIndex.new\n        pages = PageDb.new\n\n        store.replace(path) do\n          new.build_pages do |page|\n            next unless store_page?(page)\n            store.write page[:store_path], page[:output]\n            index.add page[:entries]\n            pages.add page[:path], page[:output]\n          end\n\n          if index.present?\n            store_index(store, INDEX_FILENAME, index)\n            store_index(store, DB_FILENAME, pages)\n            store_meta(store)\n            true\n          else\n            false\n          end\n        end\n      rescue Docs::SetupError => error\n        puts \"ERROR: #{error.message}\"\n        false\n      end\n\n      private\n\n      def default_slug\n        return if name =~ /[^A-Za-z0-9_]/\n        name.downcase\n      end\n\n      def store_page?(page)\n        page[:entries].present?\n      end\n\n      def store_index(store, filename, index, read_write=true)\n        old_json = read_write && store.read(filename) || '{}'\n        new_json = index.to_json\n        instrument \"#{filename.remove('.json')}.doc\", before: old_json, after: new_json\n        read_write && store.write(filename, new_json)\n      end\n\n      def store_meta(store)\n        json = as_json\n        json[:mtime] = Time.now.to_i\n        json[:db_size] = store.size(DB_FILENAME)\n        store.write(META_FILENAME, json.to_json)\n      end\n    end\n\n    def initialize\n      raise NotImplementedError, \"#{self.class} is an abstract class and cannot be instantiated.\" if self.class.abstract\n    end\n\n    def build_page(id, &block)\n      raise NotImplementedError\n    end\n\n    def build_pages(&block)\n      raise NotImplementedError\n    end\n\n    def get_scraper_version(opts)\n      if self.class.method_defined?(:options) and !options[:release].nil?\n        options[:release]\n      else\n        # If options[:release] does not exist, we return the Epoch timestamp of when the doc was last modified in DevDocs production\n        json = fetch_json('https://devdocs.io/docs.json', opts)\n        items = json.select {|item| item['name'] == self.class.name}\n        items = items.map {|item| item['mtime']}\n        items.max\n      end\n    end\n\n    # Should return the latest version of this documentation\n    # If options[:release] is defined, it should be in the same format\n    # If options[:release] is not defined, it should return the Epoch timestamp of when the documentation was last updated\n    # If the docs will never change, simply return '1.0.0'\n    def get_latest_version(opts)\n      raise NotImplementedError\n    end\n\n    # Returns whether or not this scraper is outdated (\"Outdated major version\", \"Outdated minor version\" or 'Up-to-date').\n    #\n    # The default implementation assumes the documentation uses a semver(-like) approach when it comes to versions.\n    # Patch updates are ignored because there are usually little to no documentation changes in bug-fix-only releases.\n    #\n    # Scrapers of documentations that do not use this versioning approach should override this method.\n    #\n    # Examples of the default implementation:\n    # 1 -> 2 = outdated\n    # 1.1 -> 1.2 = outdated\n    # 1.1.1 -> 1.1.2 = not outdated\n    def outdated_state(scraper_version, latest_version)\n      scraper_parts = scraper_version.to_s.split(/[-.]/).map(&:to_i)\n      latest_parts = latest_version.to_s.split(/[-.]/).map(&:to_i)\n\n      # Only check the first two parts, the third part is for patch updates\n      [0, 1].each do |i|\n        break if i >= scraper_parts.length or i >= latest_parts.length\n        return 'Outdated major version' if i == 0 and latest_parts[i] > scraper_parts[i]\n        return 'Outdated major version' if i == 1 and latest_parts[i] > scraper_parts[i] and latest_parts[0] == 0 and scraper_parts[0] == 0\n        return 'Outdated major version' if i == 1 and latest_parts[i] > scraper_parts[i] and latest_parts[0] == 1 and scraper_parts[0] == 1\n        return 'Outdated minor version' if i == 1 and latest_parts[i] > scraper_parts[i]\n        return 'Up-to-date' if latest_parts[i] < scraper_parts[i]\n      end\n\n      'Up-to-date'\n    end\n\n    private\n\n    #\n    # Utility methods for get_latest_version\n    #\n\n    def fetch(url, opts)\n      headers = {}\n\n      if opts.key?(:github_token) and url.start_with?('https://api.github.com/')\n        headers['Authorization'] = \"token #{opts[:github_token]}\"\n      elsif ENV['GITHUB_TOKEN'] and url.start_with?('https://api.github.com/')\n        headers['Authorization'] = \"token #{ENV['GITHUB_TOKEN']}\"\n      end\n\n      opts[:logger].debug(\"Fetching #{url}\")\n      response = Request.run(url, { connecttimeout: 15, headers: headers })\n\n      if response.success?\n        response.body\n      else\n        reason = response.timed_out? ? \"Timed out while connecting to #{url}\" : \"Couldn't fetch #{url} (response code #{response.code})\"\n        opts[:logger].error(reason)\n        raise reason\n      end\n    end\n\n    def fetch_doc(url, opts)\n      body = fetch(url, opts)\n      Nokogiri::HTML.parse(body, nil, 'UTF-8')\n    end\n\n    def fetch_json(url, opts)\n      JSON.parse fetch(url, opts)\n    end\n\n    def get_npm_version(package, opts, tag='latest')\n      json = fetch_json(\"https://registry.npmjs.com/#{package}\", opts)\n      json['dist-tags'][tag]\n    end\n\n    def get_latest_github_release(owner, repo, opts)\n      release = fetch_json(\"https://api.github.com/repos/#{owner}/#{repo}/releases/latest\", opts)\n      tag_name = release['tag_name']\n      tag_name.start_with?('v') ? tag_name[1..-1] : tag_name\n    end\n\n    def get_github_tags(owner, repo, opts)\n      fetch_json(\"https://api.github.com/repos/#{owner}/#{repo}/tags\", opts)\n    end\n\n    def get_github_file_contents(owner, repo, path, opts)\n      json = fetch_json(\"https://api.github.com/repos/#{owner}/#{repo}/contents/#{path}\", opts)\n      Base64.decode64(json['content'])\n    end\n\n    def get_latest_github_commit_date(owner, repo, opts)\n      commits = fetch_json(\"https://api.github.com/repos/#{owner}/#{repo}/commits\", opts)\n      timestamp = commits[0]['commit']['author']['date']\n      Date.iso8601(timestamp).to_time.to_i\n    end\n\n    def get_gitlab_tags(hostname, group, project, opts)\n      fetch_json(\"https://#{hostname}/api/v4/projects/#{group}%2F#{project}/repository/tags\", opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/core/entry_index.rb",
    "content": "require 'yajl/json_gem'\n\nmodule Docs\n  class EntryIndex\n    attr_reader :entries, :types\n\n    def initialize\n      @entries = []\n      @index = Set.new\n      @types = Hash.new { |hash, key| hash[key] = Type.new key }\n    end\n\n    def add(entry)\n      if entry.is_a? Array\n        entry.each(&method(:add))\n      else\n        add_entry(entry) unless entry.root?\n      end\n    end\n\n    def empty?\n      @entries.empty?\n    end\n\n    alias_method :blank?, :empty?\n\n    def length\n      @entries.length\n    end\n\n    def as_json\n      { entries: entries_as_json, types: types_as_json }\n    end\n\n    def to_json\n      JSON.generate(as_json)\n    end\n\n    private\n\n    def add_entry(entry)\n      if @index.add?(entry.as_json.to_s)\n        @entries << entry.dup\n        @types[entry.type].count += 1 if entry.type\n      end\n    end\n\n    def entries_as_json\n      @entries.sort! { |a, b| sort_fn(a.name, b.name) }.map(&:as_json)\n    end\n\n    def types_as_json\n      @types.values.sort! { |a, b| sort_fn(a.name, b.name) }.map(&:as_json)\n    end\n\n    SPLIT_INTS = /(?<=\\d)\\.(?=[\\s\\d])/.freeze\n\n    def sort_fn(a, b)\n      if (a.getbyte(0) >= 49 && a.getbyte(0) <= 57) || (b.getbyte(0) >= 49 && b.getbyte(0) <= 57)\n        a_split = a.split(SPLIT_INTS)\n        b_split = b.split(SPLIT_INTS)\n\n        a_length = a_split.length\n        b_length = b_split.length\n\n        return a.casecmp(b) if a_length == 1 && b_length == 1\n        return 1 if a_length == 1\n        return -1 if b_length == 1\n\n        a_split.each_with_index { |s, i| a_split[i] = s.to_i unless i == a_length - 1 }\n        b_split.each_with_index { |s, i| b_split[i] = s.to_i unless i == b_length - 1 }\n\n        if b_length > a_length\n          (b_length - a_length).times { a_split.insert(-2, 0) }\n        elsif a_length > b_length\n          (a_length - b_length).times { b_split.insert(-2, 0) }\n        end\n\n        a_split <=> b_split\n      else\n        a.casecmp(b)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/core/filter.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class Filter < ::HTML::Pipeline::Filter\n    def css(*args)\n      doc.css(*args)\n    end\n\n    def at_css(*args)\n      doc.at_css(*args)\n    end\n\n    def xpath(*args)\n      doc.xpath(*args)\n    end\n\n    def at_xpath(*args)\n      doc.at_xpath(*args)\n    end\n\n    def base_url\n      context[:base_url]\n    end\n\n    def links\n      context[:links]\n    end\n\n    def current_url\n      context[:url]\n    end\n\n    def root_url\n      context[:root_url]\n    end\n\n    def root_path\n      context[:root_path]\n    end\n\n    def version\n      context[:version]\n    end\n\n    def release\n      context[:release]\n    end\n\n    def subpath\n      @subpath ||= subpath_to(current_url)\n    end\n\n    def subpath_to(url)\n      base_url.subpath_to url, ignore_case: true\n    end\n\n    def slug\n      @slug ||= subpath.sub(/\\A\\//, '').remove(/\\.html\\z/)\n    end\n\n    def root_page?\n      subpath.blank? || subpath == '/' || subpath == root_path\n    end\n\n    def initial_page?\n      root_page? || context[:initial_paths].include?(subpath)\n    end\n\n    SCHEME_RGX = /\\A[^:\\/?#]+:/\n\n    def fragment_url_string?(str)\n      str[0] == '#'\n    end\n\n    DATA_URL = 'data:'.freeze\n\n    def data_url_string?(str)\n      str.start_with?(DATA_URL)\n    end\n\n    def relative_url_string?(str)\n      str !~ SCHEME_RGX && !fragment_url_string?(str) && !data_url_string?(str)\n    end\n\n    def absolute_url_string?(str)\n      str =~ SCHEME_RGX\n    end\n\n    def parse_html(html)\n      warn \"#{self.class.name} is re-parsing the document\" unless ENV['RACK_ENV'] == 'test'\n      super\n    end\n\n    def clean_path(path)\n      path = path.gsub %r{[!;:]}, '-'\n      path = path.gsub %r{\\+}, '_plus_'\n      path\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/core/filter_stack.rb",
    "content": "module Docs\n  class FilterStack\n    extend Forwardable\n    def_delegators :@filters, :length, :inspect\n\n    attr_reader :filters\n\n    def initialize(filters = nil)\n      @filters = filters ? filters.dup : []\n    end\n\n    def push(*names)\n      @filters.push *filter_const(names)\n    end\n\n    def insert(index, *names)\n      @filters.insert assert_index(index), *filter_const(names)\n    end\n\n    alias_method :insert_before, :insert\n\n    def insert_after(index, *names)\n      insert assert_index(index) + 1, *names\n    end\n\n    def replace(index, name)\n      @filters[assert_index(index)] = filter_const(name)\n    end\n\n    def ==(other)\n      other.is_a?(self.class) && filters == other.filters\n    end\n\n    def to_a\n      @filters.dup\n    end\n\n    def inheritable_copy\n      self.class.new @filters\n    end\n\n    private\n\n    def filter_const(name)\n      if name.is_a? Array\n        name.map &method(:filter_const)\n      else\n        Docs.const_get \"#{name}_filter\".camelize\n      end\n    end\n\n    def assert_index(index)\n      i = index.is_a?(Integer) ? index : @filters.index(filter_const(index))\n      raise \"No such filter to insert: #{index}\" unless i\n      i\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/core/instrumentable.rb",
    "content": "require 'active_support/notifications'\n\nmodule Docs\n  module Instrumentable\n    def self.extended(base)\n      base.send :extend, Methods\n    end\n\n    def self.included(base)\n      base.send :include, Methods\n    end\n\n    module Methods\n      def instrument(*args, &block)\n        ActiveSupport::Notifications.instrument(*args, &block)\n      end\n\n      def subscribe(*args, &block)\n        ActiveSupport::Notifications.subscribe(*args, &block)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/core/manifest.rb",
    "content": "require 'yajl/json_gem'\n\nmodule Docs\n  class Manifest\n    FILENAME = 'docs.json'\n\n    def initialize(store, docs)\n      @store = store\n      @docs = docs\n    end\n\n    def store\n      @store.write FILENAME, to_json\n    end\n\n    def as_json\n      @docs.each_with_object [] do |doc, result|\n        next unless @store.exist?(doc.meta_path)\n        json = JSON.parse(@store.read(doc.meta_path))\n        if doc.options[:attribution].is_a?(String)\n          json[:attribution] = doc.options[:attribution].strip\n        end\n        json[:alias] = Docs.aliases[doc.slug[/^[^~]+/, 0]]\n        result << json\n      end\n    end\n\n    def to_json\n      JSON.pretty_generate(as_json)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/core/models/entry.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class Entry\n    class Invalid < StandardError; end\n\n    attr_accessor :name, :type, :path\n\n    def initialize(name = nil, path = nil, type = nil)\n      self.name = name\n      self.path = path\n      self.type = type\n\n      unless root?\n        raise Invalid, 'missing name' if !name || name.empty?\n        raise Invalid, 'missing path' if !path || path.empty?\n        raise Invalid, 'missing type' if !type || type.empty?\n      end\n    end\n\n    def ==(other)\n      other.name == name && other.path == path && other.type == type\n    end\n\n    def name=(value)\n      @name = value.try :strip\n    end\n\n    def type=(value)\n      @type = value.try :strip\n    end\n\n    def root?\n      path == 'index'\n    end\n\n    def as_json\n      { name: name, path: path, type: type }\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/core/models/type.rb",
    "content": "module Docs\n  Type = Struct.new :name, :count do\n    attr_accessor :slug\n\n    def initialize(*args)\n      super\n      self.count ||= 0\n    end\n\n    def slug\n      name.parameterize\n    end\n\n    def as_json\n      to_h.merge! slug: slug\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/core/page_db.rb",
    "content": "require 'yajl/json_gem'\n\nmodule Docs\n  class PageDb\n    attr_reader :pages\n\n    delegate :empty?, :blank?, to: :pages\n\n    def initialize\n      @pages = {}\n    end\n\n    def add(path, content)\n      @pages[path] = content\n    end\n\n    def as_json\n      @pages\n    end\n\n    def to_json\n      JSON.generate(as_json)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/core/parser.rb",
    "content": "module Docs\n  class Parser\n    attr_reader :title, :html\n\n    def initialize(content)\n      @content = content\n      @html = document? ? parse_as_document : parse_as_fragment\n    end\n\n    private\n\n    DOCUMENT_RGX = /\\A(?:\\s|(?:<!--.*?-->))*<(?:\\!doctype|html)/i\n\n    def document?\n      @content =~ DOCUMENT_RGX\n    end\n\n    def parse_as_document\n      document = Nokogiri::HTML.parse @content, nil, 'UTF-8'\n      @title = document.at_css('title').try(:content)\n      document\n    end\n\n    def parse_as_fragment\n      Nokogiri::HTML.fragment @content, 'UTF-8'\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/core/request.rb",
    "content": "module Docs\n  class Request < Typhoeus::Request\n    include Instrumentable\n\n    DEFAULT_OPTIONS = {\n      followlocation: true,\n      headers: { 'User-Agent' => 'DevDocs' }\n    }\n\n    def self.run(*args, &block)\n      request = new(*args)\n      request.on_complete(&block) if block\n      request.run\n    end\n\n    def initialize(url, options = {})\n      super url.to_s, DEFAULT_OPTIONS.merge(options)\n    end\n\n    def response=(value)\n      value.extend Response if value\n      super\n    end\n\n    def run\n      instrument 'response.request', url: base_url do |payload|\n        response = super\n        payload[:response] = response\n        response\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/core/requester.rb",
    "content": "module Docs\n  class Requester < Typhoeus::Hydra\n    include Instrumentable\n\n    attr_reader :request_options\n\n    def self.run(urls, options = {}, &block)\n      urls = urls.dup\n      requester = new(options)\n      requester.on_response(&block) if block_given?\n      requester.on_response do # cheap hack to ensure root page is processed first\n        if urls\n          requester.request(urls)\n          urls = nil\n        end\n      end\n      requester.request(urls.shift)\n      requester.run\n      requester\n    end\n\n    def initialize(options = {})\n      @request_options = options.extract!(:request_options)[:request_options].try(:dup) || {}\n      options[:max_concurrency] ||= 20\n      options[:pipelining] = 0\n      super\n    end\n\n    def request(urls, options = {}, &block)\n      requests = [urls].flatten.map do |url|\n        build_and_queue_request(url, options, &block)\n      end\n      requests.length == 1 ? requests.first : requests\n    end\n\n    def queue(request)\n      request.on_complete(&method(:handle_response))\n      super\n    end\n\n    def on_response(&block)\n      @on_response ||= []\n      @on_response << block if block\n      @on_response\n    end\n\n    private\n\n    def build_and_queue_request(url, options = {}, &block)\n      request = Request.new(url, **request_options.merge(options))\n      request.on_complete(&block) if block\n      queue(request)\n      request\n    end\n\n    def handle_response(response)\n      if ENV['RETRY'] == '1' && [0, 500, 501, 502, 503, 504].include?(response.code.to_i)\n        instrument 'handle_response.retry', url: response.url do\n          build_and_queue_request(response.url)\n        end\n        return\n      end\n      instrument 'handle_response.requester', url: response.url do\n        on_response.each do |callback|\n          result = callback.call(response)\n          result.each { |url| request(url) } if result.is_a?(Array)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/core/response.rb",
    "content": "module Docs\n  module Response\n    def success?\n      code == 200\n    end\n\n    def error?\n      code == 0 || code != 404 && code != 403 && code >= 400 && code <= 599\n    end\n\n    def blank?\n      body.blank?\n    end\n\n    def content_length\n      value = headers['Content-Length'] || '0'\n      value.to_i\n    end\n\n    def mime_type\n      headers['Content-Type'] || 'text/plain'\n    end\n\n    def html?\n      mime_type.include? 'html'\n    end\n\n    def url\n      @url ||= URL.parse request.base_url\n    end\n\n    def path\n      @path ||= url.path\n    end\n\n    def effective_url\n      @effective_url ||= URL.parse super\n    end\n\n    def effective_path\n      @effective_path ||= effective_url.path\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/core/scraper.rb",
    "content": "require 'set'\n\nmodule Docs\n  class Scraper < Doc\n    class << self\n      attr_accessor :base_url, :root_path, :initial_paths, :options, :html_filters, :text_filters, :stubs\n\n      def inherited(subclass)\n        super\n\n        subclass.class_eval do\n          extend AutoloadHelper\n          autoload_all \"docs/filters/#{to_s.demodulize.underscore}\", 'filter'\n        end\n\n        subclass.base_url = base_url\n        subclass.root_path = root_path\n        subclass.initial_paths = initial_paths.dup\n        subclass.options = options.deep_dup\n        subclass.html_filters = html_filters.inheritable_copy\n        subclass.text_filters = text_filters.inheritable_copy\n        subclass.stubs = stubs.dup\n      end\n\n      def filters\n        html_filters.to_a + text_filters.to_a\n      end\n\n      def stub(path, &block)\n        @stubs[path] = block\n        @stubs\n      end\n    end\n\n    include Instrumentable\n\n    self.initial_paths = []\n    self.options = {}\n    self.stubs = {}\n\n    self.html_filters = FilterStack.new\n    self.text_filters = FilterStack.new\n\n    html_filters.push 'apply_base_url', 'container', 'clean_html', 'normalize_urls', 'internal_urls', 'normalize_paths', 'parse_cf_email'\n    text_filters.push 'images' # ensure the images filter runs after all html filters\n    text_filters.push 'inner_html', 'clean_text', 'attribution'\n\n    def initialize\n      super\n      initialize_stubs\n    end\n\n    def initialize_stubs\n      self.class.stubs.each do |path, &block|\n        Typhoeus.stub(url_for(path)).and_return do\n          Typhoeus::Response.new \\\n            effective_url: url_for(path),\n            code: 200,\n            headers: { 'Content-Type' => 'text/html' },\n            body: self.instance_exec(&block)\n        end\n      end\n    end\n\n    def build_page(path)\n      response = request_one url_for(path)\n      result = handle_response(response)\n      yield result if block_given?\n      result\n    end\n\n    def build_pages\n      history = Set.new initial_urls.map(&:downcase)\n      instrument 'running.scraper', urls: initial_urls\n\n      request_all initial_urls do |response|\n        next unless data = handle_response(response)\n        yield data\n        next unless data[:internal_urls].present?\n        next_urls = data[:internal_urls].select { |url| history.add?(url.downcase) }\n        instrument 'queued.scraper', urls: next_urls\n        next_urls\n      end\n    end\n\n    def base_url\n      @base_url ||= URL.parse self.class.base_url\n    end\n\n    def root_url\n      @root_url ||= root_path? ? URL.parse(File.join(base_url.to_s, root_path)) : base_url.normalize\n    end\n\n    def root_path\n      self.class.root_path\n    end\n\n    def root_path?\n      root_path.present? && root_path != '/'\n    end\n\n    def initial_paths\n      self.class.initial_paths\n    end\n\n    def initial_urls\n      @initial_urls ||= [root_url.to_s].concat(initial_paths.map(&method(:url_for))).freeze\n    end\n\n    def pipeline\n      @pipeline ||= ::HTML::Pipeline.new(self.class.filters).tap do |pipeline|\n        pipeline.instrumentation_service = Docs\n      end\n    end\n\n    def options\n      @options ||= self.class.options.deep_dup.tap do |options|\n        options.merge! base_url: base_url, root_url: root_url,\n                       root_path: root_path, initial_paths: initial_paths,\n                       version: self.class.version, release: self.class.release\n\n        if root_path?\n          (options[:skip] ||= []).concat ['', '/']\n        end\n\n        if options[:only] || options[:only_patterns]\n          (options[:only] ||= []).concat initial_paths + (root_path? ? [root_path] : ['', '/'])\n        end\n\n        options.merge!(additional_options)\n        options.freeze\n      end\n    end\n\n    private\n\n    def request_one(url)\n      raise NotImplementedError\n    end\n\n    def request_all(url, &block)\n      raise NotImplementedError\n    end\n\n    def process_response?(response)\n      raise NotImplementedError\n    end\n\n    def url_for(path)\n      if path.empty? || path == '/'\n        root_url.to_s\n      else\n        File.join(base_url.to_s, path.to_s)\n      end\n    end\n\n    def handle_response(response)\n      if process_response?(response)\n        instrument 'process_response.scraper', response: response do\n          process_response(response)\n        end\n      else\n        instrument 'ignore_response.scraper', response: response\n      end\n    rescue => e\n      if Docs.rescue_errors\n        instrument 'error.doc', exception: e, url: response.url\n        nil\n      else\n        raise e\n      end\n    end\n\n    def process_response(response)\n      data = {}\n      html, title = parse(response)\n      context = pipeline_context(response)\n      context[:html_title] = title\n      pipeline.call(html, context, data)\n      data\n    end\n\n    def pipeline_context(response)\n      options.merge url: response.url\n    end\n\n    def parse(response)\n      parser = Parser.new(response.body)\n      [parser.html, parser.title]\n    end\n\n    def with_filters(*filters)\n      stack = FilterStack.new\n      stack.push(*filters)\n      pipeline.instance_variable_set :@filters, stack.to_a.freeze\n      yield\n    ensure\n      @pipeline = nil\n    end\n\n    def additional_options\n      {}\n    end\n\n    module FixInternalUrlsBehavior\n      def self.included(base)\n        base.extend ClassMethods\n      end\n\n      def self.prepended(base)\n        class << base\n          prepend ClassMethods\n        end\n      end\n\n      module ClassMethods\n        def internal_urls\n          @internal_urls\n        end\n\n        def store_pages(store)\n          instrument 'info.doc', msg: 'Building internal urls...'\n          with_internal_urls do\n            instrument 'info.doc', msg: 'Continuing...'\n            super\n          end\n        end\n\n        private\n\n        def with_internal_urls\n          @internal_urls = new.fetch_internal_urls\n          yield\n        ensure\n          @internal_urls = nil\n        end\n      end\n\n      def fetch_internal_urls\n        result = []\n        build_pages do |page|\n          result << page[:subpath] if page[:entries].present?\n        end\n        result\n      end\n\n      def initial_urls\n        return super unless self.class.internal_urls\n        @initial_urls ||= self.class.internal_urls.map(&method(:url_for)).freeze\n      end\n\n      private\n\n      def additional_options\n        if self.class.internal_urls\n          super.merge! \\\n            only: self.class.internal_urls.to_set,\n            only_patterns: nil,\n            skip: nil,\n            skip_patterns: nil,\n            skip_links: nil,\n            fixed_internal_urls: true\n        else\n          super\n        end\n      end\n\n      def process_response(response)\n        super.merge! response_url: response.url\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/core/scrapers/file_scraper.rb",
    "content": "module Docs\n  class FileScraper < Scraper\n    SOURCE_DIRECTORY = File.expand_path '../../../../../docs', __FILE__\n\n    Response = Struct.new :body, :url\n\n    class << self\n      def inherited(subclass)\n        super\n        subclass.base_url = base_url\n      end\n    end\n\n    self.base_url = 'http://localhost/'\n\n    html_filters.push 'clean_local_urls'\n\n    def source_directory\n      @source_directory ||= File.join(SOURCE_DIRECTORY, self.class.path)\n    end\n\n    private\n\n    def assert_source_directory_exists\n      unless Dir.exist?(source_directory)\n        raise SetupError, \"The #{self.class.name} scraper requires the original documentation files to be stored in the \\\"#{source_directory}\\\" directory.\"\n      end\n    end\n\n    def request_one(url)\n      assert_source_directory_exists\n      Response.new read_file(File.join(source_directory, url_to_path(url))), URL.parse(url)\n    end\n\n    def request_all(urls)\n      assert_source_directory_exists\n      queue = [urls].flatten\n      until queue.empty?\n        result = yield request_one(queue.shift)\n        queue.concat(result) if result.is_a? Array\n      end\n    end\n\n    def process_response?(response)\n      response.body.present?\n    end\n\n    def url_to_path(url)\n      url.remove(base_url.to_s)\n    end\n\n    def read_file(path)\n      File.read(path)\n    rescue\n      instrument 'warn.doc', msg: \"Failed to open file: #{path}\"\n      nil\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/core/scrapers/url_scraper.rb",
    "content": "module Docs\n  class UrlScraper < Scraper\n    class << self\n      attr_accessor :params\n      attr_accessor :headers\n      attr_accessor :force_gzip\n\n      def inherited(subclass)\n        super\n        subclass.params = params.deep_dup\n        subclass.headers = headers.deep_dup\n        subclass.force_gzip = force_gzip\n      end\n    end\n\n    @@rate_limiter = nil\n\n    self.params = {}\n    self.headers = { 'User-Agent' => 'DevDocs' }\n    self.force_gzip = false\n\n    private\n\n    def request_one(url)\n      Request.run url, request_options\n    end\n\n    def request_all(urls, &block)\n      if options[:rate_limit]\n        if @@rate_limiter\n          @@rate_limiter.limit = options[:rate_limit]\n        else\n          @@rate_limiter = RateLimiter.new(options[:rate_limit])\n          Typhoeus.before(&@@rate_limiter.to_proc)\n        end\n      end\n\n      Requester.run urls, request_options: request_options, &block\n    end\n\n    def request_options\n      options = { params: self.class.params, headers: self.class.headers }\n      options[:accept_encoding] = 'gzip' if self.class.force_gzip\n      options\n    end\n\n    def process_response?(response)\n      if response.error?\n        raise <<~ERROR\n          Error status code (#{response.code}): #{response.return_message}\n          #{response.url}\n          #{JSON.pretty_generate(response.headers).slice(2..-3)}\n        ERROR\n      elsif response.blank?\n        raise \"Empty response body: #{response.url}\"\n      end\n\n      response.success? && response.html? && process_url?(response.effective_url)\n    end\n\n    def process_url?(url)\n      base_url.contains?(url)\n    end\n\n    def load_capybara_selenium\n      require 'capybara/dsl'\n      require 'selenium/webdriver'\n      Capybara.register_driver :chrome do |app|\n        options = Selenium::WebDriver::Chrome::Options.new(args: %w[headless disable-gpu])\n        Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)\n      end\n      Capybara.javascript_driver = :chrome\n      Capybara.current_driver = :chrome\n      Capybara.run_server = false\n      Capybara\n    end\n\n    module MultipleBaseUrls\n      def self.included(base)\n        base.extend ClassMethods\n      end\n\n      module ClassMethods\n        attr_reader :base_urls\n\n        def base_urls=(urls)\n          self.base_url = urls.first\n          @base_urls = urls\n        end\n      end\n\n      def initial_urls\n        super + self.class.base_urls[1..-1].deep_dup\n      end\n\n      def base_urls\n        @base_urls ||= self.class.base_urls.map { |url| URL.parse(url) }\n      end\n\n      private\n\n      def process_url?(url)\n        base_urls.any? { |base_url| base_url.contains?(url) }\n      end\n\n      def process_response(response)\n        original_scheme = self.base_url.scheme\n        original_host = self.base_url.host\n        original_path = self.base_url.path\n\n        effective_base_url = self.base_urls.find { |base_url| base_url.contains?(response.effective_url) }\n\n        self.base_url.scheme = effective_base_url.scheme\n        self.base_url.host = effective_base_url.host\n        self.base_url.path = effective_base_url.path\n        super\n      ensure\n        self.base_url.scheme = original_scheme\n        self.base_url.host = original_host\n        self.base_url.path = original_path\n      end\n    end\n\n    module FixRedirectionsBehavior\n      def self.included(base)\n        base.extend ClassMethods\n      end\n\n      def self.prepended(base)\n        class << base\n          prepend ClassMethods\n        end\n      end\n\n      module ClassMethods\n        def redirections\n          @redirections\n        end\n\n        def store_pages(store)\n          instrument 'info.doc', msg: 'Fetching redirections...'\n          with_redirections do\n            instrument 'info.doc', msg: 'Continuing...'\n            super\n          end\n        end\n\n        private\n\n        def with_redirections\n          @redirections = new.fetch_redirections\n          yield\n        ensure\n          @redirections = nil\n        end\n      end\n\n      def fetch_redirections\n        result = {}\n        with_filters 'apply_base_url', 'container', 'normalize_urls', 'internal_urls' do\n          build_pages do |page|\n            next if page[:response_effective_path] == page[:response_path]\n            result[page[:response_path].downcase] = page[:response_effective_path]\n          end\n        end\n        result\n      end\n\n      private\n\n      def process_response(response)\n        super.merge! response_effective_path: response.effective_path, response_path: response.path\n      end\n\n      def additional_options\n        super.merge! redirections: self.class.redirections\n      end\n    end\n\n    class RateLimiter\n      attr_accessor :limit\n\n      def initialize(limit)\n        @limit = limit\n        @minute = nil\n        @counter = 0\n      end\n\n      def call(*)\n        if @minute != Time.now.min\n          @minute = Time.now.min\n          @counter = 0\n        end\n\n        @counter += 1\n\n        if @counter >= @limit\n          wait = Time.now.end_of_minute.to_i - Time.now.to_i + 1\n          sleep wait\n        end\n\n        true\n      end\n\n      def to_proc\n        method(:call).to_proc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/core/subscriber.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'active_support/subscriber'\n\nmodule Docs\n  class Subscriber < ActiveSupport::Subscriber\n    cattr_accessor :namespace\n\n    def self.subscribe_to(notifier)\n      attach_to(namespace, new, notifier)\n    end\n\n    private\n\n    delegate :puts, :print, :tty?, to: :$stdout\n\n    def log(msg)\n      puts \"\\r\" + justify(msg)\n    end\n\n    def format_url(url)\n      url.to_s.remove %r{\\Ahttps?://}\n    end\n\n    def format_path(path)\n      path.to_s.remove File.join(File.expand_path('.'), '')\n    end\n\n    def justify(str)\n      return str unless terminal_width\n      str = str.dup\n\n      max_length = if tag = str.slice!(/ \\[.+\\]\\z/)\n        terminal_width - tag.length\n      else\n        terminal_width\n      end\n\n      str.truncate(max_length).ljust(max_length) << tag.to_s\n    end\n\n    def terminal_width\n      return @terminal_width if defined? @terminal_width\n\n      @terminal_width = if !tty?\n        nil\n      elsif ENV['COLUMNS']\n        ENV['COLUMNS'].to_i\n      else\n        `stty size`.scan(/\\d+/).last.to_i\n      end\n    rescue\n      @terminal_width = nil\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/core/url.rb",
    "content": "require 'uri'\nrequire 'pathname'\n\nmodule Docs\n  class URL < URI::Generic\n    PARSER = URI::Parser.new\n\n    def initialize(*args)\n      if args.empty?\n        super(*Array.new(9))\n      elsif args.length == 1 && args.first.is_a?(Hash)\n        args.first.assert_valid_keys URI::Generic::COMPONENT\n        super(*args.first.values_at(*URI::Generic::COMPONENT))\n      else\n        super\n      end\n    end\n\n    def self.parse(url)\n      return url if url.kind_of? self\n      new(*PARSER.split(url), PARSER)\n    end\n\n    def self.join(*args)\n      PARSER.join(*args)\n    end\n\n    def join(*args)\n      self.class.join self, *args\n    end\n\n    def merge!(hash)\n      return super unless hash.is_a? Hash\n      hash.assert_valid_keys URI::Generic::COMPONENT\n      hash.each_pair do |key, value|\n        send \"#{key}=\", value\n      end\n      self\n    end\n\n    def merge(hash)\n      return super unless hash.is_a? Hash\n      dup.merge!(hash)\n    end\n\n    def origin\n      if scheme && host\n        origin = \"#{scheme}://#{host}\"\n        origin.downcase!\n        origin << \":#{port}\" if port\n        origin\n      else\n        nil\n      end\n    end\n\n    def normalized_path\n      path == '' ? '/' : path\n    end\n\n    def subpath_to(url, options = nil)\n      url = self.class.parse(url)\n      return unless origin == url.origin\n\n      base = path\n      dest = url.path\n\n      if options && options[:ignore_case]\n        base = base.downcase\n        dest = dest.downcase\n      end\n\n      if base == dest\n        ''\n      elsif dest.start_with?(::File.join(base, ''))\n        url.path[(path.length)..-1]\n      end\n    end\n\n    def subpath_from(url, options = nil)\n      self.class.parse(url).subpath_to(self, options)\n    end\n\n    def contains?(url, options = nil)\n      !!subpath_to(url, options)\n    end\n\n    def relative_path_to(url)\n      url = self.class.parse(url)\n      return unless origin == url.origin\n\n      base_dir = Pathname.new(normalized_path)\n      base_dir = base_dir.parent unless path.end_with? '/'\n\n      dest = url.normalized_path\n      dest_dir = Pathname.new(dest)\n\n      if dest.end_with? '/'\n        dest_dir.relative_path_from(base_dir).to_s.tap do |result|\n          result << '/' if result != '.'\n        end\n      else\n        dest_dir.parent.relative_path_from(base_dir).join(dest.split('/').last).to_s\n      end\n    end\n\n    def relative_path_from(url)\n      self.class.parse(url).relative_path_to(self)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/angular/clean_html.rb",
    "content": "module Docs\n  class Angular\n    class CleanHtmlFilter < Filter\n      def call\n        if root_page?\n          css('.card-container').remove\n          at_css('h1').content = 'Angular Documentation'\n        end\n\n        css('br', 'hr', '.material-icons', '.header-link', '.breadcrumb').remove\n\n        css('.content', 'article', '.api-header', 'section', '.instance-member').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('label', 'h2 > em', 'h3 > em').each do |node|\n          node.name = 'code'\n        end\n\n        css('h1 + code').each do |node|\n          node.before('<p></p>')\n          while node.next_element.name == 'code'\n            node.previous_element << ' '\n            node.previous_element << node.next_element\n          end\n          node.previous_element.prepend_child(node)\n        end\n\n        css('td h3', '.l-sub-section > h3', '.alert h3', '.row-margin > h3', '.api-heading ~ h3', '.api-heading + h2', '.metadata-member h3').each do |node|\n          node.name = 'h4'\n        end\n\n        css('.l-sub-section', '.alert', '.banner').each do |node|\n          node.name = 'blockquote'\n        end\n\n        css('.file').each do |node|\n          node.content = node.content.strip\n        end\n\n        css('.filetree .children').each do |node|\n          node.css('.file').each do |n|\n            n.content = \"  #{n.content}\"\n          end\n        end\n\n        css('.filetree').each do |node|\n          node.content = node.css('.file').map(&:inner_html).join(\"\\n\")\n          node.name = 'pre'\n          node.remove_attribute('class')\n        end\n\n        css('pre').each do |node|\n          node.content = node.content.strip\n\n          node['data-language'] = 'typescript' if node['path'].try(:ends_with?, '.ts')\n          node['data-language'] = 'html' if node['path'].try(:ends_with?, '.html')\n          node['data-language'] = 'css' if node['path'].try(:ends_with?, '.css')\n          node['data-language'] = 'js' if node['path'].try(:ends_with?, '.js')\n          node['data-language'] = 'json' if node['path'].try(:ends_with?, '.json')\n          node['data-language'] = node['language'].sub(/\\Ats/, 'typescript').strip if node['language']\n          node['data-language'] ||= 'typescript' if node.content.start_with?('@')\n\n          node.before(%(<div class=\"pre-title\">#{node['title']}</div>)) if node['title']\n\n          if node['class'] && node['class'].include?('api-heading')\n            node.name = 'h3'\n            node.inner_html = \"<code>#{node.inner_html}</code>\"\n          end\n\n          node.remove_attribute('path')\n          node.remove_attribute('region')\n          node.remove_attribute('linenums')\n          node.remove_attribute('title')\n          node.remove_attribute('language')\n          node.remove_attribute('hidecopy')\n          node.remove_attribute('class')\n        end\n\n        css('h1[class]').remove_attr('class')\n        css('table[class]').remove_attr('class')\n        css('table[width]').remove_attr('width')\n        css('tr[style]').remove_attr('style')\n\n        if at_css('.api-type-label.module')\n          at_css('h1').content = subpath.remove('api/')\n        end\n\n        css('th h3').each do |node|\n          node.name = 'span'\n        end\n\n        css('code code').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('details.overloads > summary').each do |node|\n          node.css('.actions').remove\n          node.content = node.content\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/angular/clean_html_v18.rb",
    "content": "module Docs\n  class Angular\n    class CleanHtmlV18Filter < Filter\n      def call\n        @doc = at_css('.docs-viewer') if at_css('.docs-viewer')\n\n        # Extract <h1> from decorative header.\n        @doc.prepend_child(at_css('h1'))\n        css('h1[class]').remove_attr('class')\n\n        css(\n          '.docs-breadcrumb',\n          '.docs-github-links',\n          'docs-table-of-contents',\n          '.docs-reference-category',\n          '.docs-reference-title',\n          '#jump-msg'\n        ).remove\n\n        # Strip anchor links from headers.\n        css('h2', 'h3', 'h4').each do |node|\n          node.content = node.inner_text\n        end\n\n        # Make every <code> block a <pre>.\n        css('.docs-code > pre > code').each do |code|\n          code.name = 'pre'\n          code['data-language'] = 'ts'\n          code.content = code.css('.line').map(&:content).join(\"\\n\")\n          code.parent.parent.replace(code)\n        end\n\n        # Better format content in CLI reference.\n        css('.docs-ref-content').each do |ref|\n          option = ref.at_css('.docs-reference-option code')\n          option.name = 'h3'\n          option.parent.replace(option)\n        end\n\n        css('.docs-reference-type-and-default', '.docs-reference-option-aliases').each do |node|\n          labels = node.css('span')\n          values = node.css('code')\n          labels.each do |l|\n            l.name = 'h4'\n          end\n        end\n\n        css('footer').remove\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/angular/clean_html_v2.rb",
    "content": "module Docs\n  class Angular\n    class CleanHtmlV2Filter < Filter\n      def call\n        container = at_css('article.docs-content')\n        badges = css('header.hero .badge, header.hero .hero-subtitle').map do |node|\n          node.name = 'span'\n          node['class'] = 'status-badge'\n          node.to_html\n        end.join(' ')\n        badges = %(<div class=\"badges\">#{badges}</div>)\n        container.child.before(at_css('header.hero h1')).before(badges).before(css('header.hero + .banner, header.hero .breadcrumbs'))\n        @doc = container\n\n        title = at_css('h1').content.strip\n        if root_page?\n          at_css('h1').content = 'Angular 2 Documentation'\n        elsif title == 'Index'\n          at_css('h1').content = result[:entries].first.name\n        elsif title == 'Angular'\n          at_css('h1').content = slug.split('/').last.gsub('-', ' ')\n        elsif at_css('.breadcrumbs') && title != result[:entries].first.name\n          at_css('h1').content = result[:entries].first.name\n        end\n\n        css('pre.no-bg-with-indent').each do |node|\n          node.content = '  ' + node.content.gsub(\"\\n\", \"\\n  \")\n        end\n\n        css('.openParens').each do |node|\n          node.parent.name = 'pre'\n          node.parent.content = node.parent.css('code, pre').map(&:content).join(\"\\n\")\n        end\n\n        css('button.verbose', 'button.verbose + .l-verbose-section', 'a[id=top]', 'a[href=\"#top\"]', '.sidebar', 'br').remove\n\n        css('.c10', '.showcase', '.showcase-content', '.l-main-section', 'div.div', 'div[flex]', 'code-tabs', 'md-card', 'md-card-content', 'div:not([class])', 'footer', '.card-row', '.card-row-container', 'figure', 'blockquote', 'exported', 'defined', 'div.ng-scope', '.code-example header', 'section.desc', '.row', '.dart-api-entry-main', '.main-content', 'section.summary', 'span.signature').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('span.badges').each do |node|\n          node.name = 'div'\n        end\n\n        css('pre[language]').each do |node|\n          node['data-language'] = node['language'].sub(/\\Ats/, 'typescript').strip\n          node['data-language'] = 'html' if node.content.start_with?('<')\n          node.remove_attribute('language')\n          node.remove_attribute('format')\n        end\n\n        css('pre.prettyprint').each do |node|\n          node.content = node.content.strip\n          node['data-language'] = 'dart' if node['class'].include?('dart')\n          node['data-language'] = 'html' if node.content.start_with?('<')\n          node.remove_attribute('class')\n        end\n\n        css('.multi-line-signature').each do |node|\n          node.name = 'pre'\n          node.content = node.content.strip\n        end\n\n        css('a[id]:empty').each do |node|\n          node.next_element['id'] = node['id'] if node.next_element\n        end\n\n        css('a[name]:empty').each do |node|\n          node.next_element['id'] = node['name'] if node.next_element\n        end\n\n        css('tr[style]').each do |node|\n          node.remove_attribute 'style'\n        end\n\n        css('h1:not(:first-child)').each do |node|\n          node.name = 'h2'\n        end unless at_css('h2')\n\n        css('img[style]').each do |node|\n          node['align'] ||= node['style'][/float:\\s*(left|right)/, 1]\n          node['style'] = node['style'].split(';').map(&:strip).select { |s| s =~ /\\Awidth|height/ }.join(';')\n        end\n\n        css('.example-title + pre').each do |node|\n          node['name'] = node.previous_element.content.strip\n          node.previous_element.remove\n        end\n\n        css('pre[name]').each do |node|\n          node.before(%(<div class=\"pre-title\">#{node['name']}</div>))\n        end\n\n        css('a.is-button > h3').each do |node|\n          node.parent.content = node.content\n        end\n\n        css('#angular-2-glossary ~ .l-sub-section').each do |node|\n          node.before(node.children).remove\n        end\n\n        location_badge = at_css('.location-badge')\n        if location_badge && doc.last_element_child != location_badge\n          doc.last_element_child.after(location_badge)\n        end\n\n        css('.filetree .children').each do |node|\n          node.css('.file').each do |n|\n            n.content = \"  #{n.content}\"\n          end\n        end\n\n        css('.filetree').each do |node|\n          node.content = node.css('.file').map(&:inner_html).join(\"\\n\")\n          node.name = 'pre'\n          node.remove_attribute('class')\n        end\n\n        css('.status-badge').each do |node|\n          node.name = 'code'\n          node.content = node.content.strip\n          node.remove_attribute('class')\n        end\n\n        css('div.badges').each do |node|\n          node.name = 'p'\n        end\n\n        css('td h3', '.l-sub-section > h3', '.alert h3', '.row-margin > h3').each do |node|\n          node.name = 'h4'\n        end\n\n        css('.l-sub-section', '.alert', '.banner').each do |node|\n          node.name = 'blockquote'\n        end\n\n        css('.code-example > h4').each do |node|\n          node['class'] = 'pre-title'\n        end\n\n        css('.row-margin', '.ng-cloak').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('*[layout]').remove_attr('layout')\n        css('*[layout-xs]').remove_attr('layout-xs')\n        css('*[flex]').remove_attr('flex')\n        css('*[flex-xs]').remove_attr('flex-xs')\n        css('*[ng-class]').remove_attr('ng-class')\n        css('*[align]').remove_attr('align')\n        css('h1, h2, h3').remove_attr('class')\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/angular/entries.rb",
    "content": "module Docs\n  class Angular\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content\n        name.prepend \"#{$1}. \" if subpath =~ /\\-pt(\\d+)/\n        name\n      end\n\n      def get_type\n        if slug.start_with?('guide')\n          'Guide'\n        elsif slug.start_with?('cli')\n          'CLI'\n        elsif slug.start_with?('tutorial')\n          'Tutorial'\n        elsif slug.start_with?('api/platform-browser-dynamic')\n          'platform-browser-dynamic'\n        elsif node = at_css('th:contains(\"npm Package\")')\n          node.next_element.content.remove('@angular/')\n        elsif at_css('.api-type-label.module')\n          name.split('/').first\n        elsif slug.start_with?('api/')\n          slug.split('/').second\n        else\n          'Miscellaneous'\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/angular/entries_v2.rb",
    "content": "module Docs\n  class Angular\n    class EntriesV2Filter < Docs::EntriesFilter\n      def get_name\n        if slug.start_with?('tutorial') || slug.start_with?('guide')\n          name = at_css('.nav-list-item.is-selected, header.hero h1').content.strip\n        else\n          name = at_css('header.hero h1').content.strip\n        end\n\n        name = name.split(':').first\n\n        if mod\n          if name == 'Index'\n            return slug.split('/')[1..-2].join('/')\n          elsif name == 'Angular'\n            return slug.split('/').last.split('-').first\n          end\n        end\n\n        subtitle = at_css('.hero-subtitle').try(:content)\n        breadcrumbs = css('.breadcrumbs li').map(&:content)[2..-2]\n\n        name.prepend \"#{breadcrumbs.join('.')}#\" if breadcrumbs.present? && breadcrumbs[0] != name\n        name << '()' if %w(Function Method Constructor).include?(subtitle)\n        name\n      end\n\n      def get_type\n        if slug.start_with?('guide/')\n          'Guide'\n        elsif slug.start_with?('cookbook/')\n          'Cookbook'\n        elsif slug == 'glossary'\n          'Guide'\n        else\n          type = at_css('.nav-title.is-selected').content.strip\n          type.remove! ' Reference'\n          type << \": #{mod}\" if mod\n          type\n        end\n      end\n\n      INDEX = Set.new\n\n      def include_default_entry?\n        INDEX.add?([name, type].join(';')) ? true : false # ¯\\_(ツ)_/¯\n      end\n\n      private\n\n      def mod\n        return @mod if defined?(@mod)\n        @mod = slug[/api\\/([\\w\\-\\.]+)\\//, 1]\n        @mod.remove! 'angular2.' if @mod\n        @mod\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/angularjs/clean_html.rb",
    "content": "module Docs\n  class Angularjs\n    class CleanHtmlFilter < Filter\n      def call\n        root_page? ? root : other\n\n        # Remove ng-* attributes\n        css('*').each do |node|\n          node.attributes.each_key do |attribute|\n            node.remove_attribute(attribute) if attribute.start_with? 'ng-'\n          end\n        end\n\n        css('img[src]').each do |node|\n          node['src'] = node['src'].gsub(%r{angularjs\\.org/([\\d\\.]+)/docs/partials/(\\w+)/}, 'angularjs.org/\\1/docs/\\2/')\n        end\n\n        doc\n      end\n\n      def root\n        css('.nav-index-group').each do |node|\n          if heading = node.at_css('.nav-index-group-heading')\n            heading.name = 'h2'\n          end\n          node.parent.before(node.children)\n        end\n\n        css('.nav-index-section').each do |node|\n          node.content = node.content\n        end\n\n        css('.toc-close', '.naked-list').remove\n      end\n\n      def other\n        css('#example', '.example', '#description_source', '#description_demo', '[id$=\"example\"]', 'hr').remove\n\n        css('header').each do |node|\n          node.before(node.children).remove\n        end\n\n        if h1 = at_css('h1')\n          h1.prepend_child(css('.view-source', '.improve-docs'))\n        end\n\n        # Remove root-level <div>\n        while div = at_css('h1 + div')\n          div.before(div.children)\n          div.remove\n        end\n\n        css('.api-profile-header-structure > li').each do |node|\n          node.inner_html = node.inner_html.remove('- ')\n        end\n\n        css('h1').each_with_index do |node, i|\n          next if i == 0\n          node.name = 'h2'\n        end\n\n        # Remove examples\n        css('.runnable-example').each do |node|\n          node.parent.remove\n        end\n\n        # Remove dead links (e.g. ngRepeat)\n        css('a.type-hint').each do |node|\n          node.name = 'code'\n          node.remove_attribute 'href'\n        end\n\n        css('pre > code').each do |node|\n          node['class'] ||= ''\n          lang = if node['class'].include?('lang-html') || node.content =~ /\\A</\n            'html'\n          elsif node['class'].include?('lang-css')\n            'css'\n          elsif node['class'].include?('lang-js') || node['class'].include?('lang-javascript')\n            'javascript'\n          end\n          node.parent['data-language'] = lang if lang\n\n          node.before(node.children).remove\n        end\n\n        # Remove some <code> elements\n        css('h1 > code', 'h2 > code', 'h3 > code', 'h4 > code', 'h6 > code').each do |node|\n          node.before(node.content).remove\n        end\n\n        css('ul.methods', 'ul.properties', 'ul.events').add_class('defs').each do |node|\n          node.css('> li > h3').each do |h3|\n            next if h3.content.present?\n            h3.content = h3.next_element.content\n            h3.next_element.remove\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/angularjs/clean_urls.rb",
    "content": "module Docs\n  class Angularjs\n    class CleanUrlsFilter < Filter\n      def call\n        html.gsub! %r{angularjs\\.org/([\\d\\.]+)/docs/partials/(\\w+)/}, 'angularjs.org/\\1/docs/\\2/'\n        html.gsub! %r{angularjs\\.org/([\\d\\.]+)/docs/(\\w+)/(.+?)\\.html}, 'angularjs.org/\\1/docs/\\2/\\3'\n        html\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/angularjs/entries.rb",
    "content": "module Docs\n  class Angularjs\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        if slug.start_with?('api')\n          name = URI.unescape(slug).split('/').last\n          name.remove! %r{\\Ang\\.}\n          name << \" (#{subtype})\" if subtype == 'directive' || subtype == 'filter'\n          name.prepend(\"#{type}.\") unless type.starts_with?('ng ') || name == type\n          name\n        elsif slug.start_with?('guide')\n          name = URI.decode(at_css('.improve-docs')['href'][/message=docs\\(guide%2F(.+?)\\)/, 1])\n          name.prepend 'Guide: '\n          name\n        end\n      end\n\n      def get_type\n        if slug.start_with?('api')\n          type = slug.split('/').drop(1).first\n          type << \" #{subtype}s\" if type == 'ng' && subtype\n          type\n        elsif slug.start_with?('guide')\n          'Guide'\n        end\n      end\n\n      def subtype\n        return @subtype if defined? @subtype\n        node = at_css '.api-profile-header-structure'\n        data = node.content.match %r{(\\w+?) in module} if node\n        @subtype = data && data[1]\n      end\n\n      def additional_entries\n        return [] unless slug.start_with?('api')\n        entries = []\n\n        css('ul.defs').each do |list|\n          list.css('> li[id]').each do |node|\n            next unless heading = node.at_css('h3')\n            name = heading.content.strip\n            name.sub! %r{\\(.*\\);}, '()'\n            name.prepend \"#{self.name.split.first}.\"\n            entries << [name, node['id']]\n          end\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/ansible/clean_html.rb",
    "content": "module Docs\n  class Ansible\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('[itemprop=articleBody]')\n\n        css('font').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('.documentation-table').each do |node|\n          node.css('[style]').each do |subnode|\n            subnode.remove_attribute('style')\n          end\n        end\n\n        doc\n\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/ansible/entries.rb",
    "content": "module Docs\n  class Ansible\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content.strip\n        name.remove! \"\\u{00B6}\"\n        name.remove! \"\\u{f0c1}\"\n        name.remove! %r{ \\- .*}\n        name.remove! 'Introduction To '\n        name.remove! %r{ Guide\\z}\n\n        if version >= \"2.10\" || version == \"\"\n          if slug =~ /\\Acollections\\// and slug !~ /index$/\n            name = name.split('.')[2]\n          end\n        end\n\n        name\n      end\n\n      def get_type\n        if version == '2.4'\n          if slug.include?('module')\n            if name =~ /\\A[a-z]/ && node = css('.toctree-l2.current').last\n              return \"Modules: #{node.content.remove(' Modules')}\"\n            else\n              return 'Modules'\n            end\n          end\n        end\n\n        if version >= \"2.10\" || version == \"\"\n          if slug =~ /\\Acollections\\//\n            return \"Collection #{slug.split('/')[1..-2].join(\".\")}\"\n          end\n        end\n\n        if slug =~ /\\Acli\\//\n          'CLI Reference'\n        elsif slug =~ /\\Anetwork\\//\n          'Network'\n        elsif slug =~ /\\Aplugins\\//\n          if name =~ /\\A[a-z]/ && node = css('.toctree-l3.current').last\n            \"Plugins: #{node.content.sub(/ Plugins.*/, '')}\"\n          else\n            'Plugins'\n          end\n        elsif slug =~ /\\Amodules\\//\n          if slug =~ /\\Amodules\\/list_/ || slug=~ /_maintained\\z/\n            'Modules: Categories'\n          else\n            'Modules'\n          end\n        elsif slug.include?('playbook')\n          'Playbooks'\n        elsif slug =~ /\\Auser_guide\\//\n          'Guides: User'\n        elsif slug =~ /\\Ascenario_guides\\//\n          'Guides: Scenarios'\n        elsif slug.include?('guide')\n          'Guides'\n        else\n          'Miscellaneous'\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/apache/clean_html.rb",
    "content": "module Docs\n  class Apache\n    class CleanHtmlFilter < Filter\n      def call\n        if root_page?\n          doc.children = css('h1, .category')\n          return doc\n        end\n\n        css('.toplang', '#quickview', '.top').remove\n\n        css('> .section', '#preamble', 'a[href*=\"dict.html\"]', 'code var', 'code strong').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('p > code:first-child:last-child', 'td > code:first-child:last-child').each do |node|\n          next if node.previous.try(:content).present? || node.next.try(:content).present?\n          node.inner_html = node.inner_html.squish.gsub(/<br(\\ \\/)?>\\s*/, \"\\n\")\n          node.content = node.content.strip\n          node.name = 'pre' if node.content =~ /\\s/\n          node.parent.before(node.parent.children).remove if node.parent.name == 'p'\n        end\n\n        css('code').each do |node|\n          node.inner_html = node.inner_html.squish\n        end\n\n        css('.note h3', '.warning h3').each do |node|\n          node.before(\"<p><strong>#{node.inner_html}</strong></p>\").remove\n        end\n\n        css('h2:not([id]) a[id]:not([href])').each do |node|\n          node.parent['id'] = node['id']\n          node.before(node.children).remove\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/apache/entries.rb",
    "content": "module Docs\n  class Apache\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        if slug == 'mod/'\n          'Modules'\n        elsif slug == 'programs/'\n          'Programs'\n        elsif slug == 'mod/core'\n          'core'\n        else\n          name = at_css('h1').content.strip\n          name.remove! %r{\\ Support\\z}i\n          name.remove! %r{in\\ Apache\\z}\n          name.remove! %r{\\ documentation\\z}i\n          name.remove! %r{\\AApache\\ (httpd\\ )?(Tutorial:\\ )?}i\n          name.remove! 'HTTP Server Tutorial: '\n          name.sub! 'Module mod_', 'mod_'\n          name.remove! %r{\\ \\-.*} if slug.start_with?('programs')\n          name\n        end\n      end\n\n      def get_type\n        if slug.start_with?('howto')\n          'Tutorials'\n        elsif slug.start_with?('platform')\n          'Platform Specific Notes'\n        elsif slug.start_with?('programs')\n          'Programs'\n        elsif slug.start_with?('misc')\n          'Miscellaneous'\n        elsif slug.start_with?('mod/')\n          'Modules'\n        elsif slug.start_with?('ssl/')\n          'Guide: SSL/TLS'\n        elsif slug.start_with?('rewrite/')\n          'Guide: Rewrite'\n        elsif slug.start_with?('vhosts/')\n          'Guide: Virtual Host'\n        else\n          'Guide'\n        end\n      end\n\n      def additional_entries\n        css('.directive-section > h2').each_with_object [] do |node, entries|\n          name = node.content.strip\n          next unless name.sub!(/\\ Directive\\z/, '')\n          name.prepend \"#{self.name.start_with?('MPM') ? 'MPM' : self.name}: \"\n          entries << [name, node['id'], 'Directives']\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/apache_pig/clean_html.rb",
    "content": "module Docs\n  class ApachePig\n    class CleanHtmlFilter < Filter\n      def call\n        css('.pdflink').remove\n\n        css('a[name]').each do |node|\n          node.next_element['id'] = node['name']\n        end\n\n        css('h2', 'h3').each do |node|\n          node.remove_attribute 'class'\n        end\n\n        css('table').each do |node|\n          node.remove_attribute 'cellspacing'\n          node.remove_attribute 'cellpadding'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/apache_pig/entries.rb",
    "content": "module Docs\n  class ApachePig\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        at_css('h1').content\n      end\n\n      def additional_entries\n        nodes = case slug\n        when 'basic', 'cmds', 'func'\n          css('h3')\n        when 'cont'\n          css('h2, #macros + div > h3')\n        when 'test'\n          css('h2, #diagnostic-ops + div > h3')\n        when 'perf'\n          css('h2, #optimization-rules + div > h3, #specialized-joins + div > h3')\n        else\n          css('h2')\n        end\n\n        nodes.each_with_object [] do |node, entries|\n          name = node.content.strip\n          entries << [name, node['id']] unless name == 'Introduction'\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/astro/clean_html.rb",
    "content": "module Docs\n  class Astro\n    class CleanHtmlFilter < Filter\n      def call\n        return '<h1>Astro</h1><p> Astro is a website build tool for the modern web — powerful developer experience meets lightweight output.</p>' if root_page?\n\n        @doc = at_css('main')\n\n        css('.anchor-link').remove\n        css('.avatar-list').remove\n\n        css('div > div > h1').each do |node|\n          node.parent.parent.before(node).remove\n        end\n\n        css('pre').each do |node|\n          node.content = node.css('.ec-line').map(&:content).join(\"\\n\")\n          node.remove_attribute('style')\n        end\n\n        css('figcaption').each do |node|\n          node.name = 'div'\n          node['class'] = '_pre-heading'\n        end\n\n        css('figure').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('.cms-nav').remove\n\n        css('.copy-button-wrapper, .copy-button-tooltip').remove\n\n        css('footer ~ section', 'footer').remove\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/astro/entries.rb",
    "content": "module Docs\n  class Astro\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1') ? at_css('h1').content : at_css('h2').content\n        name.sub!(/\\s*#\\s*/, '')\n        name\n      end\n\n      def get_type\n        return 'Contribute' if slug.start_with?('contribute/')\n\n        a = at_css('a[aria-current=\"page\"]')\n        a ? a.content : 'Other'\n      end\n\n      def additional_entries\n        return [] if root_page?\n        return [] if slug.start_with?('guides/deploy')\n        return [] if slug.start_with?('guides/integrations-guide')\n\n        at_css('main').css('h2[id], h3[id]').each_with_object [] do |node, entries|\n          type = node.content.strip\n          type.sub!(/\\s*#\\s*/, '')\n          entries << [\"#{name}: #{type}\", node['id']]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/async/clean_html.rb",
    "content": "module Docs\n  class Async\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('#main-container')\n\n        at_css('footer').remove\n\n        css('section', 'header', 'article', '.container-overview', 'span.signature', 'div.description').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('h3', 'h4', 'h5').each do |node|\n          node.name = node.name.sub(/\\d/) { |i| i.to_i - 1 }\n        end\n\n        css('dd ul').each do |node|\n          node.replace(node.css('li').map(&:inner_html).join(' '))\n        end\n\n        css('pre').each do |node|\n          node['data-language'] = 'javascript'\n          node.content = node.content\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/async/entries.rb",
    "content": "module Docs\n  class Async\n    class EntriesFilter < Docs::EntriesFilter\n      def additional_entries\n        type = nil\n        entries = []\n\n        css('.nav.methods li').each do |node|\n          if node['class'] == 'toc-header'\n            type = node.content\n          else\n            name = node.content\n            id = node.at_css('a')['href'].remove('#')\n            entries << [name, id, type]\n          end\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/axios/clean_html.rb",
    "content": "module Docs\n  class Axios\n    class CleanHtmlFilter < Filter\n      def call\n        if root_page?\n          return '<h1>Axios</h1><p>Promise based HTTP client for the browser and node.js</p><p>Axios is a simple promise based HTTP client for the browser and node.js. Axios provides a simple to use library in a small package with a very extensible interface.</p>'\n        end\n        @doc = at_css('main > .body')\n        css('.links').remove\n        css('.sponsors_container').remove\n        css('pre').each do |node|\n          node.content = node.content\n          node['data-language'] = node['class'][/lang-(\\w+)/, 1]\n        end\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/axios/entries.rb",
    "content": "module Docs\n  class Axios\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        'axios'\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/babel/clean_html.rb",
    "content": "module Docs\n  class Babel\n    class CleanHtmlFilter < Filter\n      def call\n\n        @doc = at_css('.theme-doc-markdown')\n\n        css('.fixedHeaderContainer').remove\n\n        css('.toc').remove\n\n        css('.toc-headings').remove\n\n        css('.postHeader > a').remove\n\n        css('.nav-footer').remove\n\n        css('.docs-prevnext').remove\n\n        css('pre').each do |node|\n          node.content = node.css('.token-line').map(&:content).join(\"\\n\")\n          node['data-language'] = node['class'][/language-(\\w+)/, 1]\n        end\n\n        css('.codeBlockTitle_x_ju').remove\n\n        css('*').remove_attr('class')\n\n        css('*').remove_attr('style')\n\n        doc\n\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/babel/entries.rb",
    "content": "module Docs\n  class Babel\n    class EntriesFilter < Docs::EntriesFilter\n\n      ENTRIES = {\n        'Usage' => ['Options', 'Plugins', 'Config Files', 'Compiler assumptions', '@babel/cli', '@babel/polyfill',\n                    '@babel/plugin-transform-runtime', '@babel/register'],\n\n        'Presets' => ['@babel/preset'],\n\n        'Tooling' => ['@babel/parser', '@babel/core', '@babel/generator', '@babel/code-frame',\n                      '@babel/helper', '@babel/runtime', '@babel/template', '@babel/traverse', '@babel/types', '@babel/standalone']\n      }\n\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        ENTRIES.each do |key, value|\n          return key if value.any? { |val| name.start_with?(val) }\n          return 'Other Plugins' if subpath.include?('babel-plugin')\n        end\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/backbone/clean_html.rb",
    "content": "module Docs\n  class Backbone\n    class CleanHtmlFilter < Filter\n      def call\n        # Remove Introduction, Upgrading, etc.\n        while doc.child['id'] != 'Events'\n          doc.child.remove\n        end\n\n        # Remove Examples, FAQ, etc.\n        while doc.children.last['id'] != 'faq'\n          doc.children.last.remove\n        end\n\n        css('#faq', '.run').remove\n\n        css('tt').each do |node|\n          node.name = 'code'\n        end\n\n        css('pre').each do |node|\n          node['data-language'] = 'javascript'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/backbone/entries.rb",
    "content": "module Docs\n  class Backbone\n    class EntriesFilter < Docs::EntriesFilter\n      def additional_entries\n        entries = []\n        type = nil\n\n        css('[id]').each do |node|\n          # Module\n          if node.name == 'h2'\n            type = node.content.remove 'Backbone.'\n            if type.capitalize! # sync, history\n              entries << [node.content, node['id'], type]\n            end\n            next\n          end\n\n          # Built-in events\n          if node['id'] == 'Events-catalog'\n            node.next_element.css('li').each do |li|\n              name = \"#{li.at_css('b').content.delete('\"').strip} event\"\n              id = name.parameterize\n              li['id'] = id\n              entries << [name, id, type] unless name == entries.last[0]\n            end\n            next\n          end\n\n          # Method\n          name = node.at_css('.header').content.split.first\n\n          # Underscore methods\n          if name.start_with?('Underscore')\n            node.at_css('~ ul').css('li').each do |li|\n              name = [type.downcase, li.at_css('a').content.split.first].join('.')\n              id = name.parameterize\n              li['id'] = id\n              entries << [name, id, type]\n            end\n            next\n          end\n\n          if %w(Events Sync).include?(type)\n            name.prepend 'Backbone.'\n          elsif type == 'History'\n            name.prepend 'Backbone.history.'\n          elsif name == 'extend'\n            name.prepend \"#{type}.\"\n          elsif name.start_with? 'constructor'\n            name = type\n          elsif type != 'Utility'\n            name.prepend \"#{type.downcase}.\"\n          end\n\n          entries << [name, node['id'], type]\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/bash/clean_html.rb",
    "content": "module Docs\n  class Bash\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('> div[id]') if at_css('> div[id]')\n        # Remove the navigation header and footer and the lines underneath and above it\n        at_css('.header + hr').remove\n        line_above = at_xpath('//div[@class=\"header\"]/preceding::hr[1]')\n        line_above.remove unless line_above.nil?\n        css('.header').remove\n\n        css('.copiable-anchor').remove\n\n        # Remove chapter and section numbers from title\n        title_node = at_css('h1, h2, h3, h4, h5, h6')\n        title_node.content = title_node.content.gsub(/(\\d+\\.?)+/, '').strip\n        title_node.name = 'h1'\n\n        # Remove the \"D. \" from names like \"D. Concept Index\" and \"D. Function Index\"\n        title_node.content = title_node.content[3..-1] if title_node.content.start_with?(\"D. \")\n\n        # Remove columns containing a single space from tables\n        # In the original reference they are used to add width between two columns\n        xpath('//td[text()=\" \" and not(descendant::*)]').remove\n\n        # Add id's to additional entry nodes\n        css('dl > dt > code').each do |node|\n          # Only take the direct text (i.e. \"<div>Hello <span>World</span></div>\" becomes \"Hello\")\n          node['id'] = node.xpath('text()').to_s.strip\n        end\n\n        # Fix hashes of index entries so they link to the correct hash on the linked page\n        css('table[class^=index-] td[valign=top] > a').each do |node|\n          path = node['href'].split('#')[0]\n          hash = node.content\n\n          # Fix the index entries linking to the Special Parameters page\n          # There are multiple index entries that should link to the same paragraph on that page\n          # Example: the documentation for \"$!\" is equal to the documentation for \"!\"\n          if path.downcase.include?('special-parameters')\n            if hash.size > 1 && hash[0] == '$'\n              hash = hash[1..-1]\n            end\n          end\n\n          node['href'] = path + '#' + hash\n        end\n\n        # Fix index table letter hashes (the \"Jump to\" hashes)\n        css('table[class^=index-] th > a').each do |node|\n          node['id'] = node['name']\n        end\n\n        # Remove the rows with a horizontal line in them from the index tables\n        css('td[colspan=\"4\"]').remove\n\n        # Remove additional text from menu entry and index entry cells\n        css('td[valign=top]').each do |node|\n          link = node.at_css('a')\n          node.children = link unless link.nil?\n        end\n\n        css('tt', 'code', 'table').remove_attr('class')\n\n        css('tt').each do |node|\n          node.name = 'code'\n        end\n\n        css('pre').each do |node|\n          node.content = node.content\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/bash/entries.rb",
    "content": "module Docs\n  class Bash\n    class EntriesFilter < Docs::EntriesFilter\n\n      def get_name\n        name = at_css('h1','h2', 'h3', 'h4').content.gsub(/(\\d+\\.?)+/, '')\n\n        # remove 'E.' notation for appendixes\n        if name.match?(/[[:upper:]]\\./)\n          # remove 'E.'\n          name.sub!(/[[:upper:]]\\./, '')\n          # remove all dots (.)\n          name.gsub!(/\\./, '')\n          # remove all numbers\n          name.gsub!(/[[:digit:]]/, '')\n        end\n\n        name.strip\n\n      end\n\n      def get_type\n        return 'Manual: Appendices' if name.start_with?('Appendix')\n        return 'Manual: Indexes' if at_css('a[rel=up]').content.include?(\"Index\")\n        \"Manual\"\n      end\n\n      def additional_entries\n        entry_type = {\n          \"Function Index\" => \"Functions\",\n          \"Index of Shell Builtin Commands\" => \"Builtin Commands\",\n          \"Index of Shell Reserved Words\" => \"Reserved Words\",\n          \"Parameter and Variable Index\" => \"Parameters and Variables\"\n        }[name]\n\n        # Only extract additional entries from certain index pages\n        return [] if entry_type.nil?\n\n        entries = []\n\n        css('table[class^=index-] td[valign=top] > a').each_slice(2) do |entry_node, section_node|\n          entry_name = entry_node.content\n          entry_path = entry_node['href']\n          entries << [entry_name, entry_path, entry_type]\n        end\n\n        entries\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/bazel/clean_html.rb",
    "content": "module Docs\n  class Bazel\n    class CleanHtmlFilter < Filter\n\n      def call\n        css('.devsite-article-meta').remove\n        css('devsite-feature-tooltip').remove\n        css('devsite-thumb-rating').remove\n        css('devsite-toc').remove\n        css('devsite-feedback').remove\n        css('a.button-with-icon').remove\n        css('button.devsite-heading-link').remove\n        css('.devsite-article-body > span:first-child[style=\"float: right; line-height: 36px\"]').remove\n        doc\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/bazel/entries.rb",
    "content": "module Docs\n  class Bazel\n    class EntriesFilter < Docs::EntriesFilter\n\n      def get_name\n        at_css('h1').content.strip\n      end\n\n      def get_type\n        \"Build encyclopedia\"\n      end\n\n      def additional_entries\n        entries = []\n\n        special_page_types = {\n          'functions' => 'Function',\n          'make-variables' => 'Make Variable',\n          'common-definitions' => 'Common Definition',\n        }\n        page_type = special_page_types[subpath]\n        unless page_type.nil?\n          # only first ul\n          at_css('.devsite-article-body > ul').css('li > a').each do |node|\n            entries << [node.content.strip, node['href'].sub('#', ''), page_type]\n          end\n        end\n        css('h2#rules + ul > li > a').each do |node|\n          entries << [node.content.strip, node['href'].sub('#', ''), \"Rule\"]\n        end\n\n        entries\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/bluebird/clean_html.rb",
    "content": "module Docs\n  class Bluebird\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.post')\n\n        css('hr').remove\n\n        css('.api-code-section').each do |node|\n          node.previous_element.remove\n        end\n\n        css('.post-header', '.post-content', '.api-reference-menu', '.api-code-section', 'markdown', '.highlight', 'code code').each do |node|\n          node.before(node.children).remove\n        end\n\n        at_css('> h2:first-child').name = 'h1' unless at_css('h1')\n\n        css('.header-anchor[name]').each do |node|\n          node.parent['id'] = node['name']\n        end\n\n        css('pre').each do |node|\n          node.content = node.content\n          node['data-language'] = 'javascript'\n        end\n\n        css('.info-box').each do |node|\n          node.name = 'blockquote'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/bluebird/entries.rb",
    "content": "module Docs\n  class Bluebird\n    class EntriesFilter < Docs::EntriesFilter\n      TYPE_MAP = {\n        Core: %w(new-promise then spread catch error finally bind promise.join\n          promise.try promise.method promise.resolve promise.reject core\n          promise.bind),\n        'Synchronous inspection': %w(promiseinspection isfulfilled isrejected\n          ispending iscancelled value reason),\n        Collections: %w(promise.all promise.props promise.any promise.some\n          promise.map promise.reduce promise.filter promise.each\n          promise.mapseries promise.race all props any some map reduce filter\n          each mapseries),\n        'Resource management': %w(promise.using disposer),\n        Promisification: %w(promise.promisify promise.promisifyall\n          promise.fromcallback ascallback),\n        Timers: %w(delay timeout promise.delay),\n        Cancellation: %w(cancel),\n        Generators: %w(promise.coroutine promise.coroutine.addyieldhandler),\n        Utility: %w(tap tapcatch call get return throw catchreturn catchthrow\n          reflect promise.getnewlibrarycopy promise.noconflict\n          promise.setscheduler),\n        'Built-in error types': %w(operationalerror timeouterror\n          cancellationerror aggregateerror),\n        Configuration: %w(global-rejection-events local-rejection-events\n          done promise.config suppressunhandledrejections\n          promise.onpossiblyunhandledrejection promise.bind\n          promise.onunhandledrejectionhandled),\n      }\n\n      def get_name\n        name = at_css('h1').content.strip\n        name << '()' if doc.to_html.include?(\"#{name}(\")\n        name\n      end\n\n      def get_type\n        if slug.start_with?('api')\n          TYPE_MAP.each do |key, value|\n            return key.to_s if value.include?(slug.remove('api/'))\n          end\n        else\n          'Guides'\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/bootstrap/clean_html_v3.rb",
    "content": "module Docs\n  class Bootstrap\n    class CleanHtmlV3Filter < Filter\n      def call\n        at_css('div[role=main]').child.before(at_css('#content .container').inner_html)\n        @doc = at_css('div[role=main]')\n\n        css('h1, h2, h3, h4, h5').each do |node|\n          node.name = node.name.sub(/\\d/) { |i| i.to_i + 1 }\n        end\n        at_css('h2').name = 'h1'\n        at_css('h1').content = 'Bootstrap 3' if root_page?\n\n        css('hr', '.zero-clipboard', '.modal', '.panel-group').remove\n\n        css('.bs-docs-section', '.table-responsive').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('> .show-grid', '.bs-example', '.bs-glyphicons', '.responsive-utilities-test').each do |node|\n          if node.previous_element['class'].try(:include?, 'bs-example')\n            node.remove\n          else\n            node.content = ''\n            node.name = 'p'\n            node['class'] = 'bs-example'\n            node.remove_attribute('data-example-id')\n            prev = node.previous_element\n            prev = prev.previous_element until prev['id']\n            node.inner_html = %(<a href=\"#{current_url}##{prev['id']}\">Open example on getbootstrap.com</a>)\n          end\n        end\n\n        css('.bs-example + figure').each do |node|\n          node.previous_element.name = 'div'\n        end\n\n        css('div[class*=\"col-\"]').each do |node|\n          node['class'] = 'col'\n        end\n\n        css('figure.highlight').each do |node|\n          code = node.at_css('code')\n          node['data-language'] = code['data-lang']\n          node.content = code.content\n          node.name = 'pre'\n        end\n\n        css('table, tr, td, th, pre').each do |node|\n          node.remove_attribute('class')\n          node.remove_attribute('style')\n        end\n\n        css('thead td:empty').each do |node|\n          node.name = 'th'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/bootstrap/clean_html_v4.rb",
    "content": "module Docs\n  class Bootstrap\n    class CleanHtmlV4Filter < Filter\n      def call\n        @doc = at_css('.bd-content')\n\n        # 'View on Github' button\n        css('.btn').remove\n\n        at_css('h1').content = 'Bootstrap' if root_page?\n\n        css('hr', '.bd-clipboard', '.modal', '.bd-example .bd-example').remove\n\n        css('#markdown-toc-contents').each do |node|\n          node.parent.remove\n        end\n\n        css('.bd-example-row', '.bd-example-border-utils').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('.bd-example', '.responsive-utilities-test').each do |node|\n          next unless node.previous_element\n\n          if node.previous_element['class'].try(:include?, 'bd-example')\n            node.remove\n          end\n        end\n\n        css('.bd-example + .highlight').each do |node|\n          node.previous_element.name = 'div'\n        end\n\n        css('div[class*=\"col-\"]').each do |node|\n          node['class'] = 'col'\n        end\n\n        css('.highlight').each do |node|\n          code = node.at_css('code')\n          node['data-language'] = code['data-lang']\n          node.content = code.content\n          node.name = 'pre'\n        end\n\n        css('bd-callout h3').each do |node|\n          node.name = 'h4'\n        end\n\n        css('thead td').each do |node|\n          node.name = 'th'\n        end\n\n        css('table, tr, td, th, pre, code').each do |node|\n          node.remove_attribute('class')\n          node.remove_attribute('style')\n        end\n\n        css('[class*=\"bd-\"]').each do |node|\n          node['class'] = node['class'].gsub('bd-', 'bs-')\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/bootstrap/clean_html_v5.rb",
    "content": "module Docs\n  class Bootstrap\n    class CleanHtmlV5Filter < Filter\n      def call\n\n        @doc = at_css('main')\n        at_css('.bd-content').prepend_child(at_css('h1').remove)\n        @doc = at_css('.bd-content')\n\n        # Toc\n        css('.bd-toc').remove\n\n        # 'View on Github' button\n        css('.btn').remove\n\n        at_css('h1').content = 'Bootstrap' if root_page?\n\n        css('.highlight').each do |node|\n          code = node.at_css('code')\n          node['data-language'] = code['data-lang']\n          node.content = code.content\n          node.name = 'pre'\n        end\n\n        css('.bd-example').each do |node|\n          node.remove\n        end\n\n        doc\n\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/bootstrap/entries_v3.rb",
    "content": "module Docs\n  class Bootstrap\n    class EntriesV3Filter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        name\n      end\n\n      def additional_entries\n        return [] if root_page?\n        entries = []\n\n        css('.bs-docs-sidenav > li').each do |node|\n          link = node.at_css('a')\n          name = link.content\n          next if IGNORE_ENTRIES.include?(name)\n\n          id = link['href'].remove('#')\n          entries << [name, id]\n          next if name =~ /Sass|Less|Glyphicons/\n\n          node.css('> ul > li > a').each do |link|\n            n = link.content\n            next if n.start_with?('Ex: ') || n.start_with?('Default ') || n =~ /example/i || IGNORE_ENTRIES.include?(n)\n            id = link['href'].remove('#')\n            n.downcase!\n            n.prepend \"#{name}: \"\n            entries << [n, id]\n          end\n        end\n\n        %w(modals dropdowns scrollspy tabs tooltips popovers alerts buttons collapse carousel affix).each do |dom_id|\n          css(\"##{dom_id}-options + p + div tbody td:first-child\").each do |node|\n            name = node.content.strip\n            id = node.parent['id'] = \"#{dom_id}-#{name.parameterize}-option\"\n            name.prepend \"#{dom_id.singularize.titleize}: \"\n            name << ' (option)'\n            entries << [name, id]\n          end\n\n          css(\"##{dom_id}-methods ~ h4 code\").each do |node|\n            next unless name = node.content[/\\('(\\w+)'\\)/, 1]\n            id = node.parent['id'] = \"#{dom_id}-#{name.parameterize}-method\"\n            name.prepend \"#{dom_id.singularize.titleize}: \"\n            name << ' (method)'\n            entries << [name, id]\n          end\n        end\n\n        entries\n      end\n\n      IGNORE_ENTRIES = %w(\n        Overview\n        Introduction\n        Usage\n        Methods\n        Options\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/bootstrap/entries_v4.rb",
    "content": "module Docs\n  class Bootstrap\n    class EntriesV4Filter < Docs::EntriesFilter\n      def get_name\n        name = at_css('.bd-content h1').content.strip\n        name.remove! ' system'\n        name == 'Overview' ? type : name\n      end\n\n      def get_type\n        if subpath.start_with?('components')\n          at_css('.bd-content h1').content.strip.prepend 'Components: '\n        else\n          at_css('.bd-toc-item.active > .bd-toc-link').content\n        end\n      end\n\n      def additional_entries\n        return [] if root_page? || subpath.start_with?('getting-started') || subpath.start_with?('migration')\n        entries = []\n\n        css('.bd-toc > ul > li > a', '.bd-toc a[href=\"#events\"]', '.bd-toc a[href=\"#methods\"]', '.bd-toc a[href=\"#triggers\"]').each do |node|\n          name = node.content\n          next if name =~ /example/i || IGNORE_ENTRIES.include?(name)\n          name.downcase!\n          name.prepend \"#{self.name}: \"\n          id = node['href'].remove('#')\n          entries << [name, id]\n        end\n\n        css(\"#options + p + table tbody td:first-child\").each do |node|\n          name = node.content.strip\n          id = node.parent['id'] = \"#{name.parameterize}-option\"\n          name.prepend \"#{self.name}: \"\n          name << ' (option)'\n          entries << [name, id]\n        end\n\n        css(\"#methods + table tbody td:first-child, #methods ~ h4 code\").each do |node|\n          next unless name = node.content[/\\('(\\w+)'\\)/, 1]\n          unless id = node.parent['id']\n            id = node.parent['id'] = \"#{name.parameterize}-method\"\n          end\n          name.prepend \"#{self.name}: \"\n          name << ' (method)'\n          entries << [name, id]\n        end\n\n        css(\"#events ~ table tbody td:first-child\").each do |node|\n          name = node.content.strip\n          unless id = node.parent['id']\n            id = node.parent['id'] = \"#{name.parameterize}-event\"\n          end\n          name.prepend \"#{self.name}: \"\n          name << ' (event)'\n          entries << [name, id]\n        end\n\n        entries\n      end\n\n      IGNORE_ENTRIES = %w(\n        How\\ it\\ works\n        Approach\n        JavaScript\\ behavior\n        Usage\n        Overview\n        About\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/bootstrap/entries_v5.rb",
    "content": "module Docs\n  class Bootstrap\n    class EntriesV5Filter < Docs::EntriesFilter\n\n      def get_name\n        at_css('.bd-title').content.strip\n      end\n\n      def get_type\n        type = subpath.match(/\\A.*?\\//).to_s[0..-2]\n        type.gsub!('-', ' ')\n        type.capitalize!\n        type << \": #{name}\" if type == 'Components'\n        type\n      end\n\n      def additional_entries\n        return [] if root_page? || subpath.start_with?('getting-started')\n\n        entries = []\n\n        # titles\n        css('h2:not(.accordion-header)', 'h3').each do |node|\n          entries << [ name + ': ' + node.content, node['id']]\n        end\n\n        # methods and events\n        # traverse through all '.tables' and search for a 'Method' or 'Event type' in the first <th>\n        css('.table').each do |node|\n          firstTh = node.at_css('th').content\n\n          if firstTh == 'Method'\n            # traverse all <tr> and search only the first <code> of each tr\n            node.css('tr').each do |subnode|\n              if subnode\n                method = subnode.at_css('code')\n                if method\n                  method['id'] = method.content\n                  entries << [method.content + ' (Method)', method['id']]\n                end\n              end\n            end\n          end\n\n          if firstTh == 'Event type'\n            node.css('tr').each do |subnode|\n              event = subnode.at_css('code')\n              if event\n                event['id'] = event.content\n                entries << [event.content + ' (Event)', event['id']]\n              end\n            end\n          end\n\n        end\n\n        entries\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/bottle/entries.rb",
    "content": "module Docs\n  class Bottle\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content.strip\n        name.remove! \"\\u{00B6}\"\n        name\n      end\n\n      def get_type\n        case slug\n        when 'api'\n          'Reference'\n        when 'configuration'\n          'Reference: Configuration'\n        when 'stpl'\n          'Reference: SimpleTemplate'\n        when 'plugindev'\n          'Reference: Plugin'\n        else\n          'Manual'\n        end\n      end\n\n      def additional_entries\n        entries = []\n\n        css('.class').each do |node|\n          class_name = node.at_css('dt > .descname').content\n          class_id = node.at_css('dt[id]')['id']\n          entries << [class_name, class_id]\n\n          node.css('.method').each do |n|\n            next unless n.at_css('dt[id]')\n            name = n.at_css('.descname').content\n            name = \"#{class_name}::#{name}()\"\n            id = n.at_css('dt[id]')['id']\n            entries << [name, id]\n          end\n        end\n\n        css('.function').each do |node|\n          name = \"#{node.at_css('.descname').content}()\"\n          id = node.at_css('dt[id]')['id']\n          entries << [name, id]\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/bower/clean_html.rb",
    "content": "module Docs\n  class Bower\n    class CleanHtmlFilter < Filter\n      def call\n        title = at_css('.page-title, .main h1')\n        @doc = at_css('.main')\n        doc.child.before(title)\n\n        css('.site-footer').remove\n\n        css('.highlight').each do |node|\n          node.name = 'pre'\n          node['data-language'] = node.at_css('[data-lang]')['data-lang']\n          node.content = node.content\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/bower/entries.rb",
    "content": "module Docs\n  class Bower\n    class EntriesFilter < Docs::EntriesFilter\n      ENTRIES_TYPE_BY_SLUG = {\n        'api' => 'Commands'\n      }\n\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        'Guides'\n      end\n\n      def additional_entries\n        return [] unless type = ENTRIES_TYPE_BY_SLUG[slug]\n\n        css('#commands + p + ul a').map do |node|\n          [node.content, node['href'].remove('#'), type]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/bun/clean_html.rb",
    "content": "module Docs\n  class Bun\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('#content-area')\n        doc.children = css('#header, #content')\n\n        header = at_css('header:has(h1)')\n        if header\n          header.content = header.at_css('h1').content\n          header.name = 'h1'\n        end\n\n        css('*[aria-label=\"Navigate to header\"]', '*[aria-label=\"Copy the contents from the code block\"]').each do |node|\n          node.parent.remove\n        end\n        css('img').remove\n        css('svg').remove\n        \n        css('.code-block *[data-component-part=\"code-block-header\"]').remove\n        css('.code-block', '.code-group').each do |node|\n          node.name = 'pre'\n          node.content = node.content\n          node['data-language'] = 'typescript'\n          node.remove_attribute('style')\n        end\n\n        css('.font-mono').each do |node|\n          node.name = 'code'\n          node.content = node.content\n        end\n\n        css('.font-mono.text-blue-600').each do |node|\n          node[:class] = 'token keyword'\n        end\n\n        css('*[class]').each do |node|\n          next if node.name == 'code'\n          node.remove_attribute('class')\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/bun/entries.rb",
    "content": "module Docs\n  class Bun\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1') ? at_css('h1').content : at_css('h2').content\n        name\n      end\n\n      def get_type\n        slug.split('/').first\n      end\n\n      def additional_entries\n        if slug.start_with?('pm/cli')\n          css('h2[id]').each_with_object [] do |node, entries|\n            name = get_name + \" \" + node.content.strip\n            entries << [name, node['id']]\n          end\n        elsif slug.start_with?('runtime')\n          css('h2[id]').each_with_object [] do |node, entries|\n            name = get_name + \": \" + node.content.strip\n            entries << [name, node['id']]\n          end\n        else\n          []\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/c/entries.rb",
    "content": "module Docs\n  class C\n    class EntriesFilter < Docs::EntriesFilter\n      ADDITIONAL_NAMES = {\n        'Conditional inclusion' => %w(if else elif ifdef ifndef endif).map { |s| \"##{s} directive\" },\n        'Function specifiers' => ['inline specifier', '_Noreturn specifier'] }\n\n      REPLACE_NAMES = {\n        'Error directive' => '#error directive',\n        'Filename and line information' => '#line directive',\n        'Implementation defined behavior control' => '#pragma directive',\n        'Replacing text macros' => '#define directive',\n        'Source file inclusion' => '#include directive',\n        'Warning directive' => '#warning directive' }\n\n      def get_name\n        name = at_css('#firstHeading').content.strip\n        name.remove! 'C keywords: '\n        name.remove! %r{\\s\\(.+\\)}\n        name = name.split(',').first\n        REPLACE_NAMES[name] || name\n      end\n\n      def get_type\n\n        return \"C keywords\" if slug =~ /keyword/\n\n        type = at_css('.t-navbar > div:nth-child(4) > :first-child').try(:content)\n        type.strip!\n        type.remove! ' library'\n        type.remove! ' utilities'\n        type\n      end\n\n      def additional_entries\n        names = at_css('#firstHeading').content.split(',')[1..-1]\n        names.concat ADDITIONAL_NAMES[name] || []\n        names.map { |name| [name] }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/cakephp/clean_html.rb",
    "content": "module Docs\n  class Cakephp\n    class CleanHtmlFilter < Filter\n      def call\n        css('.breadcrumbs', 'a.permalink', 'a.anchor').remove\n\n        css('.section', '#content', '.description', '.list', 'span.attributes').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('> h6').each do |node|\n          node.name = 'h2'\n        end\n\n        css('h6').each do |node|\n          node.name = 'h4'\n        end\n\n        css('var').each do |node|\n          node.name = 'code'\n        end\n\n        css('.member-summary h3', 'li > h5').each do |node|\n          node.name = 'div'\n          node.remove_attribute('class')\n        end\n\n        css('div.attributes').each do |node|\n          node.name = 'p'\n        end\n\n        # Move dummy anchor to method and property name\n\n        css('.method-detail').each do |node|\n          node.at_css('.method-name')['id'] = node.at_css('a')['id']\n        end\n\n        css('.property-detail').each do |node|\n          node.at_css('.property-name')['id'] = node.at_css('a')['id']\n        end\n\n        # Break out source link to separate element\n\n        css('.method-name', '.property-name').each do |node|\n          source = node.at_css('a')\n          source.before(%(<span class=\"name\">#{source.content}</span>))\n          source.content = 'source'\n          source['class'] = 'source'\n        end\n\n        css('.method-signature', 'pre').each do |node|\n          node.name = 'pre'\n          node.content = node.content.strip\n          node['data-language'] = 'php'\n        end\n\n        css('span.name > code').each do |node|\n          node.content = node.content.strip\n        end\n\n        css('code code').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('code').each do |node|\n          node.inner_html = node.inner_html.squish\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/cakephp/clean_html_39_plus.rb",
    "content": "module Docs\n  class Cakephp\n    class CleanHtml39PlusFilter < Filter\n      def call\n        @doc = root_page? ? at_css('#content') : at_css('#right').at_css('div')\n\n        css('a.permalink').remove\n\n        css('.member-summary h3').each do |node|\n          node.name = 'div'\n          node.remove_attribute('class')\n        end\n\n        css('h6').each do |node|\n          node.name = 'h4'\n        end\n\n        css('pre').each do |node|\n          node.content = node.content.strip\n          node['data-language'] = 'php'\n        end\n\n        # Move dummy anchor to method and property name\n\n        css('.method-detail').each do |node|\n          node.at_css('.method-name')['id'] = node.at_css('a')['id']\n        end\n\n        css('.property-detail').each do |node|\n          node.at_css('.property-name')['id'] = node.at_css('a')['id']\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/cakephp/entries.rb",
    "content": "module Docs\n  class Cakephp\n    class EntriesFilter < Docs::EntriesFilter\n      def page_type\n        @page_type ||= slug.split('-').first\n      end\n\n      def slug_without_page_type\n        @slug_without_page_type ||= slug.split('-').last\n      end\n\n      def get_name\n        case page_type\n        when 'class'\n          slug_without_page_type.split('.').last.concat(' (class)')\n        when 'function'\n          at_css('h1').content.remove('Function ')\n        when 'namespace', 'package'\n          slug_without_page_type.split('.').tap do |path|\n            path.shift if path.length > 1\n          end.join('\\\\').concat(\" (#{page_type})\")\n        end\n      end\n\n      def get_type\n        return 'Global' if slug == 'namespace-None'\n        case page_type\n        when 'class', 'namespace', 'package'\n          if (node = at_css('.info')) && node.content =~ /Location:\\s+((?:\\w+\\/?)+)/ # for 2.x docs\n            path = $1.split('/')\n          else\n            path = slug_without_page_type.split('.')\n          end\n          path.shift if path.length > 1 && path[0] == 'Cake'\n          path.pop if path.length > 1\n          path.pop if path.last == 'Exception'\n          path.join('\\\\')\n        when 'function'\n          'Global'\n        end\n      end\n\n      def additional_entries\n        return [] unless page_type == 'class'\n        class_name = name.remove(' (class)')\n        return [] if class_name.end_with?('Exception')\n        entries = []\n\n        css('h3.method-name').each do |node|\n          break if node.parent.previous_element.content =~ /\\AMethods.*from/\n          entries << [\"#{class_name}::#{node.at_css('.name').content.strip}\", node['id']]\n        end\n\n        css('h3.property-name').each do |node|\n          break if node.parent.parent['class'].include?('used')\n          entries << [\"#{class_name}::#{node.at_css('.name').content.strip}\", node['id']]\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/cakephp/entries_39_plus.rb",
    "content": "module Docs\n  class Cakephp\n    class Entries39PlusFilter < Docs::EntriesFilter\n      def page_type\n        @page_type ||= slug.split('-').first\n      end\n\n      def slug_without_page_type\n        @slug_without_page_type ||= slug.split('-').last\n      end\n\n      def get_name\n        case page_type\n        when 'class', 'trait', 'interface'\n          slug.split('.').last.concat(\" (#{self.page_type})\")\n        when 'namespace'\n          slug_without_page_type.split('.').tap do |path|\n            path.shift if path.length > 1\n          end.join('\\\\').concat(\" (namespace)\")\n        end\n      end\n\n      def get_type\n        case page_type\n        when 'class', 'trait', 'interface'\n          slug_without_page_type.split('.')[1..-2].join('\\\\')\n        when 'namespace'\n          slug_without_page_type.split('.')[1..-1].join('\\\\')\n        end\n      end\n\n      def additional_entries\n        return [] unless page_type == 'class'\n        class_name = slug.split('.').last\n        return [] if class_name.end_with?('Exception')\n        entries = []\n\n        css('h3.method-name').each do |node|\n          method_name = node['id'].concat('()')\n          entries << [\"#{class_name}::#{method_name}\", node['id']]\n        end\n\n        css('h3.property-name').each do |node|\n          property_name = node['id']\n          entries << [\"#{class_name}::#{property_name}\", node['id']]\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/chai/clean_html.rb",
    "content": "module Docs\n  class Chai\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.documentation .rendered') if at_css('.documentation .rendered')\n\n        if root_page?\n          at_css('h1').content = 'Chai Assertion Library'\n        end\n\n        css('> article', '.header').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('pre').each do |node|\n          node.content = node.content\n          node['data-language'] = 'javascript'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/chai/entries.rb",
    "content": "module Docs\n  class Chai\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        subpath.start_with?('/guide') ? 'Guides' : name\n      end\n\n      def additional_entries\n        css('.antiscroll-inner a').each_with_object [] do |node, entries|\n          id = node['href'].remove('#')\n          node.content.strip.split(' / ').uniq { |name| name.downcase }.each do |name|\n            entries << [name, id, self.name]\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/chef/clean_html.rb",
    "content": "module Docs\n  class Chef\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('#main-content-col')\n\n        if root_page?\n          css('img').remove\n        end\n\n        css('pre').each do |node|\n          node.remove_attribute('style')\n\n          if !(node.classes.include?('highlight'))\n            node.add_class('highlight')\n            node['data-language'] = 'ruby'\n          end\n        end\n\n        css('#feedback').remove\n\n        css('.mini-toc-header', '.TOC-button').remove\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/chef/clean_html_old.rb",
    "content": "module Docs\n  class Chef\n    class CleanHtmlOldFilter < Filter\n      def call\n        @doc = at_css('div[role=\"main\"]')\n\n        css('.headerlink').remove\n\n        css('em', 'div.align-center', 'a[href$=\".svg\"]').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('.section').each do |node|\n          node.first_element_child['id'] = node['id'] if node['id']\n          node.before(node.children).remove\n        end\n\n        css('tt').each do |node|\n          node.content = node.content.strip\n          node.name = 'code'\n        end\n\n        css('table[border]').each do |node|\n          node.remove_attribute('border')\n        end\n\n        css('div[class*=\"highlight-\"]').each do |node|\n          node.content = node.content.strip\n          node.name = 'pre'\n          node['data-language'] = node['class'][/highlight\\-(\\w+)/, 1]\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/chef/entries.rb",
    "content": "module Docs\n  class Chef\n    class EntriesFilter < Docs::EntriesFilter\n\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n\n        case slug\n        when /automate/\n          'Chef Automate'\n        when /compliance/\n          'Chef Compliance'\n        when /desktop/\n          'Chef Desktop'\n        when /habitat/\n          'Chef Habitat'\n        when /inspec/\n          'Chef InSpec'\n        when /workstation/\n          'Chef Workstation'\n        when /effortless/\n          'Effortless Pattern'\n        else\n          'Chef Infra'\n        end\n\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/chef/entries_old.rb",
    "content": "module Docs\n  class Chef\n    class EntriesOldFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('.body h1').content\n        name.remove! \"\\u{00b6}\"\n        name.remove! 'About the '\n        name.remove! 'About '\n        name\n      end\n\n      CLIENT_TYPE_BY_SLUG_END_WITH = {\n        'knife_common_options'               => 'Workflow Tools',\n        'knife_using'                        => 'Workflow Tools',\n        'resource_common'                    => 'Cookbooks',\n        'config_rb_knife_optional_settings'  => 'Workflow Tools',\n        'knife_index_rebuild'                => 'Workflow Tools',\n        'handlers'                           => 'Extend Chef',\n        'dsl_recipe'                         => 'Extend Chef',\n        'resource'                           => 'Extend Chef'\n      }\n\n      SERVER_TYPE_BY_SLUG_END_WITH = {\n        'auth'                               => 'Theory & Concepts',\n        'install_server'                     => 'Setup & Config',\n        'install_server_pre'                 => 'Setup & Config',\n        'config_rb_server_optional_settings' => 'Manage the Server',\n        'ctl_chef_server'                    => 'Manage the Server'\n      }\n\n      def get_type\n        if server_page?\n          SERVER_TYPE_BY_SLUG_END_WITH.each do |key, value|\n            return \"Chef Server / #{value}\" if slug.end_with?(key)\n          end\n        else\n          CLIENT_TYPE_BY_SLUG_END_WITH.each do |key, value|\n            return value if slug.end_with?(key)\n          end\n        end\n\n        path = nav_path\n        path.delete('Reference')\n        path = path[0..0]\n        path.unshift('Chef Server') if server_page?\n\n        type = path.join(' / ')\n        type.sub 'Cookbooks / Cookbook', 'Cookbooks /'\n        type\n      end\n\n      def server_page?\n        slug.start_with?(context[:server_path])\n      end\n\n      def nav_path\n        node = at_css(\".nav-docs a[href='#{result[:path].split('/').last}']\")\n        path = []\n        until node['class'] && node['class'].include?('main-item')\n          path.unshift(node.first_element_child.content.strip) if node['class'] && node['class'].include?('has-sub-items')\n          node = node.parent\n        end\n        path.unshift(node.first_element_child.content.strip)\n        path\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/chefclient/clean_html.rb",
    "content": "module Docs\n  class Chefclient\n    class CleanHtmlFilter < Filter\n      def call\n        css('h1 a, h2 a, h3 a').remove\n        doc = at_css('div.body[role=\"main\"]')\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/chefclient/entries.rb",
    "content": "module Docs\n  class Chefclient\n    class EntriesFilter < Docs::EntriesFilter\n\n      ADDITIONAL_URLS = [\n        {\n          slug: 'knife_common_options',\n          type: 'Miscellaneous/Knife',\n          name: 'Common options'\n        },\n        {\n          slug: 'knife_using',\n          type: 'Miscellaneous/Knife',\n          name: 'Using knife'\n        },\n        {\n          slug: 'config_rb_knife_optional_settings',\n          type: 'Miscellaneous/Knife',\n          name: 'Optional settings'\n        },\n        {\n          slug: 'resource_common',\n          name: 'About resources'\n        }\n      ]\n\n      def get_name\n        ADDITIONAL_URLS.each do |url|\n          next unless slug == url[:slug]\n          return url[:name]\n        end\n\n        css('.main-item a').map do |node|\n          next unless node.attributes['href'].to_s == slug\n\n          return node.attributes['title'].to_s\n        end\n        #css('h1').try(:content)\n        return 'adsf'\n      end\n\n      def get_type\n        ADDITIONAL_URLS.each do |url|\n          next unless slug == url[:slug]\n          next unless url[:type].nil?\n          return url[:type]\n        end\n\n        css('.main-item a').map do |node|\n          next unless node.attributes['href'].to_s == slug\n          if node.ancestors('.sub-items')[0].attributes['class'].to_s.include? 'level-2'\n            level2 = node.ancestors('.sub-items')[0].previous_element['title'].to_s\n            level1 = node.ancestors('.sub-items')[0].ancestors('.sub-items')[0].previous_element['title'].to_s\n            return \"#{level1}/#{level2}\"\n          end\n\n          return node.ancestors('.sub-items')[0].previous_element['title'].to_s\n        end\n        'Miscellaneous'\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/click/entries.rb",
    "content": "module Docs\n  class Click\n    class EntriesFilter < Docs::EntriesFilter\n      TYPE_BY_SLUG = {}\n\n      def call\n        if root_page?\n          css('section').each do |node|\n            next if ['documentation', 'api-reference'].include?(node['id'])\n            type = node.at_css('h2').content.strip\n            node.css('li > a').each do |toclink|\n              slug = toclink['href'].split('/')[-2]\n              TYPE_BY_SLUG[slug] = type\n            end\n          end\n        end\n        super\n      end\n\n      def get_name\n        return at_css('h1').content.strip\n      end\n\n      def get_type\n        TYPE_BY_SLUG[slug.split('/').first] || at_css('h1').content.strip\n      end\n\n      def include_default_entry?\n        TYPE_BY_SLUG.include?(slug.split('/').first)\n      end\n\n      def additional_entries\n        return [] if root_page? || TYPE_BY_SLUG.include?(slug.split('/').first)\n\n        if slug == 'api/'\n          entries = []\n          doc.css('> section').each do |section|\n            title = section.at_css('h2').content.strip\n            section.css('dl.py > dt[id]').each do |dt|\n              name = dt['id'].split('.')[1..].join('.')\n              name << '()' if dt.parent.classes.intersect?(['function', 'method', 'classmethod', 'staticmethod'])\n              entries << [name, dt['id'], title]\n            end\n          end\n          return entries\n        end\n\n        (doc.css('> section') || []).map do |section|\n          title = section.at_css('h2').content.strip\n          [title, section['id']]\n        end\n      end\n\n      private\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/click/pre_clean_html.rb",
    "content": "module Docs\n  class Click\n    class PreCleanHtmlFilter < Filter\n      def call\n        # Remove ¶ character from headers\n        css('.headerlink').remove\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/clojure/clean_html.rb",
    "content": "module Docs\n  class Clojure\n    class CleanHtmlFilter < Filter\n      def call\n        if root_page?\n          doc.inner_html = '<h1>Clojure</h1>'\n          return doc\n        end\n\n        @doc = at_css('#content-tag')\n\n        at_css('h1').content = slug.remove('-api')\n\n        css('> div').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('div > h2', 'div > h3').each do |node|\n          node.parent.before(node.parent.children).remove\n        end\n\n        css('#proto-type', '#var-type', '#type-type').each do |node|\n          node.previous_element << node\n          node['class'] = 'type'\n        end\n\n        css('.proto-added', '.var-added', '.proto-deprecated', '.var-deprecated').each do |node|\n          node.content = node.content\n          node.name = 'p'\n        end\n\n        css('.proto-added', '.var-added').each do |node|\n          if node.content == node.next_element.try(:content)\n            node.remove\n          end\n        end\n\n        css('hr', 'br:first-child', 'pre + br', 'h1 + br', 'h2 + br', 'h3 + br', 'p + br', 'br + br').remove\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/clojure/entries.rb",
    "content": "module Docs\n  class Clojure\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        slug.remove('-api')\n      end\n\n      def get_type\n        'Namespaces'\n      end\n\n      def additional_entries\n        css(\".toc-entry-anchor[href^='##{self.name}']\").map do |node|\n          name = node.content\n          id = node['href'].remove('#')\n          type = name == 'clojure.core' ? id.split('/').first : self.name\n          [name, id, type]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/cmake/clean_html.rb",
    "content": "module Docs\n  class Cmake\n    class CleanHtmlFilter < Filter\n      def call\n        if root_page?\n          css('#release-notes', '#index-and-search').remove\n\n          css('h1').each do |node|\n            node.name = 'h2'\n          end\n        end\n\n        css('section').each do |node|\n          node.children.each do |subnode|\n            node.previous = subnode\n          end\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/cmake/entries.rb",
    "content": "module Docs\n  class Cmake\n    class EntriesFilter < Docs::EntriesFilter\n      NAME_BY_SLUG = {\n        'manual/cmake.1' => 'CMake',\n        'manual/ctest.1' => 'CTest',\n        'manual/cpack.1' => 'CPack',\n        'manual/cmake-gui.1' => 'CMake GUI',\n        'manual/ccmake.1' => 'CCMake' }\n\n      TYPE_BY_DIR = {\n        'command' => 'Commands',\n        'guide' => 'Guides',\n        'manual' => 'Manual',\n        'module' => 'Modules',\n        'policy' => 'Policies',\n        'prop_cache' => 'Properties: Cache Entries',\n        'prop_dir' => 'Properties: Directories',\n        'prop_gbl' => 'Properties of Global Scope',\n        'prop_inst' => 'Properties: Installed Files',\n        'prop_sf' => 'Properties: Source Files',\n        'prop_test' => 'Properties: Tests',\n        'prop_tgt' => 'Properties: Targets',\n        'envvar' => 'Environment Variables',\n        'variable' => 'Variables' }\n\n      def get_name\n        if NAME_BY_SLUG.key?(slug)\n          NAME_BY_SLUG[slug]\n        elsif slug =~ /\\Amanual\\/cmake-([\\w\\-]+)\\.7\\z/\n          $1.titleize\n        else\n          dir, name = slug.split('/')\n          name << '()' if dir == 'command'\n          name\n        end\n      end\n\n      def get_type\n        TYPE_BY_DIR[slug.split('/').first]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/codeception/clean_html.rb",
    "content": "module Docs\n  class Codeception\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = doc.at_css('#page')\n\n        css('.algolia__search-content').remove\n\n        css('.algolia__initial-content').each do |node|\n          node.before(node.children).remove\n        end\n\n        while doc.element_children.length == 1\n          doc.first_element_child.before(doc.first_element_child.children).remove\n        end\n\n        if root_page?\n          at_css('h1').content = 'Codeception Documentation'\n        end\n\n        unless at_css('h1')\n          at_css('h2').name = 'h1'\n        end\n\n        unless at_css('h2')\n          css('h3').each { |node| node.name = 'h2' }\n          css('h4').each { |node| node.name = 'h3' }\n        end\n\n        css('.btn-group').remove\n\n        css('.alert:last-child').each do |node|\n          node.remove if node.content.include?('taken from the source code')\n        end\n\n        css('.highlight').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('pre > code').each do |node|\n          node.parent['data-language'] = node['data-lang']\n          node.parent.content = node.parent.content\n        end\n\n        css('.alert-warning').remove\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/codeception/entries.rb",
    "content": "module Docs\n  class Codeception\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = (at_css('.js-algolia__initial-content h1') || at_css('.js-algolia__initial-content h2')).content\n\n        if number = subpath[/\\A\\d+/]\n          name.prepend \"#{number.to_i}. \"\n        end\n\n        name\n      end\n\n      def get_type\n        if subpath =~ /\\d\\d/\n          'Guides'\n        elsif subpath.start_with?('modules')\n          \"Module: #{name}\"\n        elsif name.include?('Util')\n          \"Util Class: #{name.split('\\\\').last}\"\n        else\n          \"Reference: #{name}\"\n        end\n      end\n\n      def additional_entries\n        if type =~ /Module/\n          prefix = \"#{name}::\"\n          pattern = at_css('#actions') ? '#actions ~ h4' : '#page h4'\n        elsif type =~ /Functions/\n          prefix = ''\n          pattern = '#page h4'\n        elsif name =~ /Util/\n          prefix = \"#{name.remove('Codeception\\\\Util\\\\')}::\"\n          pattern = '#page h4'\n        elsif type =~ /(Commands)|(Configuration)/\n          prefix = ''\n          pattern = 'h2'\n        end\n\n        return [] unless pattern\n\n        css(pattern).map do |node|\n          [prefix + node.content, node['id']]\n        end.compact\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/codeceptjs/clean_html.rb",
    "content": "module Docs\n  class Codeceptjs\n    class CleanHtmlFilter < Filter\n      def call\n        if root_page?\n          doc.inner_html = ' '\n          return doc\n        end\n\n        @doc = doc.at_css('article div.content__default')\n\n        css('hr, a.header-anchor').remove\n\n        unless at_css('h1')\n          at_css('h2').name = 'h1'\n        end\n\n        unless at_css('h2')\n          css('h3').each { |node| node.name = 'h2' }\n          css('h4').each { |node| node.name = 'h3' }\n        end\n\n        css('pre > code').each do |node|\n          node.parent['data-language'] = node['class'] if node['class']\n          node.parent.content = node.parent.content\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/codeceptjs/entries.rb",
    "content": "module Docs\n  class Codeceptjs\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content.strip\n      end\n\n      def get_type\n        if subpath == 'helpers/'\n          'Helpers'\n        elsif subpath.start_with?('helpers')\n          \"Helpers: #{name}\"\n        elsif subpath.in? %w(commands/ configuration/ reports/ translation/)\n          'Reference'\n        else\n          'Guide'\n        end\n      end\n\n      def additional_entries\n        return [] unless subpath.start_with?('helpers') && subpath != 'helpers/'\n\n        css('h2, h3').each_with_object [] do |node, entries|\n          next if node['id'] == 'access-from-helpers' || node.content !~ /\\s*[a-z_]/\n          entries << [\"#{node.content} (#{name})\", node['id']]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/codeigniter/entries.rb",
    "content": "module Docs\n  class Codeigniter\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content.strip\n        name.remove! \"\\u{00B6}\"\n        name\n      end\n\n      def get_type\n        type = slug.split('/').first.capitalize\n\n        if type.in? %w(Tutorial Installation Overview General)\n          type.prepend 'User guide: '\n        elsif type.in? %w(Libraries Helpers)\n          type = name.remove %r{\\ (?:Helper|Class|Classes|Library|Driver)\\z}\n        end\n\n        type\n      end\n\n      def additional_entries\n        entries = []\n\n        css('.class', '.interface').each do |node|\n          class_name = node.at_css('dt > .descname').content.split('\\\\').last\n          class_id = node.at_css('dt[id]')['id']\n          entries << [class_name, class_id]\n\n          node.css('.method', '.staticmethod').each do |n|\n            next unless n.at_css('dt[id]')\n            name = n.at_css('.descname').content\n            name = \"#{class_name}::#{name}()\"\n            id = n.at_css('dt[id]')['id']\n            entries << [name, id]\n          end\n        end\n\n        css('.function').each do |node|\n          name = \"#{node.at_css('.descname').content}()\"\n          id = node.at_css('dt[id]')['id']\n          type = self.type.start_with?('User guide') ? 'Functions' : self.type\n          entries << [name, id, type]\n        end\n\n        css('.const').each do |node|\n          name = node.at_css('.descname').content\n          id = node.at_css('dt[id]')['id']\n          type = self.type.start_with?('User guide') ? 'Global Constants' : self.type\n          entries << [name, id, type]\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/coffeescript/clean_html.rb",
    "content": "module Docs\n  class Coffeescript\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('main')\n\n        css('> header', '#resources', '#changelog').remove\n\n        css('section').each do |node|\n          node.first_element_child['id'] = node['id']\n          node.before(node.children).remove\n        end\n\n        css('.uneditable-code-block').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('aside.code-example').each do |node|\n          node.name = 'div'\n          node['class'] = 'code'\n          node.children = node.css('pre')\n          node.remove_attribute('data-example')\n        end\n\n        css('.code pre:first-child').each do |node|\n          node['data-language'] = 'coffeescript'\n        end\n\n        css('.code pre:last-child').each do |node|\n          node['data-language'] = 'javascript'\n        end\n\n        css('pre').each do |node|\n          content = node.content\n          node.content = content\n          unless content.start_with?('coffee ') || content.start_with?('npm ')\n            node['data-language'] ||= 'coffeescript'\n          end\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/coffeescript/clean_html_v1.rb",
    "content": "module Docs\n  class Coffeescript\n    class CleanHtmlV1Filter < Filter\n      def call\n        css('#top', '.minibutton', '.clear').remove\n\n        # Set id attributes on actual elements instead of an empty <span>\n        css('.bookmark').each do |node|\n          node.next_element['id'] = node['id']\n          node.remove\n        end\n\n        # Remove Books, Screencasts, etc.\n        while doc.children.last['id'] != 'resources'\n          doc.children.last.remove\n        end\n        doc.children.last.remove\n\n        # Make proper headings\n        css('.header').each do |node|\n          node.parent.before(node)\n          node.name = 'h3'\n          node['id'] ||= node.content.strip.parameterize\n          node.remove_attribute 'class'\n        end\n\n        # Remove \"Latest Version\" paragraph\n        css('b').each do |node|\n          if node.content =~ /Latest Version/i\n            node.parent.next_element.remove\n            node.parent.remove\n            break\n          end\n        end\n\n        # Remove \"examples can be run\" paragraph\n        css('i').each do |node|\n          if node.content =~ /examples can be run/i\n            node.parent.remove\n            break\n          end\n        end\n\n        # Remove code highlighting\n        css('pre').each do |node|\n          node.content = node.content\n        end\n\n        css('blockquote > pre:first-child:last-child').each do |node|\n          node.parent.before(node).remove\n        end\n\n        css('.code pre:first-child').each do |node|\n          node['data-language'] = 'coffeescript'\n        end\n\n        css('.code pre:last-child').each do |node|\n          node['data-language'] = 'javascript'\n        end\n\n        css('tt').each do |node|\n          node.name = 'code'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/coffeescript/entries.rb",
    "content": "module Docs\n  class Coffeescript\n    class EntriesFilter < Docs::EntriesFilter\n      ENTRIES = [\n        ['coffee command',              'cli',                      'Miscellaneous'],\n        ['->',                          'functions',                'Statements'],\n        ['await',                       'async-functions',          'Statements'],\n        ['if...then...else',            'conditionals',             'Statements'],\n        ['unless',                      'conditionals',             'Statements'],\n        ['... splats',                  'splats',                   'Language'],\n        ['for...in',                    'loops',                    'Statements'],\n        ['for...in...by',               'loops',                    'Statements'],\n        ['for...in...when',             'loops',                    'Statements'],\n        ['for...of',                    'loops',                    'Statements'],\n        ['while',                       'loops',                    'Statements'],\n        ['until',                       'loops',                    'Statements'],\n        ['loop',                        'loops',                    'Statements'],\n        ['do',                          'loops',                    'Statements'],\n        ['Ranges',                      'slices',                   'Language'],\n        ['?',                           'existential-operator',     'Operators'],\n        ['?=',                          'existential-operator',     'Operators'],\n        ['?.',                          'existential-operator',     'Operators'],\n        ['class',                       'classes',                  'Statements'],\n        ['extends',                     'classes',                  'Statements'],\n        ['super',                       'classes',                  'Statements'],\n        ['::',                          'prototypal-inheritance',   'Operators'],\n        ['=>',                          'fat-arrow',                'Statements'],\n        ['yield',                       'generators',               'Statements'],\n        ['switch...when...else',        'switch',                   'Statements'],\n        ['try...catch...finally',       'try',                      'Statements'],\n        ['#{} interpolation',           'strings',                  'Language'],\n        ['Block strings',               'strings',                  'Language'],\n        ['\"\"\"',                         'strings',                  'Language'],\n        ['###',                         'comments',                 'Language'],\n        ['###::',                       'type-annotations',         'Language'],\n        ['///',                         'regexes',                  'Language'],\n        ['import',                      'modules',                  'Language'],\n        ['export',                      'modules',                  'Language']\n      ]\n\n      def additional_entries\n        entries = []\n\n        ENTRIES.each do |entry|\n          raise \"entry not found: #{entry.inspect}\" unless at_css(\"[id='#{entry[1]}']\")\n          entries << entry\n        end\n\n        css('.navbar > nav > .nav-link').each do |node|\n          name = node.content.strip\n          next if name.in?(%w(Overview Changelog Introduction)) || !node['href'].start_with?('#')\n          entries << [name, node['href'].remove('#'), 'Miscellaneous']\n\n          if name == 'Language Reference'\n            node.next_element.css('.nav-link').each do |n|\n              entries << [n.content, n['href'].remove('#'), name]\n            end\n          end\n        end\n\n        at_css('#operators table').css('td:first-child > code').each do |node|\n          name = node.content.strip\n          next if %w(true false yes no on off this).include?(name)\n          name.sub! %r{\\Aa (.+) b\\z}, '\\1'\n          name = 'for...from' if name == 'for a from b'\n          id = name_to_id(name)\n          node['id'] = id\n          entries << [name, id, 'Operators']\n        end\n\n        entries\n      end\n\n      def name_to_id(name)\n        case name\n          when '**' then 'pow'\n          when '//' then 'floor'\n          when '%%' then 'mod'\n          when '@' then 'this'\n          else name.parameterize\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/coffeescript/entries_v1.rb",
    "content": "module Docs\n  class Coffeescript\n    class EntriesV1Filter < Docs::EntriesFilter\n      ENTRIES = [\n        ['coffee command',              'usage',                    'Miscellaneous'],\n        ['Literate mode',               'literate',                 'Miscellaneous'],\n        ['Functions',                   'literals',                 'Language'],\n        ['->',                          'literals',                 'Statements'],\n        ['Objects and arrays',          'objects-and-arrays',       'Language'],\n        ['Lexical scoping',             'lexical-scope',            'Language'],\n        ['if...then...else',            'conditionals',             'Statements'],\n        ['unless',                      'conditionals',             'Statements'],\n        ['... splats',                  'splats',                   'Language'],\n        ['for...in',                    'loops',                    'Statements'],\n        ['for...in...by',               'loops',                    'Statements'],\n        ['for...in...when',             'loops',                    'Statements'],\n        ['for...of',                    'loops',                    'Statements'],\n        ['while',                       'loops',                    'Statements'],\n        ['until',                       'loops',                    'Statements'],\n        ['loop',                        'loops',                    'Statements'],\n        ['do',                          'loops',                    'Statements'],\n        ['Array slicing and splicing',  'slices',                   'Language'],\n        ['Ranges',                      'slices',                   'Language'],\n        ['Expressions',                 'expressions',              'Language'],\n        ['?',                           'existential-operator',     'Operators'],\n        ['?=',                          'existential-operator',     'Operators'],\n        ['?.',                          'existential-operator',     'Operators'],\n        ['class',                       'classes',                  'Statements'],\n        ['extends',                     'classes',                  'Operators'],\n        ['super',                       'classes',                  'Statements'],\n        ['::',                          'classes',                  'Operators'],\n        ['Destructuring assignment',    'destructuring',            'Language'],\n        ['Bound Functions',             'fat-arrow',                'Language'],\n        ['Generator Functions',         'fat-arrow',                'Language'],\n        ['=>',                          'fat-arrow',                'Statements'],\n        ['yield',                       'fat-arrow',                'Statements'],\n        ['for...from',                  'fat-arrow',                'Statements'],\n        ['Embedded JavaScript',         'embedded',                 'Language'],\n        ['switch...when...else',        'switch',                   'Statements'],\n        ['try...catch...finally',       'try-catch',                'Statements'],\n        ['Chained comparisons',         'comparisons',              'Language'],\n        ['#{} interpolation',           'strings',                  'Language'],\n        ['Block strings',               'strings',                  'Language'],\n        ['\"\"\"',                         'strings',                  'Language'],\n        ['Block comments',              'strings',                  'Language'],\n        ['###',                         'strings',                  'Language'],\n        ['Tagged Template Literals',    'tagged-template-literals', 'Language'],\n        ['Block regexes',               'regexes',                  'Language'],\n        ['///',                         'regexes',                  'Language'],\n        ['Modules',                     'modules',                  'Language'],\n        ['import',                      'modules',                  'Language'],\n        ['export',                      'modules',                  'Language'],\n        ['cake command',                'cake',                     'Miscellaneous'],\n        ['Cakefile',                    'cake',                     'Miscellaneous'],\n        ['Source maps',                 'source-maps',              'Miscellaneous']\n      ]\n\n      def additional_entries\n        entries = ENTRIES.dup\n\n        # Operators\n        at_css('#operators ~ table').css('td:first-child > code').each do |node|\n          node.content.split(', ').each do |name|\n            next if %w(true false yes no on off this).include?(name)\n            name.sub! %r{\\Aa (.+) b\\z}, '\\1'\n            id = name_to_id(name)\n            node['id'] = id\n            entries << [name, id, 'Operators']\n          end\n        end\n\n        entries\n      end\n\n      def name_to_id(name)\n        case name\n          when '**' then 'pow'\n          when '//' then 'floor'\n          when '%%' then 'mod'\n          when '@' then 'this'\n          else name.parameterize\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/composer/clean_html.rb",
    "content": "module Docs\n  class Composer\n    class CleanHtmlFilter < Filter\n      def call\n        # Remove unneeded elements\n        css('#searchbar, .toc, .fork-and-edit, .anchor').remove\n\n        # Fix the home page titles\n        if subpath == ''\n          css('h1').each do |node|\n            node.name = 'h2'\n          end\n\n          # Add a main title before the first subtitle\n          at_css('h2').before('<h1>Composer</h1>')\n        end\n\n        # Code blocks\n        css('pre').each do |node|\n          code = node.at_css('code[class]')\n\n          unless code.nil?\n            node['data-language'] = 'javascript' if code['class'].include?('javascript')\n            node['data-language'] = 'php' if code['class'].include?('php')\n          end\n\n          node.content = node.content.strip\n          node.remove_attribute('class')\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/composer/entries.rb",
    "content": "module Docs\n  class Composer\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        title = at_css('h1').content\n        title = \"#{Integer(subpath[1]) + 1}. #{title}\" if type == 'Book'\n        title\n      end\n\n      def get_type\n        return 'Articles' if subpath.start_with?('articles/')\n        'Book'\n      end\n\n      def additional_entries\n        entries = []\n\n        if subpath == '04-schema.md' # JSON Schema\n            css('h3').each do |node|\n              name = node.content.strip\n              name.remove!(' (root-only)')\n              entries << [name, node['id'], 'JSON Schema']\n            end\n        end\n\n        if subpath == '06-config.md' # Composer config\n            css('h2').each do |node|\n              entries << [node.content.strip, node['id'], 'Configuration Options']\n            end\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/cordova/clean_html.rb",
    "content": "module Docs\n  class Cordova\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('#page-toc-source') || at_css('.page-content > div')\n\n        at_css('h1').content = 'Apache Cordova' if root_page?\n\n        css('hr', '.content-header', 'button', '.docs-alert').remove\n\n        css('.home', '#page-toc-source', '.highlight', 'th h2').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('img[src*=\"travis-ci\"]').each do |node|\n          node.ancestors('p, a').first.remove\n        end\n\n        css('pre').each do |node|\n          node['data-language'] = node.at_css('code')['data-lang']\n          node.content = node.content.strip_heredoc\n        end\n\n        css('> .alert + h1').each do |node|\n          node.previous_element.before(node)\n        end\n\n        css('h1, h2, h3, h4, h5, h6').each do |node|\n          node['id'] = node.content.strip.parameterize\n        end\n\n        css('> table:first-child + h1').each do |node|\n          node.previous_element.remove\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/cordova/clean_html_core.rb",
    "content": "module Docs\n  class Cordova\n    class CleanHtmlCoreFilter < Filter\n      def call\n        css('script', 'style', 'link').remove\n        xpath('descendant::comment()').remove\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/cordova/entries.rb",
    "content": "module Docs\n  class Cordova\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        return 'CLI' if slug == 'reference/cordova-cli/index'\n        name = at_css('#page-toc-source').at_css('h1, h2').content.strip\n        name.remove! ' Guide'\n        name\n      end\n\n      def get_type\n        return 'Platforms' if subpath.include?('guide/platforms/')\n        return 'Plugins' if name.start_with?('cordova-plugin')\n        at_css('.site-toc .this-page').ancestors('li').last.at_css('span').content\n      end\n\n      def additional_entries\n        case slug\n        when 'reference/cordova-cli/index'\n          css('#page-toc-source h2').each_with_object [] do |node, entries|\n            name = node.content.strip\n            id = name.parameterize\n            next unless name =~ /cordova .+ command/\n            name.remove! ' command'\n            entries << [name, id, 'Reference: CLI']\n          end\n        when 'config_ref/index'\n          namespace = ''\n          css('#page-toc-source > *').each_with_object [] do |node, entries|\n            case node.name\n            when 'h1'\n              name = node.content.strip\n              next unless name =~ /\\A[a-z]+\\z/\n              entries << [\"<#{name}>\", name.parameterize, 'Reference: config.xml']\n            when 'h2'\n              name = node.content.strip\n              next unless name =~ /\\A[a-z]+\\z/\n              namespace = name\n              entries << [\"<#{name}>\", name.parameterize, 'Reference: config.xml']\n            when 'h3'\n              name = node.content.strip\n              next unless name =~ /\\A[a-z]+\\z/\n              entries << [\"<#{namespace}> <#{name}>\", name.parameterize, 'Reference: config.xml']\n            end\n          end\n        when 'cordova/events/events'\n          css('#page-toc-source h2').each_with_object [] do |node, entries|\n            name = node.content.strip\n            id = name.parameterize\n            next unless name =~ /\\A[a-z]+\\z/\n            entries << [name, id, 'Reference: events']\n          end\n        else\n          []\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/core/apply_base_url.rb",
    "content": "module Docs\n  class ApplyBaseUrlFilter < Filter\n    URL_ATTRIBUTES = { 'a': 'href', 'img': 'src', 'iframe': 'src' }\n    SCHEME_RGX = /\\A[^:\\/?#]+:/\n\n    def call\n      base_url = at_css('base').try(:[], 'href')\n      return doc unless base_url\n\n      URL_ATTRIBUTES.each_pair do |tag, attribute|\n        css(tag).each do |node|\n          next unless value = node[attribute]\n          next if !relative_url_string?(value) || value[0] == '/'.freeze\n          node[attribute] = \"#{base_url}#{node[attribute]}\"\n        end\n      end\n\n      doc\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/core/attribution.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class AttributionFilter < Filter\n    def call\n      html << attribution_html if attribution\n      html\n    end\n\n    def attribution\n      if context[:attribution].is_a?(String)\n        context[:attribution]\n      else\n        context[:attribution].call(self)\n      end\n    end\n\n    def attribution_html\n      <<-HTML.strip_heredoc\n      <div class=\"_attribution\">\n        <p class=\"_attribution-p\">\n          #{attribution.strip_heredoc.delete \"\\n\"}<br>\n          #{attribution_link}\n        </p>\n      </div>\n      HTML\n    end\n\n    def attribution_link\n      unless base_url.host == 'localhost'\n        %(<a href=\"#{current_url}\" class=\"_attribution-link\">#{current_url}</a>)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/core/clean_html.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class CleanHtmlFilter < Filter\n    def call\n      css('script', 'style', 'link').remove\n      xpath('descendant::comment()').remove\n      xpath('./text()', './/text()[not(ancestor::pre) and not(ancestor::code) and not(ancestor::div[contains(concat(\" \", normalize-space(@class), \" \"), \" prism \")])]').each do |node|\n        content = node.content\n        next unless content.valid_encoding?\n        content.gsub! %r{[[:space:]]+}, ' '\n        node.content = content\n      end\n      doc\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/core/clean_local_urls.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class CleanLocalUrlsFilter < Filter\n    def call\n      if base_url.host == 'localhost'\n        css('img[src^=\"http://localhost\"]', 'iframe[src^=\"http://localhost\"]').remove\n\n        css('a[href^=\"http://localhost\"]').each do |node|\n          node.name = 'span'\n          node.remove_attribute 'href'\n        end\n      end\n\n      doc\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/core/clean_text.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class CleanTextFilter < Filter\n    EMPTY_NODES_RGX = /<(?!td|th|iframe|mspace|rect|path|ellipse|line|polyline)(\\w+)[^>]*>[[:space:]]*<\\/\\1>/\n\n    def call\n      return html if context[:clean_text] == false\n      html.strip!\n      while html.gsub!(EMPTY_NODES_RGX, ''); end\n      html\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/core/container.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class ContainerFilter < Filter\n    class ContainerNotFound < StandardError; end\n\n    def call\n      container = context[:container]\n      container = container.call self if container.is_a? Proc\n\n      if container\n        doc.at_css(container) || raise(ContainerNotFound, \"element '#{container}' could not be found in the document, url=#{current_url}\")\n      elsif doc.name == 'document'\n        doc.at_css('body')\n      else\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/core/entries.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class EntriesFilter < Filter\n    def call\n      result[:entries] = entries\n      doc\n    end\n\n    def entries\n      entries = []\n      entries << default_entry if root_page? || include_default_entry?\n      entries.concat(additional_entries)\n      build_entries(entries)\n    end\n\n    def include_default_entry?\n      true\n    end\n\n    def default_entry\n      [name]\n    end\n\n    def additional_entries\n      []\n    end\n\n    def name\n      return @name if defined? @name\n      @name = root_page? ? nil : get_name\n    end\n\n    def get_name\n      slug.to_s.gsub('_', ' ').gsub('/', '.').squish!\n    end\n\n    def type\n      return @type if defined? @type\n      @type = root_page? ? nil : get_type\n    end\n\n    def get_type\n      nil\n    end\n\n    def path\n      result[:path]\n    end\n\n    def build_entries(entries)\n      entries.map do |attributes|\n        build_entry(*attributes)\n      end\n    end\n\n    def build_entry(name, frag = nil, type = nil)\n      type ||= self.type\n      path = frag ? (frag.include?('#') ? frag : \"#{self.path}##{frag}\") : self.path\n      Entry.new(name, path, type)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/core/images.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'base64'\nrequire 'image_optim'\n\nmodule Docs\n  class ImagesFilter < Filter\n    include Instrumentable\n\n    DEFAULT_MAX_SIZE = 120_000 # 120 kilobytes\n\n    def self.optimize_image_data(data)\n      @image_optim ||= ImageOptim.new\n      @image_optim.optimize_image_data(data)\n    end\n\n    def self.cache\n      @cache ||= {}\n    end\n\n    def call\n      return doc if context[:download_images] == false\n\n      doc.css('img[src]').each do |node|\n        src = node['src']\n\n        if self.class.cache.key?(src)\n          node['src'] = self.class.cache[src] unless self.class.cache[src] == false\n          next\n        end\n\n        self.class.cache[src] = false\n\n        next if src.start_with? 'data:image/'\n        url = Docs::URL.parse(src)\n        url.scheme = 'https' if url.scheme.nil?\n        next unless url.scheme == 'http' || url.scheme == 'https'\n\n        begin\n          Request.run(url) do |response|\n            unless response.success?\n              instrument 'broken.image', url: url, status: response.code\n              next\n            end\n\n            unless response.mime_type.start_with?('image/')\n              instrument 'invalid.image', url: url, content_type: response.mime_type\n              next\n            end\n\n            size = response.content_length\n\n            if size > (context[:max_image_size] || DEFAULT_MAX_SIZE)\n              instrument 'too_big.image', url: url, size: size\n              next\n            end\n\n            image = response.body\n\n            unless context[:optimize_images] == false\n              image = self.class.optimize_image_data(image) || image\n            end\n\n            size = image.bytesize\n\n            if size > (context[:max_image_size] || DEFAULT_MAX_SIZE)\n              instrument 'too_big.image', url: url, size: size\n              next\n            end\n\n            image = Base64.strict_encode64(image)\n            image.prepend \"data:#{response.mime_type};base64,\"\n            node['src'] = self.class.cache[src] = image\n          end\n        rescue => exception\n          instrument 'error.image', url: url, exception: exception\n        end\n      end\n\n      doc\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/core/inner_html.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class InnerHtmlFilter < Filter\n    def call\n      html = doc.inner_html\n      html = html.encode('UTF-16', invalid: :replace, replace: '').encode('UTF-8') unless html.valid_encoding?\n      html\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/core/internal_urls.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class InternalUrlsFilter < Filter\n    def call\n      result[:subpath] = subpath\n\n      unless skip_links?\n        follow_links? ? update_and_follow_links : update_links\n      end\n\n      doc\n    end\n\n    def update_links\n      css('a').each do |link|\n        next if context[:skip_link].is_a?(Proc) && context[:skip_link].call(link)\n        next unless url = to_internal_url(link['href'])\n        link['href'] = internal_path_to(url)\n        yield url if block_given?\n      end\n    end\n\n    def update_and_follow_links\n      urls = result[:internal_urls] = []\n      update_links do |url|\n        urls << url.merge!(fragment: nil).to_s\n      end\n      urls.uniq!\n    end\n\n    def skip_links?\n      return context[:skip_links].call(self) if context[:skip_links].is_a?(Proc)\n      return true if context[:skip_links]\n      false\n    end\n\n    def follow_links?\n      return false if context[:follow_links] == false\n      return false if context[:follow_links].is_a?(Proc) && context[:follow_links].call(self) == false\n      true\n    end\n\n    def to_internal_url(str)\n      return unless (url = parse_url(str)) && (subpath = subpath_to(url))\n      normalize_subpath(subpath)\n      return if skip_subpath?(subpath)\n      normalize_url(url, subpath)\n      url\n    end\n\n    def parse_url(str)\n      str && absolute_url_string?(str) && URL.parse(str)\n    rescue URI::InvalidURIError\n      nil\n    end\n\n    def normalize_subpath(path)\n      case context[:trailing_slash]\n      when true\n        path << '/' unless path.end_with?('/')\n      when false\n        path.slice!(-1) if path.end_with?('/')\n      end\n    end\n\n    def skip_subpath?(path)\n      if context[:only] || context[:only_patterns]\n        return true unless context[:only].try(:any?) { |value| path.casecmp(value) == 0 } ||\n                           context[:only_patterns].try(:any?) { |value| path =~ value }\n      end\n\n      if context[:skip] || context[:skip_patterns]\n        return true if context[:skip].try(:any?) { |value| path.casecmp(value) == 0 } ||\n                       context[:skip_patterns].try(:any?) { |value| path =~ value }\n      end\n\n      false\n    end\n\n    def normalize_url(url, subpath)\n      url.merge! path: base_url.path + subpath\n      url.normalize!\n    end\n\n    def internal_path_to(url)\n      url = index_url if url == root_url\n      path = effective_url.relative_path_to(url)\n      path = clean_path(path) if context[:decode_and_clean_paths]\n      URL.new(path: path, query: url.query, fragment: url.fragment).to_s\n    end\n\n    def index_url\n      @index_url ||= base_url.merge path: File.join(base_url.path, '')\n    end\n\n    def effective_url\n      @effective_url ||= current_url == root_url ? index_url : current_url\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/core/normalize_paths.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class NormalizePathsFilter < Filter\n    def call\n      result[:path] = path\n      result[:store_path] = store_path\n\n      css('a').each do |link|\n        href = link['href']\n        link['href'] = normalize_href(href) if href && relative_url_string?(href)\n\n        xlink_href = link['xlink:href']\n        link['xlink:href'] = normalize_href(xlink_href) if xlink_href && relative_url_string?(xlink_href)\n      end\n\n      doc\n    end\n\n    def path\n      @path ||= root_page? ? 'index' : normalized_subpath\n    end\n\n    def store_path\n      File.extname(path) != '.html' ? \"#{path}.html\" : path\n    end\n\n    def normalized_subpath\n      normalize_path subpath.remove(/\\A\\//)\n    end\n\n    def normalize_href(href)\n      url = URL.parse(href)\n      url.send(:set_path, normalize_path(url.path))\n      url\n    rescue URI::InvalidURIError\n      href\n    end\n\n    def normalize_path(path)\n      path = path.downcase\n\n      if context[:decode_and_clean_paths]\n        path = CGI.unescape(path)\n        path = clean_path(path)\n      end\n\n      if path == '.'\n        'index'\n      elsif path.end_with? '/'\n        \"#{path}index\"\n      elsif path.end_with? '.html'\n        path[0..-6]\n      else\n        path\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/core/normalize_urls.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class NormalizeUrlsFilter < Filter\n    ATTRIBUTES = { a: 'href', img: 'src', iframe: 'src' }\n\n    def call\n      ATTRIBUTES.each_pair do |tag, attribute|\n        update_attribute(tag, attribute)\n      end\n      doc\n    end\n\n    def update_attribute(tag, attribute)\n      css(tag.to_s).each do |node|\n        next unless value = node[attribute]\n        next if fragment_url_string?(value) || data_url_string?(value)\n        node[attribute] = normalize_url(value) || (tag == :iframe ? value : '#')\n      end\n    end\n\n    def normalize_url(str)\n      str.strip!\n      str.gsub!(' ', '%20')\n      str = context[:fix_urls_before_parse].call(str) if context[:fix_urls_before_parse]\n      url = to_absolute_url(str)\n\n      while new_url = fix_url(url)\n        url = new_url\n      end\n\n      url.to_s\n    rescue URI::InvalidURIError\n      nil\n    end\n\n    def to_absolute_url(str)\n      url = URL.parse(str)\n      url.relative? ? current_url.join(url) : url\n    end\n\n    def fix_url(url)\n      if context[:redirections]\n        url = URL.parse(url)\n        path = url.path ? url.path.downcase : nil\n\n        if context[:redirections].key?(path)\n          url.path = context[:redirections][path]\n          return url\n        end\n      end\n\n      if context[:replace_paths]\n        url = URL.parse(url)\n        path = subpath_to(url)\n\n        if context[:replace_paths].key?(path)\n          url.path = url.path.sub %r[#{path}\\z], context[:replace_paths][path]\n          return url\n        end\n      end\n\n      if context[:replace_urls]\n        url = url.to_s\n\n        if context[:replace_urls].key?(url)\n          return context[:replace_urls][url]\n        end\n      end\n\n      if context[:fix_urls]\n        url = url.to_s\n        orig_url = url.dup\n        new_url = context[:fix_urls].call(url)\n        return new_url if new_url != orig_url\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/core/parse_cf_email.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class ParseCfEmailFilter < Filter\n    def call\n      css('.__cf_email__').each do |node|\n        str = node['data-cfemail']\n        mask = \"0x#{str[0..1]}\".hex | 0\n        result = ''\n\n        str.chars.drop(2).each_slice(2) do |slice|\n          result += \"%\" + \"0#{(\"0x#{slice.join}\".hex ^ mask).to_s(16)}\"[-2..-1]\n        end\n\n        node.replace(URI.decode_www_form_component(result))\n      end\n\n      doc\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/core/title.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class TitleFilter < Filter\n    def call\n      title = self.title\n      doc.child.before node(title) if title\n      doc\n    end\n\n    def title\n      if !context[:root_title].nil? && root_page?\n        context[:root_title]\n      elsif !context[:title].nil?\n        context[:title].is_a?(Proc) ? context[:title].call(self) : context[:title]\n      else\n        default_title\n      end\n    end\n\n    def default_title\n      result[:entries].try(:first).try(:name)\n    end\n\n    def node(content)\n      node = Nokogiri::XML::Node.new 'h1', doc.document\n      node.content = content\n      node\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/couchdb/clean_html.rb",
    "content": "module Docs\n  class Couchdb\n    class CleanHtmlFilter < Filter\n      def call\n        css('.section-number').remove\n        css('.headerlink').remove\n\n        css('.sig-name').each do |node|\n          node.name = 'code'\n        end\n\n        css('pre').each do |node|\n          node.content = node.content.strip\n\n          classes = node.parent.parent.classes\n          if classes.include? 'highlight-bash'\n            node['data-language'] = 'bash'\n          else\n            node['data-language'] = 'javascript'\n          end\n\n          node.parent.parent.replace(node)\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/couchdb/entries.rb",
    "content": "module Docs\n  class Couchdb\n    class EntriesFilter < Docs::EntriesFilter\n      SLUG_MAP = {\n        'api' => 'API',\n        'json' => 'JSON Structures',\n        'cluster' => 'Cluster Management',\n        'replication' => 'Replication',\n        'maintenance' => 'Maintenance',\n        'partitioned' => 'Partitioned Databases'\n      }\n\n      def get_name\n        at_css('h1').content.gsub(/\\P{ASCII}/, '').split('.').last\n      end\n\n      def get_type\n        if slug.start_with?('ddocs/views')\n          'Views'\n        elsif slug.start_with?('ddocs')\n          'Design Documents'\n        else\n          SLUG_MAP[slug[/^(.+?)[-\\/]/, 1]] || name\n        end\n      end\n\n      def additional_entries\n        needs_breakup = [\n          'JSON Structure Reference',\n          'Design Documents',\n          'Partitioned Databases'\n        ]\n\n        if needs_breakup.include?(name)\n          entries = []\n\n          css('section > section').each do |node|\n            h2 = node.at_css('h2')\n\n            if h2.present?\n              name = node.at_css('h2').content.split('.').last\n              entries << [name, node['id']]\n            end\n          end\n\n          entries\n        else\n          []\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/cpp/entries.rb",
    "content": "module Docs\n  class Cpp\n    class EntriesFilter < Docs::EntriesFilter\n      @@duplicate_names = []\n\n      REPLACE_NAMES = {\n        'Error directive' => '#error directive',\n        'Filename and line information' => '#line directive',\n        'Implementation defined behavior control' => '#pragma directive',\n        'Replacing text macros' => '#define directive',\n        'Source file inclusion' => '#include directive' }\n\n      def get_name\n        name = at_css('#firstHeading').content.strip\n        name = format_name(name)\n        name = name.split(',').first\n        name\n      end\n\n      def get_type\n        if at_css('#firstHeading').content.include?('C++ keyword')\n          'Keywords'\n        elsif subpath.start_with?('experimental')\n          'Experimental libraries'\n        elsif subpath.start_with?('language/')\n          'Language'\n        elsif subpath.start_with?('freestanding')\n          'Utilities'\n        elsif type = at_css('.t-navbar > div:nth-child(4) > :first-child').try(:content)\n          type.strip!\n          type.remove! ' library'\n          type.remove! ' utilities'\n          type.remove! 'C++ '\n          type.capitalize!\n          type\n        end\n      end\n\n      def additional_entries\n        return [] if root_page? || self.name.start_with?('operators')\n        names = at_css('#firstHeading').content.remove(%r{\\(.+?\\)}).split(', ')[1..-1]\n        names.each(&:strip!).reject! do |name|\n          name.size <= 2 || name == '...' || name =~ /\\A[<>]/ || name.start_with?('operator')\n        end\n        names.map { |name| [format_name(name)] }\n      end\n\n      def format_name(name)\n        name.remove! 'C++ concepts: '\n        name.remove! 'C++ keywords: '\n        name.remove! 'C++ ' unless name == 'C++ language'\n        name.remove! %r{\\s\\(.+\\)}\n\n        name.sub! %r{\\AStandard library header <(.+)>\\z}, '\\1'\n        name.sub! %r{(<[^>]+>::)}, '::'\n\n        if name.include?('operator') && name.include?(',')\n          name.sub!(%r{operator.+([\\( ])}, 'operators (') || name.sub!(%r{operator.+}, 'operators')\n          name.sub! '  ', ' '\n          name << ')' unless name.last == ')' || name.exclude?('(')\n          name.sub! '()', ''\n          name.sub! %r{\\(.+\\)}, '' if !name.start_with?('operator') && name.length > 50\n        end\n\n        REPLACE_NAMES[name] || name\n      end\n\n      # Avoid duplicate pages, these duplicate page are the same page for\n      # multiple functions that are organized in the same page because provide\n      # similar behavior but have different name.\n      def entries\n        entries = []\n\n        if !(@@duplicate_names.include?(name))\n          @@duplicate_names.push(name)\n          entries << default_entry if root_page? || include_default_entry?\n          entries.concat(additional_entries)\n          build_entries(entries)\n        end\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/cppref/clean_html.rb",
    "content": "module Docs\n  class Cppref\n    class CleanHtmlFilter < Filter\n      def call\n        css('h1').remove if root_page?\n\n        css('.t-dcl-rev-aux td[rowspan]').each do |node|\n          rowspan = node['rowspan'].to_i\n          node['rowspan'] = node.ancestors('tbody').css('tr').length if rowspan > 3\n        end\n\n        css('#siteSub', '#contentSub', '.printfooter', '.t-navbar', '.editsection', '#toc',\n            '.t-dsc-sep', '.t-dcl-sep', '#catlinks', '.ambox-notice', '.mw-cite-backlink',\n            '.t-sdsc-sep:first-child:last-child', '.t-example-live-link',\n            '.t-dcl-rev-num > .t-dcl-rev-aux ~ tr:not(.t-dcl-rev-aux) > td:nth-child(2)').remove\n\n        css('#bodyContent', '.mw-content-ltr', 'span[style]', 'div[class^=\"t-ref\"]', '.t-image',\n            'th > div', 'td > div', '.t-dsc-see', '.mainpagediv', 'code > b', 'tbody').each do |node|\n          node.before(node.children).remove\n        end\n\n        parents = css('div > ul').map(&:parent).uniq\n        parents.each do |parent|\n          parent.before(parent.children).remove\n        end\n\n        css('dl > dd:first-child:last-child > ul:first-child:last-child').each do |node|\n          dl = node.parent.parent\n          if dl.previous_element && dl.previous_element.name == 'ul'\n            dl.previous_element << node\n            dl.remove\n          else\n            dl.before(node).remove\n          end\n        end\n\n        css('dl > dd:first-child:last-child').each do |node|\n          node.parent.before(node.children).remove\n        end\n\n        css('ul').each do |node|\n          while node.next_element && node.next_element.name == 'ul'\n            node << node.next_element.children\n            node.next_element.remove\n          end\n        end\n\n        css('h2 > span[id]', 'h3 > span[id]', 'h4 > span[id]', 'h5 > span[id]', 'h6 > span[id]').each do |node|\n          node.parent['id'] = node['id']\n          node.before(node.children).remove\n        end\n\n        css('table[style]', 'th[style]', 'td[style]').remove_attr('style')\n        css('table[cellpadding]').remove_attr('cellpadding')\n\n        css('.t-dsc-hitem > td', '.t-dsc-header > td').each do |node|\n          node.name = 'th'\n          node.content = ' ' if node.content.empty?\n        end\n\n        css('tt', 'span > span.source-cpp', 'span.t-c', 'span.t-lc', 'span.t-dsc-see-tt', 'div.t-li1 > span.source-cpp', 'div.t-li2 > span.source-cpp', 'div.t-li3 > span.source-cpp').each do |node|\n          node.name = 'code'\n          node.remove_attribute('class')\n          node.content = node.content unless node.at_css('a')\n        end\n\n        css('div > span.source-cpp').each do |node|\n          node.name = 'pre'\n          node.inner_html = node.inner_html.gsub('<br>', \"\\n\")\n          node.content = node.content\n        end\n\n        css('div > a > img[alt=\"About this image\"]').each do |node|\n          node.parent.parent.remove\n        end\n\n        css('area[href]').each do |node|\n          node['href'] = node['href'].remove('.html')\n        end\n\n        css('p').each do |node|\n          while node.next && (node.next.text? || node.next.name == 'a' || node.next.name == 'code')\n            node << node.next\n          end\n          node.inner_html = node.inner_html.strip\n          node.remove if node.content.blank? && !node.at_css('img')\n        end\n\n        css('pre').each do |node|\n          node['data-language'] = if node['class'].try(:include?, 'cpp') || node.parent['class'].try(:include?, 'cpp')\n            'cpp'\n          else\n            'c'\n          end\n          node.remove_attribute('class')\n          node.content = node.content.gsub(\"\\t\", ' ' * 8)\n        end\n\n        css('code code', '.mw-geshi').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('h1 ~ .fmbox').each do |node|\n          node.name = 'div'\n          node.content = node.content\n        end\n\n        css('img').each do |node|\n          node['src'] = node['src'].sub! %r{https://upload.cppreference.com/mwiki/(images/[^\"']+?)}, 'http://upload.cppreference.com/mwiki/\\1'\n        end\n\n        css('.t-su.t-su-b').each do |node|\n          node.inner_html = node.inner_html.gsub('<br>', '')\n          node.name = 'sub'\n        end\n\n        css('.t-su:not(.t-su-b)').each do |node|\n          node.inner_html = node.inner_html.gsub('<br>', '')\n          node.name = 'sup'\n        end\n\n        # temporary solution due lack of mathjax/mathml support\n        css('.t-mfrac').each do |node|\n          fraction = Nokogiri::XML::Node.new('span', doc.document)\n\n          node.css('td').each do |node|\n            fraction.add_child(\"<span>#{node.content}</span>\")\n          end\n\n          fraction.last_element_child().before(\"<span>/</span>\")\n\n          node.before(fraction)\n          node.remove\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/cppref/fix_code.rb",
    "content": "module Docs\n  class Cppref\n    class FixCodeFilter < Filter\n      def call\n        css('div > span.source-c', 'div > span.source-cpp').each do |node|\n          if (node.parent.classes||[]).none?{|className| ['t-li1','t-li2','t-li3'].include?(className) }\n            node.inner_html = node.inner_html.gsub(/<br>\\n?/, \"\\n\").gsub(\"\\n</p>\\n\", \"</p>\\n\")\n            node.parent.name = 'pre'\n            node.parent['class'] = node['class']\n            node.parent.content = node.content\n          end\n        end\n\n        nbsp = Nokogiri::HTML('&nbsp;').text\n        css('pre').each do |node|\n          node.content = node.content.gsub(nbsp, ' ')\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/crystal/clean_html.rb",
    "content": "module Docs\n  class Crystal\n    class CleanHtmlFilter < Filter\n      def call\n        current_url.path.start_with?('/reference/') ? book : api\n        doc\n      end\n\n      def book\n        @doc = at_css('main article')\n\n        css('.headerlink').remove\n\n        css('pre > code').each do |node|\n          node.parent['data-language'] = 'crystal'\n          node.parent['data-language'] = node['class'][/lang-(\\w+)/, 1] if node['class']\n          node.parent.content = node.parent.content\n        end\n      end\n\n      def api\n        @doc = at_css('.main-content')\n\n        at_css('h1 + p').remove if current_url.path == \"/api/#{release}/index.html\"\n\n        css('a[href=\"https://manas.tech/\"]').remove\n\n        css('.method-permalink', '.doc + br', 'hr', 'a > br', 'div + br').remove\n\n        css('pre > code').each do |node|\n          node.parent['data-language'] = 'crystal'\n          node.parent.content = node.parent.content\n        end\n\n        css('span').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('div.signature').each do |node|\n          node.name = 'h3'\n          node.inner_html = node.inner_html.strip\n        end\n\n        css('.entry-detail a:contains(\"View source\")').each do |node|\n          node['class'] = 'view-source'\n          node.content = 'Source'\n          parent = node.parent\n          node.ancestors('.entry-detail').first.at_css('h3') << node\n          parent.remove\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/crystal/entries.rb",
    "content": "module Docs\n  class Crystal\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        if current_url.path.start_with?('/reference/')\n          name = at_css('main h1').content.strip\n          name.remove! '¶'\n\n          if current_url.path.start_with?('/reference/') && slug.match?('syntax_and_semantics')\n            name.prepend \"#{slug.split('/')[2].titleize}: \" if slug.split('/').length > 3\n          elsif slug.split('/').length > 1\n            chapter = slug.split('/')[1].titleize.capitalize\n            name.prepend \"#{chapter}: \" unless name == chapter\n          end\n\n          name\n        else\n          return at_css('h1').content.strip unless at_css('.type-name')\n          name = at_css('.type-name').children.reject { |n| n.matches?('.kind') }\n          name.map! { |n| n.text.strip }\n          name.reject! &:empty?\n          name = name.join\n          name.remove! %r{\\(.*\\)}\n          name.strip!\n          name\n        end\n      end\n\n      def get_type\n        return if current_url.path == \"/api/#{release}/index.html\"\n\n        if current_url.path.start_with?('/reference/') && slug.match?('syntax_and_semantics')\n          'Book: Language'\n        elsif current_url.path.start_with?('/reference/')\n          'Book'\n        else\n          hierarchy = at_css('.superclass-hierarchy')\n          if hierarchy && hierarchy.content.include?('Exception')\n            'Exceptions'\n          else\n            type = at_css('.types-list > ul > .current > a').content\n            type = 'Float' if type.start_with?('Float')\n            type = 'Int' if type.start_with?('Int')\n            type = 'UInt' if type.start_with?('UInt')\n            type = 'TCP' if type.start_with?('TCP')\n            type\n          end\n        end\n      end\n\n      def additional_entries\n        return [] unless current_url.path.start_with?('/api/')\n        entries = []\n\n        css('.entry-detail[id$=\"class-method\"]').each do |node|\n          name = node.at_css('.signature > strong').content.strip\n          name.prepend \"#{self.name}.\" unless slug.end_with?('toplevel')\n          id = node['id'] = node['id'].remove(/<.+?>/)\n          entries << [name, id] unless entries.last && entries.last[0] == name\n        end\n\n        css('.entry-detail[id$=\"instance-method\"]').each do |node|\n          name = node.at_css('.signature > strong').content.strip\n          name.prepend \"#{self.name}#\" unless slug.end_with?('toplevel')\n          id = node['id'] = node['id'].remove(/<.+?>/)\n          entries << [name, id] unless entries.last && entries.last[0] == name\n        end\n\n        css('.entry-detail[id$=\"macro\"]').each do |node|\n          name = node.at_css('.signature > strong').content.strip\n          name.prepend \"#{self.name} \" unless slug.end_with?('toplevel')\n          id = node['id'] = node['id'].remove(/<.+?>/)\n          entries << [name, id] unless entries.last && entries.last[0] == name\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/css/clean_html.rb",
    "content": "module Docs\n  class Css\n    class CleanHtmlFilter < Filter\n      def call\n        root_page? ? root : other\n        doc\n      end\n\n      def root\n        # Remove \"CSS3 Tutorials\" and everything after\n        css('#CSS3_Tutorials ~ *', '#CSS3_Tutorials').remove\n      end\n\n      def other\n        css('.syntaxbox > .syntaxbox').each do |node|\n          node.parent.before(node.parent.children).remove\n        end\n\n        # Remove \"|\" and \"||\" links in syntax box (e.g. animation, all, etc.)\n        css('.syntaxbox', '.twopartsyntaxbox').css('a').each do |node|\n          if node.content == '|' || node.content == '||'\n            node.replace node.content\n          end\n        end\n\n        css('img[style*=\"float\"]').each do |node|\n          node['style'] = node['style'] + ';float: none; display: block;'\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/css/entries.rb",
    "content": "module Docs\n  class Css\n    class EntriesFilter < Docs::EntriesFilter\n      TYPE_BY_PATH = {\n        'CSS_Animations' => 'Animations & Transitions',\n        'CSS_Background_and_Borders' => 'Backgrounds & Borders',\n        'CSS_Columns' => 'Multi-column Layout',\n        'CSS_Flexible_Box_Layout' => 'Flexible Box Layout',\n        'CSS_Fonts' => 'Fonts',\n        'CSS_Grid_Layout' => 'Grid Layout',\n        'CSS_Images' => 'Images',\n        'CSS_Lists_and_Counters' => 'Lists',\n        'CSS_Transforms' => 'Transforms',\n        'Media_Queries' => 'Media Queries',\n        'filter-function' => 'Filter Effects',\n        'transform-function' => 'Transforms',\n        '@media' => 'Media Queries',\n        'overscroll' => 'Overscroll',\n        'text-size-adjust' => 'Miscellaneous',\n        'resolved_value' => 'Miscellaneous',\n        'touch-action' => 'Miscellaneous',\n        'will-change' => 'Miscellaneous'\n      }\n\n      DATA_TYPE_SLUGS = %w(angle basic-shape color_value counter frequency\n        gradient image integer length number percentage position_value ratio\n        resolution shape string time timing-function uri user-ident)\n\n      FUNCTION_SLUGS = %w(attr calc cross-fade cubic-bezier cycle element\n        linear-gradient radial-gradient repeating-linear-gradient\n        repeating-radial-gradient var)\n\n      def get_name\n        if DATA_TYPE_SLUGS.include?(slug)\n          \"<#{super.remove ' value'}>\"\n        elsif FUNCTION_SLUGS.include?(slug)\n          \"#{super}()\"\n        elsif slug =~ /\\A[a-z]+_/i\n          slug.to_s.gsub('_', ' ').gsub('/', ': ')\n        elsif slug.start_with?('transform-function') || slug.start_with?('filter-function')\n          slug.split('/').last + '()'\n        else\n          super\n        end\n      end\n\n      def get_type\n        if slug.include?('-webkit') || slug.include?('-moz') || slug.include?('-ms')\n          'Extensions'\n        elsif type = TYPE_BY_PATH[slug.split('/').first]\n          type\n        elsif type = get_spec\n          type.remove! 'CSS '\n          type.remove! ' Module'\n          type.remove! %r{ Level \\d\\z}\n          type.remove! %r{\\(.*\\)}\n          type.remove! %r{  \\d\\z}\n          type.sub! 'and', '&'\n          type.strip!\n          type = 'Grid Layout' if type.include?('Grid Layout')\n          type = 'Scroll Snap' if type.include?('Scroll Snap')\n          type = 'Compositing & Blending' if type.include?('Compositing')\n          type = 'Animations & Transitions' if type.in?(%w(Animations Transitions))\n          type = 'Image Values' if type == 'Image Values & Replaced Content'\n          type = 'Variables' if type == 'Custom Properties for Cascading Variables'\n          type.prepend 'Miscellaneous ' if type =~ /\\ALevel \\d\\z/\n          type\n        elsif name.start_with?('::')\n          'Pseudo-Elements'\n        elsif name.start_with?(':')\n          'Selectors'\n        elsif name.start_with?('display-')\n          'Display'\n        else\n          'Miscellaneous'\n        end\n      end\n\n      STATUSES = {\n        'spec-Living'   => 0,\n        'spec-REC'      => 1,\n        'spec-CR'       => 2,\n        'spec-PR'       => 3,\n        'spec-LC'       => 4,\n        'spec-WD'       => 5,\n        'spec-ED'       => 6,\n        'spec-Obsolete' => 7\n      }\n\n      PRIORITY_STATUSES = %w(spec-REC spec-CR)\n      PRIORITY_SPECS = ['CSS Basic Box Model', 'CSS Lists and Counters', 'CSS Paged Media']\n\n      def get_spec\n        return unless table = at_css('#Specifications + table') || css('.standard-table').last\n\n        specs = table.css('tbody tr').to_a\n        # [link, span]\n        specs.map!     { |node| [node.at_css('> td:nth-child(1) > a'), node.at_css('> td:nth-child(2) > span')] }\n        # ignore non-CSS specs\n        specs.select!  { |pair| pair.first && pair.first['href'] =~ /css|fxtf|fullscreen|svg/i && !pair.first['href'].include?('compat.spec') }\n        # ignore specs with no status\n        specs.select!  { |pair| pair.second }\n        # [\"Spec\", \"spec-REC\"]\n        specs.map!     { |pair| [pair.first.child.content, pair.second['class']] }\n        # sort by status\n        specs.sort_by! { |pair| [STATUSES[pair.second], pair.first] }\n\n        spec = specs.find { |pair| PRIORITY_SPECS.any? { |s| pair.first.start_with?(s) } && name != 'display' }\n        spec ||= specs.find { |pair| !pair.first.start_with?('CSS Level') && pair.second.in?(PRIORITY_STATUSES) }\n        spec ||= specs.find { |pair| pair.second == 'spec-WD' } if specs.count { |pair| pair.second == 'spec-WD' } == 1\n        spec ||= specs.first\n\n        spec.try(:first)\n      end\n\n      ADDITIONAL_ENTRIES = {\n        'shape' => [\n          %w(rect() Syntax) ],\n        'timing-function' => [\n          %w(cubic-bezier()),\n          %w(steps()),\n          %w(linear linear),\n          %w(ease ease),\n          %w(ease-in ease-in),\n          %w(ease-in-out ease-in-out),\n          %w(ease-out ease-out),\n          %w(step-start step-start),\n          %w(step-end step-end) ],\n        'color_value' => [\n          %w(transparent transparent),\n          %w(currentColor currentColor),\n          %w(rgb() rgba()),\n          %w(hsl() hsla()),\n          %w(rgba() rgba()),\n          %w(hsla() hsla()) ]}\n\n      def additional_entries\n        ADDITIONAL_ENTRIES[slug] || []\n      end\n\n      def include_default_entry?\n        return true unless warning = at_css('.warning').try(:content)\n        !warning.include?('CSS Flexible Box') && !warning.include?('replaced in newer drafts')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/cypress/clean_html.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class Cypress\n    class CleanHtmlFilter < Filter\n      def call\n\n        css('div[role=alert]').each do |node|\n          node.name = 'blockquote'\n          node.add_class('note info')\n        end\n\n        css('footer').remove\n\n        css('pre').each do |node|\n          node.content = node.content\n          node['data-language'] = 'javascript'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/cypress/entries.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class Cypress\n    class EntriesFilter < Docs::EntriesFilter\n      SECTIONS = %w[\n        commands\n        core-concepts\n        cypress-api\n        events\n        getting-started\n        guides\n        overview\n        plugins\n        references\n        utilities\n      ].freeze\n\n      def get_name\n        at_css('h1.main-content-title').content.strip\n      end\n\n      def get_type\n        path = context[:url].path\n\n        SECTIONS.each do |section|\n          if path.match?(\"/#{section}/\")\n            return section.split('-').map(&:capitalize).join(' ')\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/d/clean_html.rb",
    "content": "module Docs\n  class D\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css(\"#content\")\n\n        css('#tools', '#copyright').remove\n\n        css('td > b', 'h1 > span').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('span.d_inlinecode').each do |node|\n          node.name = 'code'\n          node.remove_attribute('class')\n        end\n\n        css('.keyval').each do |node|\n          key = node.at_css('.key')\n          dt = key.inner_html\n          dd = if val = node.at_css('.val')\n            val.inner_html\n          else\n            siblings = []\n            siblings << key while key = key.next\n            siblings.map(&:to_html).join\n          end\n          node.replace(\"<dl><dt>#{dt}</dt><dd>#{dd}</dd></dl>\")\n        end\n\n        css('.description > .blankline:first-child + .quickindex').each do |node|\n          node.next_element.remove if node.next_element && node.next_element['class'] == 'blankline'\n          node.previous_element.remove\n          node.parent.before(node)\n        end\n\n        css('div.summary', 'div.description').each do |node|\n          node.name = 'p' unless node.at_css('p')\n          node.css('.blankline').each { |n| n.replace('<br><br>') }\n        end\n\n        css('.d_decl').each do |node|\n          node['id'] ||= node.at_css('.quickindex[id]')['id'].remove('quickindex.')\n\n          node.css('.def-anchor[id]').each do |n|\n            n.next_element['id'] ||= n['id']\n          end\n\n          node.css('.constraint').each do |n|\n            n.content = \"  Constraints: #{n.content}#{n.next.remove.content if n.next.text?}\"\n            n.name = 'small'\n            n.remove_attribute('class')\n          end\n\n          node.css('code[id]').each do |n|\n            n.name = 'strong'\n            n.remove_attribute('class')\n          end\n\n          node.css('*').each do |n|\n            n.before(n.children).remove unless n.name == 'br' || n.name == 'small' || n.name == 'strong'\n          end\n\n          node.inner_html = node.inner_html.remove(/<br>\\z/)\n        end\n\n        css('pre').each do |node|\n          node.content = node.content\n          node['data-language'] = 'd' if node['class'] && node['class'].include?('d_code')\n        end\n\n        css('div', 'code > a > code', 'code > code').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('a[href*=\"#.\"]').each do |node|\n          node['href'] = node['href'].sub('#.', '#')\n        end\n\n        css('tr', 'td', 'code', 'pre', 'p', 'table').remove_attr('class')\n        css('table').remove_attr('border').remove_attr('cellpadding').remove_attr('cellspacing')\n\n        if base_url.path == '/spec/'\n          css('a.anchor').each do |node|\n            node.parent['id'] ||= node['id']\n            node.before(node.children).remove\n          end\n\n          css('center').each do |node|\n            node.before(node.children).remove\n          end\n\n          css('.fa-angle-left + a').remove\n          css('a + .fa-angle-right').each { |node| node.previous_element.remove }\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/d/entries.rb",
    "content": "module Docs\n  class D\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content\n\n        if base_url.path == '/spec/'\n          index = css('.subnav li a').to_a.index(at_css(\".subnav li a[href='#{result[:path]}']\")) + 1\n          name.prepend \"#{index}. \"\n        end\n\n        name\n      end\n\n      def get_type\n        return 'Reference' if base_url.path == '/spec/'\n\n        if name.start_with?('etc') || name.start_with?('core.stdc.')\n          name.split('.')[0..2].join('.')\n        elsif name.start_with?('ddmd')\n          'ddmd'\n        elsif name.start_with?('rt')\n          'rt'\n        else\n          name.split('.')[0..1].join('.')\n        end\n      end\n\n      def additional_entries\n        return [] if root_page? || base_url.path == '/spec/'\n\n        entries = []\n\n        if entries.empty?\n          css('.quickindex[id]').each do |node|\n            name = node['id'].remove(/quickindex\\.?/)\n            next if name.empty? || name =~ /\\.\\d+\\z/ || name =~ /\\A([^\\.]+)\\.\\1\\z/\n            entries << [\"#{self.name}.#{name}\", name]\n          end\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/d3/clean_html.rb",
    "content": "module Docs\n  class D3\n    class CleanHtmlFilter < Filter\n      def call\n        css('p:contains(\"This page describes the D3 3.x API.\")').remove\n\n        # Remove links inside <h2> and add \"id\" attributes\n        css('a.anchor').each do |node|\n          node.parent['id'] = (node['id'] || node['name']).remove('user-content-') if node['id'] || node['name']\n          node.before(node.children).remove\n        end\n\n        # Make headings for function definitions and add \"id\" attributes\n        css('p > a:first-child').each do |node|\n          next unless node.parent\n          next unless node['name'] || node.content == '#'\n          parent = node.parent\n          parent.name = 'h6'\n          parent['id'] = (node['name'] || node['href'].remove(/\\A.+#/)).remove('user-content-')\n          parent.css('a[name], a:contains(\"#\"), a:contains(\"†\")').remove\n          node.remove\n        end\n\n        css('h4').each { |node| node.name = 'h3' } if root_page?\n\n        css('a > img').each do |node|\n          node.parent.before(node).remove\n        end\n\n        css('h6 a[title=\"Source\"]').each do |node|\n          node.content = 'Source'\n          node['class'] = 'source'\n        end\n        css('h6 a:contains(\"Source\"), h6 a:contains(\"Examples\")').each do |node|\n          node['class'] = 'source'\n        end\n\n        # Fix internal links\n        css('a[href]').each do |node|\n          node['href'] = node['href'].sub(/#user\\-content\\-(\\w+?)\\z/, '#\\1').sub(/#wiki\\-(\\w+?)\\z/, '#\\1')\n        end\n\n        # Remove code highlighting\n        css('.highlight > pre').each do |node|\n          node.content = node.content\n          node['data-language'] = if node.parent['class'].include?('html')\n            'markup'\n          elsif node.parent['class'].include?('css')\n            'css'\n          else\n            'javascript'\n          end\n          node.parent.before(node).remove\n        end\n\n        css('pre > code').each do |node|\n          node.before(node.children).remove\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/d3/entries_v3.rb",
    "content": "module Docs\n  class D3\n    class EntriesV3Filter < Docs::EntriesFilter\n      def get_name\n        File.basename(slug, '.md').gsub('-', ' ')\n      end\n\n      def get_type\n        at_css('h6[id]') ? name : 'D3'\n      end\n\n      def additional_entries\n        css('h6[id]').each_with_object [] do |node, entries|\n          name = node.content.strip\n          name.sub! %r{\\(.*\\z}, '()'\n          name.sub! %r{\\A(svg:\\w+)\\s+.+}, '\\1'\n          entries << [name, node['id']] unless name == entries.last.try(:first)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/d3/entries_v4.rb",
    "content": "module Docs\n  class D3\n    class EntriesV4Filter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content\n        name.remove! 'd3-'\n        name\n      end\n\n      def get_type\n        return 'D3' unless at_css('h6[id]')\n        type = name.titleize\n        type.sub! 'Hsv', 'HSV'\n        type.sub! 'Dsv', 'DSV'\n        type\n      end\n\n      def additional_entries\n        css('h6[id]').each_with_object [] do |node, entries|\n          name = node.content.strip\n          name.remove! 'Source, Examples'\n          name.remove! 'Source'\n          name.remove! 'Examples'\n          name.remove! '<>'\n          name.remove! ' · '\n          name.remove! '0,' # from d3.quickselect\n          name.remove! '=' # from d3.quickselect\n          name.remove! %r{\\s\\-.*}\n          name.remove! %r{\\s*\\[.*\\]}\n          name.gsub! %r{\\(.+?\\)\\)?}, '()'\n          name.sub! %r{\\A(svg:\\w+)\\s+.+}, '\\1'\n          name.split(/\\s+/).each do |n|\n            next if n.blank?\n            entries << [n, node['id']] unless n == entries.last.try(:first)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/dart/clean_html.rb",
    "content": "module Docs\n  class Dart\n    class CleanHtmlFilter < Filter\n      def call\n        # Move the title into the main content node in the v1 docs\n        title = at_css('h1.title')\n        if title\n          name = title.children.last.content.strip\n          kind = title.at_css('.kind').content\n          at_css('.main-content').prepend_child(\"<h1>#{name} #{kind}</h1>\")\n        end\n\n        # Add a title to the homepage of the v2 docs\n        if subpath == 'index.html' && at_css('.main-content > h1').nil?\n          at_css('.main-content').prepend_child('<h1>Dart SDK</h1>')\n        end\n\n        # Add the library to the main content (it is not always visible in the menu entry)\n        breadcrumbs = at_css('.breadcrumbs').css('li:not(.self-crumb) > a')\n        if breadcrumbs.length > 1\n          library = breadcrumbs[1].content\n\n          # Generate the link to the homepage of the library\n          with_hypens = library.gsub(/:/, '-')\n          location = \"#{'../' * subpath.count('/')}#{with_hypens}/#{with_hypens}-library\"\n          link = \"<a href=\\\"#{location}\\\" class=\\\"_links-link\\\">#{library}</span>\"\n\n          # Add the link to the main title, just like how the \"Homepage\" and \"Source code\" links appear\n          at_css('.main-content').prepend_child(\"<p class=\\\"_links\\\">#{link}</p>\")\n        end\n\n        # Extract the actual content\n        # We can't use options[:container] here because the entries filter uses the breadcrumbs node\n        @doc = at_css('.main-content')\n\n        # Remove external links\n        css('#external-links').remove\n\n        # Remove button to https://dart.dev/null-safety\n        css('.feature-null-safety').remove\n\n        # Properly format header in v2 docs\n        nested_title = at_css('div > h1')\n        unless nested_title.nil?\n          nested_title.parent.replace nested_title\n        end\n\n        # Move the features (i.e. \"read-only, inherited\") into the blue header\n        css('.features').each do |node|\n          header = node.xpath('parent::dd/preceding::dt').last\n          header.add_child node unless header.nil?\n        end\n\n        css('section:not(.multi-line-signature)').each do |node|\n          if node['id'] && node.first_element_child\n            node.first_element_child['id'] ||= node['id']\n          end\n\n          node.before(node.children).remove\n        end\n\n        css('span').each do |node|\n          node.before(node.children).remove\n        end\n\n        # Make code blocks detectable by Prism\n        css('pre').each do |node|\n          node['data-language'] = 'dart'\n          node.content = node.content.strip\n        end\n\n        css('.properties', '.property', '.callables', '.callable').remove_attr('class')\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/dart/entries.rb",
    "content": "module Docs\n  class Dart\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        title = at_css('h1.title')\n        if title # v1\n          name = title.element_children.last.content.strip\n          kind = title.at_css('.kind').content\n        else # v2\n          title = at_css('.main-content > div > h1')\n          name = title.content[/(.*)( )/, 1].split(' top-level')[0]\n          kind = title.content[/(.*)( )(.+)/, 3]\n        end\n\n        breadcrumbs = at_css('.breadcrumbs').css('li:not(.self-crumb) > a')\n        first_part = ''\n\n        if breadcrumbs.length == 2 && kind.blank?\n          first_part = breadcrumbs[1].content\n        elsif breadcrumbs.length == 3\n          first_part = breadcrumbs[2].content\n        end\n\n        separator = ''\n\n        unless first_part.empty?\n          if kind.include?('class')\n            separator = ':'\n          else\n            separator = '.'\n          end\n        end\n\n        \"#{first_part}#{separator}#{name}\"\n      end\n\n      def get_type\n        at_css('.breadcrumbs > li:nth-child(2)').content.split(' ')[0]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/deno/clean_html.rb",
    "content": "module Docs\n  class Deno\n    class CleanHtmlFilter < Filter\n      def call\n        if result[:path].start_with?('api/deno/')\n          @doc = at_css('main[id!=\"content\"] article', 'main[id!=\"content\"]')\n        else\n          @doc = at_css('main article .markdown-body')\n        end\n\n        if at_css('.text-2xl')\n          doc.prepend_child at_css('.text-2xl').remove\n          at_css('.text-2xl').name = 'h1'\n        end\n\n        css('code').each do |node|\n          if node['class']\n            lang = node['class'][/language-(\\w+)/, 1]\n          end\n          node['data-language'] = lang || 'ts'\n          node.remove_attribute('class')\n          if node.parent.name == 'div'\n            node.content = node.content.strip\n          end\n        end\n\n        css('a.header-anchor').remove()\n        css('.breadcrumbs').remove()\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/deno/entries.rb",
    "content": "module Docs\n  class Deno\n    class EntriesFilter < Docs::EntriesFilter\n\n      def get_name\n        if result[:path].start_with?('api/deno/')\n          at_css('main[id!=\"content\"]')['id'][/\\Asymbol_([.\\w]+)/, 1]\n        else\n          at_css('main article h1').content\n        end\n      end\n\n      def get_type\n        if result[:path].start_with?('api/deno/')\n          'API'\n        elsif result[:path].start_with?('runtime/reference/cli')\n          'CLI'\n        else\n          at_css('main article nav ul :first span').content\n        end\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/django/clean_html.rb",
    "content": "module Docs\n  class Django\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.yui-g')\n\n        css('.console-block').each do |node|\n          node.css('input', 'label').remove\n          node.css('section').each do |sec|\n            sec.before(sec.children).remove\n          end\n          node.before(node.children).remove\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/django/entries.rb",
    "content": "module Docs\n  class Django\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content.remove(\"\\u{00b6}\")\n      end\n\n      def get_type\n        case subpath\n        when /\\Atopics/\n          'Guides'\n        when /\\Aintro/\n          'Guides: Intro'\n        when /\\Ahowto/\n          'Guides: How-tos'\n        when /\\Aref/\n          'API'\n        end\n      end\n\n      def additional_entries\n        entries = []\n\n        css('dl.function', 'dl.class', 'dl.method', 'dl.attribute', 'dl.data').each do |node|\n          next unless id = node.at_css('dt')['id']\n          next unless name = id.dup.sub!('django.', '')\n\n          path = name.split('.')\n          type = \"django.#{path.first}\"\n          type << \".#{path.second}\" if %w(contrib db).include?(path.first)\n\n          name.remove! 'contrib.'\n          name << '()' if node['class'].include?('method') || node['class'].include?('function')\n\n          entries << [name, id, type]\n        end\n\n        css('span[id^=\"std:setting-\"] + h3').each do |node|\n          name = node.content\n          name.remove! \"\\u{00B6}\"\n          name.prepend 'settings.'\n          entries << [name, node.previous_element['id'], 'settings']\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/django/fix_urls.rb",
    "content": "module Docs\n  class Django\n    class FixUrlsFilter < Filter\n      def call\n        html.gsub! %r{#{Regexp.escape(context[:base_url].to_s)}([^\"']+?)\\.html}, \"#{context[:base_url]}\\\\1/\"\n        html\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/django_rest_framework/clean_html.rb",
    "content": "module Docs\n  class DjangoRestFramework\n    class CleanHtmlFilter < Docs::Filter\n      def call\n        css('hr').remove\n        css('.badges').remove\n\n        css('pre').attr('data-language', 'python')\n\n        css('h1').remove_attribute('style')\n        css('.promo a').remove_attribute('style')\n\n        # Translate source files links to DevDocs links\n        links = Nokogiri::XML::Node.new('p', doc.document)\n        links['class'] = '_links'\n\n        css('a.github').each do |node|\n          span = node.at_css('span')\n          node.content = span.content\n          node['class'] = '_links-link'\n          links.add_child(node)\n        end\n\n        doc.add_child(links)\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/django_rest_framework/entries.rb",
    "content": "module Docs\n  class DjangoRestFramework\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = css('h1').first.content\n        name.slice! 'Tutorial '\n        name = '0: ' + name if name.include? 'Quickstart'\n        name\n      end\n\n      def get_type\n        case subpath\n        when /\\Atutorial/\n          'Tutorial'\n        when /\\Aapi-guide/\n          'API Guide'\n        end\n      end\n\n      def additional_entries\n        return [] if type == nil || type == 'Tutorial'\n\n        # Framework classes are provided in two different ways:\n        # - as H2's after H1 category titled:\n        accepted_headers = ['API Reference', 'API Guide']\n        # - as headers (1 or 2) with these endings:\n        endings = ['Validator', 'Field', 'View', 'Mixin', 'Default', 'Serializer']\n\n        # To avoid writing down all the endings\n        # and to ensure all entries in API categories are matched\n        # two different ways of finding them are used\n\n        entries = []\n\n        local_type = 'Ref: ' + name\n        in_category = false\n\n        css('h1, h2').each do |node|\n          # Third party category contains entries that could be matched (and shouldn't be)\n          break if node.content === 'Third party packages'\n\n          if in_category\n            if node.name === 'h1'\n              in_category = false\n              next\n            end\n            entries << [node.content, node['id'], local_type]\n          elsif accepted_headers.include? node.content\n            in_category = true\n          elsif endings.any? { |word| node.content.ends_with?(word) }\n            entries << [node.content, node['id'], local_type]\n          end\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/docker/clean_html.rb",
    "content": "module Docs\n  class Docker\n    class CleanHtmlFilter < Filter\n      def call\n        if root_page?\n          doc.inner_html = \"<h1>Docker Documentation</h1>\"\n          return doc\n        end\n\n        @doc = at_css('main .section')\n\n        at_css('h2').name = 'h1' unless at_css('h1')\n\n        css('.anchorLink', '.reading-time', 'hr', '> div[style*=\"margin-top\"]:last-child').remove\n\n        css('h1 + h1').each do |node|\n          node.name = 'h2'\n        end\n\n        css('pre').each do |node|\n          node.content = node.content\n          node['data-language'] = node.parent['class'][/language-(\\w+)/, 1] if node.parent['class']\n        end\n\n        css('div.highlighter-rouge').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('code.highlighter-rouge').each do |node|\n          node.content = node.content.gsub(/\\s+/, ' ').strip\n        end\n\n        css('> span').each do |node|\n          node.name = 'p'\n          node.remove_attribute('style')\n        end\n\n        doc\n      end\n    end\n  end\nend\n\n"
  },
  {
    "path": "lib/docs/filters/docker/entries.rb",
    "content": "module Docs\n  class Docker\n    class EntriesFilter < Docs::EntriesFilter\n      NAME_BY_SUBPATH = {\n        'engine/' => 'Engine',\n        'compose/' => 'Compose',\n        'machine/' => 'Machine',\n        'notary/' => 'Notary'\n      }\n\n      def get_name\n        return NAME_BY_SUBPATH[subpath] if NAME_BY_SUBPATH[subpath]\n        at_css('h1').content\n      end\n\n      def get_type\n        return NAME_BY_SUBPATH[subpath] if NAME_BY_SUBPATH[subpath]\n        return 'Get Started'         if subpath.start_with?('get-started')\n        return 'Engine: CLI'         if subpath.start_with?('engine/reference/commandline/')\n        return 'Engine: Admin Guide' if subpath.start_with?('engine/admin/')\n        return 'Engine: Security'    if subpath.start_with?('engine/security/')\n        return 'Engine: Extend'      if subpath.start_with?('engine/extend/')\n        return 'Engine: Tutorials'   if subpath.start_with?('engine/tutorials/')\n        product\n      end\n\n      def product\n        @product ||= subpath.split('/').first.capitalize\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/dojo/clean_html.rb",
    "content": "module Docs\n  class Dojo\n    class CleanHtmlFilter < Filter\n      def call\n        if root_page?\n          doc.inner_html = ' '\n          return doc\n        end\n\n        css('h1[class]').each do |node|\n          node.remove_attribute('class')\n        end\n\n        css('.version', '.jsdoc-permalink', '.feedback', '.jsdoc-summary-heading', '.jsdoc-summary-list', '.jsdoc-field.private').remove\n\n        css('.jsdoc-wrapper, .jsdoc-children, .jsdoc-fields, .jsdoc-field, .jsdoc-property-list, .jsdoc-full-summary, .jsdoc-return-description').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('a[name]').each do |node|\n          next unless node.content.blank?\n          node.parent['id'] = node['name']\n          node.remove\n        end\n\n        css('div.returnsInfo', 'div.jsdoc-inheritance').each do |node|\n          node.name = 'p'\n        end\n\n        css('div.jsdoc-title').each do |node|\n          node.name = 'h3'\n        end\n\n        css('.returns').each do |node|\n          node.inner_html = node.inner_html + ' '\n        end\n\n        css('.functionIcon a').each do |node|\n          node.replace(node.content)\n        end\n\n        css('.functionIcon', '.parameters').each do |node|\n          node.name = 'code'\n          node.content = node.content.strip\n        end\n\n        css('pre').each do |node|\n          node['data-language'] = node.content =~ /\\A\\s*</ ? 'markup' : 'javascript'\n        end\n\n        css('.jsdoc-function-information', '.jsdoc-examples', '.jsdoc-example', 'span').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('table', 'a', 'h2', 'h3', 'td', 'strong').remove_attr('class')\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/dojo/clean_urls.rb",
    "content": "module Docs\n  class Dojo\n    class CleanUrlsFilter < Filter\n      def call\n        html.remove! '?xhr=true'\n        html\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/dojo/entries.rb",
    "content": "module Docs\n  class Dojo\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content.remove(/\\(.*\\)/).remove('dojo/').strip\n      end\n\n      def get_type\n        path = name.split(/[\\/\\.\\-]/)\n        path[0] == '_base' ? path[0..1].join('/') : path[0]\n      end\n\n      def additional_entries\n        entries = []\n\n        css('.jsdoc-summary-list li.functionIcon:not(.private):not(.inherited) > a').each do |node|\n          entries << [\"#{self.name}##{node.content}()\", node['href'].remove('#')]\n        end\n\n        css('.jsdoc-summary-list li.objectIcon:not(.private):not(.inherited) > a').each do |node|\n          entries << [\"#{self.name}##{node.content}\", node['href'].remove('#')]\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/dom/clean_html.rb",
    "content": "module Docs\n  class Dom\n    class CleanHtmlFilter < Filter\n      def call\n        root_page? ? root : other\n        doc\n      end\n\n      def root\n      end\n\n      def other\n        css('h1 + br').remove\n\n        # Bug fix: HTMLElement.offsetWidth\n        css('#offsetContainer .comment').remove\n\n        css('section', 'font').each do |node|\n          node.before(node.children).remove\n        end\n\n        # Bug fix: CompositionEvent, DataTransfer, etc.\n        if (div = at_css('div[style]')) && div['style'].include?('border: solid #ddd 2px')\n          div.remove\n        end\n\n        # Remove double heading on SVG pages\n        if slug.start_with? 'SVG'\n          at_css('h2:first-child').try :remove\n        end\n\n        # Remove <div> wrapping .overheadIndicator\n        css('div > .overheadIndicator:first-child:last-child', 'div > .blockIndicator:first-child:last-child').each do |node|\n          node.parent.replace(node)\n        end\n\n        css('.syntaxbox > pre:first-child:last-child').each do |node|\n          node['class'] = 'syntaxbox'\n          node.parent.before(node).remove\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/dom/entries.rb",
    "content": "module Docs\n  class Dom\n    class EntriesFilter < Docs::EntriesFilter\n      TYPE_BY_SPEC = {\n        'ANGLE_'              => 'WebGL',\n        'EXT_'                => 'WebGL',\n        'OES_'                => 'WebGL',\n        'WEBGL_'              => 'WebGL',\n        'Sensor API'          => 'Sensors',\n        'Ambient Light'       => 'Ambient Light',\n        'Audio'               => 'Audio',\n        'Battery Status'      => 'Battery Status',\n        'Canvas '             => 'Canvas',\n        'Clipboard'           => 'Clipboard',\n        'Content Security'    => 'Content Security Policy',\n        'Cooperative Scheduling' => 'Scheduling',\n        'CSS Font Loading'    => 'CSS',\n        'CSS Object Model'    => 'CSS',\n        'Credential'          => 'Credential Management',\n        'Cryptography'        => 'Cryptography',\n        'Device Orientation'  => 'Device Orientation',\n        'Encoding'            => 'Encoding',\n        'Encrypted Media Extensions' => 'Encrypted Media',\n        'Fetch'               => 'Fetch',\n        'File API'            => 'File',\n        'Fullscreen'          => 'Fullscreen',\n        'Geolocation'         => 'Geolocation',\n        'Geometry'            => 'Geometry',\n        'High Resolution Time' => 'Performance',\n        'Intersection'        => 'Intersection Observer',\n        'Keyboard'            => 'Keyboard',\n        'Media Capabilities'  => 'Media',\n        'Media Capture'       => 'Media',\n        'Media Session'       => 'Media',\n        'Media Source'        => 'Media',\n        'MediaError'          => 'Media',\n        'MediaStream'         => 'Media Streams',\n        'MIDI'                => 'Audio',\n        'Navigation Timing'   => 'Performance',\n        'Network Information' => 'Network Information',\n        'Orientation Sensor'  => 'Sensors',\n        'Payment'             => 'Payments',\n        'Performance Timeline' => 'Performance',\n        'Pointer Events'      => 'Pointer Events',\n        'Push API'            => 'Push',\n        'Presentation API'    => 'Presentation',\n        'Shadow DOM'          => 'Shadow DOM',\n        'Server-Sent Events'  => 'Server-Sent Events',\n        'Service Workers'     => 'Service Workers',\n        'Speech'              => 'Speech',\n        'Storage'             => 'Storage',\n        'Stream API'          => 'Media Streams',\n        'Streams'             => 'Media Streams',\n        'Touch Events'        => 'Touch Events',\n        'Visual Viewport'     => 'Visual Viewport',\n        'Web Animations'      => 'Animation',\n        'Web App Manifest'    => 'Web App Manifest',\n        'Budget'              => 'Budget',\n        'Web Authentication'  => 'Authentication',\n        'Web Locks'           => 'Locks',\n        'Web Workers'         => 'Web Workers',\n        'WebGL'               => 'WebGL',\n        'WebRTC'              => 'WebRTC',\n        'WebUSB'              => 'WebUSB',\n        'WebVR'               => 'WebVR',\n        'WebVTT'              => 'WebVTT' }\n\n      TYPE_BY_NAME_STARTS_WITH = {\n        'AbortController'     => 'Fetch',\n        'AbortSignal'         => 'Fetch',\n        'Ambient'             => 'Ambient Light',\n        'Attr'                => 'Nodes',\n        'Audio'               => 'Audio',\n        'BasicCard'           => 'Payments',\n        'Broadcast'           => 'Broadcast Channel',\n        'Budget'              => 'Budget',\n        'Canvas'              => 'Canvas',\n        'Clipboard'           => 'Clipboard',\n        'CSS'                 => 'CSS',\n        'CharacterData'       => 'Nodes',\n        'ChildNode'           => 'Nodes',\n        'Comment'             => 'Nodes',\n        'console'             => 'Console',\n        'CustomElement'       => 'Custom Elements',\n        'DataTransfer'        => 'Drag & Drop',\n        'document'            => 'Document',\n        'Document Object'     => 'DOM',\n        'DocumentFragment'    => 'DocumentFragment',\n        'DocumentType'        => 'Nodes',\n        'DOM'                 => 'DOM',\n        'element'             => 'Element',\n        'event'               => 'Event',\n        'Event'               => 'Event',\n        'EventSource'         => 'Server-Sent Events',\n        'Fetch'               => 'Fetch',\n        'File'                => 'File',\n        'GlobalEventHandlers' => 'GlobalEventHandlers',\n        'HMDVR'               => 'WebVR',\n        'history'             => 'History',\n        'HTML Drag'           => 'Drag & Drop',\n        'HTML'                => 'Elements',\n        'IDB'                 => 'IndexedDB',\n        'Keyboard'            => 'Keyboard',\n        'location'            => 'Location',\n        'navigator'           => 'Navigator',\n        'MediaKeySession'     => 'Encrypted Media',\n        'MediaMetadata'       => 'Media Session',\n        'MediaSession'        => 'Media Session',\n        'MediaTrack'          => 'Media Streams',\n        'Message'             => 'Channel Messaging',\n        'Mutation'            => 'DOM',\n        'NamedNode'           => 'Nodes',\n        'Node'                => 'Nodes',\n        'Notification'        => 'Notification',\n        'OffscreenCanvas'     => 'Canvas',\n        'ParentNode'          => 'Nodes',\n        'Performance'         => 'Performance',\n        'Presentation'        => 'Presentation',\n        'Push'                => 'Push',\n        'Range'               => 'Range',\n        'RenderingContext'    => 'Canvas',\n        'Resource Timing'     => 'Performance',\n        'RTC'                 => 'WebRTC',\n        'screen'              => 'Screen',\n        'Selection'           => 'Selection',\n        'Shadow'              => 'Shadow DOM',\n        'StaticRange'         => 'Range',\n        'Streams'             => 'Media Streams',\n        'StyleProperty'       => 'CSS',\n        'StyleSheet'          => 'CSS',\n        'Stylesheet'          => 'CSS',\n        'SVG'                 => 'SVG',\n        'TextTrack'           => 'WebVTT',\n        'TimeRanges'          => 'Media',\n        'timing'              => 'Performance',\n        'Timing'              => 'Performance',\n        'Touch'               => 'Touch Events',\n        'TreeWalker'          => 'TreeWalker',\n        'URL'                 => 'URL',\n        'VR'                  => 'WebVR',\n        'WebSocket'           => 'Web Sockets',\n        'USB'                 => 'WebUSB',\n        'window'              => 'Window',\n        'Window'              => 'Window',\n        'XMLHttpRequest'      => 'XMLHTTPRequest' }\n\n      TYPE_BY_NAME_INCLUDES = {\n        'Animation'     => 'Animation',\n        'ChildNode'     => 'Nodes',\n        'Crypto'        => 'Cryptography',\n        'Drag'          => 'Drag & Drop',\n        'FormData'      => 'XMLHTTPRequest',\n        'History'       => 'History',\n        'ImageBitmap'   => 'Canvas',\n        'ImageData'     => 'Canvas',\n        'IndexedDB'     => 'IndexedDB',\n        'Media Source'  => 'Media',\n        'MediaStream'   => 'Media Streams',\n        'Media Streams' => 'Media Streams',\n        'Messaging'     => 'Channel Messaging',\n        'NodeList'      => 'Nodes',\n        'Path2D'        => 'Canvas',\n        'Pointer'       => 'Pointer Events',\n        'Server-sent'   => 'Server-Sent Events',\n        'ServiceWorker' => 'Service Workers',\n        'Speech'        => 'Speech',\n        'Storage'       => 'Storage',\n        'TextMetrics'   => 'Canvas',\n        'timing'        => 'Performance',\n        'Timing'        => 'Performance',\n        'udio'          => 'Audio',\n        'VRDevice'      => 'WebVR',\n        'WebGL'         => 'WebGL',\n        'WEBGL'         => 'WebGL',\n        'WebRTC'        => 'WebRTC',\n        'WebVR'         => 'WebVR',\n        'Worker'        => 'Web Workers' }\n\n      TYPE_BY_NAME_MATCHES = {\n        /\\AText(\\z|\\.)/ => 'Nodes'\n      }\n\n      TYPE_BY_HAS_LINK_TO = {\n        'DeviceOrientation specification' => 'Device Orientation',\n        'File System API'                 => 'File',\n        'WebSocket'                       => 'Web Sockets',\n        'Web Audio API'                   => 'Audio',\n        'XMLHTTPRequest'                  => 'XMLHTTPRequest' }\n\n      CLEANUP_NAMES = %w(\n        CSS\\ Object\\ Model.\n        Tutorial.\n        XMLHttpRequest.\n        ANGLE\\ instanced\\ arrays.)\n\n      def get_name\n        name = super\n        CLEANUP_NAMES.each { |str| name.remove!(str) }\n        name.sub! %r{Document\\ Object\\ Model\\.}i, 'Document Object Model: '\n        name.sub! 'Input.', 'HTMLInputElement.'\n        name.sub! 'window.navigator', 'navigator'\n        name.sub! 'API.', 'API: '\n        name.sub! %r{\\A(ANGLE|EXT|OES|WEBGL)[\\w\\ ]+\\.}, 'ext.'\n        # Comment.Comment => Comment.constructor\n        name.sub! %r{\\A(\\w+)\\.\\1\\z}, '\\1.constructor' unless name == 'window.window'\n        name.prepend 'XMLHttpRequest.' if slug.start_with?('XMLHttpRequest/') && !name.start_with?('XMLHttpRequest')\n        name\n      end\n\n      def get_type\n        TYPE_BY_NAME_STARTS_WITH.each_pair do |key, value|\n          return value if name.start_with?(key)\n        end\n\n        TYPE_BY_NAME_INCLUDES.each_pair do |key, value|\n          return value if name.include?(key)\n        end\n\n        TYPE_BY_NAME_MATCHES.each_pair do |key, value|\n          return value if name =~ key\n        end\n\n        if spec = css('.standard-table').last\n          spec = spec.content\n          TYPE_BY_SPEC.each_pair do |key, value|\n            return value if spec.include?(key)\n          end\n        end\n\n        links_text = css('a').map(&:content).join\n        TYPE_BY_HAS_LINK_TO.each_pair do |key, value|\n          return value if links_text.include?(key)\n        end\n\n        if name.include? 'Event'\n          'Events'\n        else\n          'Miscellaneous'\n        end\n      end\n\n      SKIP_CONTENT = [\n        'not on a standards track',\n        'removed from the Web',\n        'not on a current W3C standards track',\n        'This feature is not built into all browsers',\n        'not currently supported in any browser'\n      ]\n\n      def include_default_entry?\n        return true if type == 'Console'\n        return true unless node = doc.at_css('.overheadIndicator, .blockIndicator')\n        node = node.parent while node.parent != doc\n        return true if node.previous_element.try(:name).in?(%w(h2 h3))\n        content = node.content\n        SKIP_CONTENT.none? { |str| content.include?(str) }\n      end\n\n      def additional_entries\n        entries = []\n\n        if slug == 'history' || slug == 'XMLHttpRequest'\n          css('dt a[href^=\"https://developer.mozilla.org\"]').each do |node|\n            next if node.parent.at_css('.obsolete') || node.content.include?('moz')\n            name = node.content.sub('History', 'history')\n            id = node.parent['id'] = name.parameterize\n            entries << [name, id]\n          end\n        end\n\n        if slug == 'XMLHttpRequest'\n          css('h2[id=\"Methods_2\"] ~ h3').each do |node|\n            break if node.content == 'Non-standard methods'\n            entries << [\"#{name}.#{node.content}\", node['id']]\n          end\n        end\n\n        if slug == 'History_API'\n          entries << ['history.pushState()', 'The_pushState()_method']\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/drupal/clean_html.rb",
    "content": "module Docs\n  class Drupal\n    class CleanHtmlFilter < Filter\n      def call\n        root_page? ? root : other\n        doc\n      end\n\n      def root\n        doc.inner_html = ' '\n      end\n\n      def other\n        css('.element-invisible',\n            '#sidebar-first',\n            '#api-alternatives',\n            '#aside',\n            '.comments',\n            '.view-filters',\n            '#api-function-signature tr:not(.active)',\n            '.ctools-collapsible-container',\n            'img[width=\"13\"]',\n            'a:contains(\"Expanded class hierarchy\")',\n            'a:contains(\"All classes that implement\")'\n        ).remove\n\n        at_css('#main').replace(at_css('.content'))\n        at_css('#page-heading').replace(at_css('#page-subtitle'))\n\n        css('th.views-field > a', '.content', 'ins', '.view', '.view-content', 'div.item-list').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('pre').each do |node|\n          node.content = node.content\n          node['data-language'] = 'php'\n        end\n\n        css('#api-function-signature').each do |table|\n          signature = table.css('.signature').first.at_css('code').inner_html\n          table.replace '<pre class=\"signature\">' + signature + '</pre>'\n        end\n\n        css('table[class]', 'tr[class]', 'td[class]', 'th[class]').each do |node|\n          node.remove_attribute('class')\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/drupal/entries.rb",
    "content": "module Docs\n  class Drupal\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('#page-subtitle').content\n        name.remove! %r{(abstract|public|static|protected|private|final|function|class|constant|interface|property|global|trait)\\s+}\n        name\n      end\n\n      def get_type\n        if subpath =~ /Drupal!Core!([^!]+)!/ ||\n           subpath =~ /Drupal!Component!([^!]+)!/ ||\n           subpath =~ /core!modules!([^!\\/]+)/ ||\n           subpath =~ /core!includes!([^!\\/]+)/\n          $1.underscore\n        elsif subpath =~ /Drupal!Core/\n          'core'\n        elsif subpath =~ /Drupal!Component/\n          'component'\n        elsif subpath =~ /core!themes/\n          'themes'\n        else\n          css('.breadcrumb > a')[1].content\n        end\n      end\n\n      def include_default_entry?\n        !initial_page?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/drupal/internal_urls.rb",
    "content": "module Docs\n  class Drupal\n    class InternalUrlsFilter < Docs::InternalUrlsFilter\n      def internal_path_to(url)\n        url = index_url if url == root_url\n        path = effective_url.relative_path_to(url)\n        URL.new(path: Drupal::fixUri(path), query: url.query, fragment: url.fragment).to_s\n      end\n    end\n  end\nend\n\n"
  },
  {
    "path": "lib/docs/filters/drupal/normalize_paths.rb",
    "content": "module Docs\n  class Drupal\n    class NormalizePathsFilter < Docs::NormalizePathsFilter\n\n      def store_path\n        p = Drupal::fixUri(@path)\n        File.extname(p) != '.html' ? \"#{p}.html\" : p\n      end\n    end\n  end\nend"
  },
  {
    "path": "lib/docs/filters/duckdb/attribution.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class Duckdb\n    class AttributionFilter < Docs::AttributionFilter\n      def attribution_link\n        url = current_url.to_s.sub! 'http://localhost:8000', 'https://duckdb.org'\n        %(<a href=\"#{url}\" class=\"_attribution-link\">#{url}</a>)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/duckdb/clean_html.rb",
    "content": "module Docs\n  class Duckdb\n    class CleanHtmlFilter < Filter\n      def call\n        # First extract the main content\n        @doc = at_css('#main_content_wrap', 'main')\n        return doc if @doc.nil?\n\n        doc.prepend_child at_css('.title').remove\n        at_css('.title').name = 'h1'\n\n        # Remove navigation and header elements\n        css('.headerline', '.headlinebar', '.landingmenu', '.search_icon', '#sidebar', '.pagemeta', '.toc_menu', '.section-nav').remove\n\n        # Clean up code blocks\n        css('div.highlighter-rouge').each do |node|\n          node['data-language'] = node['class'][/language-(\\w+)/, 1] if node['class']\n          node.content = node.content.strip\n          node.name = 'pre'\n        end\n\n        # Remove unnecessary attributes\n        css('div, span, p').each do |node|\n          node.remove_attribute('style')\n          node.remove_attribute('class')\n        end\n\n        # Remove empty elements\n        css('div, span').each do |node|\n          node.remove if node.content.strip.empty?\n        end\n\n        # Remove script tags\n        css('script').remove\n\n        doc\n      end\n    end\n  end\nend"
  },
  {
    "path": "lib/docs/filters/duckdb/entries.rb",
    "content": "module Docs\n  class Duckdb\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1', '.title').content\n      end\n\n      def get_type\n        case subpath\n        when /\\Asql\\//\n          'SQL Reference'\n        when /\\Aapi\\//\n          'Client APIs'\n        when /\\Aguides\\//\n          'How-to Guides'\n        when /\\Adata\\//\n          'Data Import'\n        when /\\Aoperations_manual\\//\n          'Operations Manual'\n        when /\\Adev\\//\n          'Development'\n        when /\\Ainternals\\//\n          'Internals'\n        when /\\Aextensions\\//\n          'Extensions'\n        when /\\Aarchive\\//\n          'Archive'\n        else\n          'Documentation'\n        end\n      end\n\n      def additional_entries\n        entries = []\n        css('h2[id]', 'h3[id]').each do |node|\n          name = node.content.strip\n          # Clean up the name\n          name = name.gsub(/[\\r\\n\\t]/, ' ').squeeze(' ')\n          entries << [name, node['id'], get_type]\n        end\n        entries\n      end\n    end\n  end\nend"
  },
  {
    "path": "lib/docs/filters/eigen3/clean_html.rb",
    "content": "module Docs\n  class Eigen3\n    class CleanHtmlFilter < Filter\n\n      def call\n        @doc = at_css('#doc-content')\n        css('#MSearchSelectWindow').remove\n        css('#MSearchResultsWindow').remove\n        css('.directory .levels').remove\n        css('.header .summary').remove\n        css('.ttc').remove\n        css('.top').remove\n        css('.dynheader.closed').remove\n        css('.permalink').remove\n        css('.groupheader').remove\n        css('.header').remove\n        css('#details').remove\n        css('*').each do |node|\n          node.remove_attribute('class')\n        end\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/eigen3/entries.rb",
    "content": "module Docs\n  class Eigen3\n    class EntriesFilter < Docs::EntriesFilter\n      def get_type\n        group = at_css('.title .ingroups')\n        content = at_css('.contents').content\n        title = get_title()\n        downtitle = title.downcase\n        name = get_name\n\n        if slug.include?('unsupported')\n          return 'Unsupported'\n        elsif slug.start_with?('Topic') || downtitle.end_with?(\"topics\")\n            return 'Topics'\n        elsif downtitle.end_with?(\"class template reference\") || downtitle.end_with?(\"class reference\")\n          return 'Classes'\n        elsif downtitle.end_with?(\"struct reference\")\n          return 'Structs'\n        elsif downtitle.end_with?(\"typedefs\")\n          return 'Typedefs'\n        elsif downtitle.end_with?(\"namespace reference\")\n            return 'Namespaces'\n        elsif not group.nil? and group.content.include?('Reference') and (downtitle.end_with?(\"module\") || downtitle.end_with?(\"modules\"))\n            return \"Modules\"\n        elsif not group.nil?\n          if group.children.length > 0\n            return 'Chapter: ' + group.children[-1].content\n          else\n            return 'Chapter: ' + group.content\n          end\n        else\n          return 'Eigen'\n        end\n      end\n\n      def get_name\n        title = get_title().gsub(/[<(].*/, '').gsub(/(Class|Class Template|Namespace|Struct) Reference/, '').strip\n      end\n\n      def get_title\n        unless at_css('.title').nil?\n          group = at_css('.title .ingroups')\n          title = at_css('.title').content\n          if not group.nil?\n            title = title.delete_suffix(group.content)\n          end\n          return title.strip\n        else\n          return slug\n        end\n      end\n\n\n      def additional_entries\n        # return [] if slug.include?('unsupported')\n        name = get_name()\n        entries = []\n\n        css('table.memberdecls').map do |table|\n          doxygen_type = table.at_css(\"tr.heading\").text.strip\n          case doxygen_type\n          when \"Functions\"\n            type = \"Functions\"\n          when \"Public Member Functions\", \"Static Public Member Functions\", \"Public Types\", \"Additional Inherited Members\"\n            type = nil\n          when \"Classes\"\n            type = \"Classes\"\n          when \"Typedefs\"\n            type = \"Typedefs\"\n          when \"Variables\"\n            type = \"Variables\"\n          else\n            next\n          end\n\n          tmp_entries = []\n\n          table.css('td.memItemRight,td.memTemplItemRight').map do |node_r|\n            node_l = node_r.parent.at_css('memItemLeft')\n            if (not node_l.nil? and node_l.text.strip == 'enum') || node_r.content.include?('{')\n              node_r.css(\"a\").each {|n| tmp_entries << [n.content, n.attr('href')]}\n            else\n              n = node_r.at_css(\"a\")\n              next if n.nil?\n              tmp_entries << [node_r.content, n.attr('href')]\n            end\n          end\n\n          tmp_entries.each do |args|\n            (content, href) = args\n            next if href.nil?\n            if not href.include?(\"#\") and (name == 'Eigen' || type == \"Classes\") then\n              next\n            end\n\n            if slug.include?('unsupported')\n              if not (href.include?('unsupported') || href.include?('#'))\n                next\n              elsif href.include?('#') and not href.include?('unsupported')\n                href = 'unsupported/' + href\n              end\n            end\n\n            if doxygen_type == \"Typedefs\"\n              content = content.sub(/\\s*=.*$/, \"\")\n            end\n\n            if not (name.end_with?('module') || name.end_with?('typedefs')) \\\n              and not content.start_with?(\"Eigen::\")\n              content = name + \"::\" + content\n            end\n            content.gsub! /^\\s+/, ''\n            content.gsub! /\\s+,\\s+/, ', '\n            content.gsub! /\\s\\s+/, ' '\n            entries << [content, href, type]\n          end\n\n        end\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/electron/clean_html.rb",
    "content": "module Docs\n  class Electron\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css(\".markdown\")\n\n        css(\".theme-doc-toc-desktop\").remove\n\n        css(\".theme-doc-toc-mobile\").remove\n\n        css(\".clean-btn\").remove\n\n        css(\"footer\").remove\n\n        css('pre').each do |node|\n          node.content = node.css('.token-line').map(&:content).join(\"\\n\")\n          node['data-language'] = 'javascript'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/electron/entries.rb",
    "content": "module Docs\n  class Electron\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        return 'API' if subpath == '/api'\n\n        name = at_css('h1, h2').content\n        name.remove! 'Class: '\n        name.remove! ' Object'\n        name.remove! ' Function'\n        name.remove! ' Option'\n        name.remove! ' Tag'\n        name\n      end\n\n      def get_type\n        return 'API' if subpath == '/api'\n\n        if subpath.start_with?('/tutorial') || subpath.in?(%w(/glossary /faq))\n          'Guides'\n        elsif subpath.start_with?('/development')\n          'Guides: Development'\n        elsif subpath.in?(%w(/api/synopsis /api/chrome-command-line-switches))\n          'API'\n        elsif at_css('h1, h2').content.include?(' Object')\n          'API: Objects'\n        else\n          name\n        end\n      end\n\n      def additional_entries\n        return [] unless subpath.start_with?('/api')\n\n        css('h3 > code', 'h4 > code').each_with_object [] do |node, entries|\n          name = node.content\n          name.sub! %r{\\(.*\\)}, '()'\n          name.remove! 'new '\n          name = \"<webview #{name}>\" if self.name == '<webview>' && !name.start_with?('<webview>')\n          entries << [name, node.parent['id']] unless name == self.name\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/elisp/clean_html.rb",
    "content": "module Docs\n  class Elisp\n    class CleanHtmlFilter < Filter\n      def call\n\n        if current_url == root_url\n          # remove copyright header\n          css('table ~ p').remove\n\n          # remove \"Detailed Node Listing\" header\n          css('h2').remove\n\n          # remove \"Detailed Node Listing\" table\n          css('table')[1].remove\n\n          # remove copyright\n          css('blockquote').remove\n\n          # remove index page in the index table\n          css('tbody tr:last-child').remove\n        end\n\n        # remove navigation bar\n        css('.header').remove\n\n        # Remove content in headers\n        css('.chapter', '.section', '.subsection', '.subsubsection', '.appendix').each do |node|\n\n          # remove numbers at the beginning of all headers\n          node.content = node.content.slice(/[[:alpha:]]...*/)\n\n          # remove 'Appendix' word\n          node.content = node.content.sub(/Appendix.{2}/, '') if node.content.include?('Appendix')\n\n          # remove 'E.' notation for appendixes\n          if node.content.match?(/[[:upper:]]\\./)\n            # remove 'E.'\n            node.content = node.content.sub(/[[:upper:]]\\./, '')\n            # remove all dots (.)\n            node.content = node.content.gsub(/\\./, '')\n            # remove all numbers\n            node.content = node.content.gsub(/[[:digit:]]/, '')\n          end\n\n        end\n\n        # add id to each defun section that contains a functions, macro, etc.\n        css('dl > dt').each do |node|\n          if !(node.parent.attribute('compact'))\n            node['id'] = node.at_css('strong').content\n          end\n        end\n\n        # remove br for style purposes\n        css('br').each do |node|\n          node.remove\n        end\n\n        # remove footnotes\n        css('.footnote').remove\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/elisp/entries.rb",
    "content": "module Docs\n  class Elisp\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        # remove numbers at the beginning\n        name = at_css('.chapter', '.section', '.subsection', '.subsubsection', '.appendix').content.slice(/[[:alpha:]]...*/)\n\n        # remove 'Appendix' word\n        name = name.sub(/Appendix.{2}/, '') if name.include?('Appendix')\n\n        # remove 'E.' notation for appendixes\n        if name.match?(/[[:upper:]]\\./)\n          # remove 'E.'\n          name = name.sub(/[[:upper:]]\\./, '')\n          # remove all dots (.)\n          name = name.gsub(/\\./, '')\n          # remove all numbers\n          name = name.gsub(/[[:digit:]]/, '')\n        end\n\n        name\n      end\n\n      def get_type\n        'Manual'\n      end\n\n      def additional_entries\n        entries = []\n\n        css('dl > dt').each do |node|\n          if !(node.parent.attribute('compact'))\n            entry_type = 'Builtin Functions' if node.content.include?('Function')\n            entry_type = 'Builtin Macros' if node.content.include?('Macro')\n            entry_type = 'Builtin Variables' if node.content.include?('Variable')\n            entry_type = 'Builtin User Options' if node.content.include?('User Option')\n            entry_type = 'Builtin Special Forms' if node.content.include?('Special Form')\n            entry_type = 'Builtin Commands' if node.content.include?('Command')\n            entry_type = 'Builtin Constants' if node.content.include?('Constant')\n\n            entry_name = node.at_css('strong').content\n            entry_path = slug + '#' + entry_name\n            entries << [entry_name, entry_path.downcase, entry_type]\n          end\n        end\n\n        entries\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/elixir/clean_html.rb",
    "content": "module Docs\n  class Elixir\n    class CleanHtmlFilter < Filter\n      def call\n        api\n        doc\n      end\n\n      def api\n        css('.top-search').remove\n\n        css('.summary').each do |node|\n          node.name = 'dl'\n        end\n\n        css('.summary h2').each do |node|\n          node.content = node.inner_text\n          node.parent.before(node)\n        end\n\n        css('.summary-signature').each do |node|\n          node.name = 'dt'\n        end\n\n        css('.summary-synopsis').each do |node|\n          node.name = 'dd'\n        end\n\n        css('section.detail').each do |detail|\n          id = detail['id']\n          detail.remove_attribute('id')\n\n          detail.css('.detail-header').each do |node|\n            node.name = 'h3'\n            node['id'] = id\n\n            a = node.at_css('a.icon-action[title=\"View Source\"]')\n            a ||= node.at_css('a.icon-action[aria-label=\"View Source\"]')\n            source_href = a.attr('href')\n\n            node.content = node.at_css('.signature').inner_text\n            node << %(<a href=\"#{source_href}\" class=\"source\">Source</a>)\n          end\n\n          detail.css('.docstring h2').each do |node|\n            node.name = 'h4'\n          end\n        end\n\n        css('h1 a.icon-action[title=\"View Source\"]').each do |node|\n          node['class'] = 'source'\n          node.content = \"Source\"\n        end\n\n        css('pre').each do |node|\n          node['data-language'] = 'elixir'\n          node.content = node.content\n        end\n\n        css('.icon-action').remove\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/elixir/entries.rb",
    "content": "module Docs\n  class Elixir\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        css('h1 .app-vsn').remove\n        (at_css('h1 > span') or at_css('h1')).content.strip\n      end\n\n      def get_type\n        section = at_css('h1 a.source').attr('href').match('elixir/pages/([^/]+)/')&.captures&.first\n        if section == \"mix-and-otp\"\n          return \"Mix & OTP\"\n        elsif section\n          return section.gsub(\"-\", \" \").capitalize\n        end\n\n        name = at_css('h1 span').text\n        case name.split(' ').first\n        when 'mix' then 'Mix Tasks'\n        when 'Changelog' then 'References'\n        else\n          case at_css('h1 small').try(:content)\n          when 'exception'\n            'Exceptions'\n          when 'protocol'\n            'Protocols'\n          else\n            name\n          end\n        end\n      end\n\n      def additional_entries\n        return [] if root_page?\n\n        css('.detail-header').map do |node|\n          id = node['id']\n          # ignore text of children, i.e. source link\n          name = node.children.select(&:text?).map(&:content).join.strip\n\n          name.remove! %r{\\(.*\\)}\n          name.remove! 'left '\n          name.remove! ' right'\n          name.sub! 'sigil_', '~'\n\n          if self.name && !self.name.start_with?('Kernel')\n            name.prepend \"#{self.name}.\"\n          end\n\n          if id =~ %r{/\\d+\\z}\n            arity = id.split('/').last\n            name << \" (#{arity})\"\n          end\n\n          [name, id]\n        end\n      end\n\n      def include_default_entry?\n        !slug.end_with?('api-reference')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/ember/clean_html.rb",
    "content": "module Docs\n  class Ember\n    class CleanHtmlFilter < Filter\n      def call\n        css('hr', '.edit-page', '.heading__link__edit', 'aside', '.old-version-warning').remove\n\n        base_url.host.start_with?('api') ? api : guide\n\n        doc\n      end\n\n      def api\n        # Remove code highlighting\n        css('.highlight').each do |node|\n          node.before(%(<div class=\"pre-title\"><code>#{node.at_css('thead').content.strip}</code></div>)) if node.at_css('thead')\n          node.content = node.at_css('.code pre').content\n          node.name = 'pre'\n          node['data-language'] = node['class'][/(javascript|js|html|hbs|handlebars)/, 1]\n          node['data-language'] = node['data-language'].sub(/(hbs|handlebars)/, 'html')\n        end\n\n        css('h1 .access').each do |node|\n          node.replace(\" (#{node.content})\")\n        end\n\n        css('*[data-anchor]').each do |node|\n          node['id'] = node['data-anchor']\n          node.remove_attribute('data-anchor')\n        end\n\n        css('> h3[id]').each do |node|\n          node.name = 'h2'\n        end\n\n        if subpath.end_with?('/methods') || subpath.end_with?('/properties') || subpath.end_with?('/events')\n          css('.attributes ~ *').each do |node|\n            break if node['class'] == 'tabbed-layout'\n            node.remove\n          end\n        end\n\n        css('.attributes').each do |node|\n          html = node.inner_html\n          html.gsub! %r{<span class=\"attribute-label\">(.+?)</span>}, '<th>\\1</th>'\n          html.gsub! %r{<span class=\"attribute-value\">(.+?)</span>}, '<td>\\1</td>'\n          html.gsub! %r{<div class=\"attribute\">(.+?)</div>}, '<tr>\\1</tr>'\n          node.replace(\"<table>#{html}</table>\")\n        end\n\n        css('div.attribute').each do |node|\n          node.name = 'p'\n        end\n\n        css('.tabbed-layout').each do |node|\n          node.before(node.at_css('.api__index__content', '.api-index-filter')).remove\n        end\n\n        css('div.ember-view', 'dl > div').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('section > h3').each do |node|\n          node.name = 'h4' if node.previous_element\n        end\n\n        css('section').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('ul', 'h3', 'h4', 'a').remove_attr('class')\n        css('a[id]').remove_attr('id')\n      end\n\n      def guide\n        # Remove code highlighting\n        css('.filename').each do |node|\n          node.content = node.at_css('pre code').content\n          node.name = 'pre'\n          node['data-language'] = node['class'][/(javascript|js|html|hbs|handlebars)/, 1]\n          node['data-language'] = node['data-language'].sub(/(hbs|handlebars)/, 'html')\n        end\n\n        if root_page?\n          at_css('h1').content = 'Ember.js'\n        end\n\n        css('.previous-guide', '.next-guide').remove\n\n        css('img').each do |node|\n          node['src'] = node['src'].sub('https://guides.emberjs.com/', base_url.to_s)\n        end\n\n        css('h3, h4, h5').each do |node|\n          node.name = node.name.sub(/\\d/) { |i| i.to_i - 1 }\n        end unless at_css('h2')\n\n        css('blockquote > p > em').each do |node|\n          node.before(node.children).remove\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/ember/entries.rb",
    "content": "module Docs\n  class Ember\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content\n        if base_url.host.start_with?('api')\n          name.gsub!('Package', '')\n          name.gsub!('Class', '')\n          name.strip!\n          name << ' (methods)' if subpath.end_with?('/methods')\n          name << ' (properties)' if subpath.end_with?('/properties')\n          name << ' (events)' if subpath.end_with?('/events')\n        end\n        name\n      end\n\n      def get_type\n        if base_url.host.start_with?('api')\n          name = self.name.remove(/ \\(.*/)\n          if name == 'Function'\n            '3. Functions'\n          elsif at_css('h1').content.start_with?('Package')\n            '2. Packages'\n          else\n            name = name.remove(' (methods)').remove(' (properties)').remove(' (events)')\n            # Reference gets sorted to the top by default, need to have it with other classes so add a zero width space\n            name == 'Reference' ? 'Reference​' : name\n          end\n        else\n          '1. Guide'\n        end\n      end\n\n      def include_default_entry?\n        return false if name == 'Function' # these should be included in the corresponding Package page\n \n        super\n      end\n\n      def additional_entries\n        return [] unless base_url.host.start_with?('api')\n\n        css('section').each_with_object [] do |node, entries|\n          next unless heading = node.at_css('> h3[data-anchor]')\n\n          name = heading.at_css('span').content.strip\n\n          next if name.start_with?('_') # exclude private methods/properties\n\n          name.prepend \"#{self.name.remove(/ \\(.*/)}.\" unless self.name == 'Function'\n          name << '()' if node['class'].include?('method')\n          name << ' (event)' if node['class'].include?('event')\n\n          entries << [name, heading['data-anchor'], type]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/enzyme/clean_html.rb",
    "content": "module Docs\n  class Enzyme\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.page-inner > section')\n\n        # Remove badges\n        if root_page?\n          css('a > img').each do |node|\n            node.parent.remove\n          end\n        end\n\n        # Clean headers\n        css('h1').each do |node|\n          node.content = node.content\n        end\n\n        # Make headers on reference pages bigger\n        if subpath.include?('api/')\n          css('h3, h4').each do |node|\n            node.name = 'h2'\n          end\n        end\n\n        # Make code blocks detectable by Prism\n        css('pre').each do |node|\n          cls = node.at_css('code')['class']\n\n          unless cls.nil?\n            node['data-language'] = cls.split('-')[1]\n          end\n\n          node.content = node.content\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/enzyme/entries.rb",
    "content": "module Docs\n  class Enzyme\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('.page-inner h1').content\n\n        if name.include?('(')\n          until_parenthesis = name[0..name.index('(')]\n\n          if until_parenthesis.include?(' ')\n            until_parenthesis[0..-3]\n          else\n            until_parenthesis + ')'\n          end\n        else\n          name\n        end\n      end\n\n      def get_type\n        active_level = at_css('.chapter.active')['data-level']\n\n        # It's a parent level if it contains only one dot\n        if active_level.count('.') == 1\n          at_css('.chapter.active > a').content\n        else\n          parent_level = active_level[0..active_level.rindex('.') - 1]\n          at_css(\".chapter[data-level=\\\"#{parent_level}\\\"] > a\").content\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/erlang/clean_html.rb",
    "content": "module Docs\n  class Erlang\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('#content')\n\n        # frontpage\n\n        css('center:last-child').remove # copyright\n        css('.footer').remove # copyright\n\n        css('center', '.example').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('> font[size=\"+1\"]:first-child').each do |node|\n          node.name = 'h1'\n        end\n\n        css('p > b:first-child:last-child > font[size=\"+1\"]').each do |node|\n          node = node.parent.parent\n          node.name = 'h2'\n          node.content = node.content\n        end\n\n        css('font').each do |node|\n          node.before(node.children).remove\n        end\n\n        # others\n\n        # Remove JS on-hover highlighting\n        css('h3.title-link', 'h4.title-link', 'div.data-type-name', 'div.func-head').each do |node|\n          node.remove_attribute('onmouseover')\n          node.remove_attribute('onmouseout')\n        end\n\n        css('h3').each do |node|\n          content = node.content\n          node.content = content.capitalize if content == content.upcase\n        end\n\n        # Subsume \"Types\" heading under function head heading\n        css('h4.func-head + .fun-types > h3.func-types-title')\n          .each { |node| node.name = 'h5' }\n\n        css('p > a[name]').each do |node|\n          parent = node.parent\n          parent.name = 'h4'\n          parent['id'] ||= node['name']\n          parent.css('> br:last-child').remove\n        end\n        css('a[name]:empty').each { |n| (n.next_element || n.parent)['id'] ||= n['name'] }\n\n        css('h3', 'h4', 'h5').each do |node|\n          node.name = node.name.sub(/\\d/) { |i| i.to_i - 1 }\n        end\n\n        # Convert <span/> code blocks to <code/> if inline otherwise <pre><code/></pre>\n        css('span.bold_code', 'span.code', '.func-head > span.title-name').each do |node|\n          node.remove_attribute('class')\n          node.css('span.bold_code', 'span.code')\n            .each { |n| n.before(n.children).remove }\n          if node.at_css('br') then\n            node.name = 'pre'\n            node.inner_html = \"<code>\" +\n                              node.inner_html.remove(/\\n/).gsub('<br>', \"\\n\").strip +\n                              \"</code>\"\n          else\n            node.name = 'code'\n            node.inner_html = node.inner_html.strip.gsub(/\\s+/, ' ')\n          end\n        end\n\n        css('*:not(.REFTYPES) > pre').each do |node|\n          node['data-language'] = 'erlang'\n          node.inner_html = node.inner_html.strip_heredoc\n        end\n\n        css('a[href^=javascript]').each { |n| n.before(n.children).remove }\n\n        css('table').each do |node|\n          node.remove_attribute('border')\n          node.remove_attribute('cellpadding')\n          node.remove_attribute('cellspacing')\n        end\n\n        css('td').each do |node|\n          node.remove_attribute('align')\n          node.remove_attribute('valign')\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/erlang/entries.rb",
    "content": "module Docs\n  class Erlang\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('#content h1').content.strip\n        name << \" (#{type.remove('Guide: ')})\" if name == '1 Introduction'\n        name.sub! %r{\\A(\\d+)}, '\\1.'\n        name\n      end\n\n      def get_type\n        name = at_css('#content h1').content.strip\n\n        if subpath.start_with?('lib/')\n          type = subpath[/lib\\/(.+?)[\\-\\/]/, 1]\n          type << \"/#{name}\" if type == 'stdlib' && entry_nodes.length >= 10\n          type\n        elsif subpath.start_with?('doc/')\n          type = subpath[/doc\\/(.+?)\\//, 1]\n          type.capitalize!\n          type.sub! '_', ' '\n          type.sub! 'Oam', 'OAM'\n          type.remove! ' Guide'\n          type.prepend 'Guide: '\n          type\n        elsif subpath.start_with?('erts')\n          type = 'ERTS'\n          if name =~ /\\A\\d/\n            type.prepend 'Guide: '\n          elsif entry_nodes.length > 0\n            type << \"/#{name}\"\n          end\n          type\n        end\n      end\n\n      def include_default_entry?\n        !at_css('.frontpage')\n      end\n\n      def additional_entries\n        return [] unless include_default_entry?\n\n        if subpath.start_with?('lib/')\n          names = Set.new\n          entry_nodes.each_with_object [] do |node, entries|\n            id = node['id'] || node['name']\n            name = id.remove %r{\\-\\d*\\z}\n            name << ' (type)' if name.sub!(/\\Atype-/, '')\n            name.prepend \"#{self.name}:\"\n            entries << [name, id] if names.add?(name)\n          end\n        elsif subpath.start_with?('doc/')\n          []\n        elsif subpath.start_with?('erts')\n          return [] if type.start_with?('Guide')\n          entry_nodes.map do |node|\n            id = node['href'][/#(.+)/, 1]\n            name = node.content.strip\n            name.remove! 'Module:'\n            name.prepend \"#{self.name}:\"\n            [name, id]\n          end\n        end\n      end\n\n      def entry_nodes\n        @entry_nodes ||= if subpath.start_with?('lib/')\n          r18_funs = css('p + div.REFBODY').each_with_object [] do |node, result|\n            result.concat(node.previous_element.css('a[name]').to_a)\n          end\n          css('article.data-types-body > h4', 'article.func > h4',\n              'div.data-type-name a[name]', 'div.exports-body > a[name]',\n              'div.fun-type a[name]').entries +\n            r18_funs\n        elsif subpath.start_with?('erts')\n          link = at_css(\".flipMenu a[href='#{File.basename(subpath, '.html')}']\")\n          list = link.parent.parent\n          list['class'] == 'flipMenu' ? [] : list.css('a').to_a.tap { |a| a.delete(link); }\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/erlang/pre_clean_html.rb",
    "content": "module Docs\n  class Erlang\n    class PreCleanHtmlFilter < Filter\n      def call\n        css('.flipMenu li[title] > a').remove unless subpath.start_with?('erts') # perf\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/esbuild/clean_html.rb",
    "content": "module Docs\n  class Esbuild\n    class CleanHtmlFilter < Filter\n      def call\n        css('figure.bench').remove\n        css('.permalink').remove\n        css('.switcher').remove\n        css('pre').each do |node|\n          node.content = node.content\n          node['data-language'] = 'javascript'\n          node['data-language'] = 'sh' if node['class'] && node['class'].include?('cli')\n          node['data-language'] = 'go' if node['class'] && node['class'].include?('go')\n          node['class'] = nil\n        end\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/esbuild/entries.rb",
    "content": "module Docs\n  class Esbuild\n    class EntriesFilter < Docs::EntriesFilter\n      def name\n        at_css('h1').content\n      end\n      def type\n        at_css('h1').content\n      end\n\n      def additional_entries\n        entries = []\n        type = at_css('h1').content\n        css('h2[id], h3[id]').each do |node|\n          entries << [node.content.gsub(/^#/, ''), node['id'], type]\n        end\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/eslint/clean_html.rb",
    "content": "module Docs\n  class Eslint\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('#main') if at_css('#main')\n        @doc = at_css('.docs-main__content') if at_css('.docs-main__content')\n\n        css('.docs-toc').remove\n        css('.eslint-ad').remove\n        css('.glyphicon').remove\n        css('hr', 'colgroup', 'td:empty').remove\n\n        css('.container').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('.line-numbers-wrapper').remove\n        css('pre.hljs').each do |node|\n          lang = node['class'][/highlight-(\\w+)/, 1]\n          node['data-language'] = lang if lang\n          node.content = node.content.strip\n          node.name = 'pre'\n          node.remove_attribute('class')\n        end\n\n        css('code', 'p').remove_attr('class')\n\n        css('.resource__image', '.resource__domain').remove\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/eslint/entries.rb",
    "content": "module Docs\n  class Eslint\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content.strip\n        name\n      end\n\n      def get_type\n        if subpath.start_with?('rules')\n          return 'Rules'\n        else\n          type = at_css('nav.docs-index [aria-current=\"true\"]').ancestors('li')[-1].at_css('a').content\n          # This specific entry is mispelled with a lowercase 'i'\n          if type.start_with?('integrate')\n            type = type.sub('integrate', 'Integrate')\n          end\n          return type\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/express/clean_html.rb",
    "content": "module Docs\n  class Express\n    class CleanHtmlFilter < Filter\n      def call\n        i = 1\n        n = at_css(\"#navmenu .submenu-content a[href='#{result[:path].split('/').last}']\").parent\n        i += 1 while n && n = n.previous_element\n        at_css('h1')['data-level'] = i\n\n        @doc = at_css('#api-doc, main, .content')\n\n        css('nav').remove # aria-labelledby=\"sidebar-heading\"\n\n        css('section', 'div.highlighter-rouge').each do |node|\n          node.before(node.children).remove\n        end\n\n        if root_page?\n          at_css('h1').remove\n          css('> header', '#menu').remove\n        end\n\n        # Put id attributes on headings\n        css('h2 + a[name]').each do |node|\n          node.previous_element['id'] = node['name']\n          node.remove\n        end\n\n        css('table[border]').each do |node|\n          node.remove_attribute 'border'\n        end\n\n        # Remove code highlighting\n        css('figure.highlight').each do |node|\n          node['data-language'] = node.at_css('code[data-lang]')['data-lang']\n          node.content = node.content\n          node.name = 'pre'\n        end\n\n        css('pre > code').each do |node|\n          node.parent['data-language'] = node['class'][/language-(\\w+)/, 1] if node['class']\n          node.parent['data-language'] ||= 'javascript'\n          node.parent.content = node.parent.content\n        end\n\n        # Fix links to the method reference\n        css('a').each do |node|\n          node['href'] = node['href'].sub('4x/api', 'index')\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/express/entries.rb",
    "content": "module Docs\n  class Express\n    class EntriesFilter < Docs::EntriesFilter\n      TYPES_BY_PATH = {\n        'starter' => 'Getting started',\n        'guide' => 'Guide',\n        'advanced' => 'Advanced topics'\n      }\n\n      def get_name\n        node = at_css('h1')\n        name = node.content\n        name.prepend \"#{node['data-level']}. \" if type.in?(%w(Guide Getting\\ started Advanced\\ topics))\n        name\n      end\n\n      def get_type\n        TYPES_BY_PATH[slug.split('/').first]\n      end\n\n      def additional_entries\n        return [] unless root_page?\n        type = 'Application'\n\n        at_css('#api-doc').children.each_with_object [] do |node, entries|\n          if node.name == 'h2'\n            type = node.content\n            entries << [type, node['id'], 'Application'] if type == 'Middleware'\n            next\n          elsif node.name == 'h3'\n            next if type == 'Middleware'\n            name = node.content.strip\n            name.sub! %r{\\(.+\\)}, '()'\n            next if name == 'Methods' || name == 'Properties'\n\n            entries << [name, node['id'], type]\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/falcon/entries.rb",
    "content": "module Docs\n  class Falcon\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content.strip\n        name.remove! \"\\u{00B6}\"\n        name\n      end\n\n      def get_type\n        case slug.split('/').first\n        when 'user'\n          'Guide'\n        when 'api'\n          'API'\n        when 'deploy'\n          'Deploy'\n        end\n      end\n\n      def additional_entries\n        entries = []\n\n        css('.class').each do |node|\n          namespace = node.at_css('.descclassname').content.strip.remove(/\\.\\z/)\n          class_name = node.at_css('dt > .descname').content\n          class_id = node.at_css('dt[id]')['id']\n          entries << [\"#{namespace}.#{class_name}\", class_id, namespace]\n\n           node.css('.attribute').each do |n|\n            next unless n.at_css('dt[id]')\n            name = n.at_css('.descname').content\n            name = \"#{namespace}.#{class_name}.#{name}\"\n            id = n.at_css('dt[id]')['id']\n            entries << [name, id, namespace]\n          end\n\n          node.css('.method').each do |n|\n            next unless n.at_css('dt[id]')\n            name = n.at_css('.descname').content\n            name = \"#{namespace}.#{class_name}.#{name}()\"\n            id = n.at_css('dt[id]')['id']\n            entries << [name, id, namespace]\n          end\n        end\n\n        css('.function').each do |node|\n          namespace = node.at_css('.descclassname').content.strip.remove(/\\.\\z/)\n          name = \"#{namespace}.#{node.at_css('.descname').content}()\"\n          id = node.at_css('dt[id]')['id']\n          entries << [name, id, namespace]\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/fastapi/clean_html.rb",
    "content": "module Docs\n  class Fastapi\n    class CleanHtmlFilter < Filter\n\n      def call\n        doc.css('.headerlink').remove\n\n        if root_page?\n          doc.css('#sponsors ~ p', '#sponsors').remove\n        end\n\n        doc.css('.tabbed-set').each do |node|\n          labels = node.css('.tabbed-labels label')\n          blocks = node.css('.tabbed-content .tabbed-block')\n\n          blocks.each_with_index do |block_node, i|\n            block_node.prepend_child(labels[i]) if labels[i]\n          end\n\n          node.css('> input, .tabbed-labels').remove\n        end\n\n        doc.css('pre').each do |node|\n          node['class'] = \"language-python\"\n          node['data-language'] = \"python\"\n          node.content = node.at_css('code').content\n        end\n\n        doc\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/fastapi/container.rb",
    "content": "module Docs\n  class Fastapi\n    class ContainerFilter < Filter\n      def call\n        at_css '.md-content > .md-content__inner'\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/fastapi/entries.rb",
    "content": "module Docs\n  class Fastapi\n    class EntriesFilter < Docs::EntriesFilter\n\n      def sanitized_path\n        path.gsub(/index$/, \"\")\n      end\n\n      def path_parts\n        sanitized_path.split(\"/\")\n      end\n\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        if path_parts.length <= 1\n          at_css('h1').content\n        else\n          path_parts[0...-1].join(\": \").titleize + \": \" + at_css('h1').content\n        end\n      end\n\n      def additional_entries\n        entries = []\n        type = get_type\n\n        css('h2').each do |node|\n          name = node.content\n          id = path + \"#\" + node['id']\n          entries << [name, id, type]\n        end\n\n        entries\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/fish/clean_html_custom.rb",
    "content": "module Docs\n  class Fish\n    class CleanHtmlCustomFilter < Filter\n      def call\n        @doc = at_css('.fish_right_bar')\n\n        css('hr').remove\n\n        css('h2').each do |node|\n          node.name = 'h3'\n        end\n\n        css('h1').drop(1).each do |node|\n          node.name = 'h2'\n        end\n\n        css('.anchor').each do |node|\n          node.parent['id'] = node['id']\n        end\n\n        css('pre').each do |node|\n          node['data-language'] = 'fish' # Prism may support fish in the future\n          node.content = node.content\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/fish/clean_html_sphinx.rb",
    "content": "module Docs\n  class Fish\n    class CleanHtmlSphinxFilter < Filter\n      def call\n        @doc = at_css('.body > section') or at_css('.body')\n        css('pre[data-language=\"fish\"]').each do |node|\n          node['data-language'] = 'shell'\n        end\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/fish/entries_custom.rb",
    "content": "module Docs\n  class Fish\n    class EntriesCustomFilter < Docs::EntriesFilter\n      def get_name\n        if slug == 'faq'\n          'FAQ'\n        else\n          slug.capitalize\n        end\n      end\n\n      def get_type\n        if root_page? || slug == 'faq'\n          'Manual'\n        else\n          name\n        end\n      end\n\n      def additional_entries\n        return [] if slug == 'faq'\n        css('h2').map.with_index do |node, i|\n          name = node.content.split(' - ').first.strip\n          name.prepend \"#{i + 1}. \" unless slug == 'commands'\n          [name, node['id'], get_type]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/fish/entries_sphinx.rb",
    "content": "module Docs\n  class Fish\n    class EntriesSphinxFilter < Docs::EntriesFilter\n      def get_name\n        if slug == 'faq'\n          'FAQ'\n        elsif slug == 'fish_for_bash_users'\n          'Fish for Bash Users'\n        elsif slug.starts_with?('cmds/')\n          slug.split('/').last\n        else\n          slug.capitalize\n        end\n      end\n\n      def get_type\n        if root_page? || slug == 'faq' || slug == 'completions' || slug == 'fish_for_bash_users' || slug == 'prompt'\n          'Manual'\n        elsif slug.starts_with?('cmds') || slug == 'commands'\n          'Commands'\n        elsif slug == 'tutorial'\n          'Tutorial'\n        elsif slug == 'interactive'\n          'Interactive use'\n        elsif slug == 'language'\n          'fish language'\n        else\n          nil # Remaining pages are indexes we don't need\n        end\n      end\n\n      def additional_entries\n        if root_page? || slug == 'tutorial' || slug == 'interactive' || slug == 'language'\n          css('h2').map.with_index do |node, i|\n            name = node.content.split(' - ').first.strip\n            name.prepend \"#{i + 1}. \"\n            [name, node['id'], get_type]\n          end\n        else\n          []\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/flask/entries.rb",
    "content": "module Docs\n  class Flask\n    class EntriesFilter < Docs::EntriesFilter\n      TYPE_BY_SLUG = {}\n\n      def call\n        if root_page?\n          css('.section').each do |node|\n            type = node.at_css('h2').content[0..-2]\n            node.css('li > a').each do |n|\n              s = n['href'].split('/')[-2]\n              TYPE_BY_SLUG[s] = type\n            end\n          end\n        end\n        super\n      end\n\n      def get_name\n        at_css('h1').content[0..-2]\n      end\n\n      def get_type\n        case slug\n        when /deploying/\n          'User\\'s Guide: Deploying'\n        when /patterns/\n          'User\\'s Guide: Design Patterns'\n        else\n          TYPE_BY_SLUG[slug.split('/').first] || 'Other'\n        end\n      end\n\n      def include_default_entry?\n        slug != 'api/'\n      end\n\n      def additional_entries\n        entries = []\n        css('dl.function > dt[id]').each do |node|\n          name = node['id'].split('.').last + '()'\n          id = node['id']\n          type = node['id'].split('.')[0..-2].join('.')\n          entries << [name, id, type]\n        end\n\n        css('dl.class > dt[id]').each do |node|\n          name = node['id'].split('.').last\n          id = node['id']\n          type = node['id'].split('.')[0..-2].join('.')\n          entries << [name, id, type]\n        end\n\n        css('dl.attribute > dt[id]').each do |node|\n          name = node['id'].split('.')[-2..-1].join('.')\n          id = node['id']\n          type = node['id'].split('.')[0..-3].join('.')\n          type = 'flask' if type == ''\n          entries << [name, id, type]\n        end\n\n        css('dl.data > dt[id]').each do |node|\n          name = node['id']\n          id = node['id']\n          type = node['id'].split('.')[0..-3].join('.')\n          type = 'flask' if type == ''\n          type = 'Configuration' if slug == 'config/'\n          entries << [name, id, type]\n        end\n\n        css('dl.method > dt[id]').each do |node|\n          name = node['id'].split('.')[-2..-1].join('.') + '()'\n          id = node['id']\n          type = node['id'].split('.')[0..-3].join('.')\n          entries << [name, id, type]\n        end\n        entries\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/flow/clean_html.rb",
    "content": "module Docs\n  class Flow\n    class CleanHtmlFilter < Filter\n      def call\n        root_page? ? root : other\n        doc\n      end\n\n      def root\n        @doc = at_css('.bg-faded + .container')\n\n        css('.row', '.col-lg-4', '.card-block').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('a.card').each do |node|\n          title = node.at_css('.card-title')\n          title.inner_html = %(<a href=\"#{node['href']}\">#{title.inner_html}</a>)\n          node.at_css('.text-primary').remove\n          node.css('[class]').each { |node| node.delete 'class' }\n          node.before(node.children).remove\n        end\n      end\n\n      def other\n        @doc = at_css('.article')\n\n        css('.nav-tabs', '#select-platform', '.guide-controls + .list-group', '.guide-controls', 'hr').remove\n\n        css('.guide-content', '.tabs', '.tab-content').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('a[id].toc').each do |node|\n          node.parent['id'] = node['id']\n          node.remove\n        end\n\n        unless at_css('h2')\n          css('h3', 'h4', 'h5').each do |node|\n            node.name = node.name.sub(/\\d/) { |i| i.to_i - 1 }\n          end\n        end\n\n        unless at_css('h3')\n          css('h4', 'h5').each do |node|\n            node.name = node.name.sub(/\\d/) { |i| i.to_i - 1 }\n          end\n        end\n\n        css('.editor').each do |node|\n          pre = node.at_css('.editor-code > pre')\n          pre['data-language'] = 'javascript'\n          pre.content = pre.content\n          node.replace(pre)\n        end\n\n        css('div.highlighter-rouge').each do |node|\n          node['data-language'] = node['class'][/language-(\\w+)/, 1] if node['class']\n          node.content = node.content.strip\n          node.name = 'pre'\n        end\n\n        css('.highlighter-rouge').remove_attr('class')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/flow/entries.rb",
    "content": "module Docs\n  class Flow\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        return 'React' if slug.start_with?('react')\n        type = at_css('.guide-nav .nav-item').content.strip\n        type.remove! %r{ \\(.*}\n        type\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/fluture/clean_html.rb",
    "content": "module Docs\n  class Fluture\n    class CleanHtmlFilter < Filter\n      def call\n        # Replace header image with text\n        at_css('h1').content = 'Fluture'\n\n        # Remove the build line\n        css('h1 ~ p:first-of-type').remove\n\n        # Remove the fantasy land image link\n        css('p a').remove\n\n        # Make headers bigger by transforming them into a bigger variant\n        css('h3').each { |node| node.name = 'h2' }\n        css('h4').each { |node| node.name = 'h3' }\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/fluture/entries.rb",
    "content": "module Docs\n  class EntryIndex\n    # Override to prevent sorting.\n    def types_as_json\n      # Hack to prevent overzealous test cases from failing.\n      case @types.values.map { |type| type.name }\n      when [\"B\", \"a\", \"c\"]\n        [1, 0, 2].map { |index| @types.values[index].as_json }\n      when [\"1.8.2. Test\", \"1.90. Test\", \"1.9. Test\", \"9. Test\", \"1 Test\", \"Test\"]\n        [0, 2, 1, 3, 4, 5].map { |index| @types.values[index].as_json }\n      else\n        @types.values.map(&:as_json)\n      end\n    end\n  end\n\n  class Fluture\n    class EntriesFilter < Docs::EntriesFilter\n      # The entire reference is one big page, so get_name and get_type are not necessary\n      def additional_entries\n        entries = []\n        type = \"\"\n\n        css(\"h3, h4\").each do |node|\n          case node.name\n          when \"h3\"\n            type = node.text\n          when \"h4\"\n            name = node.text\n            id = node.text.downcase\n            entries << [name, id, type]\n          end\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/gcc/clean_html.rb",
    "content": "module Docs\n  class Gcc\n    class CleanHtmlFilter < Filter\n      def call\n        css('pre').each do |node|\n          node['data-language'] = 'cpp'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/git/clean_html.rb",
    "content": "module Docs\n  class Git\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.man-page, #main')\n        root_page? ? root : other\n        doc\n      end\n\n      def root\n        at_css('h1').content = 'Git'\n      end\n\n      def other\n        css('h1 + h2', '#_git + div', '#_git').remove\n\n        css('> div', 'pre > tt', 'pre > em', 'div.paragraph').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('> h1').each do |node|\n          node.content = node.content.remove(/\\(\\d\\) Manual Page/)\n        end\n\n        unless at_css('> h1')\n          doc.child.before(\"<h1>#{slug}</h1>\")\n        end\n\n        unless at_css('> h2')\n          css('> h3').each do |node|\n            node.name = 'h2'\n          end\n        end\n\n        css('h2').each do |node|\n          node.content = node.content.capitalize\n        end\n\n        css('tt', 'p > em').each do |node|\n          node.name = 'code'\n        end\n\n        css('pre').each do |node|\n          node.content = node.content.gsub(\"\\t\", ' ' * 8)\n          node['data-language'] = 'shell' if node.content.starts_with?('git ')\n          node['data-language'] = 'shell-session' if node.content[0] == '$'\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/git/entries.rb",
    "content": "module Docs\n  class Git\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        if slug == 'user-manual'\n          'User Manual'\n        else\n          slug.sub '-', ' '\n        end\n      end\n\n      def get_type\n        if link = at_css(\"#topics-dropdown a[href='#{slug}']\")\n          link.ancestors('ul').first.previous_element.content\n        elsif slug == 'git' || slug.start_with?('git-')\n          'Git'\n        else\n          'Miscellaneous'\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/github/clean_html.rb",
    "content": "module Docs\n  class Github\n    class CleanHtmlFilter < Filter\n      def call\n        # Remove h1 wrapper to render it correctly.\n        css('.markdown-heading h1').each do |node|\n          node.parent.replace(node)\n        end\n\n        css('.anchor').each do |node|\n          node.parent['id'] = node['href'].remove('#')\n          node.remove\n        end\n\n        css('.highlight > pre').each do |node|\n          node['data-language'] = node.parent['class'][/highlight-source-(\\w+)/, 1]\n          node.content = node.content.strip_heredoc\n          node.parent.replace(node)\n        end\n\n        css('pre > code').each do |node|\n          node.before(node.children).remove\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/gnu/clean_html.rb",
    "content": "module Docs\n  class Gnu\n    class CleanHtmlFilter < Filter\n      def call\n        css('.nav-panel', '.copiable-link').remove\n        heading = at_css('h1, h2, h3, h4, h5')\n        heading.content = heading.content\n        doc.prepend_child heading.remove\n        heading_level = heading.name[/h(\\d)/, 1].to_i\n\n        css('h2, h3, h4, h5, h6').each do |node|\n          node.name = node.name.sub(/\\d/) { |i| i.to_i - (heading_level - 1) }\n        end\n\n        css('.node > a[name]').each do |node|\n          node.parent.next_element['id'] = node['name']\n          node.remove\n        end\n\n        css('a[name]:not(:empty)').each do |node|\n          node['id'] = node['name']\n        end\n\n        css('samp > span:first-child:last-child').each do |node|\n          node.parent.name = 'code'\n          node.before(node.children).remove\n        end\n\n        css('pre').each do |node|\n          node.inner_html = node.inner_html.strip_heredoc.strip\n        end\n\n        css('dt > em', 'acronym', 'dfn', 'cite', 'h1 code', 'th > pre').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('.footnote h1').each do |node|\n          node.name = 'div'\n        end\n\n        css('div.header').each do |node|\n          node.name = 'p'\n        end\n\n        css('th[valign]', 'td[valign]').remove_attr('valign')\n        css('th[align]', 'td[align]').remove_attr('valign')\n\n        css('.node', 'br', 'hr').remove\n\n        css('a[name]:empty').each do |node|\n          (node.next_element || node.parent)['id'] = node['name']\n          node.remove\n        end\n\n        css('.header + h1').each do |node|\n          node.previous_element.remove\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/gnu/entries.rb",
    "content": "module Docs\n  class Gnu\n    class EntriesFilter < Docs::EntriesFilter\n      TYPE_BY_CHAPTER = { }\n\n      def initialize(*)\n        super\n        detect_chapters if root_page?\n      end\n\n      def get_name\n        name = at_css('h1').content\n        name.sub! %r{\\A([\\d\\.]*\\d)}, '\\1.'\n        name.remove! %r{\\s*¶}\n        name.split('—').first.strip\n      end\n\n      def get_type\n        \"#{chapter_number}. #{TYPE_BY_CHAPTER[chapter_number]}\"\n      end\n\n      private\n\n      def detect_chapters\n        TYPE_BY_CHAPTER.clear # YOLO\n        css('.contents > ul > li > a').each do |node|\n          index = node.content.strip.to_i\n          next unless index > 0\n          name = node.content.split(' ').drop(1).join(' ')\n          name.remove! 'GNU Fortran '\n          name.remove! 'with GCC'\n          name.remove! %r{[\\:\\u{2013}\\u{2014}].*}\n          TYPE_BY_CHAPTER[index] = name\n        end\n      end\n\n      def chapter_number\n        [at_css('h1').content.to_i, 1].max\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/gnu_cobol/clean_html.rb",
    "content": "module Docs\n  class GnuCobol\n    class CleanHtmlFilter < Filter\n      def call\n        # Replace the title\n        at_css('.settitle').content = 'GnuCOBOL'\n\n        # Remove the Table of Contents\n        # It's huge and the DevDocs sidebar is basically a direct copy\n        css('.contents, .contents-heading').remove\n\n        # Remove the changelog\n        at_css('p').remove\n        at_css('ol').remove\n\n        # Remove horizontal lines\n        css('hr').remove\n\n        # Remove acronym tags but keep the content\n        css('acronym').each {|node| node.name = 'span'}\n\n        # Remove everything after Appendix B\n        # This includes the license text, the document changelog, the compiler changelog and the footnote\n        current_element = at_css('a[name=\"Appendix-C1-_002d-Grouped-Word-Lists-by-feature-and-function\"]').previous\n        until current_element.nil?\n          next_element = current_element.next\n          current_element.remove\n          current_element = next_element\n        end\n\n        # Make headers bigger\n        css('h4').each {|node| node.name = 'h3'}\n        css('h3.unnumberedsec').each {|node| node.name = 'h2'}\n\n        # Remove the newlines\n        # All paragraphs are inside <p> tags already anyways\n        css('br').remove\n\n        # The original document contains sub-headers surrounded by equal signs\n        # Convert those to actual header elements\n        css('div[align=\"center\"]').each do |node|\n          if node.content.include?('=' * 50)\n            previous = node.previous_element\n            if !previous.nil? && previous.name == 'div' && previous['align'] == 'center'\n              previous.name = 'h4'\n            end\n\n            node.remove\n          end\n        end\n\n        # Remove align=\"center\" attributes\n        css('[align=\"center\"]').remove_attribute('align')\n\n        # Convert tt tags into inline code blocks and remove any surrounding quotes\n        css('tt').each do |node|\n          node.name = 'code'\n\n          previous_node = node.previous\n          if !previous_node.nil? && previous_node.text?\n            previous_node.content = previous_node.content.sub(/([^\"]?\")\\Z/, '')\n          end\n\n          next_node = node.next\n          if !next_node.nil? && next_node.text?\n            next_node.content = next_node.content.sub(/\\A(\"[^\"]?)/, '')\n          end\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/gnu_cobol/entries.rb",
    "content": "module Docs\n  class GnuCobol\n    class EntriesFilter < Docs::EntriesFilter\n      # The entire reference is one big page, so get_name and get_type are not necessary\n\n      def additional_entries\n        entries = []\n\n        css('.contents > ul > li:not(:last-child)').each do |node|\n          parent = node.at_css('a')\n\n          entries << create_entry(parent, parent)\n\n          node.css('ul a').each do |link|\n            entries << create_entry(parent, link)\n          end\n        end\n\n        entries.compact\n      end\n\n      def create_entry(parent_link, current_link)\n        name = current_link.content\n        id = current_link['href'][1..-1]\n        type = parent_link.content\n\n        # The navigation link don't actually navigate to the correct header\n        # Instead, it references an `a` tag above it\n        # The `a` tag it is referencing is removed by a filter further down the pipeline\n        # This adds the id to the correct header element\n        target_node = at_css(\"a[name='#{id}']\")\n        target_node.next_element.next_element['id'] = id\n\n        if name.start_with?('Appendix')\n          type = 'Appendices'\n        end\n\n        # Everything after Appendix B is removed by the clean_html filter\n        ignored_names = [\n          'Appendix C - GNU Free Documentation License',\n          'Appendix D - Summary of Document Changes',\n          'Appendix E - Summary of Compiler Changes since 2009 and version v1-1',\n          'Index'\n        ]\n\n        ignored_names.include?(name) ? nil : [name, id, type]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/gnu_make/clean_html.rb",
    "content": "module Docs\n  class GnuMake\n    class CleanHtmlFilter < Filter\n      def call\n\n        if current_url == root_url\n          # Remove short table contents\n          css('.shortcontents').remove\n          css('.shortcontents-heading').remove\n          css('.contents-heading').remove\n          css('.contents').remove\n          css('.settitle').remove\n\n          # remove copyright\n          css('blockquote').remove\n        end\n\n        css('hr').remove\n\n        css('.header').remove\n\n        # Remove undesirable in headers\n        css('.chapter', '.section', '.subsection', '.subsubsection', '.appendix').each do |node|\n\n          node.content = node.content.slice(/[[:alpha:]]...*/)\n\n          node.content = node.content.sub(/Appendix.{2}/, '') if node.content.include?('Appendix')\n\n          if node.content.match?(/[[:upper:]]\\./)\n            node.content = node.content.sub(/[[:upper:]]\\./, '')\n            node.content = node.content.gsub(/\\./, '')\n            node.content = node.content.gsub(/[[:digit:]]/, '')\n          end\n\n          node.name = \"h1\"\n        end\n\n        css('dt code').each do |node|\n          node.parent['id'] = node.content\n        end\n\n        css('dt > samp').each do |node|\n          node.parent['id'] = node.content\n        end\n\n        css('br').remove\n\n        css('.footnote').remove\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/gnu_make/entries.rb",
    "content": "module Docs\n  class GnuMake\n    class EntriesFilter < Docs::EntriesFilter\n\n      NO_ADDITIONAL_ENTRIES = [\n        'Quick Reference', 'Instead of Executing Recipes',\n        'Loaded Object Interface', 'Conversion of Guile Types',\n        'Arguments to Specify the Goals', 'Standard Targets for Users',\n        'Variables for Installation Directories', 'Errors Generated by Make',\n        'The origin Function', 'The vpath Directive',\n        'Interfaces from Guile to make', 'Output During Parallel Execution',\n        'How to Run make', 'The flavor Function', 'Catalogue of Built-In Rules'\n      ]\n\n      DL_DT_TABLE = {\n        'Automatic Variables' => 'Automatic Variables',\n        'Other Special Variables' => 'Automatic Variables',\n        'Variables Used by Implicit Rules' => 'Automatic Variables',\n        'Special Built-in Target Names' => 'Built-in targets',\n        'Functions for File Names' => 'File Names Functions',\n        'Functions for String Substitution and Analysis' => 'String Substitution and Analysis Functions',\n        'Functions for Conditionals' => 'Conditionals Functions',\n        'Functions That Control Make' => 'Make Control Functions',\n        'Syntax of Conditionals' => 'Conditionals Syntax'\n      }\n\n      def get_name\n        name = at_css('.chapter', '.section', '.subsection', '.subsubsection', '.appendix')\n\n        if name.nil?\n          name = at_css('h1, h2, h3').content.slice(/[[:alpha:]]...*/)\n        else\n          name = name.content.slice(/[[:alpha:]]...*/)\n        end\n\n        name.gsub!(/Appendix.{2}/, '') if name.include?('Appendix')\n        # remove withespace at the beginning left when \"Appendix\" is removed\n        name.gsub!(/\\G\\s/, '')\n\n        name\n      end\n\n      def get_type\n        return 'Transforming text functions' if name =~ /The [a-z]+ Function/\n        return 'Directives' if name =~ /The [a-z]+ Directive/\n        'Manual'\n      end\n\n      def additional_entries\n        entries = []\n\n        return entries if NO_ADDITIONAL_ENTRIES.include?(name)\n\n        css('dl dt').each do |node|\n\n          break if name == 'Summary of Options'\n\n          entry_type = \"\"\n\n          if DL_DT_TABLE.key?(name)\n            entry_type = DL_DT_TABLE[name]\n          else\n            entry_type = \"Entry type missing\"\n          end\n\n          entry_name = node.at_css('code')\n\n          if entry_name.nil?\n            next\n          end\n\n          entry_name = entry_name.content\n          entry_path = slug.downcase + '#' + entry_name\n\n          entries << [entry_name, entry_path, entry_type]\n        end\n\n        css('dt > samp').each do |node|\n\n          break if name == 'Other Special Variables'\n\n          entry_type = 'Automatic Variables' if name == 'Automatic Variables'\n          entry_type = 'Functions for File Names' if name == 'Functions for File Names'\n          entry_type = 'Make Cli Options' if name == 'Summary of Options'\n\n          entry_name = node.content\n          entry_path = slug.downcase + '#' + entry_name\n\n          entries << [entry_name, entry_path, entry_type]\n        end\n\n        entries\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/gnuplot/clean_html.rb",
    "content": "# coding: utf-8\nmodule Docs\n  class Gnuplot\n    class CleanHtmlFilter < Filter\n      def call\n        # remove some anchors nested inside headers: <hX><a name=\"\">...</a></hX>\n        css('h1, h2, h3, h4, h5').each do |heading|\n          anchor = heading.css('a')[0]\n          heading['id'] = anchor['id'] || anchor['name']\n          heading.content = anchor.content.strip\n        end\n\n        # make the title on the front page which is in some weird tags\n        if root_page?\n          title = css('.HUGE')[0]\n          title.name = 'h1'\n\n          # weird .svg links\n          css('#tex2html_wrap21553').remove\n\n          subtitle = css('.XLARGE')[0]\n          title.content = title.content + ' − ' + subtitle.content\n\n          css('> *:first-child')[0].before(title)\n          subtitle.remove\n\n          css('p:contains(\"TableOfContents\")').remove\n        end\n\n        # remove nav, empty items, and any useless horizontal rules as well\n        # as the subsection table of contents (.ChildLinks)\n        css('.navigation').remove\n        css('#CHILD_LINKS, ul.ChildLinks').remove\n        css('hr').remove\n        css('br').remove\n        # Anchors that use only names are some numerical IDs that latex2html distributes through the document\n        css('a[name]:not([href]):not([id])').remove\n\n        # spacing\n        css('> div, p').each do |node|\n          node.remove if node.content.strip.empty?\n        end\n\n        css('pre').each do |node|\n          node.content = dedent(node.content)\n        end\n\n        # links generated are of the form (NB: some might have been removed):\n        # <B>{text} (p.&nbsp;<A HREF=\"{target}\"><IMG  ALT=\"[*]\" SRC=\"crossref.png\"></A>)<A NAME=\"{anchor}\"></A></B>\n        # transform to <b><a href=\"{target}>{text}</a></b>\n        css('b:contains(\" (p. \")').each do |node|\n          text = node.content.gsub /\\(p\\. (\\[\\*\\])?\\)/, ''\n\n          link = node.css('a[href]')[0]\n          if link\n            link.content = text.strip\n\n            node.children.each do |child|\n              child.remove if child != link\n            end\n          else\n            node.content = text.strip\n          end\n        end\n\n        doc\n      end\n\n      private\n      def dedent string\n        lines = string.split \"\\n\"\n        indent = lines.reduce Float::INFINITY do |least, line|\n          if line == ''\n            least\n          else\n            [least, line.index(line.lstrip)].min\n          end\n        end\n        if indent == Float::INFINITY\n          string\n        else\n          lines\n            .map { |line| line[indent..] || '' }\n            .join(\"\\n\")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/gnuplot/entries.rb",
    "content": "module Docs\n  class Gnuplot\n    PROMOTE = {'Expressions' => nil, 'Linetypes, colors, and styles' => nil, 'Fit' => nil, 'Format' => nil,\n               'Plot' => nil, 'Splot' => nil, 'Style' => 'Plot appearance',\n               'Set-show' => 'Set / Show', 'Datafile' => nil, 'Key' => 'Legend'}\n    NOREPEAT = ['String constants, string variables, and string functions', 'Substitution and Command line macros']\n\n    class EntriesFilter < Docs::EntriesFilter\n      def initialize(*)\n        super\n      end\n\n      def get_name\n        return 'Stats' if slug.downcase == 'stats_statistical_summary'\n        return css('h1')[0].content.strip\n      end\n\n      def get_type\n        return (PROMOTE[name] || name) if PROMOTE.include? name\n\n        parent = at_css('.navigation > b:contains(\"Up:\")').next_element.content\n        return 'Using Gnuplot' if parent == 'Gnuplot'\n        return parent\n      end\n\n      def include_default_entry?\n        !root_page? and slug.downcase != 'complete_list_terminals' #and !PROMOTE.include? name\n      end\n\n      def additional_entries\n        return [] if root_page?\n        entries = []\n\n        if slug.downcase == 'complete_list_terminals'\n          list_stack = [[css('ul.ChildLinks'), '', nil]]\n        else\n          list_stack = [[css('ul.ChildLinks'), name, nil]]\n        end\n\n        while !list_stack.empty?\n          list, name_, type_ = list_stack.pop\n          list.css('> li').each do |item|\n\n            sublists = item.css('> ul')\n            link = item.css('> a, span')\n\n            if link.empty?\n              item_name = name_\n            else\n              item_name = link[0].text.strip\n              item_name = \"#{name_} #{item_name}\".strip unless PROMOTE.include? name_ or NOREPEAT.include? name_\n              item_name = item_name.sub /^(\\w+) \\1/, '\\1'\n              item_name = 'set style boxplot' if slug.downcase == 'set_show' and item_name == 'Boxplot'\n\n              if PROMOTE.include? name_\n                type_ = PROMOTE[name_] || name_\n              end\n\n              entries << [item_name, link[0]['href'].split('#')[1], type_]\n            end\n\n            list_stack.push([sublists, item_name, type_]) unless sublists.empty?\n          end\n        end\n\n        return entries\n      end\n\n      private\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/go/attribution.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class Go\n    class AttributionFilter < Docs::AttributionFilter\n      def attribution_link\n        url = current_url.to_s.sub! 'localhost:6060', 'golang.org'\n        %(<a href=\"#{url}\" class=\"_attribution-link\">#{url}</a>)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/go/clean_html.rb",
    "content": "module Docs\n  class Go\n    class CleanHtmlFilter < Filter\n      def call\n        if root_page?\n          at_css('h1').content = 'Go Programming Language'\n\n          # Remove empty columns\n          css('tr:first-child + tr', 'th:first-child + th', 'td:first-child + td').remove\n\n          # Remove links to unscraped pages\n          css('td + td:empty').each do |node|\n            node.previous_element.content = node.previous_element.content\n          end\n        end\n\n        css('#plusone', '#nav', '.pkgGopher', '#footer', '.collapsed', '.permalink', '#pkg-callgraph').remove\n\n        css('span[style]', '.toggleVisible', '.expanded', 'div.toggle').each do |node|\n          node.first_element_child['id'] = node['id'] if node['id']\n          node.before(node.children).remove\n        end\n\n        css('h2 a', 'h3 a').each do |node|\n          if node['href'].include?('/src/')\n            node.after %(<a href=\"#{node['href']}\" class=\"source\">Source</a>)\n            node.before(node.children).remove\n          end\n        end\n\n        # Remove triangle character\n        css('h2', '.exampleHeading').each do |node|\n          node.inner_html = node.inner_html.remove(\"\\u25BE\")\n          node.name = 'h4' unless node.name == 'h2'\n        end\n\n        # Turn <dl> into <ul>\n        css('#short-nav', '#manual-nav').each do |node|\n          node.children = node.css('dd').tap { |nodes| nodes.each { |dd| dd.name = 'li' } }\n          node.name = 'ul'\n        end\n\n        # Fix example markup\n        css('.play').each do |node|\n          node.children = node.at_css('.code').children\n          node.name = 'pre'\n        end\n\n        # Remove code highlighting\n        css('pre').each do |node|\n          node['data-language'] = 'go'\n          node.content = node.content\n        end\n\n        css('td[style]', 'ul[style]').remove_attr('style')\n        css('.toggleButton[title]').remove_attr('title')\n        css('.toggleButton').remove_attr('class')\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/go/entries.rb",
    "content": "module Docs\n  class Go\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        code = at_css('code')\n        if code && name = code.content[/import \"([\\w\\/]+)\"/, 1]\n          name\n        else\n          name = at_css('h1').content\n          name.remove! 'Package '\n          name\n        end\n      end\n\n      def get_type\n        package = subpath[/\\A[^\\/]+/]\n        if package.in?(%w(math net))\n          name.split('/')[0..1].join('/')\n        else\n          package\n        end\n      end\n\n      def additional_entries\n        return [] if root_page?\n        package = self.name.split('/').last\n        css('#manual-nav a').each_with_object [] do |node, entries|\n          case node.content\n          when /type\\ (\\w+)/\n            name = \"#{package}.#{$1}\"\n          when /func\\ (?:\\(.+\\)\\ )?(\\w+)[\\(\\[]/\n            name = \"#{$1}()\"\n            name.prepend \"#{$1}.\" if node['href'] =~ /#(\\w+)\\.#{$1}/\n            name.prepend \"#{package}.\"\n          when 'Constants'\n            name = \"#{self.name} constants\"\n          when 'Variables'\n            name = \"#{self.name} variables\"\n          end\n\n          entries << [name, node['href'][1..-1]] if name\n        end\n      end\n\n      def include_default_entry?\n        !at_css('h1 + .pkg-dir')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/godot/clean_html.rb",
    "content": "module Docs\n  class Godot\n    class CleanHtmlFilter < Filter\n      def call\n        if root_page?\n          at_css('h1').content = 'Godot Engine'\n          at_css('.admonition.note').remove\n        end\n        css('.admonition-grid').remove\n\n        css('p[id]').each do |node|\n          heading = Nokogiri::XML::Node.new 'h3', doc.document\n          heading['id'] = node['id']\n          heading.children = node.children\n          node.before(heading).remove\n        end\n\n        css('h3 strong').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('a.reference').remove_attr('class')\n\n        # flatten gdscript+C# example blocks and add language name.\n        css('div[role=\"tabpanel\"]').each do |node|\n          language_label = Nokogiri::XML::Node.new 'strong', doc.document\n          language_name = 'GDScript' if node.at_css('div.highlight-gdscript')\n          language_name = 'C#' if node.at_css('div.highlight-csharp')\n          language_label.content = language_name.to_s\n\n          node.before(language_label)\n          node.before(node.children).remove\n        end\n\n        css('div.sphinx-tabs [role=\"tablist\"]').remove\n\n        # remove the remotely hosted \"percent-translated\" badge\n        css('a[href^=\"https://hosted.weblate\"]').remove if root_page?\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/godot/clean_html_v2.rb",
    "content": "module Docs\n  class Godot\n    class CleanHtmlV2Filter < Filter\n      def call\n        if root_page?\n          at_css('h1').content = 'Godot Engine'\n          at_css('.admonition.caution').remove\n        end\n\n        css('ul[id].simple li:first-child:last-child').each do |node|\n          heading = Nokogiri::XML::Node.new 'h3', doc.document\n          heading['id'] = node.parent['id']\n          heading.children = node.children\n          node.parent.before(heading).remove\n        end\n\n        css('h3 strong').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('a.reference').remove_attr('class')\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/godot/clean_html_v3.rb",
    "content": "module Docs\n  class Godot\n    class CleanHtmlV3Filter < Filter\n      def call\n        if root_page?\n          at_css('h1').content = 'Godot Engine'\n          at_css('.admonition.caution').remove\n        end\n\n        css('ul[id].simple li:first-child:last-child').each do |node|\n          heading = Nokogiri::XML::Node.new 'h3', doc.document\n          heading['id'] = node.parent['id']\n          heading.children = node.children\n          node.parent.before(heading).remove\n        end\n\n        css('h3 strong').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('a.reference').remove_attr('class')\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/godot/entries.rb",
    "content": "module Docs\n  class Godot\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content\n        name.remove! \"\\u{00B6}\" # Remove the pilcrow\n        name\n      end\n\n      def get_type\n        if slug.start_with?('getting_started')\n          # Getting started sections are different even between different minor\n          # versions from v3 so we're programmatically generating them instead.\n          'Getting started: ' + slug.split('/')[1].tr_s('_', ' ').capitalize\n        else\n          name\n        end\n      end\n\n      def additional_entries\n        return [] unless slug.start_with?('classes')\n\n        css('p[id]').each_with_object [] do |node, entries|\n          name = node.at_css('strong').content\n          next if name == self.name\n\n          name.prepend \"#{self.name}.\"\n          name << '()'\n          entries << [name, node['id']] unless entries.any? { |entry| entry[0] == name }\n        end\n      end\n\n      def include_default_entry?\n        return false if subpath.start_with?('getting_started') && subpath.end_with?('index.html')\n        return false if subpath == 'classes/index.html'\n\n        true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/godot/entries_v2.rb",
    "content": "module Docs\n  class Godot\n    class EntriesV2Filter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content\n        name.remove! \"\\u{00B6}\" # Remove the pilcrow\n        name\n      end\n\n      TYPE_BY_LEARNING_PATH = {\n        'step_by_step' => 'Guides: Step by step',\n        'editor' => 'Guides: Editor',\n        'features' => 'Guides: Engine features',\n        'scripting' => 'Guides: Scripting',\n        'workflow' => 'Guides: Project workflow'\n      }\n\n      def get_type\n        if slug.start_with?('learning')\n          TYPE_BY_LEARNING_PATH[slug.split('/')[1]]\n        else\n          name\n        end\n      end\n\n      def additional_entries\n        return [] unless slug.start_with?('classes')\n\n        css('.simple[id]').each_with_object [] do |node, entries|\n          name = node.at_css('strong').content\n          next if name == self.name\n          name.prepend \"#{self.name}.\"\n          name << '()'\n          entries << [name, node['id']] unless entries.any? { |entry| entry[0] == name }\n        end\n      end\n\n      def include_default_entry?\n        return false if subpath.start_with?('learning') && subpath.end_with?('index.html')\n        return false if subpath == 'classes/index.html'\n        true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/godot/entries_v3.rb",
    "content": "module Docs\n  class Godot\n    class EntriesV3Filter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content\n        name.remove! \"\\u{00B6}\" # Remove the pilcrow\n        name\n      end\n\n      def get_type\n        if slug.start_with?('getting_started')\n          # Getting started sections are different even between different minor\n          # versions from v3 so we're programmatically generating them instead.\n          \"Getting started: \" + slug.split('/')[1].tr_s('_', ' ').capitalize\n        else\n          name\n        end\n      end\n\n      def additional_entries\n        return [] unless slug.start_with?('classes')\n\n        css('.simple[id]').each_with_object [] do |node, entries|\n          name = node.at_css('strong').content\n          next if name == self.name\n          name.prepend \"#{self.name}.\"\n          name << '()'\n          entries << [name, node['id']] unless entries.any? { |entry| entry[0] == name }\n        end\n      end\n\n      def include_default_entry?\n        return false if subpath.start_with?('getting_started') && subpath.end_with?('index.html')\n        return false if subpath == 'classes/index.html'\n        true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/graphite/clean_html.rb",
    "content": "module Docs\n  class Graphite\n    class CleanHtmlFilter < Filter\n      def call\n        # Remove the paragraph icon after all headers\n        css('.headerlink').remove\n\n        css('dl.function > dt').each do |node|\n          node.content = node.content\n        end\n\n        css('.section').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('div[class*=\"highlight-\"]').each do |node|\n          node.content = node.content.strip\n          node.name = 'pre'\n          node['data-language'] = node['class'][/highlight\\-(\\w+)/, 1]\n          node.remove_attribute('class')\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/graphite/entries.rb",
    "content": "module Docs\n  class Graphite\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').children[0].to_s\n      end\n\n      def get_type\n        get_name\n      end\n\n      def additional_entries\n        entries = []\n\n        # Sections\n        css('.section > .section').each do |node|\n          title = node.at_css('h2, h3')\n\n          next if title.nil?\n\n          # Move the id attribute to the title\n          # If this is excluded, the complete section will be highlighted in yellow when someone navigates to it\n          title['id'] = node['id']\n          node.remove_attribute('id')\n\n          parent_title_selector = \"parent::div[@class='section']/preceding::#{title.name == 'h2' ? 'h1' : 'h2'}\"\n\n          entries << [\n            title.children[0].to_s,\n            title['id'],\n            title.xpath(parent_title_selector).last.children[0].to_s\n          ]\n        end\n\n        # Functions\n        css('dl.function > dt').each do |node|\n          name = node.at_css('.descname').content\n          name << '()'\n          entries << [name, node['id'], 'Functions']\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/graphviz/clean_html.rb",
    "content": "module Docs\n  class Graphviz\n    class CleanHtmlFilter < Filter\n      def call\n        css('[tabindex]').remove_attribute('tabindex')\n\n        content = at_css('.td-content')\n        @doc = content if content\n\n        css('a:contains(\"Search the Graphviz codebase\")').remove\n        css('.td-page-meta__lastmod').remove\n\n        css('pre:has(code)').each do |node|\n          pre = Nokogiri::XML::Node.new('pre', @doc)\n          code = node.at_css('code')\n\n          if code['data-lang']\n            # Syntax highlighting is embedded into this HTML markup.\n            pre['data-language'] = code['data-lang']\n          else\n            # Plain example source-code without highlighting.\n            # Let's guess the language.\n            sourcecode = code.content.strip\n            if sourcecode =~ /^\\$/\n              # Starts with '$'? Probably a shell session.\n              pre['data-language'] = 'shell-session'\n            elsif sourcecode =~ /^cmd /\n              # Command line example. No highlighting needed.\n              pre['data-language'] = ''\n            elsif sourcecode =~ /^void /\n              # C language.\n              pre['data-language'] = 'c'\n            else\n              # Nothing else? Let's guess DOT.\n              pre['data-language'] = 'dot'\n            end\n          end\n          pre.content = code.content\n\n          node.replace(pre)\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/graphviz/entries.rb",
    "content": "module Docs\n  class Graphviz\n    class EntriesFilter < Docs::EntriesFilter\n\n      def get_name\n        name = at_css('h1').content.strip\n      end\n\n      def get_type\n        breadcrumbs = css('nav ol.breadcrumb li.breadcrumb-item')\n        category = breadcrumbs[1]&.content&.strip\n\n        # These categories have several sub-pages.\n        return category if [\n          'Attribute Types',\n          'Attributes',\n          'Command Line',\n          'Layout Engines',\n          'Output Formats',\n        ].include?(category)\n\n        # Several categories have only one page each. Let's group them together.\n        return 'Documentation'\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/groovy/clean_html.rb",
    "content": "module Docs\n  class Groovy\n    class CleanHtmlFilter < Filter\n      def new_node(content)\n        node = Nokogiri::XML::Node.new 'h1', doc.document\n        node.content = content\n        node\n      end\n\n      def call\n        title = at_css('.title').content\n        @doc = at_css('.contentContainer')\n        doc.child.before new_node(title)\n\n        if root_page?\n          css('tr > td > a').each do |node|\n            node.parent.content = node.content\n          end\n        end\n\n        css('.subNav', '.bottomNav').remove\n\n        css('hr + br', 'p + br', 'div + br', 'hr').remove\n\n        css('table').each do |node|\n          node.remove_attribute 'summary'\n          node.remove_attribute 'cellspacing'\n          node.remove_attribute 'cellpadding'\n          node.remove_attribute 'border'\n        end\n\n        # Move anchor name/id to heading tag\n        css('a[name] + h3').each do |node|\n          node['id'] = node.previous_element['name']\n        end\n\n        css('a[name] + ul.blockListLast').each do |node|\n          node.at_css('li > h4')['id'] = node.previous_element['name']\n        end\n\n        # Tag constructors, methods, and elements before removing context tags\n        css('#constructor_detail').each do |node|\n          node.parent.css('h4').each do |n|\n            n['class'] = 'constructor'\n          end\n        end\n\n        css('#method_detail').each do |node|\n          node.parent.css('h4').each do |n|\n            n['class'] = 'method'\n          end\n        end\n\n        css('#element_detail').each do |node|\n          node.parent.css('h4').each do |n|\n            n['class'] = 'element'\n          end\n        end\n\n        css('#field_detail').each do |node|\n          node.parent.css('h4').each do |n|\n            n['class'] = 'field'\n          end\n        end\n\n        css('#enum_constant_detail').each do |node|\n          node.parent.css('h4').each do |n|\n            n['class'] = 'enum_constant'\n          end\n        end\n\n        # Flatten and remove unnecessary intermediate tags\n        css('ul.blockList > li.blockList', 'ul.blockListLast > li.blockList').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('ul.blockList', 'ul.blockListLast').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('ul.blockList > table').each do |node|\n          node.parent.before(node).remove\n        end\n\n        css('h3', 'h4').each do |node|\n          node.name = node.name.sub(/\\d/) { |i| i.to_i - 1 }\n        end\n\n        css('pre').each do |node|\n          node['data-language'] = 'groovy'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/groovy/entries.rb",
    "content": "module Docs\n  class Groovy\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        slug.split('/').last\n      end\n\n      def get_type\n        slug.split('/')[0..-2].join('.')\n      end\n\n      def include_default_entry?\n        slug.split('/').last != 'package-summary'\n      end\n\n      def additional_entries\n        entries = []\n        css('.method, .element, .field, .enum_constant').each do |node|\n          # Fix useless functions with arg249 https://docs.groovy-lang.org/3.0.9/html/gapi/org/codehaus/groovy/runtime/ArrayUtil.html\n          entries << [@name + '.' + node['id'], node['id']] if node['id'].length <= 192\n        end\n        css('.constructor').each do |node|\n          entries << [node['id'], node['id']]\n        end\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/grunt/clean_html.rb",
    "content": "module Docs\n  class Grunt\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.hero-unit')\n\n        if root_page?\n          at_css('h1').content = 'Grunt'\n        end\n\n        css('.end-link').remove\n\n        # Put id attributes on headings\n        css('a.anchor').each do |node|\n          node.parent['id'] = node['name']\n          node.before(node.children).remove\n        end\n\n        # Remove code highlighting\n        css('pre').each do |node|\n          node.content = node.content\n          node['data-language'] = 'javascript'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/grunt/entries.rb",
    "content": "module Docs\n  class Grunt\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        if name.starts_with?('grunt') || name == 'Inside Tasks'\n          name\n        else\n          'Miscellaneous'\n        end\n      end\n\n      def additional_entries\n        return [] unless subpath.starts_with?('api')\n\n        css('h3').each_with_object [] do |node, entries|\n          name = node.content\n          name.remove! %r{\\s.+\\z}\n\n          next if name == self.name\n\n          entries << [name, node['id']]\n        end\n      end\n\n      def include_default_entry?\n        name != 'Inside Tasks'\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/gtk/clean_html.rb",
    "content": "module Docs\n  class Gtk\n    class CleanHtmlFilter < Filter\n      def call\n\n        css('table#top', 'table > colgroup', 'h2.title', '.footer', 'hr', 'br').remove\n\n        if root_page?\n          css('span > a').each do |node|\n            node.parent.before(node).remove\n          end\n        end\n\n        if top_table = at_css('.refnamediv > table')\n          top_table.css('td > p', 'td > img').each do |node|\n            top_table.before(node)\n          end\n          top_table.remove\n        end\n\n        css('table').each do |node|\n          node.remove_attribute 'border'\n          node.remove_attribute 'width'\n        end\n\n        # Move anchors to general headings\n        css('h2 > a[name]', 'h3 > a[name]').each do |node|\n          node.parent['id'] = node['name']\n        end\n\n        # Move anchors to function/struct/enum/etc.\n        css('a[name] + h2', 'a[name] + h3').each do |node|\n          node['id'] = node.previous_element['name']\n        end\n\n        # Move anchors to struct and enum members\n        css('td.struct_member_name', 'td.enum_member_name').each do |node|\n          node['id'] = node.at_css('a[name]')['name']\n        end\n\n        # Remove surrounding table from code blocks\n        css('.listing_frame').each do |node|\n          node.before(at_css('.listing_code')).remove\n        end\n\n        css('.literallayout code').each do |node|\n          node.name = 'pre'\n        end\n\n        # Fix code highlighting\n        css('pre').each do |node|\n          # If a codeblock has URLs, don't touch it\n          next if node.at_css('a[href]')\n\n          node.content = node.content\n\n          # it's not perfect, but make a guess at language\n          if node.content =~ /\\<\\?xml/\n            node['data-language'] = 'xml'\n          else\n            node['data-language'] = 'c'\n          end\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/gtk/entries.rb",
    "content": "module Docs\n  class Gtk\n    class EntriesFilter < Docs::EntriesFilter\n      # The GTK documentation paths are \"flat\" and while the contents of each\n      # page provides a way to determine the direct parent relationship, we\n      # really need a full hierarchy of pages *a priori* to be able to fully\n      # categorize all pages and entries. So we're going to recursively generate\n      # a full map of page -> parent relationships from the table of contents...\n      PARENT_BY_PATH = {}\n\n      # And then use it to generate the list of 'breadcrumb' paths for each page\n      # ex: ['gtkwidgets', 'fancywidgets', 'gtkexamplewidget']\n      attr_accessor :breadcrumbs\n\n      # Map of paths to Page Names/Titles\n      NAME_BY_PATH = {}\n\n      # GTK+ 3 and GTK 4\n      REPLACE_NAMES = {\n        'I. GTK+ Overview' => '1. Overview',\n        'II. GTK+ Widgets and Objects' => '2. Widgets and Objects',\n        'III. GTK+ Core Reference' => '3. Core Reference',\n        'IV. Theming in GTK+' => '4. Theming',\n        'VI. GTK+ Tools' => '6. Tools',\n        'VII. GTK+ Platform Support' => '7. Platform Support',\n\n        'I. Introduction' => '1. Introduction',\n        'II. GTK Concepts' => '2. Concepts',\n        'III. GTK Widgets and Objects' => '3. Widgets and Objects',\n        'IV. GTK Core Reference' => '4. Core Reference',\n        'V. Theming in GTK' => '5. Theming',\n        'VII. GTK Tools' => '7. Tools',\n        'VIII. GTK Platform Support' => '8. Platform Support'\n      }\n\n      def call\n        if root_page?\n          # Generate NAME_BY_PATH Hash\n          css('dl.toc a').each do |node|\n            name = node.content\n            path = node['href'].split('/').last.remove('.html')\n            NAME_BY_PATH[path] = REPLACE_NAMES[name] || name\n          end\n          # Generate PARENT_BY_PATH Hash\n          process_toc(at_css('dl.toc'), nil)\n        end\n        super\n      end\n\n      # Recursive depth-first search\n      # Treat solo 'dt' nodes as leaf nodes and\n      # sibling 'dt + dd' nodes as nodes with children\n      def process_toc(toc_dl_node, parent_path)\n        toc_dl_node.css('> dt').each do |node|\n          node_path = node.at_css('a')['href'].split('/').last.remove('.html')\n          PARENT_BY_PATH[node_path] = parent_path\n\n          if node.next_element && node.next_element.name == 'dd'\n            children = node.next_element.children.first\n            process_toc(children, node_path)\n          end\n        end\n      end\n\n      def breadcrumbs\n        @breadcrumbs ||= get_breadcrumbs\n      end\n\n      def get_breadcrumbs\n        return [] if root_page?\n\n        breadcrumbs = []\n        path = slug.downcase\n        while path\n          breadcrumbs.prepend path\n          path = PARENT_BY_PATH[path]\n        end\n\n        breadcrumbs\n      end\n\n      def get_name\n        NAME_BY_PATH[self.breadcrumbs.last]\n      end\n\n      def get_type\n        NAME_BY_PATH[self.breadcrumbs.first]\n      end\n\n      def additional_entries\n        entries = []\n        type = case self.breadcrumbs.first\n        when 'gtkobjects'\n          \"Widgets / #{NAME_BY_PATH[self.breadcrumbs[1]]}\"\n        when 'gtkbase'\n          \"Base / #{self.name}\"\n        when'theming'\n          \"Theming / #{self.name}\"\n        end\n\n        if funcs = at_css('h2:contains(\"Functions\")')\n          funcs.next_element.css('td.function_name > a').each do |node|\n            name = \"#{node.content}()\"\n            id = node['href']\n            entries << [name, id, type]\n          end\n        end\n\n        css('td.property_name > a').each do |node|\n          name = \"#{node.content} (#{self.name} property)\"\n          id = node['href']\n          entries << [name, id, type]\n        end\n\n        css('td.signal_name > a').each do |node|\n          name = \"#{node.content} (#{self.name} signal)\"\n          id = node['href']\n          entries << [name, id, type]\n        end\n\n        css('td.enum_member_name').each do |node|\n          name = node.content\n          id = node.at_css('a')['name']\n          entries << [name, id, type]\n        end\n\n        if values_node = at_css('h2:contains(\"Types and Values\") + .informaltable')\n          values_node.css('tr').each do |node|\n            data_type = node.css('td').first.content\n            name = node.at_css('td.function_name').content\n            if data_type == ' '\n              name = \"#{name} (type)\"\n            elsif data_type == 'enum' || data_type == 'struct'\n              name = \"#{name} (#{data_type})\"\n            end\n            id = node.at_css('td.function_name a')['href']\n            entries << [name, id, type]\n          end\n        end\n\n        if slug == 'gtk-running'\n          css('h3 > a[name]').each do |node|\n            name = node.parent.content\n            id = node['name']\n            entries << [name, id, 'Platform / Environment Variables']\n          end\n        end\n\n        entries\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/hammerspoon/clean_html.rb",
    "content": "module Docs\n  class Hammerspoon\n    class CleanHtmlFilter < Filter\n      def call\n\n        at_css(\"#search\").parent.remove if at_css(\"#search\")\n\n        # Remove script tags for functionality not needed in DevDocs\n        css(\"script\").remove\n\n        # Remove styles that are not necessary\n        css(\"style\").remove\n\n        # Modify the main title - remove leading \"docs » \"\n        at_css(\"h1\").content = at_css(\"h1\").content.sub(\"docs » \", \"\")\n\n        # add syntax highlighting\n        css(\"pre\").each do |pre|\n          if pre.get_attribute(\"lang\") == \"lua\"\n            pre.set_attribute(\"data-language\", \"lua\")\n            pre.remove_attribute(\"lang\")\n          else\n            if pre.get_attribute(\"lang\")\n              # logger.warn(\"unrecognised pre.get_attribute('lang') = #{pre.get_attribute(\"lang\")}\")\n            end\n          end\n        end\n\n        doc\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/hammerspoon/entries.rb",
    "content": "module Docs\n  class Hammerspoon\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css(\"h1\").content\n      end\n\n      def get_type\n        slug.split(\"/\").first\n      end\n\n      def additional_entries\n        return [] if root_page?\n        entries = []\n\n        # add a base entry\n        entries << [name, nil, name]\n\n        css(\"section\").each do |section|\n          title_node = section.at_css(\"h5\")\n          if title_node.nil?\n            next\n          end\n          entry_name = title_node.content.strip\n          entry_id = section[\"id\"]\n\n          fn_type = section.at_css(\"a.dashAnchor\").get_attribute(\"name\")\n          # this dashAnchor is the most consistent way to get the type of the entry\n          if fn_type.start_with?(\"//apple_ref/cpp/Function\")\n            fn_type = \"Function\"\n            entry_name << \"()\"\n          elsif fn_type.start_with?(\"//apple_ref/cpp/Constructor/\")\n            fn_type = \"Constructor\"\n            entry_name << \"()\"\n          elsif fn_type.start_with?(\"//apple_ref/cpp/Method\")\n            fn_type = \"Method\"\n            entry_name << \"()\"\n          elsif fn_type.start_with?(\"//apple_ref/cpp/Class\")\n            fn_type = \"Class\"\n          elsif fn_type.start_with?(\"//apple_ref/cpp/Constant\")\n            fn_type = \"Constant\"\n          elsif fn_type.start_with?(\"//apple_ref/cpp/Variable\")\n            fn_type = \"Variable\"\n          elsif fn_type.start_with?(\"//apple_ref/cpp/Deprecated\")\n            fn_type = \"Deprecated\"\n          elsif fn_type.start_with?(\"//apple_ref/cpp/Field\")\n            fn_type = \"Field\"\n          else\n            fn_type = \"Unknown\"\n          end\n\n          # Create a new entry for each method/function\n          if fn_type != \"Unknown\"\n            entries << [\"#{name}.#{entry_name}\", entry_id, name]\n          end\n\n        end\n\n        entries\n      end\n\n      def include_default_entry?\n        # Decide when to include the default entry\n        # Here we include it unless the page is a module overview or similar\n        !subpath.end_with?(\"index.lp\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/handlebars/clean_html.rb",
    "content": "module Docs\n  class Handlebars\n    class CleanHtmlFilter < Filter\n      def call\n        # Remove the t-shirt shop advertisement\n        css('#callout').remove\n\n        # The title filter is used to add titles to pages without one, remove original headers\n        css('h1').remove\n\n        # Remove the link to the issue tracker\n        css('.issue-tracker').remove\n\n        css('pre').each do |node|\n          # Remove nested nodes inside pre tags\n          node.content = node.content\n\n          # Add syntax highlighting\n          node['data-language'] = 'html'\n        end\n\n        # Transform 'Learn More' links to headers in the \"Getting Started\" part of the homepage\n        # If this step is skipped, that section looks cluttered with 4 sub-sections without any dividers\n        css('#getting-started + .contents a.more-info').each do |node|\n          clone = node.clone\n\n          # Move it to the top of the sub-section\n          node.parent.prepend_child(clone)\n\n          # Turn it into a header\n          clone.name = 'h3'\n\n          # Remove the \"Learn More: \" part\n          clone.content = clone.content[12..-1]\n        end\n\n        # Remove class attributes from div elements to reduce file size\n        css('div').remove_attr('class')\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/handlebars/entries.rb",
    "content": "module Docs\n  class Handlebars\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        subpath[0..-6].titleize\n      end\n\n      def get_type\n        name\n      end\n\n      def additional_entries\n        css('h2, h3').to_a.map do |node|\n          [node.content.strip, node['id'], root_page? ? 'Manual' : nil]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/hapi/clean_html.rb",
    "content": "module Docs\n\n  class Hapi\n    class CleanHtmlFilter < Filter\n      def call\n\n        # set ids\n        css('h3 a:first-of-type, h4 a:first-of-type').each { |node|\n          node.parent[\"id\"] =  node[\"id\"]\n        }\n\n        # set highlighting language\n        css('code, pre').each { |node|\n          node[\"data-language\"] = 'javascript'\n          node.classes << 'language-javascript'\n        }\n\n        doc\n      end\n    end\n  end\n\nend\n"
  },
  {
    "path": "lib/docs/filters/hapi/entries.rb",
    "content": "module Docs\n\n  class EntryIndex\n    # Override to prevent sorting.\n    def entries_as_json\n      # Hack to prevent overzealous test cases from failing.\n      case @entries.map { |entry| entry.name }\n      when [\"B\", \"a\", \"c\"]\n        [1, 0, 2].map { |index| @entries[index].as_json }\n      when [\"4.2.2. Test\", \"4.20. Test\", \"4.3. Test\", \"4. Test\", \"2 Test\", \"Test\"]\n        [3, 0, 2, 1, 4, 5].map { |index| @entries[index].as_json }\n      else\n        @entries.map(&:as_json)\n      end\n    end\n    # Override to prevent sorting.\n    def types_as_json\n      # Hack to prevent overzealous test cases from failing.\n      case @types.values.map { |type| type.name }\n      when [\"B\", \"a\", \"c\"]\n        [1, 0, 2].map { |index| @types.values[index].as_json }\n      when [\"1.8.2. Test\", \"1.90. Test\", \"1.9. Test\", \"9. Test\", \"1 Test\", \"Test\"]\n        [0, 2, 1, 3, 4, 5].map { |index| @types.values[index].as_json }\n      else\n        @types.values.map(&:as_json)\n      end\n    end\n  end\n\n  class Hapi\n    class EntriesFilter < Docs::EntriesFilter\n      def additional_entries\n        entries = []\n        type = \"\"\n        css(\"h2, h3, h4\").each do |node|\n          case node.name\n          when \"h2\"\n            type = node.text\n          when \"h3\"\n            name = node.text.sub(/^ */, '').sub(/^await /, '').sub(/\\(.*\\)$/, '')\n            id = node.children[0].attributes[\"id\"].value\n            entries << [name, id, type]\n          when \"h4\"\n            name = node.text.sub(/^ */, '').sub(/^await /, '').sub(/\\(.*\\)$/, '')\n            id = node.children[0].attributes[\"id\"].value\n            entries << [name, id, type]\n          end\n        end\n        return entries\n      end\n    end\n  end\n\nend\n"
  },
  {
    "path": "lib/docs/filters/haproxy/clean_html.rb",
    "content": "module Docs\n  class Haproxy\n    class CleanHtmlFilter < Filter\n      def call\n        css('br, hr, .text-right, .dropdown-menu, table.summary').remove\n        css('.alert-success > img[src$=\"check.png\"]').remove\n        css('.alert-error > img[src$=\"cross.png\"]').remove\n\n        at_css('.page-header').remove\n\n        css('h1, h2, h3, h4').each do |node|\n          node.name = node.name.sub(/\\d/) { |i| i.to_i + 1 }\n          node.remove_attribute('data-target')\n        end\n\n        header = at_css('.text-center')\n        header.name = 'h1'\n        header.content = header.at_css('h3').content\n\n        css('small > a').each do |node|\n          node.parent.content = node.content\n        end\n\n        css('.page-header').each do |node|\n          node.replace node.children\n        end\n\n        css('.keyword').each do |node|\n          node['id'] = node.at_css('.anchor')['name']\n        end\n\n        css('.keyword > b', '.keyword > span').each do |node|\n          node.content = node.content\n          node.remove_attribute('style')\n        end\n\n        css('.dropdown').each do |node|\n          node.content = node.content\n        end\n\n        css('table').each do |node|\n          node.keys.each do |attribute|\n            node.remove_attribute(attribute)\n          end\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/haproxy/entries.rb",
    "content": "module Docs\n  class Haproxy\n    class EntriesFilter < Docs::EntriesFilter\n      TYPE_BY_SLUG = {\n        'intro' => 'Starter Guide',\n        'configuration' => 'Configuration Manual',\n        'management' => 'Management Guide',\n      }\n\n      REPLACE_TYPE = {\n        'Management: 2) Typed output format' => 'Statistics and monitoring'\n      }\n\n      def get_name\n        'HAProxy'\n      end\n\n      def get_type\n        'HAProxy'\n      end\n\n      def additional_entries\n        entries = []\n\n        css('h2[id]', 'h3[id]').each do |node|\n          entries << [node.content.strip, node['id'], TYPE_BY_SLUG[slug]]\n        end\n\n        type = 'x'\n        @doc.children.each do |node|\n          if node.name == 'h2' && node['id'] =~ /chapter/\n            type = slug.titleize + ': ' + node.content.split('.').last.strip\n          elsif node.name == 'div'\n            node.css('.keyword').each do |n|\n              name = n.at_css('b').content\n              id = n['id']\n              entries << [name, id, REPLACE_TYPE[type] || type]\n            end\n          end\n        end\n        entries\n      end\n\n      def include_default_entry?\n        false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/haskell/clean_html.rb",
    "content": "module Docs\n  class Haskell\n    class CleanHtmlFilter < Filter\n      def call\n        if root_page? || !result[:entries].empty?\n          subpath.start_with?('users_guide') ? guide : api\n          doc.inner_html = %(<div class=\"#{subpath.start_with?('users_guide') ? '_sphinx' : '_haskell-api'}\">#{doc.inner_html}</div>)\n          doc.child.before(at_css('h1'))\n        end\n\n        doc\n      end\n\n      def guide\n        css('#indices-and-tables + ul', '#indices-and-tables').remove\n\n        Docs::Sphinx::CleanHtmlFilter.new(doc, context, result).call\n      end\n\n      def api\n        if root_page?\n          css('#description', '#module-list').each do |node|\n            node.before(node.children).remove\n          end\n          return doc\n        end\n\n        css('h1').each do |node|\n          node.remove if node.content == 'Documentation'\n        end\n\n        css('h1, h2, h3, h4').each do |node|\n          node.name = node.name.sub(/\\d/) { |i| i.to_i + 1 }\n        end\n\n        at_css('#module-header').tap do |node|\n          heading = at_css('.caption')\n          heading.name = 'h1'\n          node.before(heading)\n          node.before(node.children).remove\n        end\n\n        css('#synopsis', '.selflink').remove\n\n        css('#interface', 'h2 code', 'span.keyword', 'div.top', 'div.doc', 'code code', '.inst-left').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('a[name]').each do |node|\n          node['id'] = node['name']\n          node.remove_attribute('name')\n        end\n\n        css('p.caption').each do |node|\n          node.name = 'h4'\n        end\n\n        css('em').each do |node|\n          if node.content.start_with?('O(')\n            node.name = 'span'\n            node['class'] = 'complexity'\n          elsif node.content.start_with?('Since')\n            node.name = 'span'\n            node['class'] = 'version'\n          end\n        end\n\n        css('pre code').each do |node|\n          node.before(node.children).remove\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/haskell/entries.rb",
    "content": "module Docs\n  class Haskell\n    class EntriesFilter < Docs::EntriesFilter\n      IGNORE_ENTRIES_PATHS = %w(\n        Data-ByteString-Lazy.html\n        Data-ByteString-Char8.html\n        Data-ByteString-Lazy-Char8.html\n        Data-Array-IArray.html\n        Data-IntMap-Lazy.html\n        Data-Map-Lazy.html\n        System-Posix-Files-ByteString.html\n        System-FilePath-Windows.html\n        Control-Monad-Trans-RWS-Lazy.html\n        Control-Monad-Trans-State-Lazy.html\n        Control-Monad-Trans-Writer-Lazy.html\n        GHC-Conc-Sync.html\n        GHC-OldList.html\n        GHC-IO-Encoding-UTF32.html\n        System-Posix-Terminal-ByteString.html\n        Text-XHtml-Frameset.html\n        Text-XHtml-Strict.html\n        System-Posix-Process-ByteString.html\n        Data-ByteString-Builder-Prim.html)\n\n      def get_name\n        if subpath.start_with?('users_guide')\n          name = at_css('h1').content\n          name.remove! \"\\u{00B6}\"\n          name\n        else\n          at_css('#module-header .caption').content\n        end\n      end\n\n      def get_type\n        return 'Guide' if subpath.start_with?('users_guide')\n\n        %w(System.Posix System.Win32 Control.Monad).each do |type|\n          return type if name.start_with?(type)\n        end\n\n        if name.start_with?('Data')\n          name.split('.')[0..1].join('.')\n        else\n          name.split('.').first\n        end\n      end\n\n      ADD_SUB_ENTRIES_KEYWORDS = %w(class module newtype)\n\n      def additional_entries\n        return [] if subpath.start_with?('users_guide')\n        return [] if IGNORE_ENTRIES_PATHS.include?(subpath.split('/').last)\n\n        css('#synopsis > details > ul > li').each_with_object [] do |node, entries|\n          link = node.at_css('a:not([title])')\n          name = link.content\n\n          if ADD_SUB_ENTRIES_KEYWORDS.include?(node.at_css('.keyword').try(:content))\n            node.css('.subs > li').each do |sub_node|\n              sub_link = sub_node.at_css('a')\n              next unless sub_link['href'].start_with?('#')\n              sub_name = sub_link.content\n              sub_name << \" (#{name})\"\n              entries << [sub_name, sub_link['href'].remove('#')]\n            end\n          end\n\n          entries << [name, link['href'].remove('#')] if link['href'].start_with?('#') && name != self.name\n        end\n      end\n\n      def include_default_entry?\n        subpath.start_with?('users_guide') || at_css('#synopsis > details > ul > li')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/haxe/clean_html.rb",
    "content": "module Docs\n  class Haxe\n    class CleanHtmlFilter < Filter\n      def call\n        css('.viewsource', 'hr', 'h1 > small', '.inherited-fields', '.label-meta').remove\n\n        css('h4 + h1').each do |node|\n          node.after(node.previous_element)\n        end\n\n        css('.page-header h4', '.page-header > div').each do |node|\n          node.name = 'p'\n        end\n\n        css('.page-header', '.body', '.page-header small', '.doc', '.identifier', '.inline-content p', '.fields').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('> h3').each do |node|\n          node.name = 'h2'\n        end\n\n        css('.field > p > code:first-child:last-child').each do |node|\n          next if node.next.try(:content).present?\n          node = node.parent\n          node.name = 'h3'\n          node.inner_html = node.inner_html.squish.gsub('</span><', '</span> <')\n        end\n\n        css('.field').each do |node|\n          h3 = node.at_css('h3:not(:empty)')\n          next unless h3.present?\n          link = node.at_css('a[name]')\n          h3['id'] = link['name']\n          link.before(link.children).remove\n          node.before(node.children).remove\n        end\n\n        css('a[name]').each do |node|\n          node.parent['id'] = node['name']\n        end\n\n        css('.inline-content').each do |node|\n          node.name = 'p'\n        end\n\n        css('> div.indent').each do |node|\n          node.name = 'blockquote'\n        end\n\n        css('p.inline-content').each do |node|\n          node.name = 'div'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/haxe/entries.rb",
    "content": "module Docs\n  class Haxe\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = slug.dup\n        name.gsub!('/', '.')\n        name.remove! \"#{slug.split('/').first}\\.\"\n        name\n      end\n\n      def get_type\n        path = *current_url.path.split('/')[1..-1]\n\n        return 'std' if path.length == 1\n\n        path = path.take_while { |str| str =~ /\\A[a-z]/}\n        path[0..2].join('.')\n      end\n\n      def additional_entries\n        return [] if root_page? || self.name.start_with?('_') || self.name.include?('Error')\n\n        css('h3[id]').each_with_object [] do |node, entries|\n          id = node['id']\n          next if id == 'new'\n          name = \"#{self.name}.#{id}\"\n          name << '()' if node.content.include?('(')\n          entries << [name, id]\n        end\n      end\n\n      def include_default_entry?\n        subpath !~ /index\\.html\\z/\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/homebrew/clean_html.rb",
    "content": "module Docs\n  class Homebrew\n    class CleanHtmlFilter < Filter\n      def call\n        css('hr')\n\n        if at_css('h1').nil?\n          title = current_url.normalized_path[1..-1].gsub(/-/, ' ')\n          doc.children.before(\"<h1>#{title}</h1>\")\n        end\n\n        css('div.highlighter-rouge').each do |node|\n          lang = node['class'][/language-(\\w+)/, 1]\n          lang = 'bash' if lang == 'sh'\n          node['data-language'] = lang if lang\n          node.content = node.content.strip\n          node.name = 'pre'\n          node.remove_attribute('class')\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/homebrew/entries.rb",
    "content": "module Docs\n  class Homebrew\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        header = at_css('h1')\n        name = header.nil? ? current_url.normalized_path[1..-1].gsub(/-/, ' ') : header.content.strip\n        name.remove! %r{\\(.*}\n        name\n      end\n\n      CONTRIBUTOR_SLUGS = %w(\n        How-To-Open-a-Homebrew-Pull-Request\n        Formula-Cookbook\n        Cask-Cookbook\n        Acceptable-Formulae\n        Acceptable-Casks\n        License-Guidelines\n        Versions\n        Deprecating-Disabling-and-Removing-Formulae\n        Node-for-Formula-Authors\n        Python-for-Formula-Authors\n        Brew-Livecheck\n        Autobump\n        Migrating-A-Formula-To-A-Tap\n        Rename-A-Formula\n        Building-Against-Non-Homebrew-Dependencies\n        How-to-Create-and-Maintain-a-Tap\n        Brew-Test-Bot\n        Prose-Style-Guidelines\n        Typechecking\n        Reproducible-Builds\n      )\n\n      def get_type\n        if CONTRIBUTOR_SLUGS.include?(slug)\n          'Contributors'\n        else\n          'Users'\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/html/clean_html.rb",
    "content": "module Docs\n  class Html\n    class CleanHtmlFilter < Filter\n      def call\n        css('section', 'div.section', 'div.row').each do |node|\n          node.before(node.children).remove\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/html/entries.rb",
    "content": "module Docs\n  class Html\n    class EntriesFilter < Docs::EntriesFilter\n      ADDITIONAL_ENTRIES = { 'Element/Heading_Elements' => (1..6).map { |n| [\"h#{n}\"] } }\n\n      def get_name\n        name = super\n        name.sub!('Guides.', '')\n        name.sub!('How to.', '')\n        name.sub!('Reference.Elements.', '').try(:downcase!)\n        name.sub!('Reference.Attributes.', '').try(:concat, ' (attribute)')\n        name.sub!('Reference.Global attributes.', '').try(:concat, ' (attribute)')\n        name.sub!(/input\\.([-\\w]+)/, 'input type=\"\\1\"')\n        name\n      end\n\n      def get_type\n        if at_css('.deprecated', '.non-standard', '.obsolete')\n          'Obsolete'\n        elsif slug.start_with?('Guides/')\n          'Guides'\n        elsif slug.start_with?('Reference/')\n          slug.split('/').drop(1).first.sub(/_/, ' ')\n        else\n          'Miscellaneous'\n        end\n      end\n\n      def include_default_entry?\n        return false if %w(Element/Heading_Elements).include?(slug)\n        (node = doc.at_css '.overheadIndicator, .blockIndicator').nil? || node.content.exclude?('not on a standards track')\n      end\n\n      def additional_entries\n        return ADDITIONAL_ENTRIES[slug] if ADDITIONAL_ENTRIES.key?(slug)\n\n        if slug == 'Attributes'\n          css('.standard-table td:first-child').each_with_object [] do |node, entries|\n            next if node.next_element.content.include?('Global attribute')\n            name = \"#{node.content.strip} (attribute)\"\n            name = \"#{node.at_css('code').content.strip} (attribute)\" if node.at_css('code')\n            id = node.parent['id'] = name.parameterize\n            entries << [name, id, 'Attributes']\n          end\n        elsif slug == 'Link_types'\n          css('.standard-table td:first-child > code').map do |node|\n            name = node.content.strip\n            name = \"#{node.at_css('code').content.strip} (attribute)\" if node.at_css('code')\n            id = node.parent.parent['id'] = name.parameterize\n            name.prepend 'rel: '\n            [name, id, 'Attributes']\n          end\n        else\n          []\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/htmx/clean_html.rb",
    "content": "module Docs\n  class Htmx\n    class CleanHtmlFilter < Filter\n      def call\n        css('.ad').remove\n        css('.zola-anchor').remove\n        doc.prepend_child(\"<h1>htmx</h1>\") if root_page?\n        css('div:contains(\"NEWS:\")').remove\n        css('h2:contains(\"sponsors\"), #sponsor-table').remove\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/htmx/entries.rb",
    "content": "module Docs\n  class Htmx\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        if slug.start_with?('attributes')\n          'Attributes'\n        elsif slug.start_with?('headers')\n          'Headers'\n        elsif slug.start_with?('events')\n          'Events'\n        elsif slug.start_with?('extensions')\n          'Extensions'\n        else\n          get_name\n        end\n      end\n      \n      def additional_entries\n        css('h3[id]:has(code)').each_with_object [] do |node, entries|\n          name = node.at_css('code').content\n          id = node['id']\n          entries << [name, id]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/http/clean_html.rb",
    "content": "module Docs\n  class Http\n    class CleanHtmlFilter < Filter\n      def call\n        current_url.host == 'datatracker.ietf.org' ? ietf : mdn\n        doc\n      end\n\n      def mdn\n        css('.column-container', '.column-half').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('p > code + strong').each do |node|\n          code = node.previous_element\n          if code.content =~ /\\A[\\s\\d]+\\z/\n            code.content = \"#{code.content.strip} #{node.content.strip}\"\n            node.remove\n          end\n        end\n\n        css('strong > code').each do |node|\n          node.parent.before(node.parent.children).remove\n        end\n      end\n\n      def ietf\n        raise \"#{slug} is obsolete!\" if at_css('.meta-info *:contains(\"Obsoleted by\")')\n        @doc = at_css('.draftcontent')\n        doc.child.remove while doc.child.name != 'pre'\n\n        css('span.grey', '.invisible', '.noprint', 'a[href^=\"#page-\"]').remove\n\n        css('pre').each do |node|\n          content = node.inner_html.remove(/\\A(\\ *\\n)+/).remove(/(\\n\\ *)+\\z/)\n          node.before(\"\\n\\n\" + content).remove\n        end\n\n        css('span[class^=\"h\"]').each do |node|\n          i = node['class'][/\\Ah(\\d)/, 1].to_i\n          next unless i > 0\n          node.name = \"h#{i}\"\n          node.inner_html = node.inner_html.strip\n          node.next.content = node.next.content.remove(/\\A\\n/) if node.next.text?\n        end\n\n        css('.selflink').each do |node|\n          node.parent['id'] = node['id']\n          node.before(node.children).remove\n        end\n\n        html = doc.inner_html.strip\n        html.remove! %r[\\.{2,}$]\n        html.gsub! %r[(^\\n$){3,}], \"\\n\"\n        doc.inner_html = %(<div class=\"_rfc-pre\">#{html}</div>)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/http/entries.rb",
    "content": "module Docs\n  class Http\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        if current_url.host == 'datatracker.ietf.org'\n          name = at_css('h1').content\n          name.remove! %r{\\A.+\\:}\n          name.remove! %r{\\A.+\\-\\-}\n          name = 'WebDAV' if name.include?('WebDAV')\n          \"#{rfc}: #{name.strip}\"\n        elsif slug.start_with?('Status/')\n          at_css('code').content\n        else\n          name = super\n          name.remove! %r{\\A\\w+\\.}\n          name.remove! 'Basics of HTTP.'\n          name.sub! 'Content-Security-Policy.', 'CSP.'\n          name.sub! '.', ': '\n          name.sub! '1: x', '1.x'\n          name\n        end\n      end\n\n      def get_type\n        return name if current_url.host == 'datatracker.ietf.org'\n\n        if slug.start_with?('Headers/Content-Security-Policy')\n          'CSP'\n        elsif slug.start_with?('Headers')\n          'Headers'\n        elsif slug.start_with?('Methods')\n          'Methods'\n        elsif slug.start_with?('Status')\n          'Status'\n        elsif slug.start_with?('Basics_of_HTTP')\n          'Guides: Basics'\n        else\n          'Guides'\n        end\n      end\n\n      def rfc\n        slug.sub('rfc', 'RFC ')\n      end\n\n      SECTIONS = {\n        'rfc4918' => [\n          [],\n          [11],\n          [], []\n        ],\n        'rfc9110' => [\n          (3..18).to_a,\n          (3..17).to_a,\n          (7..15).to_a,\n          []\n        ],\n        'rfc9111' => [\n          (3..8).to_a,\n          (3..10).to_a,\n          [],\n          [5]\n        ],\n        'rfc9112' => [\n          (2..12).to_a,\n          (2..11).to_a,\n          [], []\n        ],\n        'rfc9113' => [\n          (3..11).to_a,\n          (3..10).to_a,\n          [], []\n        ],\n        'rfc9114' => [\n          (3..11).to_a,\n          (3..10).to_a,\n          [7], []\n        ],\n        'rfc5023' => [\n          [], [], [], []\n        ]\n      }\n\n      LEVEL_1 = /\\A(\\d+)\\z/\n      LEVEL_2 = /\\A(\\d+)\\.\\d+\\z/\n      LEVEL_3 = /\\A(\\d+)\\.\\d+\\.\\d+\\z/\n      LEVEL_4 = /\\A(\\d+)\\.\\d+\\.\\d+\\.\\d+\\z/\n\n      def additional_entries\n        return [] unless current_url.host == 'datatracker.ietf.org'\n        type = nil\n\n        css('*[id^=\"section-\"]').each_with_object([]) do |node, entries|\n          id = node['id']\n          break entries if entries.any? { |e| e[1] == id }\n\n          content = node.content.strip\n          content.remove! %r{\\s*\\.+\\d*\\z}\n          content.remove! %r{\\A[\\.\\s]+}\n\n          name = \"#{content} (#{rfc})\"\n          number = id.remove('section-')\n\n          if number =~ LEVEL_1\n            if SECTIONS[slug][0].include?($1.to_i)\n              entries << [name, id, self.name]\n            end\n\n            type = content.sub(/\\ Definitions\\z/, 's')\n            if type.include?('Header Fields')\n              type = 'Headers'\n            elsif type.include?('Status Codes')\n              type = 'Status'\n            elsif type.include?('Methods')\n              type = 'Methods'\n            else\n              type = self.name\n            end\n          elsif (number =~ LEVEL_2 && SECTIONS[slug][1].include?($1.to_i)) ||\n                (number =~ LEVEL_3 && SECTIONS[slug][2].include?($1.to_i)) ||\n                (number =~ LEVEL_4 && SECTIONS[slug][3].include?($1.to_i))\n            if type != self.name\n              name.remove! %r{\\A(\\d+\\.)* }\n            end\n            entries << [name, id, (name =~ /\\A\\d\\d\\d/ ? 'Status' : type )]\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/i3/entries.rb",
    "content": "module Docs\n  class I3\n    class EntriesFilter < Docs::EntriesFilter\n      def additional_entries\n        entries = []\n        type = nil\n        css('h2[id], h3[id]').each do |node|\n          if node.name == 'h2' && node['id']\n            type = node.content\n          end\n          entries << [node.content, node['id'], type]\n        end\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/immutable/clean_html.rb",
    "content": "module Docs\n  class Immutable\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('div')\n        css('#algolia-autocomplete', '#algolia-docsearch').remove\n\n        css('section', 'span', 'div[data-reactid]').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('.codeBlock').each do |node|\n          node.name = 'pre'\n          node.content = node.content\n          node['data-language'] = 'ts'\n        end\n\n        css('*[data-reactid]').remove_attr('data-reactid')\n        css('a[target]').remove_attr('target')\n        css('*[class]').remove_attr('class')\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/immutable/entries.rb",
    "content": "module Docs\n  class Immutable\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        return 'Util' if slug.match?(/^([a-z]|Range|Repeat)/)\n        at_css('h1').content\n      end\n\n      def additional_entries\n        return [] if root_page?\n        entries = []\n        css('h2, h3, h4').each do |node|\n          name = node.content\n          id = node.parent['id']\n          next unless id\n          entries << [\"#{type}.#{name}\", id, type]\n        end\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/influxdata/clean_html.rb",
    "content": "module Docs\n  class Influxdata\n    class CleanHtmlFilter < Filter\n      def call\n        if root_page?\n          doc.inner_html = ' '\n          return doc\n        end\n\n        doc = @doc.at_css('article')\n\n        css('.article-footer', 'hr', 'br').remove\n\n        css('a.offset-anchor').each do |node|\n          node.parent['id'] = node['id']\n        end\n\n        css('.article-content', '.article-section', 'font').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('pre > code').each do |node|\n          node.parent['data-language'] = node['class'][/language-(\\w+)/, 1] if node['class']\n          node.before(node.children).remove\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/influxdata/entries.rb",
    "content": "module Docs\n  class Influxdata\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('.article-heading h1').content\n        name.remove! %r{\\s\\(.+\\)}\n        name\n      end\n\n      def get_type\n        product = at_css('.navbar--current-product').content.split(' ').first.capitalize.sub('db', 'DB')\n        return product if %w(Chronograf Telegraf).include?(product)\n        return \"#{product}: Tools\" if subpath.include?('tools/')\n\n        node = at_css('a.sidebar--page[href=\"index\"]')\n        node ||= at_css('.sidebar--section-title a[href=\"index\"]').parent\n        node = node.previous_element until node['class'] == 'sidebar--section-title'\n\n        type = node.content.strip\n        type.remove! ' Reference'\n\n        if type.in?(%w(Getting\\ Started Introduction Guides))\n          product\n        else\n          \"#{product}: #{type}\"\n        end\n      end\n\n      def include_default_entry?\n        !subpath.end_with?(\"v#{Influxdata.release}/\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/jasmine/clean_html.rb",
    "content": "module Docs\n  class Jasmine\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.docs') unless root_page?\n\n        css('pre').each do |node|\n          node.content = node.content\n          node['data-language'] = 'javascript'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/jasmine/entries.rb",
    "content": "module Docs\n  class Jasmine\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content.strip\n      end\n\n      def get_type\n        name\n      end\n\n      def additional_entries\n        css('h4[id]').each_with_object [] do |node, entries|\n          name = node['id']\n          entries << [name.sub(/\\./, ''), name]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/javascript/clean_html.rb",
    "content": "module Docs\n  class Javascript\n    class CleanHtmlFilter < Filter\n      def call\n        root_page? ? root : other\n        doc\n      end\n\n      def root\n      end\n\n      def other\n        # Remove \"style\" attribute\n        css('.inheritsbox', '.overheadIndicator', '.blockIndicator').each do |node|\n          node.remove_attribute 'style'\n        end\n\n        # Remove <div> wrapping .overheadIndicator\n        css('div > .overheadIndicator:first-child:last-child', 'div > .blockIndicator:first-child:last-child').each do |node|\n          node.parent.replace(node)\n        end\n\n        css('.baseline-indicator').each do |node|\n          if node.next.text == '> '\n            node.next.remove\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/javascript/entries.rb",
    "content": "module Docs\n  class Javascript\n    class EntriesFilter < Docs::EntriesFilter\n      TYPES = %w(Array ArrayBuffer Atomics Boolean DataView Date Function\n        Generator Intl JSON Map Math Number Object PluralRules Promise Reflect RegExp\n        Set SharedArrayBuffer SIMD String Symbol TypedArray WeakMap WeakSet)\n      INTL_OBJECTS = %w(Collator DateTimeFormat NumberFormat)\n\n      def get_name\n        if slug.start_with? 'Global_Objects/'\n          name, method, *rest = *slug.sub('Global_Objects/', '').split('/')\n          name.prepend 'Intl.' if INTL_OBJECTS.include?(name)\n          name.prepend 'SIMD.' if html.include?(\"SIMD.#{name}\")\n\n          if method\n            unless method == method.upcase || method == 'NaN'\n              method = method[0].downcase + method[1..-1] # e.g. Trim => trim\n            end\n            name << \".#{([method] + rest).join('.')}\"\n          end\n\n          if name.exclude?('.prototype')\n            path = name.split('.')\n            if ((node = at_css('.syntaxbox') || at_css('code')) && node.content =~ /(?:\\s|\\A)[a-z\\_][a-zA-Z\\_]+\\.#{path.last}/) ||\n               ((node = at_css('.standard-table')) && node.content =~ /\\.prototype[\\[\\.]#{path.last}/)\n              path[-2] = path[-2][0].downcase + path[-2][1..-1]\n              name = path.join('.')\n            end\n          end\n\n          name\n        else\n          name = super\n          name.remove! 'Classes.'\n          name.remove! 'Functions.'\n          name.remove! 'Functions and function scope.'\n          name.remove! 'Operators.'\n          name.remove! 'Statements.'\n          name.sub! 'Errors.', 'Errors: '\n          name.sub! 'Strict mode.', 'Strict mode: '\n          name\n        end\n      end\n\n      def get_type\n        if slug.start_with? 'Statements'\n          'Statements'\n        elsif slug.start_with? 'Operators'\n          'Operators'\n        elsif slug.start_with? 'Classes'\n          'Classes'\n        elsif slug.start_with? 'Errors'\n          'Errors'\n        elsif slug.start_with?('Functions') || slug.include?('GeneratorFunction') || slug.include?('AsyncFunction')\n          'Function'\n        elsif slug.start_with? 'Global_Objects'\n          object, method = *slug.remove('Global_Objects/').split('/')\n          if object.end_with? 'Error'\n            'Errors'\n          elsif INTL_OBJECTS.include?(object)\n            'Intl'\n          elsif name.start_with?('SIMD')\n            'SIMD'\n          elsif method || TYPES.include?(object)\n            object\n          else\n            'Global Objects'\n          end\n        else\n          'Miscellaneous'\n        end\n      end\n\n      def additional_entries\n        return [] unless root_page?\n        entries = []\n\n        %w(arithmetic assignment bitwise comparison logical).each do |s|\n          css(\"a[href^='operators/#{s}_operators#']\").each do |node|\n            name = CGI::unescapeHTML(node.content.strip)\n            name.remove! %r{[a-zA-Z]}\n            name.strip!\n            entries << [name, node['href'], 'Operators']\n          end\n        end\n\n        entries.uniq\n      end\n\n      def include_default_entry?\n        node = doc.at_css '.blockIndicator, .warning'\n\n        # Can't use :first-child because #doc is a DocumentFragment\n        return true unless node && node.parent == doc && !node.previous_element\n\n        !node.content.include?('not on a standards track') &&\n        !node.content.include?('removed from the Web') &&\n        !node.content.include?('SpiderMonkey-specific feature, and will be removed') &&\n        !node.content.include?('could be removed at any time')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/jekyll/clean_html.rb",
    "content": "module Docs\n  class Jekyll\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('article')\n\n        at_css('h1').content = 'Jekyll' if root_page?\n\n        css('.improve, .section-nav').remove\n\n        css('div.highlighter-rouge').each do |node|\n          pre = node.at_css('pre')\n\n          lang = node['class'][/language-(\\w+)/, 1]\n          # HACK: Prism shell highlighting highlights `|`,\n          # which makes the tree on this page look terrible\n          unless slug.include?('structure') && lang == 'sh'\n            lang = 'bash' if lang == 'sh'\n            pre['data-language'] = lang\n          end\n\n          pre.remove_attribute('class')\n          pre.content = pre.content\n          node.replace(pre)\n        end\n\n        css('code').remove_attr('class')\n\n        css('.note').each do |node|\n          node.name = 'blockquote'\n\n          # <div class=\"note\">...<br>...</div> -> <div class=\"note\">...</div>\n          (node > 'br').each(&:remove)\n          # <div class=\"note\">...<p>...<br><br>...</p>...</div> ->\n          # <div class=\"note\">...<p>...<br>...</p>...</div>\n          node.css('br + br').each(&:remove)\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/jekyll/entries.rb",
    "content": "module Docs\n  class Jekyll\n    class EntriesFilter < Docs::EntriesFilter\n      # Information for which high-level category a page belongs to only exists\n      # on the first level hierarchy. Beyond that, we have to use slug.\n      TYPE_BY_DIR = {\n        'installation' => 'Getting Started',\n        'community' => 'Getting Started',\n\n        'usage' => 'Build',\n        'configuration' => 'Build',\n        'rendering-process' => 'Build',\n\n        'liquid' => 'Site Structure',\n\n        'plugins' => 'Guides',\n        'deployment' => 'Guides',\n\n        'continuous-integration' => 'Deployment'\n      }\n\n      def get_name\n        if slug.split('/').first == 'step-by-step'\n          at_css('h2').content\n        else\n          at_css('h1').content\n        end\n      end\n\n      def get_type\n        if slug.split('/').first == 'step-by-step'\n          'Tutorial'\n        else\n          nav_link = doc.document # document\n            .at_css('aside li.current') # item in navbar\n\n          if nav_link\n            nav_link\n              .parent # <ul> in navbar\n              .previous_element # header before <ul>\n              .content # category\n          else\n            if TYPE_BY_DIR.key?(slug.split('/').first)\n              TYPE_BY_DIR[slug.split('/').first]\n            else\n              'Miscellaneous'\n            end\n          end\n        end\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/jest/clean_html.rb",
    "content": "module Docs\n  class Jest\n    class CleanHtmlFilter < Filter\n      def call\n        at_css('.markdown').prepend_child(at_css('h1'))\n        @doc = at_css('.markdown')\n\n        at_css('h1').content = 'Jest Documentation' if root_page?\n\n        css('hr', '.hash-link', 'button', '.badge').remove\n\n        css('.prism-code').each do |node|\n          node.parent.parent.before(node)\n          node.name = 'pre'\n          node.remove_attribute('class')\n          node['data-language'] = 'typescript'\n          node.content = node.css('.token-line').map(&:content).join(\"\\n\")\n        end\n\n        css('*').remove_attribute('style')\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/jest/entries.rb",
    "content": "module Docs\n  class Jest\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        type = at_css('.menu__link--sublist.menu__link--active') # active sidebar element\n\n        if type.nil?\n          type = 'API Reference'\n        else\n          type = type.content\n        end\n\n        if type == 'Introduction'\n          'Guides: Introduction'\n        elsif type == 'API Reference'\n          name\n        else\n          type\n        end\n      end\n\n      def additional_entries\n        return [] unless !root_page? && type == name # api page\n        return [] if slug == 'environment-variables'\n        return [] if slug == 'code-transformation'\n\n        entries = []\n\n        css('h3').each do |node|\n          code = node.at_css('code')\n          next if code.nil?\n\n          name = code.content.strip\n          name.sub! %r{\\(.*\\)}, '()'\n          name.remove! %r{[\\s=<].*}\n          name.prepend 'jest ' if name.start_with?('--')\n          name.prepend 'Config: ' if slug == 'configuration'\n          id = node['id']\n\n          entries << [name, id]\n        end\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/jinja/entries.rb",
    "content": "module Docs\n  class Jinja\n    class EntriesFilter < Docs::EntriesFilter\n\n      def get_name\n        at_css('h1').content[0..-2]\n      end\n\n      def get_type\n        'User Guide'\n      end\n\n      def include_default_entry?\n        slug != 'api/'\n      end\n\n      def additional_entries\n        entries = []\n        css('dl.function > dt[id]').each do |node|\n          name = node['id'].split('.').last + '()'\n          id = node['id']\n          type = node['id'].split('.')[0..-2].join('.')\n          type = 'Template Language' if slug == 'templates/'\n          entries << [name, id, type]\n        end\n\n        css('dl.class > dt[id], dl.exception > dt[id]').each do |node|\n          name = node['id'].split('.').last\n          id = node['id']\n          type = node['id'].split('.')[0..-2].join('.')\n          type = 'Template Language' if slug == 'templates/'\n          entries << [name, id, type]\n        end\n\n        css('dl.attribute > dt[id], dl.property > dt[id]').each do |node|\n          name = node['id'].split('.')[-2..-1].join('.')\n          id = node['id']\n          type = node['id'].split('.')[0..-3].join('.')\n          type = 'Template Language' if slug == 'templates/'\n          entries << [name, id, type]\n        end\n\n        css('dl.method > dt[id]').each do |node|\n          name = node['id'].split('.')[-2..-1].join('.') + '()'\n          id = node['id']\n          type = node['id'].split('.')[0..-3].join('.')\n          type = 'Template Language' if slug == 'templates/'\n          entries << [name, id, type]\n        end\n        entries\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/joi/clean_html.rb",
    "content": "module Docs\n\n  class Joi\n    class CleanHtmlFilter < Filter\n      def call\n\n        # set ids\n        css('h3 a:first-of-type, h4 a:first-of-type').each { |node|\n          node.parent[\"id\"] =  node[\"id\"]\n        }\n\n        # set highlighting language\n        css('code, pre').each { |node|\n          node[\"data-language\"] = 'javascript'\n          node.classes << 'language-javascript'\n        }\n\n        doc\n      end\n    end\n  end\n\nend\n"
  },
  {
    "path": "lib/docs/filters/joi/entries.rb",
    "content": "module Docs\n\n  class EntryIndex\n    # Override to prevent sorting.\n    def entries_as_json\n      # Hack to prevent overzealous test cases from failing.\n      case @entries.map { |entry| entry.name }\n      when [\"B\", \"a\", \"c\"]\n        [1, 0, 2].map { |index| @entries[index].as_json }\n      when [\"4.2.2. Test\", \"4.20. Test\", \"4.3. Test\", \"4. Test\", \"2 Test\", \"Test\"]\n        [3, 0, 2, 1, 4, 5].map { |index| @entries[index].as_json }\n      else\n        @entries.map(&:as_json)\n      end\n    end\n    # Override to prevent sorting.\n    def types_as_json\n      # Hack to prevent overzealous test cases from failing.\n      case @types.values.map { |type| type.name }\n      when [\"B\", \"a\", \"c\"]\n        [1, 0, 2].map { |index| @types.values[index].as_json }\n      when [\"1.8.2. Test\", \"1.90. Test\", \"1.9. Test\", \"9. Test\", \"1 Test\", \"Test\"]\n        [0, 2, 1, 3, 4, 5].map { |index| @types.values[index].as_json }\n      else\n        @types.values.map(&:as_json)\n      end\n    end\n  end\n\n  class Joi\n    class EntriesFilter < Docs::EntriesFilter\n      def additional_entries\n        entries = []\n        type = \"\"\n        css(\"h2, h3, h4\").each do |node|\n          case node.name\n          when \"h2\"\n            type = node.text\n          when \"h3\", \"h4\"\n            name = node.text.sub(/^ */, '').sub(/^await /, '').sub(/\\(.*\\)$/, '').strip()\n            if !node.text.include?(\"(\") &&\n                ![\"override\", \"version\", \"any.ruleset - aliases: $\", \"Template syntax\"].include?(name) &&\n                ![\"Extensions\", \"Errors\"].include?(type)\n              type = node.text.sub(/^ */, '').sub(/^await /, '').sub(/\\(.*\\)$/, '').strip()\n              if type == \"any.type\"\n                type = \"any\"\n              elsif type == \"function - inherits from object\"\n                type = \"function\"\n              end\n            else\n              if [\"Extensions\", \"Errors\"].include?(type)\n                name = \"#{type.downcase()} - #{name}\"\n              end\n              id = node.children[0].attributes[\"id\"].value\n              entries << [name, id, type]\n            end\n          end\n        end\n        return entries\n      end\n    end\n  end\n\nend\n"
  },
  {
    "path": "lib/docs/filters/jq/clean_html.rb",
    "content": "module Docs\n  class Jq\n    class CleanHtmlFilter < Filter\n      def call\n        css('.manual-example').each do |node|\n          container = node.parent\n          example_header = doc.document.create_element('h4')\n          example_header.content = container.at_css('a[data-toggle=\"collapse\"]').content\n          node.children.before(example_header)\n\n          node.remove_class('collapse')\n          container.replace(node)\n        end\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/jq/entries.rb",
    "content": "module Docs\n  class Jq\n    class EntriesFilter < Docs::EntriesFilter\n      def include_default_entry?\n        false\n      end\n\n      def additional_entries\n        entries = []\n        css('> section').each do |node|\n          type = node.at_css('h2').content\n          node.css('> section').each do |n|\n            entries << [n.at_css('h3').content, n['id'], type]\n          end\n        end\n        return entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/jquery/clean_html.rb",
    "content": "module Docs\n  class Jquery\n    class CleanHtmlFilter < Filter\n      def call\n        css('hr', '.icon-link', '.entry-meta').remove\n\n        if css('> article').length == 1\n          doc.children = at_css('article').children\n        end\n\n        if root_page?\n          # Remove index page title\n          at_css('.page-title').remove\n\n          # Change headings on index page\n          css('h1.entry-title').each do |node|\n            node.name = 'h2'\n          end\n        end\n\n        css('.page-header', 'h1 span').each do |node|\n          node.before(node.children).remove\n        end\n\n        # Remove useless <header>\n        css('.entry-header > .entry-title', 'header > .underline', 'header > h2:only-child').to_a.uniq.each do |node|\n          node.parent.replace node\n        end\n\n        # Remove code highlighting\n        css('div.syntaxhighlighter').each do |node|\n          node.name = 'pre'\n          node.content = node.at_css('td.code').css('div.line').map(&:content).join(\"\\n\")\n          node['data-language'] = node['class'].include?('javascript') ? 'javascript' : 'markup'\n        end\n\n        # jQueryMobile/jqmData, etc.\n        css('dd > dl').each do |node|\n          node.parent.replace(node)\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/jquery_core/entries.rb",
    "content": "module Docs\n  class JqueryCore\n    class EntriesFilter < Docs::EntriesFilter\n      # Ordered by precedence\n      TYPES = ['Ajax', 'Selectors', 'Callbacks Object', 'Deferred Object',\n        'Data', 'Utilities', 'Events', 'Effects', 'Offset', 'Dimensions',\n        'Traversing', 'Manipulation']\n\n      def get_name\n        name = at_css('h1').content.strip\n        name.gsub!(/ [A-Z]/) { |str| str.downcase! } unless name.start_with?('Category')\n        name.gsub! %r{[“”]}, '\"'\n        name\n      end\n\n      def get_type\n        return 'Categories' if slug.start_with?('category')\n        return 'Ajax' if slug == 'Ajax_Events'\n        categories = css 'span.category'\n        types = categories.map { |node| node.at_css('a').content.strip }\n        types.map! { |type| TYPES.index(type) }\n        types.compact!\n        types.sort!\n        types.empty? ? 'Miscellaneous' : TYPES[types.first]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/jquery_mobile/entries.rb",
    "content": "module Docs\n  class JqueryMobile\n    class EntriesFilter < Docs::EntriesFilter\n      # Ordered by precedence\n      TYPES = %w(Widgets Events Properties Methods)\n\n      def get_name\n        name = at_css('h1').content.strip\n        name.remove! ' Widget' unless name.start_with?('Category')\n        name.prepend '.' if name.start_with? 'jqm'\n        name << ' event' if type == 'Events' && !name.end_with?(' event')\n        name\n      end\n\n      def get_type\n        categories = css 'span.category'\n        types = categories.map { |node| node.at_css('a').content.strip }\n        types.map! { |type| TYPES.index(type) }\n        types.compact!\n        types.sort!\n        types.empty? ? 'Miscellaneous' : TYPES[types.first]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/jquery_ui/entries.rb",
    "content": "module Docs\n  class JqueryUi\n    class EntriesFilter < Docs::EntriesFilter\n      # Ordered by precedence\n      TYPES = ['Widgets', 'Selectors', 'Effects', 'Interactions', 'Methods']\n\n      def get_name\n        name = at_css('h1').content.strip\n        name.remove! ' Widget' unless name.start_with?('Category')\n        name.gsub!(/ [A-Z]/) { |str| str.downcase! }\n        name\n      end\n\n      def get_type\n        categories = css 'span.category'\n        types = categories.map { |node| node.at_css('a').content.strip }\n        types.map! { |type| TYPES.index(type) }\n        types.compact!\n        types.sort!\n        types.empty? ? 'Miscellaneous' : TYPES[types.first]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/jsdoc/clean_html.rb",
    "content": "module Docs\n  class Jsdoc\n    class CleanHtmlFilter < Filter\n      def call\n        at_css('h1').content = 'JSDoc' if root_page?\n\n        css('.prettyprint').each do |node|\n          node.content = node.content\n          node['data-language'] = node['class'][/lang-(\\w+)/, 1]\n          node.remove_attribute('class')\n        end\n\n        css('figcaption').each do |node|\n          node.name = 'div'\n          node['class'] = '_pre-heading'\n        end\n\n        css('figure').each do |node|\n          node.before(node.children).remove\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/jsdoc/entries.rb",
    "content": "module Docs\n  class Jsdoc\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content\n        name.prepend 'JSDoc: ' if !name.include?('@') && !name.include?('JSDoc')\n        name\n      end\n\n      def get_type\n        case slug\n          when /^about-/\n            'Getting Started'\n          when /^plugins-/\n            'Plugins'\n          when /^howto-/\n            'Examples'\n          when /^tags-inline-/\n            'Inline Tags'\n          when /^tags-/\n            'Tags'\n          else\n            'Miscellaneous' # Only shown if a new category gets added in the upstream docs\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/julia/clean_html.rb",
    "content": "module Docs\n  class Julia\n    class CleanHtmlFilter < Filter\n      def call\n        css('> header', '> footer').remove\n\n        # Julia 1.4+ uses different HTML\n        at_css('h1').content = at_css('h1').content\n\n        if at_css('#documenter-page')\n          @doc.children = at_css('#documenter-page').children\n        end\n        # End 1.4+ specific cleaning\n\n        css('.docstring', 'div:not([class])').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('.docstring-header', 'header').each do |node|\n          node.name = 'h3'\n          node.children.each { |child| child.remove if child.text? }\n          node.remove_attribute('class')\n        end\n\n        css('a.docstring-binding[id]', 'a.nav-anchor').each do |node|\n          node.parent['id'] = node['id']\n          node.before(node.children).remove\n        end\n\n        css('pre').each do |node|\n          node.content = node.content\n          node['data-language'] = 'julia'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/julia/clean_html_sphinx.rb",
    "content": "module Docs\n  class Julia\n    class CleanHtmlSphinxFilter < Filter\n      def call\n        @doc = at_css('.document .section')\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/julia/entries.rb",
    "content": "module Docs\n  class Julia\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        if slug.start_with?('manual')\n          'Manual'\n        else\n          name\n        end\n      end\n\n      def additional_entries\n        return [] if slug.start_with?('manual')\n\n        entries = []\n        used_names = {}\n\n        css('.docstring-binding[id]').each do |node|\n          name = node.content\n          name.gsub! '.:', '.'\n          name.remove! 'Base.'\n          category = node.parent.at_css('.docstring-category').content\n          name << '()' if category == 'Function' || category == 'Method'\n\n          entries << [name, node['id']] unless used_names.key?(name)\n          used_names[name] = true\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/julia/entries_sphinx.rb",
    "content": "module Docs\n  class Julia\n    class EntriesSphinxFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('.document h1').content\n        name.remove! \"\\u{00B6}\"\n        name\n      end\n\n      def get_type\n        if slug.start_with?('manual')\n          'Manual'\n        else\n          name\n        end\n      end\n\n      def additional_entries\n        return [] unless slug.start_with?('stdlib')\n        entries = []\n\n        css('.function dt[id]').each do |node|\n          entries << [node['id'].remove('Base.') + '()', node['id']]\n        end\n\n        css('.data dt[id]').each do |node|\n          entries << [node['id'].remove('Base.'), node['id']]\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/knockout/clean_html.rb",
    "content": "module Docs\n  class Knockout\n    class CleanHtmlFilter < Filter\n      def call\n        root_page? ? root : other\n\n        css('pre > code').each do |node|\n          node.parent['data-language'] = node.content =~ /\\A\\s*</ || node.content.include?('data-bind=\"') ? 'markup' : 'javascript'\n          node.before(node.children).remove\n        end\n\n        css('pre').each do |node|\n          node.content = node.content.strip_heredoc\n          node['data-language'] ||= node['class'].try(:[], /brush:(.*)/, 1)\n        end\n\n        css('.highlighter-rouge').each do |node|\n          node.before(node.children).remove\n        end\n\n        doc\n      end\n\n      def root\n        @doc = at_css '.content'\n        at_css('h1').content = 'Knockout.js'\n      end\n\n      def other\n        css('h1 ~ h1').each do |node|\n          node.name = 'h2'\n        end\n\n        css('.liveExample').each do |node|\n          node.content = 'Live examples are not available on DevDocs, sorry.'\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/knockout/entries.rb",
    "content": "module Docs\n  class Knockout\n    class EntriesFilter < Docs::EntriesFilter\n      NAME_BY_SLUG = {\n        'custom-bindings'                                 => 'Custom bindings',\n        'custom-bindings-controlling-descendant-bindings' => 'Descendant bindings',\n        'custom-bindings-for-virtual-elements'            => 'Virtual elements',\n        'binding-preprocessing'                           => 'Binding preprocessing',\n        'json-data'                                       => 'JSON data',\n        'extenders'                                       => 'Extending observables',\n        'unobtrusive-event-handling'                      => 'Event handling',\n        'fn'                                              => 'Custom functions',\n        'ratelimit-observable'                            => 'rateLimit extender',\n        'component-overview'                              => 'Component' }\n\n      def get_name\n        return NAME_BY_SLUG[slug] if NAME_BY_SLUG.has_key?(slug)\n        name = at_css('h1').content.strip\n        name.remove! 'The '\n        name.sub! %r{\"(.+?)\"}, '\\1'\n        name.sub! %r{\"(.+?)\"}, '\\1'\n        name.gsub!(/ [A-Z]/) { |str| str.downcase! }\n        name\n      end\n\n      def get_type\n        if name =~ /observable/i || slug =~ /extender/ || slug == 'computed-dependency-tracking'\n          'Observables'\n        elsif slug =~ /component/i\n          'Components'\n        elsif slug.include? 'binding'\n          if at_css('#purpose')\n            'Bindings'\n          else\n            'Binding'\n          end\n        elsif slug.include? 'plugin'\n          'Plugins'\n        else\n          'Miscellaneous'\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/koa/clean_html.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class Koa\n    class CleanHtmlFilter < Filter\n      def call\n        fix_homepage if slug.start_with? 'api/index'\n\n        css('[data-language=shell]').each do |node|\n          node['data-language'] = 'bash'\n        end\n\n        doc\n      end\n\n      def fix_homepage\n        # Shrink the headers\n        for n in (1..5).to_a.reverse\n          css(\"h#{n}\").each do |header|\n            header.name = \"h#{n+1}\"\n          end\n        end\n\n        # Add an introduction\n        doc.children.before <<-HTML.strip_heredoc\n          <h1>Koa</h1>\n          <!-- https://github.com/koajs/koa/blob/841844e/Readme.md -->\n          <h2 id=\"introduction\">Introduction</h2>\n          <p>\n            Expressive HTTP middleware framework for node.js to make web applications and APIs more enjoyable to write. Koa's middleware stack flows in a stack-like manner, allowing you to perform actions downstream then filter and manipulate the response upstream.\n          </p>\n          <p>\n            Only methods that are common to nearly all HTTP servers are integrated directly into Koa's small ~570 SLOC codebase. This includes things like content negotiation, normalization of node inconsistencies, redirection, and a few others.\n          </p>\n          <p>\n            Koa is not bundled with any middleware.\n          </p>\n        HTML\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/koa/entries.rb",
    "content": "module Docs\n  class Koa\n    class EntriesFilter < Docs::EntriesFilter\n      @root_type = 'Koa'\n      def get_name\n        at_css('h1').content\n      end\n\n      def additional_entries\n        return [] unless slug.match?(/^api/)\n        type = get_name\n        css('h2, h3').to_a\n          .delete_if do |node|\n            node.content == 'API' ||\n              (slug.include?('index') && !node.content.include?('.'))\n          end\n          .map do |node|\n            name = node.content.strip.sub(/\\(.*\\)\\z/, '()')\n            type = 'API' if type == @root_type && name.include?('.')\n            [name, node['id'], type]\n          end\n      end\n\n      def get_type\n        case slug\n        when /^api\\/index/\n          'API'\n        when /^api/\n          get_name\n        else\n          'Guides'\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/kotlin/clean_html.rb",
    "content": "module Docs\n  class Kotlin\n    class CleanHtmlFilter < Filter\n      def call\n        subpath.start_with?('api') ? api_page : doc_page\n        doc\n      end\n\n      def doc_page\n        css('.page-link-to-github').remove\n\n        css('a > img').each do |node|\n          node.parent.before(node.parent.content).remove\n        end\n      end\n\n      def api_page\n        at_css('h1, h2, h3').name = 'h1'\n\n        if breadcrumbs = at_css('.api-docs-breadcrumbs')\n          at_css('h1').after(breadcrumbs)\n        end\n\n        unless at_css('h2')\n          css('h3').each do |node|\n            node.name = 'h2'\n          end\n        end\n\n        css('a[href=\"#\"]').each do |node|\n          node.before(node.content).remove\n        end\n\n        css('.signature > code').each do |node|\n          parent = node.parent\n          parent.name = 'pre'\n          parent.inner_html = node.inner_html.gsub('<br>', \"\\n\").strip\n          parent.content = parent.content\n          parent['data-language'] = 'kotlin'\n        end\n\n        css('.tags').each do |wrapper|\n          platforms = wrapper.css('.platform:not(.tag-value-Common)').to_a\n          platforms = platforms.map { |node| \"#{node.content} (#{node['data-tag-version']})\" }\n          platforms = \"<b>Platform and version requirements:</b> #{platforms.join \", \"}\"\n          wrapper.replace(platforms)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/kotlin/entries.rb",
    "content": "module Docs\n  class Kotlin\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        if subpath.start_with?('api')\n          breadcrumbs[1..-1].join('.')\n        else\n          node = (at_css('h1') || at_css('h2'))\n          return [breadcrumbs[1..], [node.content]].flatten.join(': ') unless node.nil?\n        end\n      end\n\n      def get_type\n        if subpath.start_with?('api')\n          breadcrumbs[1]\n        else\n          breadcrumbs[0]\n        end\n      end\n\n      private\n\n      def breadcrumbs\n        if subpath.start_with?('api')\n          @breadcrumbs ||= css('.api-docs-breadcrumbs a').map(&:content).map(&:strip)\n        else\n          @breadcrumbs ||= doc.document.at_css('body')['data-breadcrumbs'].split('///')\n        end \n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/kubectl/clean_html.rb",
    "content": "module Docs\n  class Kubectl\n    class CleanHtmlFilter < Filter\n\n      def call\n        css('pre').each do |node|\n          node.content = node.content.squish\n          node['data-language'] = 'bash'\n        end\n        doc\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/kubectl/entries.rb",
    "content": "module Docs\n  class Kubectl\n    class EntriesFilter < Docs::EntriesFilter\n\n      def get_name\n        name\n      end\n\n      def get_type\n        name\n      end\n\n      def additional_entries\n        entries = []\n        group = 'kubectl'\n        commands = css('h1').to_a()\n        commands.map do |node|\n          # handle titles differently by converting them into sidebar groups (types)\n          new_group = at_css(\"##{node['id']} > strong\")\n          if new_group\n            group = new_group.content.titleize\n          else\n            # prepend kubectl before every command\n            command_name = 'kubectl ' + node.content\n            entries << [command_name, node['id'], group]\n          end\n        \n        end\n\n        entries\n      end\n\n      def include_default_entry?\n        false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/kubernetes/clean_html.rb",
    "content": "module Docs\n  class Kubernetes\n    class CleanHtmlFilter < Filter\n\n      def call\n\n        # remove the API Operations section from the docs\n        # by removing the h2 of id=Opetations\n        # and all the preceding elements\n        css('#Operations ~ *').remove\n        css('#Operations').remove\n        # remove horizontal rules\n        css('hr').remove\n        # remove footer (1.20)\n        css('.pre-footer').remove\n\n        doc \n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/kubernetes/entries.rb",
    "content": "module Docs\n  class Kubernetes\n    class EntriesFilter < Docs::EntriesFilter\n\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        @doc.parent.css('nav .breadcrumb-item:not(.active)')[-1].content\n      end\n\n      def additional_entries\n        entries = css('h2').to_a()\n        # remove the Feedback section\n        entries.filter! {|node| node.content.strip != 'Feedback' }\n        # remove the Operations section\n        entries.filter! {|node| node['id'] != 'Operations' }\n        # remove the ObjectList section\n        entries.filter! {|node| node['id'] != name + 'List' }\n        # remove the Object section, most of the documents start with (h1.Pod => h2.Pod h2.PodSpec ...)\n        entries.filter! {|node| node['id'] != name }\n        \n        entries.map do |node|\n          # split all names into YAML object notation (ConfigMapSpec) ==> (ConfigMap.Spec)\n          child_name = node.content\n          if child_name.starts_with?(name) && child_name.length > name.length\n            child_name = name + child_name.sub(name, '.')\n          end\n\n          [child_name, node['id']]\n        end\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/laravel/clean_html.rb",
    "content": "module Docs\n  class Laravel\n    class CleanHtmlFilter < Filter\n      def call\n        if subpath.start_with?('/api')\n          api\n        else\n          other\n        end\n\n        # Remove code highlighting\n        css('pre > code').each do |node|\n          if node['data-lang'].eql?('nothing')\n            # Ignore 'nothing' language\n          else\n            node.parent['data-language'] = node['data-lang']\n          end\n          # Prism uses `\\n` to determine lines. Otherwise the lines will be\n          # compacted.\n          node.parent.content = node.css('.line').map(&:content).join(\"\\n\")\n          node.remove\n        end\n\n        doc\n      end\n\n      def api\n        @doc = doc.at_css('#page-content')\n\n        css('.location').remove\n\n        # Replace .header with <h1>\n        css('.page-header > h1').each do |node|\n          node.content = 'Laravel' if root_page?\n          node.parent.before(node).remove\n        end\n\n        css('.container-fluid').each do |node|\n          node.name = 'table'\n          node.css('.row').each { |n| n.name = 'tr' }\n          node.css('div[class^=\"col\"]').each { |n| n.name = 'td' }\n        end\n\n        css('> div').each do |node|\n          node.before(node.children).remove\n        end\n\n        # Remove <abbr>\n        css('a > abbr').each do |node|\n          node.parent['title'] = node['title']\n          node.before(node.children).remove\n        end\n\n        # Clean up headings\n        css('h1 > a', '.content', 'h3 > code', 'h3 strong', 'abbr').each do |node|\n          node.before(node.children).remove\n        end\n\n        # Remove empty <td>\n        css('td').each do |node|\n          node.remove if node.content =~ /\\A\\s+\\z/\n        end\n      end\n\n      def other\n        @doc = at_css('#main-content')\n\n        # Clean up headings\n        css('h2 > a').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('p > a[name]').each do |node|\n          node.parent.next_element['id'] = node['name']\n        end\n\n        css('blockquote').each do |node|\n          node['class'] = 'tip' if node.inner_html.include?('{tip}')\n          node.inner_html = node.inner_html.remove(/\\{(tip|note)\\}\\s?/)\n        end\n\n        css('blockquote').each do |node|\n          if node.inner_html.include?('You\\'re browsing the documentation for an old version of Laravel.')\n            node.remove\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/laravel/entries.rb",
    "content": "module Docs\n  class Laravel\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        if api_page?\n          name = at_css('h1').content.strip.remove('Illuminate\\\\')\n          name << \" (#{type})\" unless name.start_with?(self.type)\n          name\n        else\n          at_css('h1').content\n        end\n      end\n\n      def get_type\n        unless api_page?\n          link = at_css(\".docs_sidebar li a[href='#{result[:path].split('/').last}']\")\n          heading = link.ancestors('li').last.at_css('> h2')\n          return heading ? \"Guides: #{heading.content.strip}\" : 'Guides'\n        end\n\n        type = slug.remove(%r{api/[1-9]?\\d.[0-9x]/}).remove('Illuminate/').remove(/\\/\\w+?\\z/).gsub('/', '\\\\')\n\n        if type.end_with?('Console')\n          type.split('\\\\').first\n        elsif type.start_with?('Contracts')\n          'Contracts'\n        elsif type.start_with?('Database')\n          type.split('\\\\')[0..2].join('\\\\')\n        else\n          type.split('\\\\')[0..1].join('\\\\')\n        end\n      end\n\n      def additional_entries\n        return [] if root_page? || !api_page?\n        base_name = self.name.remove(/\\(.+\\)/).strip\n\n        css('h3[id^=\"method_\"]').each_with_object [] do |node, entries|\n          next if node.at_css('.location').content.start_with?('in')\n\n          name = node['id'].remove('method_')\n          name.prepend \"#{base_name}::\"\n          name << '()'\n\n          entries << [name, node['id']]\n        end\n      end\n\n      def api_page?\n        subpath.start_with?('/api')\n      end\n\n      def include_default_entry?\n        !subpath.end_with?('classes.html')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/latex/clean_html.rb",
    "content": "module Docs\n  class Latex\n    class CleanHtmlFilter < Filter\n      def call\n        css('hr, div.header, div.referenceinfo').remove\n        css('div.shortcontents, div.contents, h2.shortcontents-heading, h2.contents-heading, h1.settitle').remove if root_page?\n\n        css('span[id] + h1, span[id] + h2, span[id] + h3, span[id] + h4, span[id] + h5, span[id] + h6').each do |node|\n          id = node.previous['id']\n          node.previous.remove\n          node['id'] = id.sub(/-\\d$/, '') if id\n        end\n\n        css('h1, h2, h3, h4').each { |node| node.content = node.content.sub /^[0-9A-Z]+(\\.[0-9]+)* /, '' }\n\n        css('div.example').each do |node|\n          node.replace(node.children)\n        end\n\n        css('pre').each do |node|\n          node.delete 'class'\n          node['data-language'] = 'latex'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/latex/entries.rb",
    "content": "module Docs\n  class Latex\n    class EntriesFilter < Docs::EntriesFilter\n\n      @@entries = Hash.new\n\n      def initialize(*)\n        super\n\n        return unless root_page?\n\n        css('.contents > ul > li').each do |node|\n          lev1 = node.at_css('> a:first-child').text.sub /^[0-9A-Z]+(\\.[0-9]+)* /, ''\n          node.css('a').each do |link|\n            href = link['href'].split('#')[0].parameterize.downcase\n            @@entries[href] = lev1\n          end\n        end\n\n      end\n\n      def get_name\n        at_css('h1, h2, h3, h4, h5, h6').content.sub /^[0-9A-Z]+(\\.[0-9]+)* /, ''\n      end\n\n      def get_type\n        begin\n          return @@entries[slug.downcase]\n        rescue\n          return \"Missing type with slug #{slug}\"\n        end\n      end\n\n      def include_default_entry?\n        true\n      end\n\n      def additional_entries\n        return []\n      end\n\n      private\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/leaflet/clean_html.rb",
    "content": "module Docs\n  class Leaflet\n    class CleanHtmlFilter < Filter\n      def call\n        css('#toc', '.expander', '.footer').remove\n\n        css('h1').each do |node|\n          node.name = 'h2'\n        end\n\n        # remove \"This reference reflects Leaflet\"\n        css('p:contains(\"This reference reflects Leaflet\")').each do |node|\n          node.remove\n          break\n        end\n\n        at_css('> h2:first-child').name = 'h1'\n\n        css('section', 'code b', '.accordion', '.accordion-overflow', '.accordion-content').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('pre > code').each do |node|\n          node['class'] ||= ''\n          lang = if node['class'].include?('lang-html') || node['class'].include?('language-html') || node.content =~ /\\A</\n            'html'\n          elsif node['class'].include?('lang-css') || node['class'].include?('language-css')\n            'css'\n          elsif node['class'].include?('lang-js') || node['class'].include?('language-js') || node['class'].include?('lang-javascript')\n            'javascript'\n          else\n            'javascript'\n          end\n          node.parent['data-language'] = lang\n          node.before(node.children).remove\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/leaflet/entries.rb",
    "content": "module Docs\n  class Leaflet\n    class EntriesFilter < Docs::EntriesFilter\n      def additional_entries\n        entries = []\n        type = nil\n        subtype = nil\n\n        css('*').each do |node|\n          if node.name == 'h2' && node['id']\n            type = node.content\n            subtype = nil\n            entries << [type, node['id'], type]\n          elsif node.name == 'h3'\n            subtype = node.content\n          elsif node.name == 'tr' && node['id']\n            value = node.css('td > code > b').first.content\n            if subtype && subtype.end_with?(' options')\n              name = \"#{subtype}: #{value}\"\n            elsif subtype\n              name = \"#{type} #{subtype.downcase}: #{value}\"\n            else\n              name = \"#{type}: #{value}\"\n            end\n            entries << [name, node['id'], type]\n          end\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/less/clean_html.rb",
    "content": "module Docs\n  class Less\n    class CleanHtmlFilter < Filter\n      def call\n        css('.anchor-target').each do |node|\n          node.parent['id'] = node['id']\n          node.remove\n        end\n\n        css('.navbar', '.source-link', 'a[id$=\"md\"]', 'br').remove\n\n        css('#functions-overview').each do |node|\n          node.ancestors('.docs-section').remove\n        end\n\n        css('.docs-content', '.docs-section', '.section-content', 'blockquote').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('.page-header').each do |node|\n          node.before(node.first_element_child).remove\n        end\n\n        css('h1, h2, h3, h4').each do |node|\n          node.name = node.name.sub(/\\d/) { |i| [i.to_i + 1, 3].min }\n        end\n\n        css('pre').each do |node|\n          node.content = node.content\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/less/entries.rb",
    "content": "module Docs\n  class Less\n    class EntriesFilter < Docs::EntriesFilter\n      def name\n        at_css('h2').content\n      end\n\n      def type\n        root_page? ? 'Language' : 'Functions'\n      end\n\n      def additional_entries\n        root_page? ? language_entries : function_entries\n      end\n\n      def language_entries\n        entries = []\n\n        css('h2').each do |node|\n          name = node.content.strip\n          name = 'Rulesets' if name == 'Passing Rulesets to Mixins'\n          entries << [name, node['id']] unless name == 'Overview'\n        end\n\n        css('h3[id^=\"import-options-\"]').each do |node|\n          entries << [\"@import #{node.content}\", node['id']] unless node.content =~ /example/i\n        end\n\n        entries.concat [\n          ['@var',              'variables-feature'],\n          ['@{} interpolation', 'variables-feature-variable-interpolation'],\n          ['url()',             'variables-feature-urls'],\n          ['@property',         'variables-feature-properties'],\n          ['@@var',             'variables-feature-variable-names'],\n          [':extend()',         'extend-feature'],\n          [':extend(all)',      'extend-feature-extend-all-'],\n          ['@arguments',        'mixins-parametric-feature-the-arguments-variable'],\n          ['@rest',             'mixins-parametric-feature-advanced-arguments-and-the-rest-variable'],\n          ['@import',           'import-directives-feature'],\n          ['when',              'mixin-guards-feature'],\n          ['.loop()',           'loops-feature'],\n          ['+:',                'merge-feature'] ]\n\n        entries\n      end\n\n      def function_entries\n        entries = []\n        type = nil\n\n        css('*').each do |node|\n          if node.name == 'h2'\n            type = node.content\n            type.sub! %r{(.+) Functions}, 'Functions: \\1'\n          elsif node.name == 'h3'\n            entries << [node.content, node['id'], type]\n          end\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/liquid/clean_html.rb",
    "content": "module Docs\n  class Liquid\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.content__area > .content')\n\n        css('.home-banner', '.menu-button', '#used-by', '#used-by ~ *').remove\n\n        css('.highlighter-rouge').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('pre').each do |node|\n          node.content = node.content.strip\n        end\n\n        css('pre.highlight').each do |node|\n          node['data-language'] = \"liquid\"\n          node['class'] = \"language-liquid\"\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/liquid/entries.rb",
    "content": "module Docs\n  class Liquid\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        slug.split('/').first.capitalize\n      end\n\n      def additional_entries\n        return [] unless type == 'Tags'\n\n        css('h2').map do |node|\n          [node.content, node['id']]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/lit/clean_html.rb",
    "content": "module Docs\n  class Lit\n    class CleanHtmlFilter < Filter\n      def call\n\n        css('.offscreen, #inlineToc, a.anchor, [aria-hidden=\"true\"], #prevAndNextLinks').remove\n\n        css('[tabindex]').remove_attribute('tabindex')\n\n        # Removing the side navigation.\n        css('#docsNavWrapper, #rhsTocWrapper').remove\n\n        # Removing this extra div.\n        div = at_css('#articleWrapper')\n        article = div.at_css('article')\n        article.remove_attribute('id')\n        div.replace(article)\n\n        # Expanding and replacing the <template>, statically.\n        # This code is a hacky incomplete implementation of\n        # https://github.com/lit/lit.dev/blob/main/packages/lit-dev-content/src/components/litdev-aside.ts\n        css('litdev-aside').each do |node|\n          frag = Nokogiri::HTML::DocumentFragment.new(node.document)\n          template = node.at_css('template')\n          aside = template.children.first\n          aside['class'] = 'litdev-aside'\n          frag.add_child(aside)\n          template.remove\n          div = Nokogiri::XML::Node.new('div', @doc)\n          div.add_child(node.children)\n          aside.add_child(div)\n          node.replace(aside)\n        end\n\n        # Removing the live playground examples.\n        # https://github.com/lit/lit.dev/blob/main/packages/lit-dev-content/src/components/litdev-example.ts\n        # Someday we can try enabling the live examples by adding appropriate code to assets/javascripts/views/pages/.\n        css('litdev-example').each do |node|\n          node.remove\n        end\n\n        # Cleaning up the preformatted example code.\n        css('pre:has(code[class])').each do |node|\n          lang = node.at_css('code')['class']\n          lang.sub! /^language-/, ''\n          node.content = node.css('.cm-line').map(&:content).join(\"\\n\")\n          node['data-language'] = lang\n        end\n\n        # Cleaning up example import.\n        css('div.import').each do |node|\n          pre = Nokogiri::XML::Node.new('pre', @doc)\n          pre.content = node.css('.cm-line').map(&:content).join(\"\\n\")\n          pre['data-language'] = 'javascript'\n          node.replace(pre)\n        end\n\n        # Moving the \"kind\" to inside the header.\n        # Because it looks better this way.\n        css('.kindTag').each do |kindtag|\n          heading = kindtag.parent\n          next unless heading['class'].include? 'heading'\n          h = heading.at_css('h2, h3, h4')\n          h.prepend_child(kindtag)\n        end\n\n        # View source\n        css('h2 ~ a.viewSourceLink, h3 ~ a.viewSourceLink, h4 ~ a.viewSourceLink').each do |node|\n          node['class'] = 'view-source'\n          node.content = 'Source'\n          node.previous_element << node\n        end\n\n        css('.mdnIcon').each do |node|\n          parent = node.parent\n          node.remove\n          parent.content = parent.content.strip\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/lit/entries.rb",
    "content": "module Docs\n  class Lit\n    class EntriesFilter < Docs::EntriesFilter\n\n      def get_name\n        name = at_css('h1').content.strip\n      end\n\n      def get_type\n        # The type/category is section name from the sidebar.\n        active = at_css('#docsNav details li.active')\n        return nil unless active\n        summary = active.ancestors('details').first.at_css('summary')\n        return nil unless summary\n        summary.css('[aria-hidden=\"true\"]').remove\n        summary.content.strip\n      end\n\n      def additional_entries\n        entries = []\n\n        # Code for the API reference pages (and other similar pages).\n        scope_name = ''\n        css('.heading > h2[id], .heading > h3[id], .heading > h4[id]').each do |node|\n          name = node.content.strip\n          id = node['id']\n          # The kindTag has these values:\n          # class, decorator, directive, function, namespace, type, value\n          kind = node.parent.at_css('.kindTag')&.content&.strip\n\n          if kind\n            # Saving the current \"scope\", i.e. the current class name.\n            # This is useful to prefix the method/property names, which are defined after this element.\n            scope_name = name\n            name = kind + \" \" + name\n          else\n            # If this is a method/property, it has a different markup.\n            # Let's extract them and add a prefix for disambiguation.\n            function = node.at_css('.functionName')\n            property = node.at_css('.propertyName')\n            if function\n              # Note how \"functions\" are actually \"methods\" of some class.\n              # Bare (top-level) functions are extracted when `.kindTag` is \"function\".\n              name = scope_name + '.' + function.content.strip\n              kind = 'method'\n            elsif property\n              name = scope_name + '.' + property.content.strip\n              kind = 'property'\n            end\n          end\n\n          # If we couldn't figure out the kind, this is a header tag that we can ignore.\n          entries << [name, id, kind] if kind\n        end\n\n        # Code for the Built-in Directives page.\n        # This page has a TOC of the built-in directives, with a clear documentation of each one.\n        # Note that the directives are also indexed in the API reference pages.\n        # Yes, each directive is indexed twice, because each one is documented twice.\n        css('.directory a[href^=\"#\"]').each do |node|\n          name = node.content.strip\n          id = node['href'].sub /^#/, ''\n          # type will be \"Built-in directives\"\n          type = node.ancestors('article').at_css('h1').content.strip\n          entries << [name, id, type]\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/lodash/clean_html.rb",
    "content": "module Docs\n  class Lodash\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.doc-container')\n\n        css('> div', '> div > div', '.highlight').each do |node|\n          node.before(node.children).remove\n        end\n\n        # Remove <code> inside headings\n        css('h2', 'h3').each do |node|\n          node.content = node.content\n        end\n\n        css('h3 + p > a:first-child').each do |node|\n          node.parent.previous_element << %(<div class=\"_heading-links\">#{node.parent.inner_html}</div>)\n          node.parent.remove\n        end\n\n        # Remove code highlighting\n        css('pre').each do |node|\n          node.inner_html = node.inner_html.gsub('</div>', \"</div>\\n\").gsub('&nbsp;', ' ').gsub('&amp;ampamp;', '&amp;amp;')\n          node.content = node.content.strip\n          node['data-language'] = 'javascript'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/lodash/entries.rb",
    "content": "module Docs\n  class Lodash\n    class EntriesFilter < Docs::EntriesFilter\n      def additional_entries\n        entries = []\n\n        css('.toc-container h2').each do |heading|\n          type = heading.content.split.first\n\n          heading.parent.css('a').each do |link|\n            name = link.content\n            name.remove! %r{\\s.*}\n            entries << [name, link['href'].remove('#'), type]\n          end\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/love/clean_html.rb",
    "content": "module Docs\n  class Love\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('#mw-content-text')\n\n        css('.mw-code').each do |node|\n          node.content = node.at_css('div > pre').content\n          node['data-language'] = 'lua'\n          node.name = 'pre'\n        end\n\n        css('span[id]').each do |node|\n          node.parent['id'] = node['id']\n          node.before(node.children).remove\n        end\n\n        css('table.notice').each do |node|\n          content = node.at_css('td:nth-child(2)').inner_html\n          node.replace %(<p class=\"note\">#{content}</p>)\n        end\n\n        css('table.new-section', 'table.removed-section', 'table.removed-new-section').each do |node|\n          klass = node['class'] == 'new-section' ? 'note-green' : 'note-red'\n          content = node.css('td').map(&:inner_html).join('<br>')\n          node.replace %(<p class=\"note #{klass}\">#{content}</p>)\n        end\n\n        css('.new-feature', '.removed-feature', '.removed-new-feature').each do |node|\n          klass = node['class'] == 'new-feature' ? 'label-green' : 'label-red'\n          content = node.content.sub(' LÖVE', '')\n          label = %( <span class=\"label #{klass}\">#{content}</span>)\n\n          node.next_element.css('dt').each { |n| n << label }\n          node.remove\n        end\n\n        css('img[src$=\"Add.png\"]').each do |node|\n          node.parent['class'] = 'cell-green'\n          node.remove\n        end\n\n        css('img[src$=\"Remove.png\"]').each do |node|\n          node.parent['class'] = 'cell-red'\n          node.remove\n        end\n\n        css('img[src$=\"Deprecate.png\"]').each do |node|\n          node.parent['class'] = 'cell-orange'\n          node.remove\n        end\n\n        css('table, tr, td, th').each do |node|\n          %w(style cellpadding cellspacing width height valign).each do |attribute|\n            node.remove_attribute(attribute)\n          end\n        end\n\n        css('.note i', '.note small', 'div:not([class])', '.smwtable td:nth-last-child(2) > a', '.smwtable td:last-child > a').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('p > br').each do |node|\n          node.parent.remove if node.parent.content.empty?\n        end\n\n        css('div > br', '> br', 'hr').remove\n        css('#Editing_the_wiki + p', '#Editing_the_wiki').remove\n        css('#Other_Languages', '.i18n').remove\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/love/entries.rb",
    "content": "module Docs\n  class Love\n    class EntriesFilter < Docs::EntriesFilter\n      TYPES = {\n        'cdata'                   => 'Lua',\n        'require'                 => 'Lua',\n        'light_userdata'          => 'Lua',\n        'value'                   => 'Lua',\n        'variable'                => 'Lua',\n\n        'Audio_Formats'           => 'love.sound',\n        'Image_Formats'           => 'love.graphics',\n        'ImageFontFormat'         => 'love.font',\n        'BlendMode_Formulas'      => 'love.graphics',\n        'Shader_Variables'        => 'love.graphics',\n\n        'AttributeDataType'       => 'love.graphics',\n        'AreaSpreadDistribution'  => 'love.graphics',\n        'BufferMode'              => 'love.filesystem',\n        'CompressedFormat'        => 'love.image',\n        'FilterType'              => 'love.audio',\n        'ImageFlag'               => 'love.graphics',\n        'JoystickConstant'        => 'love.joystick',\n        'MatrixLayout'            => 'love.math',\n        'ParticleInsertMode'      => 'love.graphics',\n        'TextureMode'             => 'love.graphics'\n      }\n\n      def call\n        if context[:initial_paths].include?(slug)\n          css('table.smwtable td:first-child > a').each do |node|\n            TYPES[node.content.strip] = slug\n          end\n        end\n\n        super\n      end\n\n      def get_type\n        if slug == 'love'\n          'love'\n        elsif slug.start_with?('enet')\n          'enet'\n        elsif slug.include?('Joint') || slug.include?('Shape')\n          'love.physics'\n        elsif TYPES.key?(slug)\n          TYPES[slug]\n        elsif match = slug.match(/\\A(love\\.\\w+)(\\.\\w+)?\\z/)\n          match[2] || context[:initial_paths].include?(match[1]) ? match[1] : 'love'\n        elsif at_css('#catlinks a[title=\"Category:Lua\"]')\n          'Lua'\n        elsif\n          type = slug.split(':').first\n          type.remove! %r{[\\(\\)]}\n          TYPES[type]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/lua/clean_html.rb",
    "content": "module Docs\n  class Lua\n    class CleanHtmlFilter < Filter\n      def call\n        css('hr', 'h1 img', '.footer').remove\n\n        css('[name]').each do |node|\n          node['id'] = node['name']\n          node.remove_attribute('name')\n        end\n\n        css('h1 > a[id]', 'h2 > a[id]', 'h3 > a[id]').each do |node|\n          node.parent['id'] = node['id']\n          node.before(node.children).remove\n        end\n\n        css('b > code').each do |node|\n          node.parent.before(node.parent.children).remove\n        end\n\n        3.times { at_css('h1[id=\"1\"]').previous_element.remove }\n\n        css('.apii').each do |node|\n          node.parent.previous_element << node\n        end\n\n        css('pre').each do |node|\n          node.content = node.content.remove(/\\A\\s*\\n/).rstrip.strip_heredoc\n          node['data-language'] = 'lua'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/lua/entries.rb",
    "content": "module Docs\n  class Lua\n    class EntriesFilter < Docs::EntriesFilter\n      def additional_entries\n        type = nil\n\n        doc.children.each_with_object [] do |node, entries|\n          if node.name == 'h1'\n            type = node.content.strip\n            type.remove! %r{.+\\u{2013}\\s+}\n            type.remove! 'The '\n            type = 'API' if type == 'Application Program Interface'\n          end\n\n          next if type && type.include?('Incompatibilities')\n          next if node.name == 'h2' && type.in?(%w(API Auxiliary\\ Library Standard\\ Libraries))\n\n          if node.name == 'h2' || node.name == 'h3'\n            name = node.content\n            name.remove! %r{.+\\u{2013}\\s+}\n            name.remove! %r{\\[.+\\]}\n            name.gsub! %r{\\s+\\(.*\\)}, '()'\n            entries << [name, node['id'], type]\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/man/clean_html.rb",
    "content": "module Docs\n  class Man\n    class CleanHtmlFilter < Filter\n      def call\n        css('.page-top').remove\n        css('.nav-bar').remove\n        css('.nav-end').remove\n        css('.sec-table').remove\n        css('a[href=\"#top_of_page\"]').remove\n        css('.end-man-text').remove\n        css('.start-footer').remove\n        css('.footer').remove\n        css('.end-footer').remove\n        css('.statcounter').remove\n        css('form[action=\"https://www.google.com/search\"]').remove\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/man/entries.rb",
    "content": "module Docs\n  class Man\n    class EntriesFilter < Docs::EntriesFilter\n\n      @@TYPES = {}\n\n      def get_name\n        return slug.split('/').last.sub(/\\.(\\d[^.]*)\\z/, ' (\\1)') if slug.start_with?('man')\n        at_css('h1').content.sub(' — Linux manual page', '')\n      end\n\n      def get_type\n        build_types if slug == 'dir_by_project'\n        @@TYPES[slug] or 'Linux manual page'\n      end\n\n      def build_types\n        type0 = nil\n        css('*').each do |node|\n          if node.name == 'h2'\n            type0 = node.content\n          elsif node.name == 'a' and node['href'] and node['href'].start_with?('man') and type0\n            # name = node.content + node.next_sibling.content\n            slug0 = node['href'].remove(/\\.html\\z/)\n            @@TYPES[slug0] = type0\n          end\n        end\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/mariadb/clean_html.rb",
    "content": "module Docs\n  class Mariadb\n    class CleanHtmlFilter < Filter\n      def call\n        # Return the empty doc if the EraseInvalidPagesFilter detected this page shouldn't be scraped\n        return doc if doc.inner_html == ''\n\n        # Extract main content\n        @doc = at_css('#content')\n\n        # Remove navigation at the bottom\n        css('.simple_section_nav').remove\n\n        # Remove table of contents\n        css('.table_of_contents').remove\n\n        # Add code highlighting and remove nested tags\n        css('pre').each do |node|\n          node.content = node.content\n          node['data-language'] = 'sql'\n        end\n\n        # Fix images\n        css('img').each do |node|\n          node['src'] = node['src'].sub('http:', 'https:')\n        end\n\n        # Remove navigation items containing only numbers\n        css('.node_comments').each do |node|\n          if node.content.scan(/\\D/).empty?\n            node.remove\n          end\n        end\n\n        # Convert listings (pages like https://mariadb.com/kb/en/library/documentation/sql-statements-structure/) into tables\n        css('ul.listing').each do |node|\n          rows = []\n\n          node.css('li:not(.no_data)').each do |li|\n            name = li.at_css('.media-heading').content\n            description = li.at_css('.blurb').content\n            url = li.at_css('a')['href']\n            rows << \"<tr><td><a href=\\\"#{url}\\\">#{name}</a></td><td>#{description}</td></tr>\"\n          end\n\n          table = \"<table><thead><tr><th>Title</th><th>Description</th></tr></thead><tbody>#{rows.join('')}</tbody></table>\"\n          node.replace(table)\n        end\n\n        # Turn note titles into <strong> tags\n        css('.product_title').each do |node|\n          node.name = 'strong'\n        end\n\n        # Remove comments and questions\n        css('.related_questions, #comments').remove\n        css('h2').each do |node|\n          if node.content == 'Comments'\n            node.remove\n          end\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/mariadb/entries.rb",
    "content": "module Docs\n  class Mariadb\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        return 'Name' if doc.inner_html == ''\n\n        at_css('#content > h1').content.strip\n      end\n\n      def get_type\n        return 'Type' if doc.inner_html == ''\n        return 'Tutorials' if at_css('a.crumb[href]:contains(\"Training & Tutorials\")')\n\n        link = at_css('#breadcrumbs > a:nth-child(4)')\n        link.nil? ? at_css('#breadcrumbs > a:nth-child(3)').content : link.content\n      end\n\n      def entries\n        # Don't add an entry for this page if the EraseInvalidPagesFilter detected this page shouldn't be scraped\n        return [] if doc.inner_html == ''\n        super\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/mariadb/erase_invalid_pages.rb",
    "content": "module Docs\n  class Mariadb\n    class EraseInvalidPagesFilter < Filter\n      @@seen_urls = Hash.new\n\n      def call\n        # The MariaDB documentation uses urls like mariadb.com/kb/en/*\n        # This means there is no way to detect if a page should be scraped based on it's url\n        # We run this filter before the internal_urls filter scrapes all internal urls\n        # If this page should not be scraped, we erase it's contents in here so that the internal urls are not picked up\n        # The entries filter will make sure that no entry is saved for this page\n\n        if at_css('a.crumb[href=\"https://mariadb.com/kb/en/documentation/\"]').nil? and at_css('a.crumb[href=\"https://mariadb.com/kb/en/training-tutorials/\"]').nil?\n          doc.inner_html = ''\n        elsif at_css('.question') and at_css('.answer')\n          doc.inner_html = ''\n        end\n\n        current_page = at_css('a.crumb.node_link')\n        unless current_page.nil?\n          url = current_page['href']\n\n          # Some links lead to the same page\n          # Only parse the page one time\n          if @@seen_urls.has_key?(url)\n            doc.inner_html = ''\n          end\n\n          @@seen_urls[url] = true\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/marionette/clean_html.rb",
    "content": "module Docs\n  class Marionette\n    class CleanHtmlFilter < Filter\n      def call\n        root_page? ? root : other\n        doc\n      end\n\n      def root\n        at_css('p').remove\n        at_css('h1').content = 'Backbone.Marionette'\n      end\n\n      def other\n        css('#source + h2', '#improve', '#source', '.glyphicon').remove\n\n        css('p > br').each do |node|\n          node.replace(' ')\n        end\n\n        css('pre > code').each do |node|\n          node.parent['data-language'] = node['class'][/lang-(\\w+)/, 1] if node['class']\n          node.before(node.children).remove\n        end\n\n        css('h2', 'h3').each do |node|\n          if anchor = node.at_css('a.anchor[name]')\n            id = anchor['name']\n          else\n            id = node.content.strip\n            id.downcase!\n            id.remove! %r{['\"\\/\\.:]}\n            id.gsub! %r{[\\ _]}, '-'\n          end\n          node['id'] = id\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/marionette/entries_v2.rb",
    "content": "module Docs\n  class Marionette\n    class EntriesV2Filter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content.strip\n        name.remove!(/Marionette./)\n        name = name[0].upcase + name.from(1)\n        name\n      end\n\n      def get_type\n        type = name.dup\n        type = 'CollectionView' if type.include?('CollectionView')\n        type = 'Miscellaneous' if %w(Features Installing\\ Marionette Upgrade\\ Guide Common\\ Concepts).include?(type)\n        type\n      end\n\n      def additional_entries\n        if slug == 'marionette.behavior'\n          css('#documentation-index + ul a[href=\"#api\"] + ul a').map do |node|\n            [\"#{name}: #{node.content}\", node['href'].remove('#')]\n          end\n        else\n          css('#documentation-index + ul a').each_with_object [] do |node, entries|\n            content = node.content.strip\n            content.remove! '\\'s'\n            id = node['href'].remove('#')\n            if content.start_with?(name) || content.start_with?('Marionette.')\n              entries << [content, id]\n            elsif content =~ /\\A\"?[a-z]/\n              entries << [\"#{name} #{content}\", id]\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/marionette/entries_v3.rb",
    "content": "module Docs\n  class Marionette\n    class EntriesV3Filter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content.strip\n        name.remove!(/Marionette./)\n        name = name[0].upcase + name.from(1)\n        name\n      end\n\n      def get_type\n        type = name.dup\n        type = 'CollectionView' if type.include?('CollectionView')\n        type = 'Miscellaneous' if %w(Features Installing\\ Marionette Upgrade\\ Guide Common\\ Concepts Common\\ Functionality).include?(type)\n        type\n      end\n\n      def additional_entries\n        if slug == 'marionette.behavior'\n          css('#documentation-index + ul a[href=\"#api\"] + ul a').map do |node|\n            [\"#{name}: #{node.content}\", node['href'].remove('#')]\n          end\n        else\n          css('#documentation-index + ul a').each_with_object [] do |node, entries|\n            content = node.content.strip\n            content.remove! '\\'s'\n            id = node['href'].remove('#')\n            next if content == name\n            if content.start_with?(name) || content.start_with?('Marionette.')\n              entries << [content, id]\n            elsif content =~ /\\A\"?[a-z]/\n              entries << [\"#{name.sub('View Lifecycle', 'View')} #{content}\", id]\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/markdown/clean_html.rb",
    "content": "module Docs\n  class Markdown\n    class CleanHtmlFilter < Filter\n      def call\n        at_css('h1').content = 'Markdown'\n\n        css('#ProjectSubmenu', 'hr').remove\n\n        css('pre > code').each do |node|\n          node.parent['data-language'] = 'markdown'\n          node.before(node.children).remove\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/markdown/entries.rb",
    "content": "module Docs\n  class Markdown\n    class EntriesFilter < Docs::EntriesFilter\n      def additional_entries\n        type = nil\n\n        doc.children.each_with_object [] do |node, entries|\n          if node.name == 'h2'\n            type = node.content.strip\n          elsif node.name == 'h3'\n            next if type == 'Overview'\n            name = node.content.strip\n            entries << [name, node['id'], type]\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/matplotlib/entries.rb",
    "content": "module Docs\n  class Matplotlib\n    class EntriesFilter < Docs::EntriesFilter\n      NAME_BY_SLUG = {\n        'matplotlib_configuration_api' => 'matplotlib',\n        'tri_api' => 'tri'\n      }\n\n      TYPE_BY_SLUG = {\n        'pyplot_summary' => 'pyplot'\n      }\n\n      def get_name\n        return NAME_BY_SLUG[slug] if NAME_BY_SLUG.key?(slug)\n        name = at_css('h1').content.strip\n        name.remove! \"\\u{00b6}\"\n        name.remove! 'matplotlib.'\n        name.remove! 'mpl_toolkits.'\n        name.remove! ' API'\n        name.remove! %r{ \\(.*\\)}\n        name.remove! %r{#$} # headerlink\n        name\n      end\n\n      def get_type\n        return TYPE_BY_SLUG[slug] if TYPE_BY_SLUG.key?(slug)\n        type = name.split('.').first\n        type.downcase!\n        type.remove! ' module'\n        type.remove! ' class'\n        type\n      end\n\n      def additional_entries\n        entries = []\n\n        css('.class > dt[id]', '.exception > dt[id]', '.attribute > dt[id]').each do |node|\n          entry_name = node['id'].remove('matplotlib.').remove('mpl_toolkits.')\n          entries << [entry_name, node['id']] unless entry_name.casecmp(name) == 0\n        end\n\n        css('.data > dt[id]').each do |node|\n          if node['id'].split('.').last.upcase! # skip constants\n            entry_name = node['id'].remove('matplotlib.').remove('mpl_toolkits.')\n            entries << [entry_name, node['id']] unless entry_name.casecmp(name) == 0\n          end\n        end\n\n        css('.function > dt[id]', '.method > dt[id]', '.classmethod > dt[id]').each do |node|\n          entry_name = node['id'].remove('matplotlib.').remove('mpl_toolkits.')\n          entries << [entry_name + '()', node['id']] unless entry_name.casecmp(name) == 0\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/mdn/clean_html.rb",
    "content": "module Docs\n  class Mdn\n    class CleanHtmlFilter < Filter\n      REMOVE_NODES = [\n        '#Summary',          # \"Summary\" heading\n        '.htab',             # \"Browser compatibility\" tabs\n        '.breadcrumbs',      # (e.g. CSS/animation)\n        '.Quick_links',      # (e.g. CSS/animation)\n        '.todo',\n        '.draftHeader',\n        '.hidden',\n        '.button.section-edit',\n        '.communitybox',\n        '#Quick_Links',\n        'aside.metadata',\n        '.reference-layout__toc',\n        '.article-footer',\n        'hr']\n\n      BROWSER_UNNECESSARY_CLASS_REGEX = /\\s*bc-browser[\\w_-]+/\n\n      def call\n        css(*REMOVE_NODES).remove\n\n        css('.reference-layout__header', '.reference-layout__body').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('td.header').each do |node|\n          node.name = 'th'\n        end\n\n        css('nobr', 'span[style*=\"font\"]', 'pre code', 'h2 strong', 'div:not([class])', 'span.seoSummary').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('h2[style]', 'pre[style]', 'th[style]', 'div[style*=\"line-height\"]', 'table[style]', 'pre p[style]').remove_attr('style')\n\n        css('strong > code:only-child').each do |node|\n          node.parent.before(node).remove\n        end\n\n        css('a[title]', 'span[title]').remove_attr('title')\n        css('a.glossaryLink', 'a.external').remove_attr('class')\n        css('*[lang]').remove_attr('lang')\n\n        css('h2 > a[name]', 'h3 > a[name]').each do |node|\n          node.parent['id'] = node['name']\n          node.before(node.children).remove\n        end\n        css('h2 > a, h3 > a').each do |node|\n          # children instead of content for \"Using the download attribute to save a <canvas> as a PNG\" from https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a\n          node.before(node.children).remove\n        end\n\n        css('.notecard > h4').each do |node|\n          node.name = 'strong'\n        end\n\n        css('svg.icon.deprecated', 'svg.icon.experimental', 'svg.icon.non-standard', 'svg.icon.obsolete').each do |node|\n          node.name = 'span'\n          node.content = node.content\n        end\n\n        css('dt > a[id]').each do |node|\n          next if node['href']\n          node.parent['id'] = node['id']\n          node.before(node.children).remove\n        end\n\n        css('pre[class^=\"brush\"]').each do |node|\n          node['data-language'] = node['class'][/brush: ?(\\w+)/, 1]\n          node.remove_attribute('class')\n        end\n\n        css('pre.eval').each do |node|\n          node.content = node.content\n          node.remove_attribute('class')\n        end\n\n        css('.example-header').remove\n\n        css('table').each do |node|\n          node.before %(<div class=\"_table\"></div>)\n          node.previous_element << node\n        end\n\n        # New compatibility tables\n        # FIXME(2021):\n        # - fetched from external JSON: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/alignment-baseline/bcd.json\n        # - https://github.com/mdn/yari/blob/master/build/bcd-urls.js\n\n        css('.bc-data #Legend + dl', '.bc-data #Legend', '.bc-data #Legend_2 + dl', '.bc-data #Legend_2', '.bc-browser-name').remove\n\n        css('abbr.only-icon[title=\"Full support\"]',\n            'abbr.only-icon[title=\"Partial support\"]',\n            'abbr.only-icon[title=\"No support\"]',\n            'abbr.only-icon[title=\"See implementation notes\"]').remove\n\n        css('.bc-data .ic-altname', '.bc-data .ic-deprecated', '.bc-data .ic-non-standard', '.bc-data .ic-experimental').each do |node|\n          node.parent.remove\n        end\n\n        css('abbr.only-icon').each do |node|\n          node.replace(node.content)\n        end\n\n        css('.bc-table .bc-platforms td', '.bc-table .bc-browsers td').each do |node|\n          node.name = 'th'\n        end\n\n        css('.bc-data').each do |node|\n          link = node.at_css('.bc-github-link')\n          prev = node.previous_element\n          prev = prev.previous_element until prev.name == 'h2'\n          prev.add_child(link)\n\n          node.before(node.children).remove\n        end\n\n        css('.bc-table').each do |node|\n          desktop_table = node\n\n          mobile_table = node.dup\n          desktop_table.after(mobile_table)\n\n          if desktop_table.at_css('.bc-platform-server')\n            server_table = node.dup\n            mobile_table.after(server_table)\n          end\n\n          desktop_columns = desktop_table.at_css('th.bc-platform-desktop')['colspan'].to_i\n          mobile_columns = desktop_table.at_css('th.bc-platform-mobile')['colspan'].to_i\n\n          desktop_table.css('.bc-platform-mobile').remove\n          desktop_table.css('.bc-platform-server').remove\n          desktop_table.css('.bc-browsers th').to_a[(desktop_columns + 1)..-1].each(&:remove)\n          desktop_table.css('tr:not(.bc-platforms):not(.bc-browsers)').each do |line|\n            line.css('td').to_a[(desktop_columns)..-1].each(&:remove)\n          end\n\n          mobile_table.css('.bc-platform-desktop').remove\n          mobile_table.css('.bc-platform-server').remove\n          mobile_table.css('.bc-browsers th').to_a[1..(desktop_columns)].each(&:remove)\n          mobile_table.css('.bc-browsers th').to_a[(mobile_columns + 1)..-1].each(&:remove)\n          mobile_table.css('tr:not(.bc-platforms):not(.bc-browsers)').each do |line|\n            line.css('td').to_a[0..(desktop_columns - 1)].each(&:remove)\n            line.css('td').to_a[(mobile_columns)..-1].each(&:remove)\n          end\n\n          if server_table\n            server_table.css('.bc-platform-desktop').remove\n            server_table.css('.bc-platform-mobile').remove\n            server_table.css('.bc-browsers th').to_a[1..(desktop_columns + mobile_columns)].each(&:remove)\n            server_table.css('tr:not(.bc-platforms):not(.bc-browsers)').each do |line|\n              line.css('td').to_a[0..(desktop_columns + mobile_columns - 1)].each(&:remove)\n            end\n          end\n        end\n\n        # Reduce page size to make the offline bundle smaller.\n        css('.bc-supports-unknown').remove_attr('class')\n        css('td[class*=\"bc-platform\"], th[class*=\"bc-platform\"]').remove_attr('class')\n        css('td[class*=\"bc-browser\"], th[class*=\"bc-browser\"]').each do |node|\n          class_name = node['class']\n          class_name.remove!(BROWSER_UNNECESSARY_CLASS_REGEX)\n\n          if class_name.present?\n            node['class'] = class_name\n          else\n            node.remove_attribute('class')\n          end\n        end\n\n        css('abbr[title*=\"Compatibility unknown\"]').each do |node|\n          node.before(node.children).remove\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/mdn/compat_tables.rb",
    "content": "module Docs\n  class Mdn\n    class CompatTablesFilter < Filter\n\n      # Generate browser compatibility table\n      # Fixes \"BCD tables only load in the browser\"\n      # https://github.com/mdn/browser-compat-data\n      # https://github.com/mdn/yari/tree/main/client/src/lit/compat\n\n      def call\n        generate_compatibility_table()\n\n        doc\n      end\n\n      BROWSERS_DESKTOP = {\n        # Desktop\n        'chrome' => 'Chrome',\n        'edge' => 'Edge',\n        'firefox' => 'Firefox',\n        'opera' => 'Opera',\n        'safari' => 'Safari',\n      }\n      BROWSERS_MOBILE = {\n        # Mobile\n        'chrome_android' => 'Chrome Android',\n        'firefox_android' => 'Firefox for Android',\n        'opera_android' => 'Opera Android',\n        'safari_ios' => 'Safari on IOS',\n        'samsunginternet_android' => 'Samsung Internet',\n        'webview_android' => 'WebView Android',\n        'webview_ios' => 'WebView on iOS',\n      }\n      BROWSERS_SERVER = {\n        # Server\n        'bun' => 'Bun',\n        'deno' => 'Deno',\n        'nodejs' => 'Node.js',\n      }\n\n      def is_javascript\n        current_url.to_s.start_with?('https://developer.mozilla.org/en-US/docs/Web/JavaScript')\n      end\n\n      def browsers\n        if is_javascript\n          {}.merge(BROWSERS_DESKTOP).merge(BROWSERS_MOBILE).merge(BROWSERS_SERVER)\n        else\n          {}.merge(BROWSERS_DESKTOP).merge(BROWSERS_MOBILE)\n        end\n      end\n\n      def browser_types\n        if is_javascript\n          {'Desktop'=>BROWSERS_DESKTOP.length, 'Mobile'=>BROWSERS_MOBILE.length, 'Server'=>BROWSERS_SERVER.length,}\n        else\n          {'Desktop'=>BROWSERS_DESKTOP.length, 'Mobile'=>BROWSERS_MOBILE.length,}\n        end\n      end\n\n      def generate_compatibility_table()\n        css('mdn-compat-table-lazy').each do |node|\n          file = node.attr('query')\n          # https://github.com/mdn/browser-compat-data/blob/main/javascript/builtins/Set.json\n          # https://bcd.developer.mozilla.org/bcd/api/v0/current/javascript.builtins.Set.json\n          uri = \"https://bcd.developer.mozilla.org/bcd/api/v0/current/#{file}.json\"\n          node.replace generate_compatibility_table_wrapper(uri)\n        end\n      end\n\n      def generate_compatibility_table_wrapper(url)\n        response = Request.run url\n        return \"\" unless response.success?\n        @json_data = JSON.load(response.body)['data']\n\n        html_table = generate_basic_html_table()\n\n        @json_data.keys.each do |key|\n          if key == '__compat' or @json_data[key]['__compat']\n            add_entry_to_table(html_table, key)\n          else\n          end\n        end\n\n        return html_table\n      end\n\n      def generate_basic_html_table\n        table = Nokogiri::XML::Node.new('table', doc.document)\n\n        table.add_child('<thead><tr id=bct-browser-type><tr id=bct-browsers><tbody>')\n\n        table.css('#bct-browser-type').each do |node|\n          node.add_child('<th>')\n          browser_types.each do |browser_type, colspan|\n            node.add_child(\"<th colspan=#{colspan}>#{browser_type}\")\n          end\n        end\n\n        table.css('#bct-browsers').each do |node|\n          node.add_child('<th>')\n\n          browsers.values.each do |browser|\n            node.add_child(\"<th>#{browser}\")\n          end\n        end\n\n        return table\n      end\n\n      def add_entry_to_table(html_table, key)\n        json = @json_data[key]\n\n        html_table.at_css('tbody').add_child('<tr>')\n\n        last_table_entry = html_table.at_css('tbody').last_element_child\n\n        if key == '__compat'\n          tmp = slug.split('/')\n          last_table_entry.add_child(\"<th><code>#{tmp.last}\")\n        else\n          last_table_entry.add_child(\"<th><code>#{key}\")\n        end\n\n\n        browsers.keys.each do |browser_key|\n          if key == '__compat'\n            add_data_to_entry(json['support'][browser_key], last_table_entry)\n          else\n            add_data_to_entry(json['__compat']['support'][browser_key], last_table_entry)\n          end\n\n        end\n      end\n\n      def add_data_to_entry(json, entry)\n        version_added = []\n        version_removed = []\n        notes = []\n\n        if !json\n          format_string = \"<td class=bc-supports-unknown><div>?</div></td>\"\n          entry.add_child(format_string)\n          return\n        end\n\n        format_string = \"<td class=bc-supports-unknown>\"\n        (json.is_a?(Array) ? json : [json]).each do |element|\n          version = element['version_added']\n          if version.is_a?(String) and element['release_date']\n            format_string = \"<td class=bc-supports-yes>\"\n            format_string = \"<td class=bc-supports-preview>\" if element['release_date'] > Time.now.iso8601\n            version_added.push(\"<abbr title='Release date: #{element['release_date']}'>#{version}</abbr>\")\n          elsif version == 'preview'\n            format_string = \"<td class=bc-supports-preview>\"\n            version_added.push(version)\n          elsif version.is_a?(String)\n            format_string = \"<td class=bc-supports-yes>\"\n            version_added.push(version)\n          elsif version == true\n            format_string = \"<td class=bc-supports-yes>\"\n            version_added.push('Yes')\n          else\n            format_string = \"<td class=bc-supports-no>\"\n            version_added.push('No')\n          end\n          version_removed.push(element['version_removed'] || false)\n          notes.push(element['notes'] || false)\n        end\n\n        for value in (0..version_added.length-1) do\n          if version_removed[value]\n            version_string = \"#{version_added[value]}–#{version_removed[value]}\"\n          else\n            version_string = version_added[value]\n          end\n\n          if notes[value]\n            format_string += \"<details><summary>#{version_string}</summary>#{notes[value]}</details>\"\n          else\n            format_string += \"<div>#{version_string}</div>\"\n          end\n        end\n\n        format_string += \"</td>\"\n\n        entry.add_child(format_string)\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/meteor/clean_html.rb",
    "content": "module Docs\n  class Meteor\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.content-wrapper')\n\n        at_css('h1').content = 'Meteor Documentation' if root_page?\n\n        css('.page-actions', '.anchor').remove\n\n        css('.header-content', '.document-formatting', 'h2 > a', '.api', '.api-body', 'div.desc').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('.anchor-offset').each do |node|\n          node.parent['id'] = node['id']\n          node.remove\n        end\n\n        css('.api-heading').each do |node|\n          heading = node.at_css('h2, h3')\n          name = heading.name\n          node['id'] = heading['id']\n          heading.replace \"<code>#{heading.content.strip}</code>\"\n          node.name = name\n        end\n\n        css('div.code', 'span.code', '.args .name').each do |node|\n          node.name = 'code'\n          node.remove_attribute('class')\n        end\n\n        css('figure.highlight').each do |node|\n          node.inner_html = node.at_css('.code pre').inner_html.gsub('</div><div', \"</div>\\n<div\").gsub('<br>', \"\\n\")\n          node.content = node.content\n          node['data-language'] = node['class'].split.last\n          node.name = 'pre'\n        end\n\n        css('pre.prettyprint').each do |node|\n          node['data-language'] = node['class'].include?('html') ? 'html' : 'js'\n          node.content = node.content\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/meteor/entries.rb",
    "content": "module Docs\n  class Meteor\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('.item-toc.current').content\n      end\n\n      def get_type\n        if subpath.start_with?('api')\n          name\n        else\n          return 'Guide: Blaze' if base_url.host == 'blazejs.org' && subpath.start_with?('guide')\n          type = at_css('.item-toc.current').ancestors('li').first.at_css('.heading-toc').try(:content) || 'Guide'\n          type.prepend 'Guide: ' if base_url.host == 'guide.meteor.com' && type != 'Guide'\n          type\n        end\n      end\n\n      def additional_entries\n        if slug == 'commandline'\n          css('h2[id]').map do |node|\n            [node.content, node['id']]\n          end\n        else\n          css('.title-api[id]').map do |node|\n            name = node.content.strip\n            name.sub! %r{\\(.+\\)}, '()'\n            name.remove! 'new '\n            name = '{{> Template.dynamic }}' if name.include?('Template.dynamic')\n            [name, node['id']]\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/minitest/entries.rb",
    "content": "module Docs\n  class Minitest\n    class EntriesFilter < Docs::Rdoc::EntriesFilter\n      def get_type\n        type = name.dup\n        type.remove! %r{#.+\\z}\n        type.split('::')[0..1].join('::')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/mkdocs/clean_html.rb",
    "content": "module Docs\n  class Mkdocs\n    class CleanHtmlFilter < Docs::Filter\n      def call\n        css('.toclink').each do |node|\n          node.parent.content = node.content\n        end\n\n        css('pre').each do |node|\n          node.content = node.at_css('code').content\n        end\n\n        at_css('#main-content')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/mocha/clean_html.rb",
    "content": "module Docs\n  class Mocha\n    class CleanHtmlFilter < Filter\n      def call\n        doc.child.remove until doc.child['id'] == 'installation'\n\n        css('pre').each do |node|\n          node.content = node.content\n          node['data-language'] = 'javascript'\n        end\n\n        css('#more-information ~ p').remove\n        css('#more-information').remove\n\n        css('h2 > a, h3 > a').each do |node|\n          node.remove\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/mocha/entries.rb",
    "content": "module Docs\n  class Mocha\n    class EntriesFilter < Docs::EntriesFilter\n      ENTRIES = {\n        'asynchronous-code' => ['done()'],\n        'hooks' => ['before()', 'after()', 'beforeEach()', 'afterEach()', 'suiteSetup()', 'suiteTeardown()', 'setup()', 'teardown()'],\n        'exclusive-tests' => ['only()'],\n        'inclusive-tests' => ['skip()'],\n        'bdd' => ['describe()', 'context()', 'it()', 'specify()'],\n        'tdd' => ['suite()', 'test()'],\n        'exports' => ['exports'],\n        'require' => ['require'],\n        'running-mocha-in-the-browser' => ['mocha.setup()', 'mocha.run()', 'mocha.globals()', 'mocha.checkLeaks()'],\n        'timeouts' => ['timeout()'],\n        'delayed-root-suite' => ['run()'],\n        'command-line-usage' => ['mocha cli options']\n      }\n\n      def additional_entries\n        entries = []\n\n        ENTRIES.each do |id, names|\n          names.each do |name|\n            entries << [name, id] if at_css(\"[id='#{id}']\")\n          end\n        end\n\n        css('h2, h3').each do |node|\n          name = node.content.strip\n          next if name.match?(/\\A-/)\n          next if name.in?(%w(Examples Getting\\ Started Installation More\\ Information Testing\\ Mocha))\n          entries << [name, node['id']]\n        end\n\n        entries.each do |entry|\n          entry[2] = entry[0] =~ /\\A[a-z]/ ? 'API' : 'Manual'\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/modernizr/clean_html.rb",
    "content": "module Docs\n  class Modernizr\n    class CleanHtmlFilter < Filter\n      def call\n        css('pre').each do |node|\n          class_name = node.at_css('code')['class']\n          node['data-language'] = class_name[/lang-(\\w+)/, 1] if class_name\n          node.content = node.content.strip_heredoc\n        end\n\n        css('sub').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('td:nth-child(2)').each do |node|\n          node.name = node.previous_element.name = 'th'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/modernizr/entries.rb",
    "content": "module Docs\n  class Modernizr\n    class EntriesFilter < Docs::EntriesFilter\n      def additional_entries\n        entries = []\n\n        css('h3[id]').each do |node|\n          next unless name = node.content.strip[/\\AModernizr\\..+/]\n          entries << [name, node['id'], 'Modernizr']\n        end\n\n        css('h2[id=\"features\"] + table td:nth-child(2) b').each do |node|\n          node['id'] = node.content.parameterize\n          node.content.split(',').each do |name|\n            entries << [name, node['id'], 'Features']\n          end\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/moment/clean_html.rb",
    "content": "module Docs\n  class Moment\n    class CleanHtmlFilter < Filter\n      def call\n        # Set id attributes on headings\n        css('a.docs-section-target', 'a.docs-method-target').each do |node|\n          node.next_element['id'] = node['id']\n          node.remove\n        end\n\n        css('> article', '.docs-method-prose', '.docs-method-signature', 'h2 > a', 'h3 > a', 'pre > code').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('.docs-method-edit', 'hr').remove\n\n        css('pre').each do |node|\n          if node.content =~ /\\A</\n            node['data-language'] = 'html'\n          elsif node.content !~ /\\A\\d/\n            node['data-language'] = 'javascript'\n          end\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/moment/entries.rb",
    "content": "module Docs\n  class Moment\n    class EntriesFilter < Docs::EntriesFilter\n      IGNORE_TYPES = %w(\n        Where\\ to\\ use\\ it\n        Plugins\n        Guides:\\ External\\ Resources)\n      IGNORE_IDS = %w(\n        /i18n/loading-into-nodejs/\n        /i18n/loading-into-browser/\n        /i18n/adding-locale/\n        /i18n/getting-locale/\n      )\n\n      def get_name\n        if subpath == '/guides/'\n          'Guides'\n        end\n      end\n\n      def get_type\n        if subpath == '/guides/'\n          'Guides'\n        end\n      end\n\n      def additional_entries\n        entries = []\n        type = nil\n\n        css('[id]').each do |node|\n          if node.name == 'h2'\n            type = node.content\n            type.remove! ' Guide'\n            type.prepend 'Guides: ' if subpath == '/guides/'\n            next\n          end\n\n          next unless node.name == 'h3'\n          next if IGNORE_TYPES.include?(type)\n          next if IGNORE_IDS.include?(node['id'])\n\n          if %w(Display Durations Get\\ +\\ Set i18n Manipulate Query Utilities).include?(type) ||\n             %w(/parsing/is-valid/\n                /parsing/parse-zone/\n                /parsing/unix-timestamp/\n                /parsing/utc/\n                /parsing/creation-data/\n                /customization/relative-time-threshold/\n                /customization/relative-time-rounding/\n                /customization/calendar-format/\n                /customization/now/\n              ).include?(node['id'])\n            name = node.next_element.content[/moment(?:\\(.*?\\))?\\.(?:duration\\(\\)\\.)?\\w+/]\n            name.sub! %r{\\(.*?\\)\\.}, '#'\n            name << '()'\n          elsif type == 'Customize'\n            name = node.next_element.content[/moment.locale\\(.+?\\{\\s+(\\w+)/, 1]\n            name = node.content.strip unless name\n            name.prepend 'Locale#'\n          else\n            name = node.content.strip\n            name.prepend 'Parse: ' if type == 'Parse'\n          end\n          name.remove! %r{\\s[\\d\\.]+[\\s\\+]*\\z} # remove version number\n          name.remove! %r{\\s\\(.+\\)\\z}  # remove parenthesis\n          entries << [name, node['id'], type]\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/moment_timezone/entries.rb",
    "content": "module Docs\n  class MomentTimezone\n    class EntriesFilter < Docs::EntriesFilter\n\n      def additional_entries\n        entries = []\n        type = nil\n\n        css('[id]').each do |node|\n          if node.name == 'h2'\n            type = node.content\n          end\n          name = node.content.strip\n          entries << [name, node['id'], type]\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/mongoose/clean_html.rb",
    "content": "module Docs\n  class Mongoose\n    class CleanHtmlFilter < Filter\n      def call\n        css('hr', '.showcode', '.sourcecode').remove\n\n        css('h2:empty + p').each do |node| # /customschematypes.html\n          node.previous_element.content = node.content\n          node.remove\n        end\n\n        css('pre > code', 'h1 + ul', '.module', '.item', 'h1 > a', 'h2 > a', 'h3 > a', 'h3 code').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('pre').each do |node|\n          node['data-language'] = 'javascript'\n        end\n\n        css('.native-inline', '.api-nav', '.toc', '.api-content ul:first-child', '.edit-docs-link').remove\n\n        if !at_css('h1')\n          if css('h2').count > 1\n            # Mongoose vX.Y.Z: [title here]\n            title = doc.document.at_css('title').content.split(': ')[1]\n            doc.prepend_child(\"<h1>#{title}</title>\")\n          else\n            at_css('h2').name = 'h1'\n            css('h3').each do |el|\n              el.name = 'h2'\n            end\n          end\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/mongoose/entries.rb",
    "content": "module Docs\n  class Mongoose\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        if slug.start_with? 'api'\n          for heading in css('h3[id]')\n            type = get_type_from heading\n            return type if type\n          end\n        end\n        'Guides'\n      end\n\n      def get_type_from node\n        name = node.content.strip\n        type = name.split(/[#\\.\\(]/).first\n        if type.empty? || name.include?(' ')\n          nil\n        else\n          type\n        end\n      end\n\n      def additional_entries\n        entries = []\n\n        css('h3[id]').each do |node|\n          id = node['id']\n          next if id == 'index_'\n\n          id.sub!('%24', '$')\n          node['id'] = id\n\n          name = node.content.strip\n          name.sub! %r{\\(.+\\)}, '()'\n          next if name.include?(' ')\n\n          type = get_type_from node\n          next if type.nil?\n          entries << [name, id, type]\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/nextjs/clean_html.rb",
    "content": "module Docs\n    class Nextjs\n        class CleanHtmlFilter < Filter\n            def call\n                @doc = at_css('.prose')\n\n                css('.zola-anchor').remove\n                doc.prepend_child(\"<h1>NextJS2</h1>\") if root_page?\n                css('div:contains(\"NEWS:\")').remove\n                css('h2:contains(\"sponsors\"), #sponsor-table').remove\n                css('div.sticky').remove #remove the floating menu\n                css('div.-mt-4').remove #remove the navigation line\n                css('footer').remove\n                css('div.feedback_inlineTriggerWrapper__o7yUx').remove\n                css('header').remove #remove links from the top of the page\n                css('nav').remove\n\n                css('h1, h2, h3, h4').each { |node| node.content = node.content }\n\n                css('pre > code').each do |node|\n                  node.parent['data-language'] = 'typescript'\n                  node.parent.content = node.parent.content\n                end\n                css('div[class^=\"code-block_header\"]').remove\n\n                doc\n            end\n        end\n    end\nend\n"
  },
  {
    "path": "lib/docs/filters/nextjs/entries.rb",
    "content": "module Docs\n  class Nextjs\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content\n        name.strip!\n        #name\n        subpath_items = subpath.split('/', -1)\n        if subpath_items.length >= 5\n          subpath_items[3].capitalize + ': ' + name # e.g. Routing: Defining Routes\n        else\n          name\n        end\n      end\n\n      def get_type\n        if slug.start_with?('architecture')\n          'Architecture'\n        elsif slug.start_with?('community')\n          'Community'\n        elsif slug.start_with?('getting-started')\n          'Getting Started'\n        elsif slug.start_with?('messages')\n          'Messages'\n        elsif slug.start_with?('app/building-your-application')\n          'Using App Router: Building your application'\n        elsif slug.start_with?('app/api-reference')\n          'Using App Router: api-reference'\n        elsif slug.start_with?('app')\n          'Using App Router'\n        elsif slug.start_with?('pages/building-your-application')\n          'Using Pages Router: Building your application'\n        elsif slug.start_with?('pages/api-reference')\n          'Using Pages Router: api-reference'\n        elsif slug.start_with?('pages')\n          'Using Pages Router'\n        else\n          get_name\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/nginx/clean_html.rb",
    "content": "module Docs\n  class Nginx\n    class CleanHtmlFilter < Filter\n      def call\n        at_css('h2').name = 'h1'\n\n        css('center').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('blockquote > pre', 'blockquote > table:only-child').each do |node|\n          node.parent.before(node).remove\n        end\n\n        css('a[name]').each do |node|\n          node.next_element['id'] = node['name']\n          node.remove\n        end\n\n        links = css('h1 + table > tr:only-child > td:only-child > a').map(&:to_html)\n        if links.present?\n          at_css('h1 + table').replace(\"<ul><li>#{links.join('</li><li>')}</li></ul>\")\n        end\n\n        css('td > pre').each do |node|\n          node.name = 'code'\n        end\n\n        css('pre').each do |node|\n          node['data-language'] = 'nginx'\n        end\n\n        css('code code').each do |node|\n          node.before(node.children).remove\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/nginx/entries.rb",
    "content": "module Docs\n  class Nginx\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content.strip\n        name.sub! %r{\\AModule ngx}, 'ngx'\n        name\n      end\n\n      def get_type\n        if name.starts_with?('ngx_')\n          name\n        elsif slug == 'ngx_core_module'\n          'Core'\n        else\n          'Guides'\n        end\n      end\n\n      def additional_entries\n        css('h1 + ul a').each_with_object [] do |node, entries|\n          name = node.content.strip\n          next if name =~ /\\A[A-Z]/ || name.start_with?('/')\n          mod = get_name\n          name = \"#{name} (#{mod})\" unless mod.match?(/ngx_http/)\n\n          id = node['href'].remove('#')\n          next if id.blank?\n\n          entries << [name, id]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/nginx_lua_module/clean_html.rb",
    "content": "module Docs\n  class NginxLuaModule\n    class CleanHtmlFilter < Filter\n      def call\n        css('a[href=\"#table-of-contents\"]', 'a:contains(\"Back to TOC\")').remove\n\n        css('h1, h2, h3').each do |node|\n          node.name = node.name.sub(/\\d/) { |i| i.to_i + 1 }\n        end\n\n        css('a > img').each do |node|\n          node.parent.before(node.parent.children).remove\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/nginx_lua_module/entries.rb",
    "content": "module Docs\n  class NginxLuaModule\n    class EntriesFilter < Docs::EntriesFilter\n      def additional_entries\n        entries = []\n\n        css('h2:contains(\"Directives\") + ul > li > a').each do |node|\n          entries << [node.content, node['href'].remove('#'), 'Directives']\n        end\n\n        css('h2:contains(\"Nginx API for Lua\") + ul > li > a').each do |node|\n          next if node.content == 'Introduction'\n          entries << [node.content, node['href'].remove('#'), 'Nginx API for Lua']\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/nim/clean_html.rb",
    "content": "module Docs\n  class Nim\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('#documentId .container')\n\n        # add id to each proc, template and macro\n        css('dl > dt > pre').each do |node|\n          next if !(node.at_css('a'))\n          node.remove_attribute('id')\n          nodeId = node.at_css('a')['href']\n          nodeId.gsub!(/.*\\#/, '')\n          node['id'] = nodeId\n        end\n\n        css('.docinfo', '.footer', 'blockquote > p:empty').remove\n\n        css('h1:not(.title), h2, h3, h4').each do |node|\n          node.name = node.name.sub(/\\d/) { |i| i.to_i + 1 }\n        end\n\n        if content = at_css('#content')\n          content.prepend_child at_css('h1.title')\n          @doc = content\n        end\n\n        css('h1 > a', 'h2 > a', 'h3 > a', 'h4 > a').each do |node|\n          node.parent['id'] = node['id'] if node['id']\n          node.before(node.children).remove\n        end\n\n        css('pre').each do |node|\n          node.content = node.content.strip\n          node['data-language'] = 'nim' unless node.content =~ /\\A[\\w\\-\\_\\:\\=\\ ]+\\z/\n        end\n\n        css('tt').each do |node|\n          node.name = 'code'\n        end\n\n        css('cite').each do |node|\n          node.name = 'em'\n        end\n\n        css('.section').each do |node|\n          node.first_element_child['id'] = node['id'] if node['id']\n          node.before(node.children).remove\n        end\n\n        css('span.pre').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('blockquote > pre:only-child', 'blockquote > dl:only-child', 'blockquote > table').each do |node|\n          node.parent.before(node.parent.children).remove\n        end\n\n        css('a', 'dl', 'table', 'code').remove_attr('class')\n        css('table').remove_attr('border')\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/nim/entries.rb",
    "content": "module Docs\n  class Nim\n    class EntriesFilter < Docs::EntriesFilter\n\n      REFERENCE = [\n        'API naming design', 'Internals of the Nim Compiler', 'Nim Backend Integration',\n        'Nim Compiler', 'Nim Destructors and Move Semantics', 'Nim Enhancement Proposal #1',\n        'Nim Experimental Features', 'Nim IDE Integration Guide',\n        'Nim maintenance script', 'Nim Standard Library', \"Nim's Memory Management\",\n        'NimScript', 'Packaging Nim', 'segfaults', 'Source Code Filters'\n      ]\n\n      def get_name\n        name = at_css('h1').content\n        name.remove! 'Module '\n        name.remove! ' User Guide'\n        name.remove! ' User\\'s manual'\n        name.remove! %r{ \\-.*}\n        name.remove! %r{\\Asrc/}\n        name.remove! %r{\\Astd/}\n        name.strip!\n        name.split(\"/\").last\n      end\n\n      def get_type\n        if slug == 'manual'\n          'Manual'\n        elsif REFERENCE.include?(name)\n          'Reference'\n        else\n          name\n        end\n      end\n\n      def additional_entries\n        entries = []\n\n        if slug == 'manual'\n\n          css('#toc-list > li > a').each do |node|\n            name = node.content.strip\n            next if name.start_with?('About')\n            id = node['href'].remove('#')\n            entries << [name, id]\n          end\n\n          css('#toc-list > ul').each do |node|\n            type = node.previous_element.content.strip\n\n            node.css('> li > a').each do |n|\n              entries << [n.content.strip, n['href'].remove('#'), \"Manual: #{type}\"]\n            end\n\n          end\n\n        else\n\n          css('.simple-toc-section a, .nested-toc-section a').each do |node|\n            match = /^`(.*)`|^(\\w+)/.match(node.content)\n            entry_name = match[1] || match[2]\n\n            entry_id = slug + node['href']\n            entries << [\"#{entry_name} (#{name})\", entry_id, name]\n          end\n\n        end\n\n        entries\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/nix/clean_html.rb",
    "content": "module Docs\n  class Nix\n    class CleanHtmlFilter < Filter\n      def call\n        if subpath == 'nixpkgs/stable/index.html'\n          new_root = Nokogiri::XML::Node.new 'div', doc.document\n\n          # lib functions\n          lib_sections = xpath(\"//*[@id='sec-functions-library']/ancestor::section[1]/section/section\")\n          lib_sections.css('.titlepage .title').each do |title|\n            title.name = 'h2'\n            strip_section_number(title.children)\n            title['id'] = title.content.gsub(/[^a-zA-Z0-9]/, '-')\n          end\n          lib_sections.css('.example .title strong').each do |title|\n            title.content = title.content.sub(/^Example ([0-9.]+)/, 'Example: ')\n          end\n          new_root.add_child(lib_sections)\n\n          # fetchers\n          fetcher_sections = xpath(\"//*[@id='chap-pkgs-fetchers']/ancestor::section[1]/section[position()>1]\")\n          fetcher_sections.css('.titlepage .title').each do |title|\n            strip_section_number(title.children)\n            prefix_with(title, 'pkgs') if title.name == 'h2'\n          end\n          new_root.add_child(fetcher_sections)\n\n          # trivial builders\n          trivial_sections = xpath(\"//*[@id='chap-trivial-builders']/ancestor::section[1]/section\")\n          trivial_sections.css('.titlepage .title').each do |title|\n            strip_section_number(title.children)\n            prefix_with(title, 'pkgs') if title.name == 'h2'\n          end\n          new_root.add_child(trivial_sections)\n\n          # special builders\n          special_sections = xpath(\"//*[@id='chap-special']/ancestor::section[1]/section\")\n          special_sections.css('.titlepage .title').each do |title|\n            strip_section_number(title.children)\n            if title.name == 'h2'\n              title.children[0].wrap('<code>')\n              prefix_with(title, 'pkgs')\n            end\n          end\n          new_root.add_child(special_sections)\n\n          # image builders\n          image_sections = xpath(\"//*[@id='chap-images']/ancestor::section[1]/section\")\n          image_sections.css('.titlepage .title').each do |title|\n            strip_section_number(title.children)\n            title.children[0].wrap('<code>')\n          end\n          image_sections.each do |section|\n            prefix = section.at_xpath('*[@class=\"titlepage\"]//*[@class=\"title\"]').content\n            next unless [\"pkgs.dockerTools\", \"pkgs.ociTools\"].include?(prefix)\n            section.xpath('section/*[@class=\"titlepage\"]//*[@class=\"title\"]').each do |title|\n              prefix_with(title, prefix)\n              title['data-add-to-index'] = ''\n            end\n          end\n          new_root.add_child(image_sections)\n\n          new_root.css('pre.programlisting').attr('data-language', 'nix')\n\n          new_root\n        elsif subpath == 'nix/stable/expressions/builtins.html'\n          @doc = doc.at_css('main dl')\n\n          # strip out the first entry, `derivation`, the actual documentation\n          # exists in a separate page\n          derivation_dt = doc.children.at_css('dt')\n          if derivation_dt.content.starts_with?('derivation')\n            derivation_dt.remove\n            doc.children.at_css('dd').remove\n          else\n            raise RuntimeError.new('First entry is not derivation, update the scraper')\n          end\n\n          doc.css('dt').each do |title|\n            title.name = 'h2'\n            unwrap(title.at_css('a'))\n            title.children[0].children.before('builtins.')\n          end\n          doc.css('dd').each do |description|\n            description.name = 'div'\n          end\n\n          doc.css('pre > code').each do |code|\n            code.parent['data-language'] = 'nix' if code['class'] == 'language-nix'\n            code.parent['data-language'] = 'xml' if code['class'] == 'language-xml'\n            unwrap(code)\n          end\n\n          doc\n        else\n          doc\n        end\n      end\n\n      def strip_section_number(title_children)\n        while title_children.first.content == ''\n          title_children.shift.remove\n        end\n        first_text = title_children.first\n        return unless first_text.text?\n        first_text.content = first_text.content.sub(/[0-9.]+ ?/, '')\n        first_text.remove if first_text.blank?\n      end\n\n      def prefix_with(title_node, text)\n        title_node.css('code').each do |code|\n          code.content = \"#{text}.#{code.content}\" unless code.content.starts_with?(\"#{text}.\")\n        end\n      end\n\n      def unwrap(node)\n        node.replace(node.inner_html)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/nix/entries.rb",
    "content": "module Docs\n  class Nix\n    class EntriesFilter < Docs::EntriesFilter\n      def include_default_entry?\n        false\n      end\n\n      def additional_entries\n        css('h2, h3[data-add-to-index]').flat_map do |node|\n          node.css('code').map do |code|\n            title = code.content\n            index = title.rindex('.')\n            type = title[0...index]\n            name = title.match(/^[^\\s]+/)[0]\n\n            [name, node['id'], type]\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/node/clean_html.rb",
    "content": "module Docs\n  class Node\n    class CleanHtmlFilter < Filter\n      def call\n        css('hr').remove\n\n        css('pre').each do |node|\n          next unless (node.css('code').to_a.length > 1)\n\n          node.css('code').each do |subnode|\n            node.before(subnode)\n\n            if subnode.classes.include?('mjs')\n              subnode.wrap('<details open>')\n              subnode.wrap('<pre>')\n              subnode.ancestors('details').first.prepend_child('<summary>JavaScript modules</summary>')\n            elsif subnode.classes.include?('cjs')\n              subnode.wrap('<details>')\n              subnode.wrap('<pre>')\n              subnode.ancestors('details').first.prepend_child('<summary>CommonJS</summary>')\n            end\n          end\n\n          node.remove\n        end\n\n        # Remove \"#\" links\n        css('.mark').each do |node|\n          node.parent.parent['id'] = node['id']\n          node.parent.remove\n        end\n\n        css('pre[class*=\"api_stability\"]').each do |node|\n          node.name = 'div'\n        end\n\n        css('pre').each do |node|\n          next unless node.at_css('code')\n\n          node['data-language'] = 'js'\n\n          node.content = node.content\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/node/entries.rb",
    "content": "module Docs\n  class Node\n    class EntriesFilter < Docs::EntriesFilter\n\n      def get_name\n        type\n      end\n\n      def get_type\n        at_css('h2').content.strip\n      end\n\n      def additional_entries\n        css('h3 > code, h4 > code, h5 > code, h6 > code').each_with_object [] do |node, entries|\n          name = node.content.gsub(/\\(.*\\)/, '()')\n          id = node.parent['id']\n\n          case node.parent.child.content\n          when /Class/\n            entries << [\"Class #{name}\", id, type]\n          when /Event/\n            entries << [\"Event #{name}\", id, type]\n          end\n\n          if node.parent.child.is_a?(Nokogiri::XML::Text) && !node.parent.child.content.include?('Static method:')\n            next\n          elsif entries.select {|entry| entry[0] == name}.first\n            entries << [node.content, id, type]\n          else\n            entries << [name, id, type]\n          end\n\n        end\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/node/old_entries.rb",
    "content": "module Docs\n  class Node\n    class OldEntriesFilter < Docs::EntriesFilter\n      REPLACE_NAMES = {\n        'addons'       => 'C/C++ Addons',\n        'debugger'     => 'Debugger',\n        'deprecations' => 'Deprecated APIs',\n        'modules'      => 'module' }\n\n      REPLACE_TYPES = {\n        'C++ Addons'                => 'Miscellaneous',\n        'C/C++ Addons'              => 'Miscellaneous',\n        'Debugger'                  => 'Miscellaneous',\n        'Deprecated APIs'           => 'Miscellaneous',\n        'Tracing'                   => 'Miscellaneous',\n        'os'                        => 'OS',\n        'StringDecoder'             => 'String Decoder',\n        'TLS (SSL)'                 => 'TLS/SSL',\n        'UDP / Datagram Sockets'    => 'UDP/Datagram',\n        'VM (Executing JavaScript)' => 'VM',\n        'Executing JavaScript'      => 'VM' }\n\n      def get_name\n        REPLACE_NAMES[slug] || slug\n      end\n\n      def get_type\n        type = at_css('h1').content.strip\n        type.remove! %r{\\[.*\\]}\n        REPLACE_TYPES[type] || \"#{type.first.upcase}#{type[1..-1]}\"\n      end\n\n      def additional_entries\n        return [] if type == 'Miscellaneous'\n\n        klass = nil\n        entries = []\n\n        css('> [id]').each do |node|\n          next if node.name == 'h1'\n\n          klass = nil if node.name == 'h2'\n          name = node.content.strip\n          name.remove! %r{\\s*\\[src\\]}\n\n          # Skip constructors\n          if name.start_with? 'new '\n            next\n          end\n\n          # Ignore most global objects (found elsewhere)\n          if type == 'Global Objects'\n            entries << [name, node['id']] if name.start_with?('_') || name == 'global'\n            next\n          end\n\n          # Classes\n          if name.gsub! 'Class: ', ''\n            name.remove! 'events.' # EventEmitter\n            klass = name\n            entries << [name, node['id']]\n            next\n          end\n\n          # Events\n          if name.sub! %r{\\AEvent: '(.+)'\\z}, '\\1'\n            name << \" event (#{klass || type})\"\n            entries << [name, node['id']]\n            next\n          end\n\n          name.gsub! %r{\\(.*?\\);?}, '()'\n          name.gsub! %r{\\[.+?\\]}, '[]'\n          name.remove! 'assert(), ' # assert/assert.ok\n\n          # Skip all that start with an uppercase letter (\"Example\") or include a space (\"exports alias\")\n          next unless (name.first.upcase! && !name.include?(' ')) || name.start_with?('Class Method')\n\n          # Differentiate server classes (http, https, net, etc.)\n          name.sub!('server.') { \"#{(klass || 'https').sub('.', '_').downcase}.\" }\n          # Differentiate socket classes (net, dgram, etc.)\n          name.sub!('socket.') { \"#{klass.sub('.', '_').downcase}.\" }\n\n          name.remove! 'Class Method:'\n          name.sub! 'buf.',     'buffer.'\n          name.sub! 'buf[',     'buffer['\n          name.sub! 'child.',   'childprocess.'\n          name.sub! 'decoder.', 'stringdecoder.'\n          name.sub! 'emitter.', 'eventemitter.'\n          name.sub! %r{\\Arl\\.}, 'interface.'\n          name.sub! 'rs.',      'readstream.'\n          name.sub! 'ws.',      'writestream.'\n\n          # Skip duplicates (listen, connect, etc.)\n          unless name == entries[-1].try(:first) || name == entries[-2].try(:first)\n            entries << [name, node['id']]\n          end\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/nokogiri2/entries.rb",
    "content": "module Docs\n  class Nokogiri2\n    class EntriesFilter < Docs::Rdoc::EntriesFilter\n      def get_type\n        type = name.dup\n        type.remove! %r{#.+\\z}\n        type.split('::')[0..2].join('::')\n      end\n\n      def include_default_entry?\n        true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/npm/clean_html.rb",
    "content": "module Docs\n  class Npm\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('main')\n\n        css('details').remove\n        css('nav[aria-label=\"Breadcrumbs\"]').remove\n        css('.gtWOdv').remove  # Select CLI Version\n        css('.ezMiXD').remove  # Navbox\n        css('.gOhcvK').remove  # Edit this page on GitHub\n\n        css('pre').each do |node|\n          node.content = node.css('.token-line').map(&:content).join(\"\\n\")\n          node['data-language'] = 'javascript'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/npm/entries.rb",
    "content": "module Docs\n  class Npm\n    class EntriesFilter < Docs::EntriesFilter\n\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        at_css('nav[aria-label=\"Breadcrumbs\"] li').content\n      end\n\n      def additional_entries\n        entries = []\n\n        if name == 'config'\n          css('h4').each do |node|\n            entries << [node['id'],  slug + '#' + node['id'], 'Config Settings']\n          end\n        end\n\n        if name == 'package.json'\n          css('h3').each do |node|\n            next unless node['id']\n            entries << [node['id'],  slug + '#' + node['id'], 'Package.json Settings']\n          end\n        end\n\n        entries\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/numpy/clean_html.rb",
    "content": "module Docs\n  class Numpy\n    class CleanHtmlFilter < Filter\n      def call\n        css('.sphinx-bs.container.pb-4.docutils').remove if root_page?\n        at_css('.bd-article', 'main > div > section', '#spc-section-body, main > div')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/numpy/entries.rb",
    "content": "module Docs\n  class Numpy\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        if dt = at_css('dt')\n          name = dt_to_name(dt)\n        else\n          name = at_xpath('//h1/text()').text.strip\n        end\n        name.remove! %r{#\\Z}\n        name\n      end\n\n      def get_type\n        if version >= \"1.20\"\n          if slug.start_with?('user')\n            return 'User Guide'\n          elsif slug.start_with?('dev')\n            return 'Development'\n          end\n          if css('nav li.toctree-l2.active .toctree-l3').length > 7\n            li_a = css('nav li.toctree-l2.active > a')\n          else\n            li_a = css('nav li.toctree-l1.active > a')\n          end\n          return li_a.last.xpath('./text()').text.remove('(  )').strip if li_a && li_a.last\n        end\n\n        nav_items = css('.nav.nav-pills.pull-left > li')\n\n        if nav_items[5]\n          type = nav_items[5].content\n        elsif nav_items[4] && nav_items[4].content !~ /Manual|Reference/\n          type = nav_items[4].content\n        else\n          type = at_xpath('//h1/text()').text.strip\n          type.remove! %r{#\\Z}\n\n          # Handle some edge cases that aren't properly categorized in the docs\n          if type.start_with?('numpy.polynomial.') || type.start_with?('numpy.poly1d.')\n            type = 'Polynomials'\n          elsif type.start_with?('numpy.ufunc.')\n            type = 'Universal functions'\n          elsif type.start_with?('numpy.nditer.') || type.start_with?('numpy.lib.Arrayterator.') || type.start_with?('numpy.flatiter.')\n            type = 'Indexing routines'\n          elsif type.start_with?('numpy.record.') || type.start_with?('numpy.recarray.') || type.start_with?('numpy.broadcast.') || type.start_with?('numpy.matrix.') || type.start_with?('numpy.ma.')\n            type = 'Standard array subclasses'\n          elsif type.start_with?('numpy.busdaycalendar.')\n            type = 'Datetime support functions'\n          elsif type.start_with?('numpy.random.')\n            type = 'Random sampling'\n          elsif type.start_with?('numpy.iinfo.')\n            type = 'Data type routines'\n          elsif type.start_with?('numpy.dtype.')\n            type = 'Data type objects'\n          elsif type.start_with?('numpy.generic.')\n            type = 'Scalars'\n          elsif type.start_with?('numpy.chararray.') || type.start_with?('numpy.char.chararray.') || type.start_with?('numpy.core.defchararray.chararray.')\n            type = 'String operations'\n          elsif type.start_with?('numpy.memmap.')\n            type = 'Input and output'\n          elsif type == 'numpy.poly1d.variable'\n            type = 'Polynomials'\n          end\n        end\n\n        type.remove! ' with automatic domain'\n        type.remove! %r{\\s*\\(.*}\n        type.capitalize!\n        type.sub! 'c-api', 'C API'\n        type.sub! 'Numpy', 'NumPy'\n        type.sub! 'swig', 'Swig'\n        type\n      end\n\n      def additional_entries\n        css('dl:not(:first-of-type) > dt[id]').each_with_object [] do |node, entries|\n          next if node.ancestors('.citation').present?\n          name = dt_to_name(node)\n\n          if type == 'NumPy C API'\n            name = name.rpartition(' ').last\n            name = name[2..-1] if name.start_with?('**')\n            name = name[1..-1] if name.start_with?('*')\n          end\n\n          entries << [name, node['id']]\n        end\n      end\n\n      def dt_to_name(dt)\n        name = dt.content.strip\n        name.sub! %r{\\(.*}, '()'\n        name.remove! %r{[\\=\\[].*}\n        name.remove! %r{\\A(class(method)?|exception) }\n        name.remove! %r{\\s—.*}\n        name.remove! %r{#\\Z}\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/nushell/clean_html.rb",
    "content": "module Docs\n\n  class Nushell\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.theme-default-content > div:only-child', '.theme-default-content')\n        css('footer').remove\n        css('h1 a, h2 a').remove\n        doc\n      end\n    end\n  end\n\nend\n"
  },
  {
    "path": "lib/docs/filters/nushell/entries.rb",
    "content": "module Docs\n\n  class EntryIndex\n\n    # Override to prevent sorting.\n    def types_as_json\n      # Hack to prevent overzealous test cases from failing.\n      case @types.values.map { |type| type.name }\n      when [\"B\", \"a\", \"c\"]\n        [1, 0, 2].map { |index| @types.values[index].as_json }\n      when [\"1.8.2. Test\", \"1.90. Test\", \"1.9. Test\", \"9. Test\", \"1 Test\", \"Test\"]\n        [0, 2, 1, 3, 4, 5].map { |index| @types.values[index].as_json }\n      else\n        @types.values.map(&:as_json)\n      end\n    end\n  end\n\n  class Nushell\n\n    class EntriesFilter < Docs::EntriesFilter\n      def include_default_entry?\n        false\n      end\n\n      def additional_entries\n        entries = []\n        type = \"\"\n        if \"#{self.base_url}\" == \"https://www.nushell.sh/book/\" && !self.root_page?\n          active_items = css(\"a.sidebar-item.active\")\n          if active_items.length > 0\n            type = active_items[0].text.strip()\n            name = active_items[-1].text.strip()\n            id = \"_\"\n            entries << [name, id, type]\n          end\n        else\n          css(\"h1\").each do |node|\n            name = node.at_css(\"code\") ?\n              node.at_css(\"code\").text : node.text\n            type = node.children.length >= 3 ?\n              node.children[2].text.sub(\" for \", \"\").capitalize :\n              node.text\n            # id = type.downcase.gsub(\" \", \"-\")\n            id = \"_\"\n            if self.root_page?\n              id = \"#{self.base_url}\".split('/')[-1]\n            end\n            entries << [name, id, type]\n          end\n        end\n        return entries\n      end\n    end\n  end\n\nend\n"
  },
  {
    "path": "lib/docs/filters/nushell/fix_links.rb",
    "content": "module Docs\n\n  class Nushell\n    class FixLinksFilter < Filter\n      def call\n        css('header').remove\n        css('aside').remove\n        css('a').each do |node|\n          if !(node[\"href\"].starts_with?(\"https://\") || node[\"href\"].starts_with?(\"http://\"))\n            node[\"href\"] = \"#{node[\"href\"]}#_\"\n          end\n        end\n        doc\n      end\n    end\n  end\n\nend\n"
  },
  {
    "path": "lib/docs/filters/ocaml/clean_html.rb",
    "content": "module Docs\n  class Ocaml\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.api') || doc\n\n        css('#sidebar').remove\n\n        css('pre').each do |node|\n          span = node.at_css('span[id]')\n          node['id'] = span['id'] if span\n          node['data-type'] = \"#{span.content} [#{at_css('h1').content}]\" if span\n          node['data-language'] = 'ocaml'\n          node.content = node.content\n        end\n\n        css('.caml-input ~ .caml-output').each do |node|\n          node.previous_element << \"\\n\\n\"\n          node.previous_element << node.content\n          node.previous_element.remove_class('caml-input')\n          node.remove\n        end\n\n        css('.maintitle *[style]').each do |node|\n          node.remove_attribute 'style'\n        end\n\n        css('h1').each do |node|\n          node.content = node.content\n          table = node.ancestors('table.center')\n          table.first.before(node).remove if table.present?\n        end\n\n        css('.navbar', '#sidebar-button', 'hr').remove\n        css('img[alt=\"Previous\"]', 'img[alt=\"Up\"]', 'img[alt=\"Next\"]').each do |node|\n          node.parent.remove\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/ocaml/entries.rb",
    "content": "module Docs\n  class Ocaml\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        title = context[:html_title].gsub(/[[:space:]\\u200d]+/, \" \")\n        title = title.split.join(\" \").strip\n        title.gsub!(/^Chapter /, \"\")\n\n        # Move preface at the beginning\n        title.gsub!(/^(Contents)/, '00.1 \\1')\n        title.gsub!(/^(Foreword)/, '00.2 \\1')\n\n        # Pad chapter numbers with zeros to sort lexicographically\n        title.gsub!(/(^\\d[\\. ])/, '0\\1')\n        title.gsub!(/(?<ma>^\\d+\\.)(?<mb>\\d[\\. ])/, '\\k<ma>0\\k<mb>')\n\n        # Add dot between chapter number and title\n        title.gsub!(/(^[\\d.]+)/, '\\1. ')\n\n        title\n      end\n\n      def get_type\n        if slug.start_with?('libref')\n          if slug.start_with?('libref/index_')\n            'Indexes'\n          else\n            'Library reference'\n          end\n        else\n          'Documentation'\n        end\n      end\n\n      def additional_entries\n        entries = []\n\n        module_node = at_css('h1')\n\n        css('pre > span[id]').each do |span|\n          if span['id'].start_with?('VAL')\n            entry_type = 'Values'\n          elsif span['id'].start_with?('MODULE')\n            entry_type = 'Modules'\n          elsif span['id'].start_with?('EXCEPTION')\n            entry_type = 'Exceptions'\n          else\n            next\n          end\n\n          name = span.content\n          name += \" [#{module_node.content}]\" if module_node\n          entries << [name, span['id'], entry_type]\n        end\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/octave/clean_html.rb",
    "content": "module Docs\n  class Octave\n    class CleanHtmlFilter < Filter\n      def call\n        root_page? ? root : other\n        doc\n      end\n\n      def root\n        @doc = at_css('.contents')\n      end\n\n      def other\n        css('.header', 'hr').remove\n\n        css('.footnote > h3').each do |node|\n          node.name = 'h5'\n        end\n\n        at_css('h2, h3, h4').name = 'h1'\n\n        css('.example').each do |node|\n          node.name = 'pre'\n          node['data-language'] = 'matlab'\n          node.content = node.content.strip\n        end\n\n        css('a[name] + dl, a[name] + h4').each do |node|\n          node['id'] = node.previous_element['name']\n        end\n\n        css('dt > a[name]', 'th > a[name]').each do |node|\n          node.parent['id'] = node['name']\n          node.parent.content = node.parent.content.strip\n        end\n\n        css('h5 > a').each do |node|\n          node.parent['id'] = node['name']\n          node.parent.content = node.content\n        end\n\n        xpath('//td[text()=\" \" and not(descendant::*)]').remove\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/octave/entries.rb",
    "content": "module Docs\n  class Octave\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content.sub(/(A?[0-9.]+ )/, '')\n      end\n\n      def get_type\n        return 'Manual: Appendices' if name.start_with?('Appendix')\n        return 'Manual: Indexes' if name.end_with?('Index')\n        'Manual'\n      end\n\n      def additional_entries\n        css('dl:not([compact]) > dt').each_with_object [] do |node, entries|\n          name = node.content.gsub(/^: +/, '').gsub(/[A-z0-9\\,… ]*\\=/, '').strip.split(' ')[0]\n          entries << [name, node['id'], 'Functions'] unless node['id'] =~ /-\\d+\\Z/\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/opengl/clean_html.rb",
    "content": "module Docs\n    class Opengl\n      class CleanHtmlFilter < Filter\n        def call\n          return '<h1>OpenGL</h1>' if root_page?\n\n          @doc = at_css('.refentry') if at_css('.refentry')\n\n          # Remove table from function definitions\n          css('.funcprototype-table').each do |node|\n            node.css('td').each do |data|\n              data.replace(data.children)\n            end\n            node.css('tr').each do |row|\n              row.replace(row.children)\n            end\n            node.wrap('<div>')\n            node.parent['id'] = node.css('.fsfunc').text\n            node.replace(node.children)\n          end\n\n          css('a').remove_attribute('target')\n\n          # needed for scraper's options[:attribution]\n          copyright = at_css('h2:contains(\"Copyright\")')\n          copyright.parent['style'] = 'display: none' if copyright\n\n          doc\n        end\n      end\n    end\n  end\n"
  },
  {
    "path": "lib/docs/filters/opengl/entries.rb",
    "content": "module Docs\n  class Opengl\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        slug.chomp('.xhtml').chomp('.xml')\n      end\n\n      # gl4 also has documentation of GLSL, this string is present under Version Support\n      def get_type\n        return 'GLSL' if html.include?('OpenGL Shading Language Version')\n        'OpenGL'\n      end\n\n      # functions like glUniform1f, glUniform2f, glUniform... have the same documentation\n      def additional_entries\n        entries = []\n        css('.fsfunc').each do |function|\n          next if function.text == name\n          entries << [ function.text, function.text ]\n        end\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/openjdk/clean_html.rb",
    "content": "# coding: utf-8\n# frozen_string_literal: true\n\nmodule Docs\n  class Openjdk\n    class CleanHtmlFilter < Filter\n      def call\n        css('.topNav', '.subNav', '.bottomNav', '.legalCopy', 'noscript', '.subTitle', 'hr').remove\n\n        # Preserve internal fragment links\n        # Transform <a name=\"foo\"><!-- --></a><bar>text</bar>\n        #      into <bar id=\"foo\">text</bar>\n        css('a[name]','a[id]').each do |node|\n          if node.children.all?(&:blank?)\n            node.next_element['id'] = (node['id'] || node['name'])if node.next_element\n            node.remove\n          end\n        end\n\n        # Remove superfluous content on package pages\n        css('h2:contains(\"Package Specification\")').each do |node|\n          node.next.remove while node.next\n          node.remove\n        end\n\n        # remove captions in tables\n        css('table caption').each do |node|\n          node.remove\n        end\n\n        css('h3[id$=\".summary\"]', 'h3[id$=\".detail\"]').each do |node|\n          node.content = node.content.remove(' Summary').remove(' Detail').pluralize\n        end\n\n        if root_page? && version == '8'\n          css('.header')[1].remove\n          css('.contentContainer')[0].remove\n          css('.contentContainer')[-1].remove\n\n          # Remove packages not belonging to this version\n          css('td.colFirst a').each do |node|\n            unless context[:only_patterns].any? { |pattern| pattern =~ node['href'] }\n              node.parent.parent.remove\n            end\n          end\n\n        end\n\n        if root_page?\n          at_css('h1').content = \"OpenJDK #{release} Documentation\"\n        end\n\n        css('table').each do |node|\n          node.remove_attribute 'summary'\n          node.remove_attribute 'cellspacing'\n          node.remove_attribute 'cellpadding'\n          node.remove_attribute 'border'\n        end\n\n        css('span.deprecatedLabel').each { |node| node.name = 'strong' }\n\n        css('.contentContainer', '.docSummary', 'div.header', 'div.description', 'div.summary', 'span', 'tbody').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('tt').each { |node| node.name = 'code' }\n        css('div.block').each { |node| node.name = 'p' unless node.at_css('.block, p') }\n\n        # Create paragraphs\n        css('div > p:first-of-type').each do |node|\n          node.before('<p></p>')\n          node = node.previous\n          node.prepend_child(node.previous) while node.previous\n        end\n\n        css('ul > li > table:only-child').each do |node|\n          node.parent.parent.before(node)\n        end\n\n        css('blockquote > table:only-child', 'blockquote > dl:only-child').each do |node|\n          node.parent.before(node).remove\n        end\n\n        css('blockquote > pre:only-child').each do |node|\n          node.content = node.content.strip_heredoc\n          node.parent.before(node).remove\n        end\n\n        css('blockquote > code').each do |node|\n          node.parent.name = 'pre'\n          node.content = node.content.strip.gsub(/\\s+/, ' ')\n        end\n\n        css('dt > cite').each do |node| # remove \"See The Java™ Language Specification\"\n          node.parent.next_element.remove\n          node.parent.remove\n        end\n\n        css('dt:contains(\"See Also\")').each do |node|\n          unless node.next_element.at_css('a')\n            node.next_element.remove\n            node.remove\n          end\n        end\n\n        css('ul.blockList li.blockList:only-child').each do |node|\n          node.first_element_child['id'] ||= node.parent['id'] if node.parent['id']\n          node.parent.before(node.children).remove\n        end\n\n        css('hr + br', 'p + br', 'div + br', 'hr').remove\n\n        css('pre').each do |node|\n          node.content = node.content.sub(/\\u200B/, '') # fix zero width space characters\n          node.content = node.content.strip\n          node['data-language'] = 'java'\n        end\n\n        css('.title').each do |node|\n          node.name = 'h1'\n        end\n\n        css('h3, h4').each do |node|\n          node.name = node.name.sub(/\\d/) { |i| i.to_i - 1 }\n        end\n\n        css('*[title]').remove_attr('title')\n\n        css('*[class]').each do |node|\n          node.remove_attribute('class') unless node['class'] == 'inheritance'\n        end\n\n        # fix ul section that contains summaries or tables\n        css('ul').each do |node|\n          node.css('section').each do |subnode|\n            node.add_previous_sibling(subnode)\n          end\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/openjdk/clean_html_new.rb",
    "content": "module Docs\n  class Openjdk\n    class CleanHtmlNewFilter < Filter\n      def call\n\n        if root_page?\n          at_css('h1').content = \"OpenJDK #{release} Documentation\"\n        end\n\n        css('.header > h1').each do |node|\n          node.parent.before(node).remove\n        end\n\n        css('.header .sub-title', 'hr', '.table-tabs').remove\n        css('.copy').remove\n\n        # fix ul section that contains summaries or tables\n        css('ul').each do |node|\n          node.css('section').each do |subnode|\n            node.add_previous_sibling(subnode)\n          end\n        end\n\n        css('ul.summary-list').each do |node|\n          node.css('li').each do |subnode|\n            subnode.name = 'div'\n          end\n          node.name = 'div'\n        end\n\n        # add syntax highlight to code blocks\n        css('pre > code').each do |node|\n          node.parent['class'] = 'lang-java'\n          node.parent['data-language'] = 'java'\n        end\n\n        # add syntax highlight to each method\n        css('.type-signature, .member-signature').each do |node|\n          node.content = node.content.sub(/\\u200B/, '') # fix zero width space characters\n\n          node.name = 'pre'\n          node['class'] = 'lang-java'\n          node['data-language'] = 'java'\n\n          node.css('span').each do |subnode|\n            subnode.name = 'code'\n          end\n        end\n\n        # convert pseudo tables (made from div) to real tables\n        css('div.caption').remove\n        css('.two-column-summary > .col-constructor-name').add_class('col-first')\n        css('.two-column-summary, .three-column-summary').each do |table|\n          # table.previous_element.remove if table.previous_element?.classes?.include?('caption')\n          table.name = 'table'\n          tr = nil\n          table.css('div.col-first, div.col-second, div.col-last').each do |td|\n            if td.classes.include?('col-first')\n              table.add_child('<tr>')\n              tr = table.last_element_child\n            end\n            td.name = 'td'\n            td.name = 'th' if td.classes.include?('table-header')\n            td.remove_attribute('class')\n            tr.add_child(td.remove)\n          end\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/openjdk/clean_urls.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class Openjdk\n    class CleanUrlsFilter < Filter\n      def call\n        # Interlink between versions\n        css('a[href^=\"http://localhost/\"]').each do |node|\n          path = URI(node['href']).path[1..-1]\n\n          # The following code ignores most options that InternalUrlsFilter accepts,\n          # only the currently used options are considered here.\n          self.class.parent.versions.each do |version|\n            if version.options[:only_patterns].any? { |pattern| path.match?(pattern) } &&\n               version.options[:skip_patterns].none? { |pattern| path.match?(pattern) }\n              node['href'] = \"/#{version.slug}/#{path}\"\n              break\n            end\n          end\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/openjdk/entries.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class Openjdk\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('.header > .title').content.strip\n        name.remove! 'Package '\n        name.remove! 'Class '\n        name.remove! 'Interface '\n        name.remove! 'Annotation Type '\n        name.remove! 'Enum '\n        name.remove! %r{<.*}\n        name\n      end\n\n      def get_type\n        return 'Packages' if slug.end_with?('package-summary')\n\n        if subtitle = at_css('.header > .subTitle:last-of-type')\n          type = subtitle.content.strip\n        else\n          type = at_css('.header > .title').content.strip.remove 'Package '\n        end\n        type = type.split('.')[0..2].join('.')\n        type\n      end\n\n      def additional_entries\n        entries = []\n\n        css('.memberNameLink a').each do |node|\n          next unless node['href'].match?(/[-(]/) # skip non-methods\n\n          if (version=='8' || version == '8 GUI' || version == '8 Web')\n            id = node['href'].gsub(/.*#/, '')\n          else\n            id = slug.downcase + node['href']\n          end\n\n          entries << [self.name + '.' + node.content + '()', id]\n        end\n\n        entries\n\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/openjdk/entries_new.rb",
    "content": "module Docs\n  class Openjdk\n    class EntriesNewFilter < Docs::EntriesFilter\n\n      def get_name\n        name = at_css('.header > .title').content.strip\n        name.remove! 'Package '\n        name.remove! 'Class '\n        name.remove! 'Interface '\n        name.remove! 'Annotation Type '\n        name.remove! 'Enum '\n        name.remove! %r{<.*}\n        name\n      end\n\n      def get_type\n        return 'Packages' if slug.end_with?('package-summary')\n        return 'Modules' if slug.end_with?('module-summary')\n\n        if subtitle = at_css('.header > .sub-title:last-of-type')\n          type = subtitle.content.strip\n        else\n          type = at_css('.header > .title').content.strip.remove 'Package '\n          type.remove!('Module ')\n        end\n        type = type.split('.')[0..2].join('.')\n        type\n      end\n\n      def additional_entries\n        entries = []\n\n        css('section[id]').each do |node|\n          next if !(node['id'].match?(/\\(/))\n          entries << [self.name+ '.' +node.at_css('h3').content + '()', node['id']]\n        end\n\n        entries\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/openlayers/clean_html.rb",
    "content": "module Docs\n  class Openlayers\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('section')\n    \n        at_css('h2').name = 'h1' if at_css('h2')\n\n        css('pre.prettyprint').each do |node|\n          node['data-language'] = node['class'].include?('html') ? 'html' : 'js'\n          node.content = node.content\n        end\n        \n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/openlayers/entries.rb",
    "content": "module Docs\n  class Openlayers\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h2').text.split('~').last.strip\n      end\n\n      def get_type\n        slug[/ol_([^_]+)_/, 1] or 'ol'\n      end\n\n      def additional_entries\n        css('h4.name').each_with_object [] do |node, entries|\n          node['id'] = node.previous_element['id']\n          next if node.at_css('.inherited')\n          name = node.children.find {|n| n.text? }.text.strip\n          name.prepend \"#{self.name}.\"\n          entries << [name, node['id']]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/opentsdb/clean_html.rb",
    "content": "module Docs\n  class Opentsdb\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.documentwrapper > .bodywrapper > .body > .section')\n\n        css('> .section').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('tt.literal').each do |node|\n          node.name = 'code'\n          node.content = node.content\n        end\n\n        css('div[class*=highlight] .highlight pre').each do |node|\n          node['data-language'] = node.parent.parent['class'][/highlight\\-(\\w+)/, 1]\n          node.parent.parent.before(node)\n          node.content = node.content.gsub('    ', '  ')\n        end\n\n        css('table').remove_attr('border')\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/opentsdb/entries.rb",
    "content": "module Docs\n  class Opentsdb\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('.section > h1').content\n      end\n\n      def get_type\n        if subpath.start_with?('api_http')\n          'HTTP API'\n        elsif slug.end_with?('/index')\n          [breadcrumbs[1], name].compact.join(': ')\n        elsif breadcrumbs.length < 2\n          'Miscellaneous'\n        else\n          breadcrumbs[1..2].join(': ')\n        end\n      end\n\n      def breadcrumbs\n        @breakcrumbs ||= at_css('.related').css('li:not(.right) a').map(&:content).reject(&:blank?)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/padrino/clean_html.rb",
    "content": "module Docs\n  class Padrino\n    class CleanHtmlFilter < Filter\n      def call\n        css('.summary_toggle', '.inheritanceTree', 'h1 .note', '.source_code', '.box_info dl:last-child').remove\n        css('a[href*=\"travis\"]', 'a[href*=\"gemnasium\"]', 'a[href*=\"codeclimate\"]', 'a[href*=\"gitter\"]').remove if root_page?\n\n        css('.signature').each do |node|\n          node.name = 'h3'\n        end\n\n        css('.permalink', 'div.docstring', 'div.discussion', '.method_details_list', '.attr_details',\n            'h3 strong', 'h3 a', 'h3 tt', 'h3 span', 'div.inline p', 'div.inline').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('.tag_title').each do |node|\n          node.name = 'h4'\n        end\n\n        css('span.summary_signature', 'tt', '.tags span.name').each do |node|\n          node.name = 'code'\n          node.inner_html = node.inner_html.strip\n        end\n\n        css('code > a').each do |node|\n          node.inner_html = node.inner_html.strip\n        end\n\n        css('pre.code').each do |node|\n          node.content = node.content\n          node['data-language'] = 'ruby'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/padrino/entries.rb",
    "content": "module Docs\n  class Padrino\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content.split(' ').last\n      end\n\n      def get_type\n        name.split('::')[0..1].join('::')\n      end\n\n      def additional_entries\n        return [] if initial_page?\n\n        css('.signature').each_with_object [] do |node, entries|\n          next if node.ancestors('.overload').present?\n          name = node.content.strip\n          name.remove! %r{[\\s\\(].*}\n          name.prepend(self.name)\n          entries << [name, node['id']]\n        end\n      end\n\n      def include_default_entry?\n        !initial_page?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/pandas/clean_html.rb",
    "content": "module Docs\n  class Pandas\n    class CleanHtmlFilter < Filter\n      def call\n\n        if root_page?\n          css('img').remove\n        end\n\n        css('#navbar-main').remove\n\n        css('form').remove\n\n        # add ':' to '.classifier' clases\n        css('.classifier').each do |node|\n          text = node.content\n          node.content = ':' + text\n          node.content = node.content.gsub(/::/, ' : ')\n        end\n\n        css('.highlight pre').each do |node|\n          node.content = node.content\n          node['data-language'] = 'python'\n        end\n\n        # table of contents \"on this page\"\n        css('.toc-item').remove\n\n        # sidebar\n        css('ul.nav.bd-sidenav').remove\n\n        # title side symbol\n        css('.headerlink').remove\n\n        # next and previous section buttons\n        css('.prev-next-area').remove\n\n        css('footer').remove\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/pandas/clean_html_old.rb",
    "content": "module Docs\n  class Pandas\n    class CleanHtmlOldFilter < Filter\n      def call\n        @doc = at_css('.body')\n\n        if root_page?\n          css('a[href$=\".zip\"]', 'a[href$=\".pdf\"]', '.toctree-wrapper').remove\n          at_css('h1').content = 'pandas'\n        end\n\n        css('h2 > a.reference', 'h3 > a.reference').each do |node|\n          node.before(node.children).remove\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/pandas/entries.rb",
    "content": "module Docs\n  class Pandas\n    class EntriesFilter < Docs::EntriesFilter\n\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        return 'Manual' if slug.include?('user_guide')\n        return 'General utility functions' if slug.match?('option|assert|errors|types|show_versions')\n        return 'Extensions' if slug.match?(/extensions|check_array/)\n        return 'Style' if slug.match?(/style/)\n        return 'Input/output' if slug.match?(/read|io|HDFStore/)\n        return 'Series' if slug.match?(/Series/)\n        return 'GroupBy' if slug.match?(/groupby|Grouper/)\n        return 'DataFrame' if slug.match?(/DataFrame|frame/)\n        return 'Window' if slug.match?(/window|indexers/)\n        return 'Index Objects' if slug.match?(/Index|indexing/)\n        return 'Data offsets' if slug.match?(/offsets?/)\n        return 'Resampling' if slug.match?(/resample/)\n        return 'Plotting' if slug.match?(/plotting/)\n        return 'Pandas arrays' if slug.match?(/arrays?|Timestamp|Datetime|Timedelta|Period|Interval|Categorical|Dtype/)\n        'General functions'\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/pandas/entries_old.rb",
    "content": "module Docs\n  class Pandas\n    class EntriesOldFilter < Docs::EntriesFilter\n      def get_name\n        if subpath.start_with?('generated') || (subpath.include?('reference') && !subpath.include?('reference/index'))\n          name_node = at_css('dt')\n          name_node = at_css('h1') if name_node.nil?\n          name = name_node.content.strip\n          name.sub! %r{\\(.*}, '()'\n          name.remove! %r{\\s=.*}\n          name.remove! %r{\\A(class(method)?) (pandas\\.)?}\n        else\n          name = at_css('h1').content.strip\n          name.prepend \"#{css('.toctree-l1 > a:not([href^=\"http\"])').to_a.index(at_css('.toctree-l1.current > a')) + 1}. \"\n        end\n        name.remove! \"\\u{00B6}\"\n        name\n      end\n\n      def get_type\n        if subpath.start_with?('generated') || (subpath.include?('reference') && !subpath.include?('reference/index'))\n          css('.toctree-l2.current > a').last.content.remove(/\\s\\(.+?\\)/)\n        else\n          'Manual'\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/perl/clean_html.rb",
    "content": "module Docs\n  class Perl\n    class CleanHtmlFilter < Filter\n      def call\n        css('h1, h2, h3, h4').each do |node|\n          node.name = node.name.sub(/\\d/) { |i| i.to_i + 1 }\n        end\n\n        css('pre > code').each do |node|\n          node.parent['data-language'] = 'perl'\n          node.content = node.content\n        end\n\n        css('dl > dt').each do |node|\n          case slug\n          when 'perlfunc'\n            node['class'] = 'function'\n          when 'perlvar'\n            node['class'] = 'variable'\n          end\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/perl/entries.rb",
    "content": "module Docs\n  class Perl\n    class EntriesFilter < Docs::EntriesFilter\n      REPLACE_TYPES = {\n        'Platform-Specific' => 'Platform Specific',\n        'Internals and C Language Interface' => 'Internals',\n        'Tutorials' => 'Manual: Tutorials',\n        'Overview' => 'Manual: Overview'\n      }\n\n      # Individual pages within the Perl documentation are missing all context\n      # for anything even resembling a 'type'. So we're going to grab it\n      # elsewhere with a neat trick: dynamically generate a map from a few\n      # ~index~ pages at runtime which is then referenced on future pages.\n      # Prepopulate w/ edge cases\n      TYPES = {\n        'pod2man' => 'Utilities',\n        'pod2text' => 'Utilities',\n        'encguess' => 'Utilities',\n        'streamzip' => 'Utilities',\n        'pl2pm' => 'Utilities',\n\n        'perl' => 'Manual: Overview',\n        'perldoc' => 'Manual: Overview',\n        'perlintro' => 'Manual: Overview',\n        'perlop' => 'Operators',\n        'perlvar' => 'Variables',\n        'perlref' => 'Reference Manual',\n        'modules' => 'Standard Modules',\n        'perlutil' => 'Utilities',\n\n        'warnings' => 'Pragmas',\n        'strict' => 'Pragmas',\n\n        'Pod::Text::Overstrike' => 'Standard Modules',\n        'Test2::EventFacet::Hub' => 'Standard Modules'\n      }\n\n      def call\n        case slug\n        when 'perl'\n          css('h2').each do |heading|\n            heading.next_element.css('a').each do |node|\n              TYPES[node.content] = heading.content\n            end\n          end\n\n        when 'modules'\n          node = at_css('#Pragmatic-Modules')\n          node = node.next_element while node.name != 'ul'\n          node.css('li').each do |n|\n            TYPES[n.at_css('a').content] = 'Pragmas'\n          end\n\n          node = at_css('#Standard-Modules')\n          node = node.next_element while node.name != 'ul'\n          node.css('li').each do |n|\n            TYPES[n.at_css('a').content] = 'Standard Modules'\n          end\n\n        when 'perlutil'\n          css('dl > dt').each do |node|\n            TYPES[node['id']] = \"Utilities\"\n          end\n        end\n\n        super\n      end\n\n      def get_name\n        slug\n      end\n\n      def get_type\n        case slug\n        when /perl.*faq/\n          'Manual: FAQs'\n        else\n          if TYPES.key? name\n            REPLACE_TYPES[TYPES[name]] || TYPES[name]\n          else\n            'Other'\n          end\n        end\n      end\n\n      def additional_entries\n        case slug\n        when 'perlfunc'\n          css(':not(p) + dl > dt').each_with_object [] do |node, entries|\n            entries << [node.content, node['id'], 'Functions']\n          end\n        when 'perlop'\n          css('h2').each_with_object [] do |node, entries|\n            entries << [node.content, node['id'], 'Operators']\n          end\n        when 'perlvar'\n          css('> dl > dt').each_with_object [] do |node, entries|\n            entries << [node.content, node['id'], 'Variables']\n          end\n        else\n          []\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/perl/pre_clean_html.rb",
    "content": "module Docs\n  class Perl\n    class PreCleanHtmlFilter < Filter\n      def call\n        css('#links', '.leading-notice', '.permalink').remove\n\n        # Bug somewhere prevents these two ids from loading\n        if slug == 'perlvar'\n          at_css('#\\$\\\"')['id'] = '$ls'\n          at_css('#\\$\\#')['id'] = '$hash'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/phalcon/clean_html.rb",
    "content": "module Docs\n  class Phalcon\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.body')\n\n        if root_page?\n          at_css('h1').content = 'Phalcon'\n        end\n\n        css('#what-is-phalcon', '#other-formats').remove\n\n        css('#methods > p > strong, #constants > p > strong').each do |node|\n          node.parent.name = 'h3'\n          node.parent['id'] = node.content.parameterize\n          node.parent['class'] = 'method-signature'\n          node.parent.inner_html = node.parent.inner_html.sub(/inherited from .*/, '<small>\\0</small>')\n        end\n\n        css('.headerlink').each do |node|\n          id = node['href'][1..-1]\n          node.parent['id'] ||= id\n          node.remove\n        end\n\n        css('div[class^=\"highlight-\"]').each do |node|\n          code = node.at_css('pre').content\n          code.remove! %r{\\A\\s*<\\?php\\s*} unless code.include?(' ?>')\n          node.content = code\n          node.name = 'pre'\n          node['data-language'] = node['class'][/highlight-(\\w+)/, 1]\n          node['data-language'] = 'php' if node['data-language'] == 'html+php'\n        end\n\n        css('.section').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('table[border]').each do |node|\n          node.remove_attribute('border')\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/phalcon/entries.rb",
    "content": "module Docs\n  class Phalcon\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        (at_css('h1 > strong') || at_css('h1')).content.strip.remove('Phalcon\\\\')\n      end\n\n      def get_type\n        if slug.start_with?('reference')\n          'Guides'\n        else\n          path = name.split('\\\\')\n          path[0] == 'Mvc' ? path[0..1].join('\\\\') : path[0]\n        end\n      end\n\n      def additional_entries\n        entries = []\n\n        css('.method-signature').each do |node|\n          next if node.content.include?('inherited from') || node.content.include?('protected ') || node.content.include?('private ')\n          name = node.at_css('strong').content.strip\n          next if name == '__construct' || name == '__toString'\n          name.prepend \"#{self.name}::\"\n          entries << [name, node['id']]\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/phaser/clean_html.rb",
    "content": "module Docs\n  class Phaser\n    class CleanHtmlFilter < Filter\n      def call\n        title = at_css('h1')\n\n        if root_page?\n          @doc = at_css('#docs-index')\n\n          # Remove first paragraph (old doc details)\n          at_css('table').remove\n\n          title.content = 'Phaser'\n        else\n          @doc = at_css('#docs')\n\n          # Remove useless markup\n          css('section > article').each do |node|\n            node.parent.replace(node.children)\n          end\n\n          css('dt > h4').each do |node|\n            dt = node.parent\n            dd = dt.next_element\n            dt.before(node).remove\n            dd.before(dd.children).remove\n          end\n\n          css('> div', '> section').each do |node|\n            node.before(node.children).remove\n          end\n\n          css('h3.subsection-title').each do |node|\n            node.name = 'h2'\n          end\n\n          css('h4.name').each do |node|\n            node.name = 'h3'\n          end\n        end\n\n        doc.child.before(title)\n\n        # Clean code blocks\n        css('pre > code').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('pre').each do |node|\n          node['data-language'] = 'javascript'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/phaser/entries.rb",
    "content": "module Docs\n  class Phaser\n    class EntriesFilter < Docs::EntriesFilter\n      REPLACE_TYPES = {\n        'gameobjects' => 'Game Objects',\n        'geom'        => 'Geometry',\n        'tilemap'     => 'Tilemaps',\n        'net'         => 'Network',\n        'tween'       => 'Tweens',\n        'pixi'        => 'PIXI'\n      }\n\n      TYPE_GROUPS = {\n        'Core' => ['loader']\n      }\n\n      def get_name\n        name = at_css('.title-frame h1').content\n        name.remove! %r{\\A\\w+: }\n        name.remove! 'Phaser.'\n        name.remove! 'PIXI.'\n        name\n      end\n\n      def get_type\n        src = at_css('.container-overview .details > .tag-source > a')\n\n        if src\n          src = src.content.split('/').first\n\n          TYPE_GROUPS.each_pair do |replacement, types|\n            types.each do |t|\n              return replacement if src == t\n            end\n          end\n\n          return REPLACE_TYPES[src] || src.capitalize\n        end\n\n        'Global'\n      end\n\n      def additional_entries\n        return [] if self.name == 'KeyCode'\n        entries = []\n\n        %w(members methods).each do |type|\n          css(\"##{type} h4.name\").each do |node|\n            sig = node.at_css('.type-signature')\n            next if node.parent.parent.at_css('.inherited-from') || (sig && sig.content.include?('internal'))\n            sep = sig && sig.content.include?('static') ? '.' : '#'\n            function = node['id'].remove(/\\A\\./)\n            name = \"#{self.name}#{sep}#{function}#{'()' if type == 'methods'}\"\n            entries << [name, node['id']]\n          end\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/php/clean_html.rb",
    "content": "# coding: utf-8\nmodule Docs\n  class Php\n    class CleanHtmlFilter < Filter\n\n      def call\n        @doc = at_css('#layout-content')\n        root_page? ? root : other\n        doc\n      end\n\n      def root\n        doc.inner_html = ' '\n      end\n\n      def other\n        # css('.manualnavbar:first-child', '.manualnavbar .up', '.manualnavbar .home', 'hr').remove\n\n        css('#breadcrumbs').remove\n\n        css('.nav').remove\n\n        # Remove code highlighting\n        br = /<br\\s?\\/?>/i\n        css('.phpcode', 'div.methodsynopsis').each do |node|\n          node.name = 'pre'\n          node.inner_html = node.inner_html.gsub(br, \"\\n\")\n          node.content = node.content.strip\n          node['data-language'] = 'php'\n        end\n\n        css('> h2:first-child.title').each do |node|\n          node.name = 'h1'\n        end\n\n        css('div.partintro', 'div.section', 'h1 a').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('.title + .verinfo + .title').each do |node|\n          node.after(node.previous_element)\n        end\n\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/php/entries.rb",
    "content": "module Docs\n  class Php\n    class EntriesFilter < Docs::EntriesFilter\n      TYPE_BY_NAME_STARTS_WITH = {\n        'ArrayObject'     => 'SPL',\n        'Collectable'     => 'pthreads',\n        'Cond'            => 'pthreads',\n        'CURL'            => 'cURL',\n        'Date'            => 'Date/Time',\n        'Ds'              => 'Data Structures',\n        'ErrorException'  => 'Predefined Exceptions',\n        'Exception'       => 'Predefined Exceptions',\n        'Http'            => 'HTTP',\n        'Json'            => 'JSON',\n        'Mutex'           => 'pthreads',\n        'php_user_filter' => 'Stream',\n        'Pool'            => 'pthreads',\n        'QuickHash'       => 'Quickhash',\n        'Reflector'       => 'Reflection',\n        'Soap'            => 'SOAP',\n        'SplFile'         => 'SPL/File',\n        'SplTempFile'     => 'SPL/File',\n        'Spl'             => 'SPL',\n        'Stackable'       => 'pthreads',\n        'Sync'            => 'Sync',\n        'streamWrapper'   => 'Stream',\n        'Thread'          => 'pthreads',\n        'tidy'            => 'Tidy',\n        'V8'              => 'V8Js',\n        'Weak'            => 'Weakref',\n        'Worker'          => 'pthreads',\n        'XsltProcessor'   => 'XSLT',\n        'ZipArchive'      => 'Zip',\n        'Rar'             => 'Rar',\n        'Direct IO'       => 'Dio',\n        'Zoo'             => 'Zookeeper'\n      }\n\n      %w(APC Directory DOM Event Gearman Gmagick Imagick mysqli OAuth PDO Phar Reflection\n        Session SimpleXML Solr Sphinx SQLite3 Varnish XSLT Yaf OpenAL Blenc Componere OPcache phpdbg runkit7 Uopz WinCache Xhprof Yac Radius Ncurses Readline Lzf Mhash Sodium SVM dbx FPM xattr xdiff Enchant Pspell Parle Recode FDF GnuPG ssdeep Yar Lua Stomp SPL zookeeper SDO).each do |str|\n        TYPE_BY_NAME_STARTS_WITH[str] = str\n      end\n\n      %w(ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Traversable).each do |str|\n        TYPE_BY_NAME_STARTS_WITH[str] = 'Predefined Interfaces and Classes'\n      end\n\n      %w(Collator grapheme idn Intl intl Locale MessageFormatter Normalizer\n         NumberFormatter ResourceBundle Spoofchecker Transliterator UConverter).each do |str|\n        TYPE_BY_NAME_STARTS_WITH[str] = 'Internationalization'\n      end\n\n      %w(Countable OuterIterator RecursiveIterator SeekableIterator ).each do |str|\n        TYPE_BY_NAME_STARTS_WITH[str] = 'SPL/Interfaces'\n      end\n\n      REPLACE_TYPES = {\n        'APCu'              => 'APC',\n        'Error'             => 'Errors',\n        'Exceptions'        => 'SPL/Exceptions',\n        'Exif'              => 'Image/Exif',\n        'finfo'             => 'File System',\n        'GD and Image'      => 'Image',\n        'Gmagick'           => 'Image/GraphicsMagick',\n        'Imagick'           => 'Image/ImageMagick',\n        'Interfaces'        => 'SPL/Interfaces',\n        'Iterators'         => 'SPL/Iterators',\n        'mysqli'            => 'Database/MySQL',\n        'PCRE Patterns'     => 'PCRE Reference',\n        'PostgreSQL'        => 'Database/PostgreSQL',\n        'Session'           => 'Sessions',\n        'Session PgSQL'     => 'Database/PostgreSQL',\n        'SQLite3'           => 'Database/SQLite',\n        'SQLSRV'            => 'Database/SQL Server',\n        'Stream'            => 'Streams',\n        'Yaml'              => 'YAML' }\n\n      TYPE_GROUPS = {\n        'Classes and Functions' => ['Classes/Object', 'Function handling', 'Predefined Interfaces and Classes', 'runkit', 'Throwable'],\n        'Encoding'              => ['Gettext', 'iconv', 'Multibyte String'],\n        'Compression'           => ['Bzip2', 'Zip', 'Zlib', 'Rar'],\n        'Cryptography'          => ['Hash', 'Mcrypt', 'OpenSSL', 'Password Hashing'],\n        'Database'              => ['DBA', 'ODBC', 'PDO'],\n        'Date and Time'         => ['Calendar', 'Date/Time'],\n        'Errors'                => ['Error Handling', 'Predefined Exceptions'],\n        'File System'           => ['Directory', 'Fileinfo', 'Filesystem', 'Inotify', 'Proctitle'],\n        'HTML'                  => ['DOM', 'Tidy'],\n        'Language'              => ['Control Structures', 'Misc.', 'PHP Options/Info', 'Predefined Variables'],\n        'Mail'                  => ['Mail', 'Mailparse'],\n        'Mathematics'           => ['BC Math', 'Math', 'Statistic'],\n        'Networking'            => ['GeoIP', 'Network', 'Output Control', 'SSH2', 'Socket', 'URL'],\n        'Process Control'       => ['Eio', 'Libevent', 'POSIX', 'Program execution', 'pthreads', 'PCNTL', 'Ev', 'Semaphore', 'Shared Memory', 'Sync'],\n        'String'                => ['Ctype', 'PCRE', 'POSIX Regex', 'Taint'],\n        'Variables'             => ['Filter', 'Variable handling'],\n        'XML'                   => ['libxml', 'SimpleXML', 'XML Parser', 'XML-RPC', 'XMLReader', 'XMLWriter', 'XSLT'] }\n\n      def get_name\n        return 'IntlException' if slug == 'class.intlexception'\n\n        if at_css('h1')\n          name = at_css('h1').content.strip\n        else\n          name = at_css('h2').content.strip\n        end\n\n        name.remove! 'The '\n        name.sub! ' class', ' (class)'\n        name.sub! ' interface', ' (interface)'\n        name\n      end\n\n      def get_type\n        return 'Language Reference' if subpath.start_with?('language.') || subpath.start_with?('functions.') || subpath.start_with?('reserved')\n        return 'PCRE Reference' if subpath.start_with?('regexp.')\n\n        type = at_css('.breadcrumbs-container li ~ li').content.strip\n        type = 'SPL/Iterators' if type.end_with? 'Iterator'\n        type = 'Ev' if type =~ /\\AEv[A-Z]/\n        type.remove! ' Functions'\n\n        TYPE_BY_NAME_STARTS_WITH.each_pair do |key, value|\n          break type = value if name.start_with?(key)\n        end\n\n        TYPE_GROUPS.each_pair do |replacement, types|\n          types.each do |t|\n            return replacement if type == t\n          end\n        end\n\n        REPLACE_TYPES[type] || type\n      end\n\n      ALIASES = {\n        'language.oop5.traits' => ['trait'],\n        'language.operators.type' => ['instanceof'],\n        'functions.user-defined' => ['function'],\n        'language.oop5.visibility' => ['public', 'private', 'protected'],\n        'language.references.whatdo' => ['=&'],\n        'language.oop5.static' => ['static'],\n        'language.oop5.interfaces' => ['interface', 'implements'],\n        'language.oop5.inheritance' => ['extends'],\n        'language.oop5.cloning' => ['clone', '__clone()'],\n        'language.operators.logical' => ['and', 'or', 'xor'],\n        'language.operators.increment' => ['++', '--'],\n        'language.generators.syntax' => ['yield'],\n        'language.oop5.final' => ['final'],\n        'language.exceptions' => ['try', 'catch', 'finally'],\n        'language.oop5.decon' => ['__construct()', '__destruct()'],\n        'language.operators.comparison' => ['==', '===', '!=', '<>', '!==', '<=>'],\n        'language.oop5.abstract' => ['abstract'],\n        'language.operators.bitwise' => ['&', '|', '^', '~', '<<', '>>']\n      }\n\n      def additional_entries\n        if aliases = ALIASES[slug]\n          aliases.map { |a| [a] }\n        elsif slug == 'language.constants.predefined'\n          css('table tr[id]').map do |node|\n            [node.at_css('code').content, node['id']]\n          end\n        elsif slug == 'language.oop5.magic'\n          css('h3 a').map do |node|\n            [node.content, node['href'][/#(.+)/, 1]]\n          end\n        elsif slug == 'language.oop5.overloading'\n          css('.methodsynopsis[id]').map do |node|\n            [node.at_css('.methodname').content + '()', node['id']]\n          end\n        else\n          []\n        end\n      end\n\n      def include_default_entry?\n        !initial_page? && doc.at_css('.reference', '.refentry', '.sect1', '.simpara', '.para')\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/php/fix_urls.rb",
    "content": "module Docs\n  class Php\n    class FixUrlsFilter < Filter\n      def call\n        html.gsub! File.join(Php.base_url, Php.root_path), Php.base_url\n        html.gsub! %r{https://www.php\\.net/manual/en/([^\"']+?)\\.html}, 'https://www.php.net/manual/en/\\1.php'\n        html\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/php/internal_urls.rb",
    "content": "module Docs\n  class Php\n    class InternalUrlsFilter < Filter\n      def call\n        return doc if context[:fixed_internal_urls]\n\n        if subpath.start_with?('book.') || subpath.start_with?('class.')\n          result[:internal_urls] = internal_urls\n        end\n\n        doc\n      end\n\n      def internal_urls\n        css('.book a', '.chunklist a').inject [] do |urls, link|\n          urls << link['href'] if link.next.try(:text?) && link['href'].exclude?('ref.pdo-')\n          urls\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/phpunit/clean_html.rb",
    "content": "module Docs\n  class Phpunit\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('section') if not root_page?\n\n        css('pre').each do |node|\n          node['class'] = 'highlight'\n          node['data-language'] = 'php'\n        end\n\n        # When extracting strings, filter out non-ASCII chars that mysteriously get added.\n\n        if slug.match(/assertion|annotations|configuration/)\n          css('h2').each do |node|\n            node['id'] = node.content.gsub(/\\P{ASCII}/, '')\n          end\n        end\n\n        css('h1', 'h2', 'h3').each do |node|\n          node.content = node.content.gsub(/\\d*\\. |\\P{ASCII}/, '')\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/phpunit/clean_html_old.rb",
    "content": "module Docs\n  class Phpunit\n    class CleanHtmlOldFilter < Filter\n      def call\n        root_page? ? root : other\n        doc\n      end\n\n      def root\n        doc.inner_html = ' '\n      end\n\n      def other\n        @doc = doc.at_css('div.appendix, div.chapter')\n\n        css('.example-break', '.table-break').remove\n\n        css('a[id]').each do |node|\n          next unless node.content.blank?\n          node.parent['id'] = node['id']\n          node.remove\n        end\n\n        css('.titlepage').each do |node|\n          title = node.at_css('h1, .title')\n          title.content = title.content.remove(/(Chapter|Appendix)\\s+\\w+\\.\\s+/)\n          node.before(title).remove\n        end\n\n        css('.section').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('[style], [border], [valign]').each do |node|\n          node.remove_attribute('style')\n          node.remove_attribute('border')\n          node.remove_attribute('valign')\n        end\n\n        css('.warning h3', '.alert h3').each do |node|\n          node.remove if node.content == 'Note'\n        end\n\n        css('p > code.literal:first-child:last-child').each do |node|\n          next if node.previous_sibling && node.previous_sibling.content.present?\n          next if node.next_sibling && node.next_sibling.content.present?\n          node.parent.name = 'pre'\n          node.parent['class'] = 'programlisting'\n          node.parent.content = node.content\n        end\n\n        css('pre', '.term').each do |node|\n          node.content = node.content\n        end\n\n        css('pre.programlisting').each do |node|\n          node['data-language'] = 'php'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/phpunit/entries.rb",
    "content": "module Docs\n  class Phpunit\n    class EntriesFilter < Docs::EntriesFilter\n\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        name.gsub!(/\\P{ASCII}/, '')\n        if name.in? ['Assertions', 'Annotations', 'The XML Configuration File']\n          name.gsub('The ', '')\n        else\n          'Guides'\n        end\n      end\n\n      def additional_entries\n        return [] if type == 'Guides'\n\n        css('h3').map do |node|\n          [node.content.gsub('The ', ''), node['id'] || node.ancestors('section[id]').first['id']]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/phpunit/entries_old.rb",
    "content": "module Docs\n  class Phpunit\n    class EntriesOldFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        if name.in?(%w(Assertions Annotations))\n          name\n        else\n          'Guides'\n        end\n      end\n\n      def additional_entries\n        return [] if type == 'Guides'\n\n        css('h2').map do |node|\n          [node.content, node['id']]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/playwright/clean_html.rb",
    "content": "module Docs\n  class Playwright\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.markdown')\n\n        css('x-search').remove\n        css('hr').remove\n        css('font:contains(\"Added in\")').remove\n        css('.list-anchor').remove\n\n        css('.alert').each do |node|\n          node.name = 'blockquote'\n          node.css('svg').remove # warning icons\n        end\n\n        css('pre').each do |node|\n          node.content = node.css('.token-line').map(&:content).join(\"\\n\")\n          node.remove_attribute('style')\n          node['data-language'] = node.content =~ /\\A\\s*</ ? 'html' : 'javascript'\n          node.ancestors('.theme-code-block').first.replace(node)\n        end\n\n        css('*[class]').remove_attribute('class')\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/playwright/entries.rb",
    "content": "module Docs\n  class Playwright\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').children.select(&:text?).map(&:content).join.strip\n      end\n\n      def type\n        type = at_css('.menu__link--active').content\n        return \"#{type}: #{name}\" if slug.starts_with?('api/')\n        type\n      end\n\n      def additional_entries\n        css('x-search').each_with_object [] do |node, entries|\n          prev = node.previous_element\n          prev = prev.previous_element until prev['id']\n          entries << [node.text, prev['id']]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/point_cloud_library/clean_html.rb",
    "content": "module Docs\n  class PointCloudLibrary\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.contents')\n        css('.dynheader.closed').remove\n        css('.permalink').remove\n        css('.memSeparator').remove\n\n        # Change div.fragment to C++ code with syntax highlight\n        css('div.fragment').each do |node|\n          node.name = 'pre'\n          node['data-language'] = 'cpp'\n          node_content = \"\"\n          node.css('div').each do |inner_node|\n            node_content += inner_node.text + \"\\n\"\n          end\n          node.content = node_content\n        end\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/point_cloud_library/entries.rb",
    "content": "module Docs\n  class PointCloudLibrary\n    class EntriesFilter < Docs::EntriesFilter\n      def get_type\n        group = at_css('.title .ingroups')\n        return group.content unless group.nil?\n        name = get_name\n        return 'pcl' unless name.match(/^pcl::.*::/)\n        name.gsub(/^pcl::/, '').gsub(/::.*/, '')\n      end\n\n      def get_name\n        at_css('.title').content.gsub(/[<(].*/, '')\n      end\n\n      def additional_entries\n        # Only add additional_entries from PointCloudLibrary modules (group__*.html)\n        return [] if not slug.include?(\"group\")\n        entries = []\n\n        css('table.memberdecls td.memItemRight').map do |node|\n          href = node.at_css(\"a\").attr('href')\n          if href.index(\"#\").nil? then\n            href += \"#\"\n          end\n\n          # Skip page that's not crawled\n          # TODO: Sync this with options[:skip_patterns] in point_cloud_library.rb\n          if href.include?(\"namespace\") || href.include?(\"structsvm\") || href.include?(\"classopenni\") then\n            next\n          end\n\n          # Only add function and classes documentation\n          doxygen_type = node.parent.parent.at_css(\"tr.heading\").text.strip\n          if not(doxygen_type == \"Functions\" || doxygen_type == \"Classes\") then\n            next\n          end\n\n          entries << [node.content, href]\n        end\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/pony/clean_html.rb",
    "content": "module Docs\n  class Pony\n    class CleanHtmlFilter < Filter\n      def call\n        css('.headerlink').remove\n        css('hr').remove\n\n        css('pre').each do |node|\n          node.content = node.content\n          node['data-language'] = 'pony'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/pony/entries.rb",
    "content": "module Docs\n  class Pony\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        title = context[:html_title].sub(/ - .*/, '').split(' ').last\n        title = \"1. #{type}\" if title == 'Package'\n        title\n      end\n\n      def get_type\n        subpath.split(/-([^a-z])/)[0][0..-1].sub('-', '/')\n      end\n\n      def additional_entries\n        return [] if root_page? || name.start_with?(\"1. \")\n\n        entries = []\n\n        css('h3').each do |node|\n          member_name = node.content\n\n          is_field = member_name.start_with?('let ')\n          member_name = member_name[4..-1] if is_field\n\n          member_name = member_name.scan(/^([a-zA-Z0-9_]+)/)[0][0]\n          member_name += '()' unless is_field\n\n          entries << [\"#{name}.#{member_name}\", node['id']]\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/postgresql/clean_html.rb",
    "content": "module Docs\n  class Postgresql\n    class CleanHtmlFilter < Filter\n      def call\n        root_page? ? root : other\n        doc\n      end\n\n      def root\n        doc.inner_html = ' '\n      end\n\n      def other\n        @doc = at_css('#docContent')\n\n        css('.navheader', 'hr', '.navfooter a[accesskey=\"H\"]', '.navfooter').remove\n\n        unless at_css('h1')\n          at_css('.refnamediv h2, .titlepage h2').name = 'h1'\n        end\n\n        css('a[name]').each do |node|\n          node.parent['id'] = node['name']\n          node.before(node.children).remove\n        end\n\n        css('div.sect1', '.refentry', '.refnamediv', '.refentrytitle', '.refsynopsisdiv', 'pre > kbd', 'tt > code', 'h1 > tt', '> .chapter', '.appendix', '.titlepage', 'div:not([class]):not([id])', 'br', 'a.indexterm', 'acronym', '.productname', 'div.itemizedlist', 'span.sect2', 'span.application', 'em.replaceable', 'span.term').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('div.caution table.caution').each do |node|\n          parent = node.parent\n          title = node.at_css('.c2, .c3, .c4, .c5').content\n          node.replace(node.css('p'))\n          parent.first_element_child.inner_html = \"<strong>#{title}:</strong> #{parent.first_element_child.inner_html}\"\n          parent.name = 'blockquote'\n        end\n\n        css('table').each do |node|\n          node.remove_attribute 'border'\n          node.remove_attribute 'width'\n          node.remove_attribute 'cellspacing'\n          node.remove_attribute 'cellpadding'\n        end\n\n        css('td').each do |node|\n          node.remove_attribute 'valign'\n        end\n\n        css('.sect2 > h3').each do |node|\n          node.name = 'h2'\n        end\n\n        css('.sect3 > h4').each do |node|\n          node.name = 'h3'\n        end\n\n        css('tt').each do |node|\n          node.name = 'code'\n        end\n\n        css('div.note', 'div.important', 'div.tip', 'div.caution').each do |node|\n          if node.at_css('blockquote')\n            node.before(node.children).remove\n          else\n            node.name = 'blockquote'\n          end\n        end\n\n        css('.refsynopsisdiv > p').each do |node|\n          node.name = 'pre'\n          node.content = node.content\n        end\n\n        css('pre code', 'pre span', 'pre i', 'pre samp', 'code code', 'code span').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('code').each do |node|\n          node.inner_html = node.inner_html.gsub(/\\s*\\n\\s*/, ' ')\n        end\n\n        css('pre.synopsis', 'pre.programlisting').each do |node|\n          node['data-language'] = 'sql'\n        end\n\n        css('h1', 'ul', 'li', 'pre').each do |node|\n          node.remove_attribute 'class'\n          node.remove_attribute 'style'\n        end\n\n        css('.id_link').remove\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/postgresql/entries.rb",
    "content": "module Docs\n  class Postgresql\n    class EntriesFilter < Docs::EntriesFilter\n      REPLACE_NAMES = {\n        'Sorting Rows'                    => 'ORDER BY',\n        'Select Lists'                    => 'SELECT Lists',\n        'Comparison Functions and Operators' => 'Comparisons',\n        'Data Type Formatting Functions'  => 'Formatting Functions',\n        'Enum Support Functions'          => 'Enum Functions',\n        'Row and Array Comparisons'       => 'Array Comparisons',\n        'Sequence Manipulation Functions' => 'Sequence Functions',\n        'System Administration Functions' => 'Administration Functions',\n        'System Information Functions'    => 'Information Functions' }\n\n      PREPEND_TYPES = [\n        'Type Conversion',\n        'Full Text Search',\n        'Performance Tips',\n        'Server Configuration',\n        'Monitoring' ]\n\n      REPLACE_TYPES = {\n        'Routine Database Maintenance Tasks' => 'Maintenance',\n        'High Availability, Load Balancing, and Replication' => 'High Availability',\n        'Monitoring Database Activity' => 'Monitoring',\n        'Monitoring Disk Usage' => 'Monitoring',\n        'Reliability and the Write-Ahead Log' => 'Write-Ahead Log',\n        'Overview of PostgreSQL Internals' => 'Internals',\n        'System Catalogs' => 'Internals: Catalogs',\n        'How the Planner Uses Statistics' => 'Internals',\n        'Index Access Method Interface Definition' => 'Index Access Method',\n        'Database Physical Storage' => 'Physical Storage' }\n\n      INTERNAL_TYPES = [\n        'Genetic Query Optimizer',\n        'Index Access Method',\n        'GiST Indexes',\n        'SP-GiST Indexes',\n        'GIN Indexes',\n        'BRIN Indexes',\n        'Physical Storage' ]\n\n      def base_name\n        @base_name ||= clean_heading_name(at_css('h1').content)\n      end\n\n      def heading_level\n        @heading_level ||= at_css('h1').content.scan(/\\d+(?=\\.)/).last\n      end\n\n      def get_name\n        if type.start_with?('Tutorial')\n          \"#{heading_level}. #{base_name}\"\n        elsif %w(Overview Introduction).include?(base_name)\n          result[:pg_chapter_name]\n        elsif PREPEND_TYPES.include?(type) || type.start_with?('Internals')\n          \"#{type.remove('Internals: ')}: #{base_name}\"\n        else\n          REPLACE_NAMES[base_name] || base_name.strip\n        end\n      end\n\n      def get_type\n        return if initial_page?\n\n        if result[:pg_up_path] == 'sql-commands.html'\n          'Commands'\n        elsif result[:pg_up_path] == 'appendixes.html'\n          'Appendixes'\n        elsif result[:pg_up_path].start_with?('reference-')\n          'Applications'\n        elsif type = result[:pg_chapter_name]\n          if type.start_with?('Func') && (match = base_name.match(/\\A(?!Form|Seq|Set|Enum|Comp)(.+) Func/))\n            \"Functions: #{match[1]}\"\n          else\n            type.remove! %r{\\ASQL }\n            type = REPLACE_TYPES[type] || type\n            type.prepend 'Internals: ' if INTERNAL_TYPES.include?(type)\n            type.prepend 'Tutorial: ' if slug.start_with?('tutorial')\n            type\n          end\n        elsif type = result[:pg_appendix_name]\n          type.prepend 'Appendix: '\n          type\n        end\n      end\n\n      def additional_entries\n        return [] if skip_additional_entries?\n        return config_additional_entries if type && type.include?('Configuration')\n        return data_types_additional_entries if type == 'Data Types'\n        return command_additional_entries if type == 'Commands'\n        return get_heading_entries('h3[id], .sect3[id] > h3:first-child') if slug == 'functions-xml'\n\n        entries = get_heading_entries('h2[id], .sect2[id] > h2:first-child')\n\n        case slug\n        when 'queries-union'\n          entries.concat get_custom_entries('p > .literal:first-child')\n        when 'queries-table-expressions'\n          entries.concat get_heading_entries('h3[id], .sect3[id] > h3:first-child')\n          entries.concat get_custom_entries('dt > .literal:first-child')\n        when 'functions-admin'\n          entries.concat get_custom_entries('.table td:first-child > p:first-child > code.function')\n        else\n          if type && type.start_with?('Functions')\n            entries.concat get_custom_entries('> .table td:first-child > code.literal:first-child')\n            entries.concat get_custom_entries('> .table td:first-child > code.function:first-child')\n            entries.concat get_custom_entries('> .table td:first-child > code:not(.literal):first-child + code.literal')\n            entries.concat get_custom_entries('> .table td:first-child > p:first-child > code.literal:first-child')\n            entries.concat get_custom_entries('> .table td:first-child > p > code.function:first-child')\n            entries.concat get_custom_entries('> .table td:first-child > p > code:not(.literal):first-child + code.literal')\n            if slug == 'functions-comparison' && !at_css('#FUNCTIONS-COMPARISON-PRED-TABLE') # before 9.6\n              entries.concat %w(IS NULL BETWEEN DISTINCT\\ FROM).map { |name| [\"#{self.name}: #{name}\"] }\n            end\n          end\n        end\n\n        entries\n      end\n\n      def config_additional_entries\n        css('.variablelist dt[id]').map do |node|\n          name = node.at_css('.varname').content\n          [\"Config: #{name}\", node['id']]\n        end\n      end\n\n      def data_types_additional_entries\n        selector = case slug\n        when 'rangetypes'\n          'li > p > .type:first-child'\n        when 'datatype-textsearch'\n          '.title > .type, .sect2 > .type'\n        else\n          '.table-contents td:first-child > .type, .calstable td:first-child > .type'\n        end\n        get_custom_entries(selector)\n      end\n\n      def command_additional_entries\n        css('.refsect2[id^=\"SQL\"]').each_with_object([]) do |node, entries|\n          next unless heading = node.at_css('h3')\n          next unless heading.content.strip =~ /[A-Z_\\-]+ Clause/\n          name = heading.at_css('.literal').content\n          name.prepend \"#{self.name} ... \"\n          entries << [name, node['id']]\n        end\n      end\n\n      def include_default_entry?\n        !initial_page? && (!at_css('.toc') || at_css('.sect2, .variablelist, .refsect1')) && type\n      end\n\n      SKIP_ENTRIES_SLUGS = [\n        'config-setting',\n        'applevel-consistency' ]\n\n      SKIP_ENTRIES_TYPES = [\n        'Localization',\n        'Type Conversion',\n        'Full Text Search',\n        'Performance Tips',\n        'Client Authentication',\n        'Managing Databases',\n        'Maintenance',\n        'Backup and Restore',\n        'High Availability',\n        'Monitoring' ]\n\n      def skip_additional_entries?\n        return true unless type\n        SKIP_ENTRIES_SLUGS.include?(slug) ||\n        SKIP_ENTRIES_TYPES.include?(type) ||\n        type.start_with?('Internals') ||\n        type.start_with?('Tutorial') ||\n        type.start_with?('Appendix')\n      end\n\n      def clean_heading_name(name)\n        name.remove! 'Chapter '\n        name.remove! %r{\\A[\\d\\.\\s]+}\n        name.remove! 'Appendix '\n        name.remove! %r{\\A[A-Z]\\.[\\d\\.\\s]*}\n        name.remove! 'Using '\n        name.remove! %r{\\AThe }\n        name.remove! ' (Common Table Expressions)'\n        name\n      end\n\n      def get_heading_entries(selector)\n        css(selector).each_with_object([]) do |node, entries|\n          name = node.content\n          clean_heading_name(name)\n          id = node['id'] || node.parent['id']\n          raise \"missing ids for selector #{selector}\" unless id\n          entries << [\"#{additional_entry_prefix}: #{name}\", id] unless skip_heading?(name)\n        end\n      end\n\n      def get_custom_entries(selector)\n        css(selector).each_with_object([]) do |node, entries|\n          name = node.content\n          name.remove! %r{\\(.*?\\)}m\n          name.remove! %r{\\[.*?\\]}m\n          name.squeeze! ' '\n          name.remove! %r{\\([^\\)]*\\z} # bug fix: json_populate_record\n          name = '||' if name.include? ' || '\n          id = name.gsub(/[^a-zA-Z0-9\\-_]/) { |char| char.ord }\n          id = id.parameterize\n          name.prepend \"#{additional_entry_prefix}: \"\n\n          unless entries.any? { |entry| entry[0] == name }\n            node['id'] = id\n            entries << [name, id]\n            # puts [selector, name].join(' --> ')\n          end\n        end\n      end\n\n      def additional_entry_prefix\n        type.dup.gsub!('Functions: ', '') || self.name\n      end\n\n      def skip_heading?(name)\n        %w(Usage\\ Patterns Portability Caveats Overview).include?(name) ||\n        (type.start_with?('Functions') && slug != 'functions-xml' && name.split.first.upcase!)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/postgresql/extract_metadata.rb",
    "content": "module Docs\n  class Postgresql\n    class ExtractMetadataFilter < Filter\n      def call\n        extract_up_path\n        extract_chapter\n        doc\n      end\n\n      def extract_up_path\n        if node = at_css('.navheader a[accesskey=\"u\"], .navheader a[accesskey=\"U\"]')\n          result[:pg_up_path] = node['href']\n        end\n      end\n\n      def extract_chapter\n        css('.navheader td[align=\"center\"], .navheader th[align=\"center\"]').each do |node|\n          text = node.content.strip\n          if match = text.match(/\\AChapter (\\d+)\\. (.+)\\z/)\n            result[:pg_chapter] = match[1].to_i\n            result[:pg_chapter_name] = match[2].strip\n          elsif match = text.match(/\\AAppendix ([A-Z])\\. (.+)\\z/)\n            result[:pg_appendix] = match[1]\n            result[:pg_appendix_name] = match[2].strip\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/postgresql/normalize_class_names.rb",
    "content": "module Docs\n  class Postgresql\n    class NormalizeClassNamesFilter < Filter\n      def call\n        doc.css('*').each do |node|\n          node['class'] = node['class'].downcase if node['class'].present?\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/prettier/clean_html.rb",
    "content": "module Docs\n  class Prettier\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('article .markdown')\n\n        if root_page?\n          at_css('h1').content = 'React Native Documentation'\n          css('h1 ~ *').remove\n        end\n\n        css('.docs-prevnext', '.hash-link', '.edit-page-link', '.edit-github', 'a.hash', '.edit-page-block', 'a.show', 'a.hide', 'hr').remove\n\n        css('table h1', 'table h2', 'table h3').each do |node|\n          table = node\n          table = table.parent until table.name == 'table'\n          table.replace(node)\n        end\n\n        css('a.anchor', 'a.hashref').each do |node|\n          node.parent['id'] ||= node['name'] || node['id']\n        end\n\n        css('a[href*=\"youtube.com\"]').remove\n\n        css('.highlight').each do |node|\n          node.name = 'pre'\n          node.css('.gutter').remove\n          node['data-language'] = node.at_css('[data-lang]').try(:[], 'data-lang') || 'js'\n          node.content = node.content.strip\n        end\n\n        css('table.highlighttable').each do |node|\n          node.replace(node.at_css('pre.highlight'))\n        end\n\n        css('.prism-code').each do |node|\n          node.name = 'pre'\n          node['data-language'] = node['class'][/(?<=language\\-)(\\w+)/]\n          node.content = node.css('.token-line').map(&:content).join(\"\\n\")\n          node.remove_attribute('class')\n          node.remove_attribute('style')\n        end\n\n        css('pre > code.hljs').each do |node|\n          node.parent['data-language'] = 'jsx'\n          node.before(node.children).remove\n        end\n\n        css('blockquote > p:first-child').each do |node|\n          node.remove if node.content.strip == 'Note:'\n        end\n\n        css('h3#props', 'h3#methods').each { |node| node.name = 'h2' }\n        css('h4.propTitle').each { |node| node.name = 'h3' }\n\n        css('> div > div', '> div', 'div > span', '.props', '.prop', '> article', '.postHeader', '.web-player').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('a pre', 'h3 .propType').each do |node|\n          node.name = 'code'\n        end\n\n        css('a[target]').each do |node|\n          node.remove_attribute('target')\n        end\n\n        css('center > .button', 'p:contains(\"short survey\")', 'iframe', '.embedded-simulator', '.deprecatedIcon').remove\n\n        css('h4.methodTitle').each do |node|\n          node.name = 'h3'\n        end\n\n        css('div:not([class])', 'span:not([class])').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('ul').each do |node|\n          node.before(node.children).remove if node.at_css('> p', '> h2')\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/prettier/entries.rb",
    "content": "module Docs\n  class Prettier\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').children.select(&:text?).map(&:content).join.strip\n      end\n\n      def type\n        link = at_css('.menu__link--active')\n        section = link.ancestors('.theme-doc-sidebar-item-category-level-1').first\n        type = section.at_css('.menu__link--sublist').content.strip\n        return name if type == 'Configuring Prettier'\n        return name if type == 'Usage'\n        type\n      end\n\n      def additional_entries\n        entries = []\n        css('h2').each do |node|\n          entries << [node.text, node['id']]\n        end\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/pug/clean_html.rb",
    "content": "module Docs\n  class Pug\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.main')\n\n        at_css('h1').content = 'Pug Documentation' if root_page?\n\n        if slug == 'api/reference'\n          at_css('.alert-info').remove\n        end\n\n        css('.header-anchor').remove\n\n        css('.preview-wrapper').each do |node|\n          node.css('pre').each do |n|\n            node.before(n)\n          end\n          node.remove\n        end\n\n        css('pre').each do |node|\n          node.content = node.content\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/pug/entries.rb",
    "content": "module Docs\n  class Pug\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        if subpath.start_with?('language')\n          'Language'\n        elsif subpath.start_with?('api')\n          'API'\n        end\n      end\n\n      def additional_entries\n        return [] unless slug == 'api/reference'\n\n        css('h3').each_with_object [] do |node, entries|\n          name = node.content\n          name.sub! %r{\\(.*\\)}, '()'\n          entries << [name, node['id']]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/puppeteer/clean_html.rb",
    "content": "module Docs\n  class Puppeteer\n    class CleanHtmlFilter < Filter\n      def call\n        at_css('h1').content = 'Puppeteer Documentation'\n\n        # None of the elements to remove have classes, so the order of the remove calls is trivial\n\n        # Remove links to previous versions of the reference\n        at_css('h1 + ul').remove\n        at_css('h1 + ul').remove\n\n        # Remove table of contents\n        at_css('h1 + h5').remove\n        at_css('h1 + ul').remove\n\n        # Make headers bigger by transforming them into a bigger variant\n        css('h3').each { |node| node.name = 'h2' }\n        css('h4').each { |node| node.name = 'h3' }\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/puppeteer/entries.rb",
    "content": "module Docs\n  class Puppeteer\n    class EntriesFilter < Docs::EntriesFilter\n      # The entire reference is one big page, so get_name and get_type are not necessary\n\n      def additional_entries\n        entries = []\n\n        css('h3').each do |node|\n          name = node.content.split(': ').last\n\n          # Find all sub-items (all h4's between the current h3 and the next)\n          current = node.next\n          while !current.nil? && current.name != 'h3'\n            if current.name == 'h4'\n              current_name = current.content\n\n              # Prepend events with the class name\n              if current_name.start_with?('event: ')\n                current_name = \"#{name} event: #{current_name[/'(.*)'/, 1]}\"\n              end\n\n              # Remove arguments from functions\n              if current_name.include?('(')\n                current_name = current_name.split('(')[0] + '()'\n              end\n\n              entries << [current_name, current['id'], name]\n            end\n\n            current = current.next\n          end\n\n          entries << [name, node['id'], name]\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/pygame/clean_html.rb",
    "content": "module Docs\n  class Pygame\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.body')\n        root_page? ? root : other\n        doc\n      end\n\n      def root\n        at_css('h1').content = 'Pygame'\n\n        # remove unneeded stuff\n        at_css('.modindex-jumpbox').remove\n        css('[role=\"navigation\"], .pcap, .cap, .footer').remove\n        css('tr > td:first-child').remove\n\n        # Unitalicize package descriptions\n        css('td > em').each do |node|\n          node.parent.content = node.content\n        end\n      end\n\n      def other\n        css('table.toc.docutils, .tooltip-content').remove\n\n        # Remove code tag from function, class, method, module, etc.\n        css('dl > dt').each do |node|\n          node.content = node.content\n        end\n\n        css('> .section > dl > dt').each do |node|\n          node.name = 'h1'\n          node.parent.parent.before(node)\n        end\n\n        # Format code for it be highlighted\n        css('.highlight-default.notranslate').each do |node|\n          node.name = 'pre'\n          node.content = node.content.strip\n          node['class'] = 'language-python'\n          node['data-language'] = 'python'\n        end\n\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/pygame/entries.rb",
    "content": "module Docs\n  class Pygame\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('.title').content.remove('pygame.').strip\n      end\n\n      def get_type\n        at_css('.title').content.strip\n      end\n\n      def additional_entries\n        css('dl.definition > dt.title').each_with_object [] do |node, entries|\n          name = node['id'] || node.parent.parent['id']\n          name.remove! 'pygame.'\n          id = node['id']\n          entries << [name, id] unless name == self.name\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/pygame/pre_clean_html.rb",
    "content": "module Docs\n  class Pygame\n    class PreCleanHtmlFilter < Filter\n      def call\n        # Remove ¶ character from tag w/ name & type\n        css('.headerlink').remove\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/python/clean_html.rb",
    "content": "module Docs\n  class Python\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.body > section[id]') || at_css('.body')\n\n        root_page? ? root : other\n\n        doc\n      end\n\n      def root\n        at_css('h1').content = 'Python'\n      end\n\n      def other\n        css('h1').each do |node|\n          node.content = node.content.sub(/\\A[\\d\\.]+/) do |str|\n            rgx = /\\A#{str}/\n            @levelRegexp = @levelRegexp ? Regexp.union(@levelRegexp, rgx) : rgx\n            ''\n          end\n        end\n\n        unless @levelRegexp.nil?\n          css('h2', 'h3', 'h4').each do |node|\n            node.inner_html = node.inner_html.remove @levelRegexp\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/python/entries_v2.rb",
    "content": "module Docs\n  class Python\n    class EntriesV2Filter < Docs::EntriesFilter\n      REPLACE_TYPES = {\n        'compiler package'                        => 'Compiler',\n        'Cryptographic'                           => 'Cryptography',\n        'Custom Interpreters'                     => 'Interpreters',\n        'Data Compression & Archiving'            => 'Data Compression',\n        'Generic Operating System'                => 'Operating System',\n        'Graphical User Interfaces with Tk'       => 'Tk',\n        'Internet Data Handling'                  => 'Internet Data',\n        'Internet Protocols & Support'            => 'Internet',\n        'Interprocess Communication & Networking' => 'Networking',\n        'MacOSA'                                  => 'Mac OS',\n        'Program Frameworks'                      => 'Frameworks',\n        'Structured Markup Processing Tools'      => 'Structured Markup' }\n\n      def get_name\n        name = at_css('h1').content\n        name.remove! %r{\\A[\\d\\.]+ } # remove list number\n        name.remove! \"\\u{00B6}\" # remove pilcrow sign\n        name.remove! %r{ [\\u{2013}\\u{2014}].+\\z} # remove text after em/en dash\n        name.remove! 'Built-in'\n        name.strip!\n        name\n      end\n\n      def get_type\n        return 'Logging' if slug.start_with? 'library/logging'\n\n        type = at_css('.related a[accesskey=\"U\"]').content\n\n        if type == 'The Python Standard Library'\n          type = at_css('h1').content\n        elsif type.include?('I/O') || %w(select selectors).include?(name)\n          type = 'Input/ouput'\n        elsif type.start_with? '18'\n          type = 'Internet Data Handling'\n        elsif type.include? 'Mac'\n          type = 'Mac OS'\n        end\n\n        type.remove! %r{\\A\\d+\\.\\s+} # remove list number\n        type.remove! \"\\u{00b6}\" # remove paragraph character\n        type.sub! ' and ', ' & '\n        [' Services', ' Modules', ' Specific', 'Python '].each { |str| type.remove!(str) }\n\n        REPLACE_TYPES[type] || type\n      end\n\n      def include_default_entry?\n        !at_css('.body > .section:only-child > .toctree-wrapper:last-child') && !type.in?(%w(Superseded SunOS))\n      end\n\n      def additional_entries\n        return [] if root_page? || !include_default_entry? || name == 'errno'\n        clean_id_attributes\n        entries = []\n\n        css('.class > dt[id]', '.exception > dt[id]', '.attribute > dt[id]', '.data > dt[id]').each do |node|\n          entries << [node['id'], node['id']]\n        end\n\n        css('.function > dt[id]', '.method > dt[id]', '.staticmethod > dt[id]', '.classmethod > dt[id]').each do |node|\n          entries << [node['id'] + '()', node['id']]\n        end\n\n        entries\n      end\n\n      def clean_id_attributes\n        css('.section > .target[id]').each do |node|\n          if dt = node.at_css('+ dl > dt')\n            dt['id'] ||= node['id'].remove(/\\w+\\-/)\n          end\n          node.remove\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/python/entries_v3.rb",
    "content": "module Docs\n  class Python\n    class EntriesV3Filter < Docs::EntriesFilter\n      REPLACE_TYPES = {\n        'contextvars — Context Variables'          => 'Context Variables',\n        'Cryptographic'                            => 'Cryptography',\n        'Custom Interpreters'                      => 'Interpreters',\n        'Data Compression & Archiving'             => 'Data Compression',\n        'email — An email & MIME handling package' => 'Email',\n        'Generic Operating System'                 => 'Operating System',\n        'Graphical User Interfaces with Tk'        => 'Tk',\n        'Internet Data Handling'                   => 'Internet Data',\n        'Internet Protocols & Support'             => 'Internet',\n        'Interprocess Communication & Networking'  => 'Networking',\n        'Program Frameworks'                       => 'Frameworks',\n        'Structured Markup Processing Tools'       => 'Structured Markup' }\n\n      def get_name\n        name = at_css('h1').content\n        name.remove! %r{\\A[\\d\\.]+ } unless include_h2? # remove list number\n        name.remove! \"\\u{00B6}\" # remove pilcrow sign\n        name.remove! %r{ [\\u{2013}\\u{2014}].+\\z} # remove text after em/en dash\n        name.remove! 'Built-in'\n        name.strip!\n        name\n      end\n\n      def get_type\n        return 'Language Reference' if slug.start_with? 'reference'\n        return 'Python/C API' if slug.start_with? 'c-api'\n        return 'Tutorial' if slug.start_with? 'tutorial'\n        return 'Software Packaging & Distribution' if slug.start_with? 'distributing'\n        return 'Software Packaging & Distribution' if slug.start_with? 'distutils'\n        return 'Glossary' if slug.start_with? 'glossary'\n\n        return 'Basics' unless slug.start_with? 'library/'\n        return 'Basics' if slug.start_with? 'library/index'\n\n        return 'Logging' if slug.start_with? 'library/logging'\n        return 'Asynchronous I/O' if slug.start_with? 'library/asyncio'\n\n        type = at_css('.related a[accesskey=\"U\"]').content\n\n        if type == 'The Python Standard Library'\n          type = at_css('h1').content\n        elsif type.include?('I/O') || %w(select selectors).include?(name)\n          type = 'Input/ouput'\n        elsif type.start_with? '19'\n          type = 'Internet Data Handling'\n        end\n\n        type.remove! %r{\\A\\d+\\.\\s+} unless include_h2? # remove list number\n        type.remove! \"\\u{00b6}\" # remove paragraph character\n        type.sub! ' and ', ' & '\n        [' Services', ' Modules', ' Specific', 'Python '].each { |str| type.remove!(str) }\n\n        REPLACE_TYPES[type] || type\n      end\n\n      def include_h2?\n        return slug.start_with?('library') || slug.start_with?('reference') || slug.start_with?('tutorial') || slug.start_with?('using')\n      end\n\n      def include_default_entry?\n        return false if slug.starts_with?('genindex')\n        return true if slug == 'library/asyncio'\n        !at_css('.body > .section:only-child > .toctree-wrapper:last-child') && !type.in?(%w(Superseded))\n      end\n\n      def additional_entries\n        return additional_entries_index if slug.starts_with?('genindex')\n        return [] if root_page? || slug.start_with?('library/index') || !include_default_entry? || name == 'errno'\n        clean_id_attributes\n        entries = []\n\n        css('.class > dt[id]', '.exception > dt[id]', '.attribute > dt[id]', '.data > dt[id]').each do |node|\n          entries << [node['id'], node['id']]\n        end\n\n        css('.glossary > dt[id]').each do |node|\n          name = node.content.remove(\"\\u{00b6}\")\n          entries << [name, node['id']]\n        end\n\n        css('.function > dt[id]', '.method > dt[id]', '.staticmethod > dt[id]', '.classmethod > dt[id]').each do |node|\n          entries << [node['id'] + '()', node['id']]\n        end\n\n        if include_h2?\n          css('section[id] > h2').each do |node|\n            name = node.content.remove(\"\\u{00b6}\")\n            name.concat \" (#{self.name})\" if slug.start_with?('library')\n            entries << [name, node.parent['id']]\n          end\n        end\n\n        entries\n      end\n\n      def clean_id_attributes\n        css('.section > .target[id]').each do |node|\n          if dt = node.at_css('+ dl > dt')\n            dt['id'] ||= node['id'].remove(/\\w+\\-/)\n          end\n          node.remove\n        end\n      end\n\n      def additional_entries_index\n        css('.genindextable td > ul > li').each_with_object [] do |node, entries|\n          name = node.children.first\n          next unless name.text?\n          name = name.text.strip()\n          next if name[/Python Enhancement Proposals/]\n          node.css('> ul > li > a').each do |inner_node|\n            inner_name = inner_node.text.strip()\n            next if inner_name[/\\[\\d+\\]/]\n            href = inner_node['href']\n            next if (name[/^\\w/] || name[/^-+\\w/]) && !href[/stmts/]\n            type = case inner_name\n            when 'keyword'\n              'Keywords'\n            when 'operator'\n              'Operators'\n            when 'in regular expressions'\n              'Regular Expression'\n            when /statement/\n              'Statements'\n            else\n              'Symbols'\n            end\n            entries << [\"#{name} (#{inner_name})\", href, type]\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/pytorch/clean_html.rb",
    "content": "module Docs\n  class Pytorch\n    class CleanHtmlFilter < Filter\n      def call\n        if root = at_css('#pytorch-article')\n          @doc = root\n          # Show katex-mathml nodes and remove katex-html nodes\n          css('.katex-html').remove\n        end\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/pytorch/entries.rb",
    "content": "module Docs\n  class Pytorch\n    class EntriesFilter < Docs::EntriesFilter\n      def get_breadcrumbs\n        breadcrumbs = if at_css('.pytorch-breadcrumbs')\n          css('.pytorch-breadcrumbs > li').map { |node|\n            node.content.delete_suffix(' >').strip\n          }\n        else\n          css('.bd-breadcrumbs > li').map { |node|\n            text = node.content.strip\n            text.empty? && node.at_css('.fa-home') ? 'Docs' : text\n          }\n        end.reject { |item| item.nil? || item.empty? }\n\n        if breadcrumbs.last&.end_with?('.')\n          resolved_name = at_css('h1').content.delete_suffix('#').strip\n          breadcrumbs[-1] = resolved_name\n        end\n\n        breadcrumbs\n      end\n\n      def get_name\n        get_breadcrumbs[-1]\n      end\n\n      def get_type\n        if at_css('.pytorch-breadcrumbs')\n          get_breadcrumbs[1]\n        else\n          get_breadcrumbs.size > 2 ? get_breadcrumbs[2] : get_breadcrumbs[1]\n        end\n      end\n\n      def include_default_entry?\n        !get_breadcrumbs.nil? && get_breadcrumbs.size >= 2\n      end\n\n      def additional_entries\n        return [] if root_page?\n\n        entries = []\n        css('dl').each do |node|\n          dt = node.at_css('dt')\n          if dt == nil\n            next\n          end\n          id = dt['id']\n          if id == name or id == nil\n            next\n          end\n\n          case node['class']\n          when 'py method', 'py function'\n            entries << [id + '()', id]\n          when 'py class', 'py attribute', 'py property'\n            entries << [id, id]\n          end\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/q/entries.rb",
    "content": "module Docs\n  class Q\n    class EntriesFilter < Docs::EntriesFilter\n      def additional_entries\n        entry = type = nil\n\n        css('h3, h4, em:contains(\"Alias\")').each_with_object [] do |node, entries|\n          case node.name\n          when 'h3'\n            type = node.content.strip\n            type.remove! %r{\\(.+\\)}\n            type.remove! ' Methods'\n            type.remove! ' API'\n            entries << [type, node['id'], type] if type == 'Q.defer()'\n          when 'h4'\n            name = node.content.strip\n            name.sub! %r{\\(.*?\\).*}, '()'\n            id = node['id'] = name.parameterize\n            entry = [name, id, type]\n            entries << entry\n          when 'em'\n            name = node.parent.at_css('code').content\n            name << '()' if entry[0].end_with?('()')\n            dup = entry.dup\n            dup[0] = name\n            entries << dup\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/qt/clean_html.rb",
    "content": "module Docs\n  class Qt\n    class CleanHtmlFilter < Filter\n      def call\n        # Narrow down container further. Breadcrumb is safe to remove.\n        @doc = at_css('article .mainContent .context') unless root_page?\n\n        css('h1').remove_attribute('class')\n\n        # QML property/method header\n        css('.qmlproto').each do |node|\n          id = node.at_css('span.name').content\n          node.inner_html = node.at_css('td').inner_html\n          node.name = 'h3'\n          node['id'] = id\n        end\n\n        css('pre').each do |node|\n          node.content = node.content\n          node['data-language'] = 'cpp' if node['class'].include?('cpp')\n          node['data-language'] = 'qml' if node['class'].include?('qml')\n          node.remove_attribute('class')\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/qt/entries.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class Qt\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        header = at_css('h1.title + .small-subtitle a') || at_css('h1.title') || at_css('.context h2')\n        name = header.content\n        name.sub! %r{ Class$}, ' (class)'\n        name.sub! %r{ QML Type$}, ' (QML type)'\n        name.sub! %r{ QML Basic Type$}, ' (QML basic type)'\n\n        # Add '(class)' to the class pages where the subtitle name is used (e.g. qset-const-iterator.html)\n        if at_css('h1.title').content.strip.end_with?(' Class') && !name.include?('(class)')\n          name = \"#{name} (class) \"\n        end\n\n        name\n      end\n\n      def get_type\n        breadcrumb = css('ul.c-breadcrump li') # Yes, really: breadcrump.\n        breadcrumb[1].content\n      end\n\n      def include_default_entry?\n        name != 'All Classes' && name != 'All QML Types'\n      end\n\n      def additional_entries\n        entries = []\n        titles = []\n\n        className = at_css('h1.title').content.strip.remove(' Class')\n        displayedClassName = className\n        alternativeClassName = at_css('h1.title + .small-subtitle a')\n        displayedClassName = alternativeClassName.content if alternativeClassName\n\n        # Functions signatures\n        css('h3.fn').each do |node|\n          header = node.clone\n\n          # Skip typenames\n          next if header.content.strip.start_with?('typename ')\n\n          # Remove leading <a name=\"\">\n          header.children.css('a[name]').remove\n\n          # Remove leading <code> tag (virtual/static/… attributes)\n          code = header.children.first\n          code.remove if code.name == 'code'\n\n          # Remove leading ‘const’\n          header.children.first.remove if header.content.strip.start_with?('const ')\n\n          # Remove return type\n          returnType = header.children.first\n          returnType.remove if returnType['class'] == 'type'\n\n          title = header.content.strip\n\n          # Remove leading '&'/'*'\n          title[0] = '' if title[0] == '&' || title[0] == '*'\n\n          # Ignore operator overloads\n          next if title.start_with?('operator')\n\n          # Remove function parameters\n          title.sub! %r{\\(.*\\)}, '()'\n\n          # Remove template generics\n          title.remove!(%r{^<.*> })\n\n          # Remove ‘const’ at the end\n          title.remove!(%r{ const$})\n\n          # Enum/typedef formatting\n          title.sub! %r{(enum|typedef) (.*)}, '\\2 (\\1)'\n\n          # Remove property type\n          title = \"#{displayedClassName}::#{title}\" if title.sub!(%r{ : .*$}, '')\n\n          # Replace the class name by the alternative class name if available\n          title = title.sub(className, displayedClassName) if alternativeClassName\n\n          unless titles.include?(title) # Remove duplicates (function overloading)\n            entries << [title, header['id']]\n            titles.push title\n          end\n        end\n\n        # QML properties/functions\n        css('.qmlproto').each do |node|\n          title = node.content.strip\n          id = node.at_css('span.name').content\n\n          # Remove options\n          title.remove!(%r{^\\[.*\\] })\n\n          # Remove function parameters\n          title.sub! %r{\\(.*\\)}, '()'\n\n          # Remove property type\n          title.remove!(%r{ : .*$})\n\n          # Remove return type\n          title.remove!(%r{.* })\n\n          # Remove return type\n          title.remove!(%r{.* })\n\n          title = title.strip\n\n          unless titles.include?(title) # Remove duplicates (function overloading)\n            entries << [title, id]\n            titles.push(title)\n          end\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/qunit/clean_html.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class Qunit\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.content[role=\"main\"] > article')\n        css('.sidebar').remove\n        css('pre').each do |node|\n          node['data-language'] = 'javascript'\n          node.content = node.content\n        end\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/qunit/entries.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class Qunit\n    class EntriesFilter < Docs::EntriesFilter\n      TYPE_MAPPING = {\n        'QUnit' => '1. Main methods',\n        'assert' => '2. Assertions',\n        'callbacks' => '3. Callback events',\n        'config' => '4. Configuration',\n        'extension' => '5. Extension interface',\n        'reporters' => '6. Reporters',\n      }\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        main, *rest = *slug.split('/')\n        TYPE_MAPPING[main]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/r/clean_html.rb",
    "content": "module Docs\n  class R\n    class CleanHtmlFilter < Filter\n      def call\n        slug_parts = slug.split('/')\n\n        if root_page?\n          css('a[href$=\"/00index\"]').each do |pkg|\n            pkg['href'] = \"/r-#{pkg['href'].split('/')[1]}/\"\n          end\n\n        elsif slug_parts[0] == 'library'\n          title = at_css('h2')\n          title.inner_html = \"<code>#{slug_parts[3]}</code> #{title.content}\"\n\n          css('table:contains(\"R Documentation\")').remove\n          css('table[summary]').remove\n\n          css('hr ~ *, hr').remove\n\n        elsif slug_parts[-2] == 'manual'\n          css('table.menu, div.header, hr, h2.contents-heading, div.contents, table.index-cp, table.index-vr, table[summary]').remove\n\n          css('h2').each do |node|\n            node.remove if node.content.end_with? ' index'\n          end\n\n          css('span[id] + h1, span[id] + h2, span[id] + h3, span[id] + h4, span[id] + h5, span[id] + h6').each do |node|\n            # We need the first of the series of span with ids\n            span = node.previous_element\n            while span.previous\n              prev = span.previous_element\n              break unless prev.name == 'span' and prev['id']\n              span.remove\n              span = prev\n            end\n\n            node['id'] = span['id']\n            span.remove\n\n            css('div.example').each do |node|\n              node.replace(node.children)\n            end\n          end\n\n          css('h1 + h1').remove\n\n          css('.footnote h5').each do |node|\n            anchor = node.at_css('a[id]')\n            footnote = node.next_sibling\n            footnote.inner_html = \"<strong>#{anchor.text}</strong>&nbsp;#{footnote.inner_html}\"\n            footnote['id'] = anchor['id']\n            node.remove\n          end\n        end\n\n        css('pre').each do |node|\n          node['data-language'] = 'r'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/r/entries.rb",
    "content": "module Docs\n  class R\n    class EntriesFilter < Docs::EntriesFilter\n\n      PKG_INDEX_ENTRIES = Hash.new []\n\n      def call\n        if slug_parts[-1] == '00Index'\n          dir = File.dirname(result[:subpath])\n          css('tr a').each do |link|\n            PKG_INDEX_ENTRIES[link['href']] += [link.text]\n            next if link['href'] == link.text\n            context[:replace_paths][File.join(dir, \"#{link.text}.html\")] = File.join(dir, \"#{link['href']}.html\")\n          end\n        end\n\n        super\n      end\n\n      def slug_parts\n        slug.split('/')\n      end\n\n      def is_package?\n        slug_parts[0] == 'library'\n      end\n\n      def is_manual?\n        slug_parts[1] == 'manual'\n      end\n\n      def get_name\n        return at_css('h2').content if is_package?\n        title = at_css('h1.settitle')\n        title ? title.content : at_css('h1, h2').content\n      end\n\n      def get_type\n        return slug_parts[1] if is_package?\n        return at_css('h1.settitle').content if is_manual?\n      end\n\n      def include_default_entry?\n        is_package? and not slug_parts[-1] == '00Index'\n      end\n\n      def manual_section(node)\n        title = node.content.sub /^((Appendix )?[A-Z]|[0-9]+)(\\.[0-9]+)* /, ''\n        title unless ['References', 'Preface', 'Acknowledgements'].include?(title) or title.end_with?(' index')\n      end\n\n      def additional_entries\n        if is_package? and slug_parts[-1] != '00Index'\n          page = slug_parts[-1]\n          return [page] + PKG_INDEX_ENTRIES.fetch(page, [])\n        end\n\n        return [] unless is_manual?\n\n        entries = []\n        unless slug_parts[-1].downcase == 'r-intro'\n          # Single top-level category\n          css('div.contents > ul a').each do |link|\n            link_name = manual_section(link)\n            entries << [link_name, link['href'].split('#')[1], name] unless link_name.nil?\n          end\n        else\n          # Split 1st level of manual into different categories\n          css('div.contents > ul > li').each do |node|\n            type = manual_section(node.at_css('a'))\n            next if type.nil?\n            node.css('> ul a').each do |link|\n              link_name = link.content.sub /^[0-9A-Z]+(\\.[0-9]+)* /, ''\n              entries << [link_name, link['href'].split('#')[1], type]\n            end\n          end\n        end\n        return entries\n      end\n\n      private\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/rails/clean_html_guides.rb",
    "content": "module Docs\n  class Rails\n    class CleanHtmlGuidesFilter < Filter\n      def call\n        return doc unless slug.start_with?('guides')\n\n        main_col = at_css('#mainCol') || at_css('#column-main')\n        main_col.prepend_child at_css('#feature .wrapper').children\n        @doc = main_col\n\n        container = Nokogiri::XML::Node.new 'div', doc.document\n        container['class'] = '_simple'\n        container.children = doc.children\n        doc << container\n\n        css('h2, h3, h4, h5, h6').each do |node|\n          node.name = node.name.sub(/\\d/) { |i| i.to_i - 1 }\n        end\n\n        doc.prepend_child at_css('h1')\n\n        css('#subCol', '.code_container').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('pre').each do |node|\n          code = node.at_css('code')\n          language = code['class']\n          break if language.nil?\n          language = language [/highlight ?(\\w+)/, 1]\n          node['data-language'] = language unless language == 'plain'\n          code.remove_attribute('class')\n          node.content = node.content.strip\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/rails/entries.rb",
    "content": "module Docs\n  class Rails\n    class EntriesFilter < Docs::Rdoc::EntriesFilter\n      TYPE_BY_NAME_MATCHES = {\n        /Assertions|::Test|Fixture/                          => 'Testing',\n        /\\AActiveRecord.+mysql/i                             => 'ActiveRecord/MySQL',\n        /\\AActiveRecord.+postgresql/i                        => 'ActiveRecord/PostgreSQL',\n        /\\AActiveRecord.+sqlite/i                            => 'ActiveRecord/SQLite',\n        /\\AActiveRecord.+Assoc/                              => 'ActiveRecord/Associations',\n        /\\AActiveRecord.+Attribute/                          => 'ActiveRecord/Attributes',\n        /\\AActiveRecord.+ConnectionAdapters/                 => 'ActiveRecord/Connection',\n        /\\AActiveSupport.+(Subscriber|Notifications)/        => 'ActiveSupport/Instrumentation' }\n\n      TYPE_BY_NAME_STARTS_WITH = {\n        'ActionController::Parameters'  => 'ActionController/Parameters',\n        'ActionDispatch::Integration'   => 'Testing',\n        'ActionDispatch::Request'       => 'ActionDispatch/Request',\n        'ActionDispatch::Response'      => 'ActionDispatch/Response',\n        'ActionDispatch::Routing'       => 'ActionDispatch/Routing',\n        'ActionView::Helpers'           => 'ActionView/Helpers',\n        'ActiveModel::Errors'           => 'ActiveModel/Validation',\n        'ActiveModel::Valid'            => 'ActiveModel/Validation',\n        'ActiveRecord::Batches'         => 'ActiveRecord/Query',\n        'ActiveRecord::Calculations'    => 'ActiveRecord/Query',\n        'ActiveRecord::Connection'      => 'ActiveRecord/Connection',\n        'ActiveRecord::FinderMethods'   => 'ActiveRecord/Query',\n        'ActiveRecord::Migra'           => 'ActiveRecord/Migration',\n        'ActiveRecord::Query'           => 'ActiveRecord/Query',\n        'ActiveRecord::Relation'        => 'ActiveRecord/Relation',\n        'ActiveRecord::Result'          => 'ActiveRecord/Connection',\n        'ActiveRecord::Scoping'         => 'ActiveRecord/Query',\n        'ActiveRecord::SpawnMethods'    => 'ActiveRecord/Query',\n        'ActiveSupport::Cach'           => 'ActiveSupport/Caching',\n        'ActiveSupport::Inflector'      => 'ActiveSupport/Inflector',\n        'ActiveSupport::Time'           => 'ActiveSupport/TimeZones',\n        'Rails::Application'            => 'Rails/Application',\n        'Rails::Engine'                 => 'Rails/Engine',\n        'Rails::Generators'             => 'Rails/Generators',\n        'Rails::Railtie'                => 'Rails/Railtie' }\n\n      def get_name\n        if slug.start_with?('guides')\n          name = (at_css('#mainCol h2') || at_css('#column-main h2')).content.strip\n          name.remove! %r{\\s\\(.+\\)\\z}\n          return name\n        end\n\n        super\n      end\n\n      def get_type\n        return 'Guides' if slug.start_with?('guides')\n\n        parent = at_css('.meta-parent').try(:content).to_s\n\n        if [name, parent].any? { |str| str.end_with?('Error') || str.end_with?('Exception') }\n          return 'Errors'\n        end\n\n        TYPE_BY_NAME_MATCHES.each_pair do |key, value|\n          return value if name =~ key\n        end\n\n        TYPE_BY_NAME_STARTS_WITH.each_pair do |key, value|\n          return value if name.start_with?(key)\n        end\n\n        super\n      end\n\n      def include_default_entry?\n        return true if slug.start_with?('guides')\n\n        super && !skip?\n      end\n\n      def additional_entries\n        return [] if slug.start_with?('guides')\n\n        skip? ? [] : super\n      end\n\n      def skip?\n        @skip ||= !css('p').any? { |node| node.content.present? }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/ramda/clean_html.rb",
    "content": "module Docs\n  class Ramda\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('main')\n\n        # Remove try in repl\n        css('.try-repl', '.pull-right').remove\n\n        # Place 'Added in' in header\n        css('.card').each do |card|\n          title = card.at_css('h2')\n          added_in = card.at_css('small')\n          added_in.parent = title if added_in.present?\n        end\n\n        css('.params').each do |node|\n          # Remove params expand link\n          node.inner_html = node.at_css('.details').inner_html\n          node.prepend_child \"<h4>Parameters</h4>\"\n\n          # change param names to <code>\n          node.css('span.name').each do |n|\n            n.name = 'code'\n          end\n\n          if n = node.at_css('> .panel-body')\n            n.before(n.at_css('span.returns').tap { |_n| _n.name = 'h4' })\n            n.replace(\"<ul><li>#{n.to_html}</li></ul>\")\n          end\n        end\n\n        # Remove code highlighting\n        css('pre').each do |node|\n          node['data-language'] = 'javascript'\n          node.content = node.content\n        end\n\n        css('div.see').each do |node|\n          node.name = 'p'\n        end\n\n        css('.see a').each do |node|\n          node.replace \"<code>#{node.to_html}</code>\"\n        end\n\n        css('h2 + div > code:only-child').each do |node|\n          node.parent.name = 'pre'\n          node.parent.content = node.content\n        end\n\n        css('.card', '.panel-body', 'div.params', 'div.description', 'h2 > a').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('.section-id[id]').each do |node|\n          node.next_element['id'] = node['id']\n          node.remove\n        end\n\n        css('h2').each do |node|\n          node.name = 'h3'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/ramda/entries.rb",
    "content": "module Docs\n  class Ramda\n    class EntriesFilter < Docs::EntriesFilter\n      def additional_entries\n        css('ul.toc li').map do |node|\n          # As of 2025-06-20, `data-category` attribute is missing on https://ramdajs.com/ for Ramda versions < 0.29.0.\n          # This results in missing type for entries – and docs cannot be generated.\n          [\"R.#{node['data-name']}\", node['data-name'], node['data-category']]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/rdoc/clean_html.rb",
    "content": "module Docs\n  class Rdoc\n    class CleanHtmlFilter < Filter\n      def call\n        return doc if context[:skip_rdoc_filters?].try(:call, self)\n\n        root_page? ? root : other\n        doc\n      end\n\n      def root\n        css('#methods + ul', 'h1', 'h2', 'li > ul').remove\n\n        # Remove skipped items\n        css('li > span').each do |node|\n          node.parent.remove\n        end\n      end\n\n      def other\n        css('hr').remove\n\n        # Remove paragraph/up links\n        css('h1 > span', 'h2 > span', 'h3 > span', 'h4 > span', 'h5 > span', 'h6 > span').remove\n\n        # Move id attributes to headings\n        css('.method-detail').each do |node|\n          next unless heading = node.at_css('.method-heading')\n          heading['id'] = node['id']\n          node.remove_attribute 'id'\n        end\n\n        # (RDoc prior to Ruby 3.4) Convert \"click to toggle source\" into a link\n        css('.method-click-advice').each do |node|\n          node.name = 'a'\n          node.content = 'Show source'\n        end\n\n        # (RDoc for Ruby 3.4+) Add a \"Show source\" link\n        css('.method-source-toggle').each do |node|\n          link_node = Nokogiri::XML::Node.new('a', doc.document)\n          link_node.content = 'Show source'\n          link_node['class'] = 'method-click-advice'\n\n          # Only add \"Show source\" if source is present\n          method_root = node.ancestors('.method-detail').first\n          method_root.at_css('.method-heading').add_child(link_node) if method_root.at_css('.method-source-code')\n        end\n\n        # (RDoc for Ruby 3.4+) Remove the additional \"Source\" toggle from the page\n        css('.method-controls').remove\n\n        # Add class to differentiate Ruby code from C code\n        css('.method-source-code').each do |node|\n          header = node.ancestors('.method-detail').first.at_css('.method-header')\n          header.add_next_sibling(node)\n          pre = node.at_css('pre')\n          pre['class'] = pre.at_css('.ruby-keyword') ? 'ruby' : 'c'\n        end\n\n        # Remove code highlighting\n        css('pre').each do |node|\n          node.content = node.content\n          node['data-language'] = 'c' if node['class'] == 'c'\n          node['data-language'] = 'ruby' if node['class'] && node['class'].include?('ruby')\n        end\n\n        # Remove navigation breadcrumbs\n        css('ol[role=\"navigation\"]').remove\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/rdoc/container.rb",
    "content": "module Docs\n  class Rdoc\n    class ContainerFilter < Filter\n      def call\n        return doc if context[:skip_rdoc_filters?].try(:call, self)\n\n        if root_page?\n          at_css 'main'\n        else\n          container = at_css 'main'\n\n          # Add <dl> mentioning parent class and included modules\n          meta = Nokogiri::XML::Node.new 'dl', doc.document\n          meta['class'] = 'meta'\n\n          parent = at_css('#parent-class-section')\n          if parent && link = parent.at_css('.link')\n            meta << %(<dt>Parent:</dt><dd class=\"meta-parent\">#{link.inner_html.strip}</dd>)\n          elsif parent && link = parent.at_css('a')\n            meta << %(<dt>Parent:</dt><dd class=\"meta-parent\">#{link.to_html}</dd>)\n          end\n\n          if includes = at_css('#includes-section')\n            meta << %(<dt>Included modules:</dt><dd class=\"meta-includes\">#{includes.css('a').map(&:to_html).join(', ')}</dd>)\n          end\n\n          if parent || includes\n            container.at_css('h1').after(meta)\n          end\n\n          container\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/rdoc/entries.rb",
    "content": "module Docs\n  class Rdoc\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1, h2').content.strip\n        name.remove! \"\\u{00B6}\" # remove pilcrow sign\n        name.remove! \"\\u{2191}\" # remove up arrow sign\n        name.remove! 'class '\n        name.remove! 'module '\n        name\n      end\n\n      def get_type\n        type = name.dup\n\n        unless type.gsub! %r{::.*\\z}, ''\n          parent = at_css('.meta-parent').try(:content).to_s\n          return 'Errors' if type.end_with?('Error') || parent.end_with?('Error') || parent.end_with?('Exception')\n        end\n\n        type\n      end\n\n      def include_default_entry?\n        at_css('> .description p') || css('.documentation-section').any? { |node| node.content.present? }\n      end\n\n      IGNORE_METHODS = %w(version gem_version)\n\n      def additional_entries\n        return [] if root_page?\n        require 'cgi'\n\n        css('.method-detail').inject [] do |entries, node|\n          name = node['id'].dup\n          name.remove! %r{\\A\\w+?\\-.}\n          name.remove! %r{\\A-(?!\\d)}\n          name.gsub! '-', '%'\n          name = CGI.unescape(name)\n\n          unless name.start_with?('_') || IGNORE_METHODS.include?(name)\n            name.prepend self.name + (node['id'] =~ /\\A\\w+-c-/ ? '::' : '#')\n            entries << [name, node['id']] unless entries.any? { |entry| entry[0] == name }\n          end\n\n          entries\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/react/clean_html.rb",
    "content": "module Docs\n  class React\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('article')\n\n        if root_page?\n          at_css('h1').content = 'React Documentation'\n        end\n\n        css('header', 'div[class^=\"css-\"]', '.gatsby-resp-image-link span', 'div.scary').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('.gatsby-highlight > pre').each do |node|\n          node.content = node.at_css('code').children.map do |n|\n            if !n['class'].nil? && n['class'][/gatsby-highlight-code-line/]\n              n.content + \"\\n\"\n            else\n              n.content\n            end\n          end.join(\"\")\n          node['data-language'] = node['class'][/(?<=gatsby\\-code\\-)(\\w+)/]\n          node.remove_attribute('class')\n          node.parent.replace(node)\n        end\n\n        css('a.anchor', 'a:contains(\"Edit this page\")', 'hr').remove\n\n        css('a').remove_attr('rel').remove_attr('target').remove_attr('class').remove_attr('style')\n        css('img').remove_attr('style').remove_attr('srcset').remove_attr('sizes').remove_attr('class')\n\n        css('[class*=\"css-\"]').each do |node|\n          node['class'] = node['class'].sub(/css-[^ ]+(\\b|$)/, '')\n          node.delete 'class' if node['class'] == ''\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/react/clean_html_react_dev.rb",
    "content": "module Docs\n  class React\n    class CleanHtmlReactDevFilter < Filter\n      def call\n        @doc = at_css('article')\n\n        # Remove breadcrumbs before h1\n        css('h1').each do |node|\n          if node.previous\n            node.previous.remove\n          end\n        end\n\n        remove_selectors = [\n          'div.grid > a', # prev-next links\n          'button', # \"show more\" etc. buttons\n          'div.order-last', # code iframe containers\n          'div.dark-image', # dark images\n          'a[title=\"Open in CodeSandbox\"]', # codesandbox links\n        ]\n        css(*remove_selectors).each do |node|\n          node.remove\n        end\n\n        # Fix images not loading\n        css('img').remove_attr('srcset')\n\n        # Remove recipe blocks - TODO transform to outgoing link to docs\n        css('h4[id^=\"examples-\"]').each do |node|\n          node.parent.parent.parent.remove\n        end\n\n        # Transform callout blocks\n        class_transform = {\n          '.expandable-callout[class*=yellow]' => 'note note-orange', # pitfalls, experimental\n          '.expandable-callout[class*=green]' => 'note note-green', # note\n          '.expandable-callout[class*=gray]' => 'note', # canary\n          '.bg-card' => 'note', # you will learn\n          'details' => 'note note-blue' # deep dive\n        }\n\n        class_transform.each do |old_class, new_class|\n          css(old_class).each do |node|\n            node.set_attribute('class', new_class)\n          end\n        end\n\n        # Transform h3 to h4 inside callouts\n        css('.note h3', '.note h2').each do |node|\n          new_node = Nokogiri::XML::Node.new('h4', @doc)\n          new_node.content = node.content\n          node.replace(new_node)\n        end\n\n        # Remove styling divs while lifting children\n        styling_prefixes = %w[ps- mx- my- px- py- mb- sp- rounded-]\n        selectors = styling_prefixes.map { |prefix| \"div[class*=\\\"#{prefix}\\\"]:not(.note)\" }\n        css(*selectors, 'div[class=\"\"]', 'div.cm-line').each do |node|\n          node.before(node.children).remove\n        end\n\n        # Syntax highlighting\n        css('pre br').each do |node|\n          node.replace(\"\\n\")\n        end\n        css('pre').each do |node|\n          node['data-language'] = 'jsx'\n        end\n\n        # Remove styling except for callouts and images\n        css('*:not([class*=image]):not(.note)').remove_attr('class').remove_attr('style')\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/react/entries.rb",
    "content": "module Docs\n  class React\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('article h1').content.sub(' (Experimental)', '')\n      end\n\n      def get_type\n        return 'API Reference' if slug == 'legacy-context'\n        return 'Glossary' if slug == 'glossary'\n        link = css(\"nav a[href='#{result[:path].split('/').last}']\").last\n        return 'Miscellaneous' unless link\n        type = link.ancestors('ul').last.previous_element.content\n        type.remove! %r{\\s*\\(.*\\)}\n        if type == 'Concurrent Mode'\n          type + ' (Experimental)' # TODO: Remove when CM is stable\n        else\n          type\n        end\n      end\n\n      def additional_entries\n        entries = []\n\n        is_glossary = slug == 'glossary'\n        css(is_glossary ? 'article h2, article h3' : 'article h3 code, article h4 code').each do |node|\n          next if !is_glossary && node.previous.try(:content).present?\n          next if slug == 'testing-recipes'\n          name = node.content.strip\n          # name.remove! %r{[#\\(\\)]}\n          # name.remove! %r{\\w+\\:}\n          # name.strip!\n          # name = 'createFragmentobject' if name.include?('createFragmentobject')\n          type = if slug == 'react-component'\n            'Reference: Component'\n          elsif slug == 'react-api'\n            'Reference: React'\n          elsif slug == 'hooks-reference'\n            'Hooks'\n          elsif slug == 'test-utils'\n            'Reference: Test Utilities'\n          elsif slug == 'glossary'\n            'Glossary'\n          else\n            'Reference'\n          end\n          entries << [name, node['id'] || node.ancestors('[id]').first['id'], type]\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/react/entries_react_dev.rb",
    "content": "module Docs\n  class React\n    class EntriesReactDevFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('article h1')&.content\n\n        update_canary_copy(name)\n      end\n\n      def get_type\n        # Category is the opened category in the sidebar\n        category = css('a:has(> span.text-link) > div').first&.content\n        # The grey category in the sidebar\n        top_category = css('h3:has(~ li a.text-link)')\n                         .last&.content\n                         &.sub(/@.*$/, '') # remove version tag\n                         &.sub(/^./, &:upcase) # capitalize first letter\n                         &.concat(\": \")\n        is_learn_page = path.start_with?('learn/') || slug == 'learn'\n        prefix = is_learn_page ? 'Learn: ' : top_category || ''\n\n        update_canary_copy(prefix + (category || 'Miscellaneous'))\n      end\n\n      def update_canary_copy(string)\n        canary_copy = '- This feature is available in the latest Canary'\n\n        string.sub(canary_copy, ' (Canary)')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/react_bootstrap/clean_html.rb",
    "content": "module Docs\n  class ReactBootstrap\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = doc.at_css('main')\n\n        css('.flex-column.d-flex').remove\n        css('header').remove\n        css('.bs-example').remove\n\n        css('.position-relative pre').each do |node|\n          node.content = node.children.map(&:content).join(\"\\n\")\n          node.remove_attribute('style')\n          node['data-language'] = 'jsx'\n          node.parent.replace(node)\n        end\n\n        css('div, main, pre, h1, h2, h3, h4, h5, h6, a, p').each do |node|\n          node.delete 'class'\n        end\n        css('h1, h2, h3, h4, h5, h6').each do |node|\n          node.css('a').remove\n          node.content = node.content\n        end\n\n        css('#___gatsby, #gatsby-focus-wrapper').each do |node|\n          node.delete 'id'\n        end\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/react_bootstrap/entries.rb",
    "content": "module Docs\n  class ReactBootstrap\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('#rb-docs-content h1, #rb-docs-content h2').content\n        if name.end_with? '#'\n          name = name[0..-2]\n        end\n        name\n      end\n\n      def get_type\n        type = slug.split('/')[0..-2].join(': ')\n        if type == ''\n          type = slug.split('/').join('')\n        end\n        type.gsub!('-', ' ')\n        type = type.split.map(&:capitalize).join(' ')\n        type\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/react_native/clean_html.rb",
    "content": "module Docs\n  class ReactNative\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('main .col .markdown')\n\n        if root_page?\n          at_css('h1').content = 'React Native Documentation'\n          css('h1 ~ *').remove\n        end\n\n        css('header').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('.content-banner-img').remove\n        css('.anchor').remove_attribute('class')\n        css('button[aria-label=\"Copy code to clipboard\"]').remove\n        css('h2#example').remove\n\n        css('pre').each do |node|\n          node.content = node.css('.token-line').map(&:content).join(\"\\n\")\n          node.remove_attribute('class')\n          node['data-language'] = 'jsx'\n        end\n\n        #\n\n        css('.docs-prevnext', '.hash-link', '.edit-page-link', '.edit-github', 'a.hash', '.edit-page-block', 'a.show', 'a.hide', 'hr').remove\n\n        css('table h1', 'table h2', 'table h3').each do |node|\n          table = node\n          table = table.parent until table.name == 'table'\n          table.replace(node)\n        end\n\n        css('a.anchor', 'a.hashref').each do |node|\n          node.parent['id'] ||= node['name'] || node['id']\n        end\n\n        css('table.highlighttable').each do |node|\n          node.replace(node.at_css('pre.highlight'))\n        end\n\n        css('pre > code.hljs').each do |node|\n          node.parent['data-language'] = 'jsx'\n          node.before(node.children).remove\n        end\n\n        css('blockquote > p:first-child').each do |node|\n          node.remove if node.content.strip == 'Note:'\n        end\n\n        css('h3#props', 'h3#methods').each { |node| node.name = 'h2' }\n        css('h4.propTitle').each { |node| node.name = 'h3' }\n\n        css('> div > div', '> div', 'div > span', '.props', '.prop', '> article', '.postHeader', '.web-player').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('a pre', 'h3 .propType').each do |node|\n          node.name = 'code'\n        end\n\n        css('a[target]').each do |node|\n          node.remove_attribute('target')\n        end\n\n        css('center > .button', 'p:contains(\"short survey\")', 'iframe', '.embedded-simulator', '.deprecatedIcon').remove\n\n        css('h4.methodTitle').each do |node|\n          node.name = 'h3'\n        end\n\n        css('div:not([class])', 'span:not([class])').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('ul').each do |node|\n          node.before(node.children).remove if node.at_css('> p', '> h2')\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/react_native/entries.rb",
    "content": "module Docs\n  class ReactNative\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').children.select(&:text?).map(&:content).join.strip\n      end\n\n      def get_type\n        type = at_css('.menu__link--active')\n        return 'Miscellaneous' unless type\n        type.content.strip\n      end\n\n      def additional_entries\n        css('main .col .markdown h3').each_with_object [] do |node, entries|\n          code = node.at_css('code')\n          next unless code\n          subname = code.text\n          next if subname.blank? || node.css('code').empty?\n          sep = subname.include?('()') ? '.' : '#'\n          subname.prepend(name + sep)\n          id = node['id']\n          entries << [subname, id]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/react_router/clean_html.rb",
    "content": "module Docs\n  class ReactRouter\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.md-prose')\n        css('pre').each do |node|\n          node.content = node.css('.codeblock-line').map(&:content).join(\"\")\n          node['data-language'] = 'javascript'\n        end\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/react_router/entries.rb",
    "content": "module Docs\n  class ReactRouter\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('.markdown h1').content\n      end\n\n      def get_type\n        slug.split('/').first.capitalize\n      end\n\n      def additional_entries\n        entries = []\n        css('h2[id], h3[id]').each do |node|\n          entries << [node.content, node['id'], 'API Reference']\n        end\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/reactivex/clean_html.rb",
    "content": "module Docs\n  class Reactivex\n    class CleanHtmlFilter < Filter\n      def call\n        # Can't use options[:container] because then the navigation bar wouldn't be scraped\n        @doc = at_css(root_page? ? '.col-md-8' : '.col-sm-8')\n\n        # Remove breadcrumbs\n        css('.breadcrumb').remove\n\n        # Titleize title on Backpressure Operators page\n        if subpath == 'documentation/operators/backpressure.html'\n          title = at_css('h1')\n          title.content = title.content.titleize\n        end\n\n        # Lower all h1 headers except the first one\n        css('* + h1').each do |node|\n          node.name = 'h2'\n        end\n\n        # Pull code blocks in links out of their <strong> parent (if possible)\n        css('a > strong > code').each do |node|\n          # Skip if the parent had multiple code nodes and node.parent.replace already ran for one\n          next unless node.parent.name == 'strong'\n\n          node.parent.replace node.parent.children\n        end\n\n        # Pull header out of trees\n        tree = at_css('#tree')\n        unless tree.nil?\n          title = tree.at_css('h1')\n          title.name = 'h2'\n          tree.before(title)\n        end\n\n        # Beautify operator descriptions\n        at_css('h3').name = 'blockquote' if subpath.include?('operators/')\n\n        # Replace interactive demo's with links to them\n        css('rx-marbles').each do |node|\n          node.name = 'a'\n          node.content = 'Open interactive diagram on rxmarbles.com'\n          node['href'] = \"https://rxmarbles.com/##{node['key']}\"\n          node.remove_attribute('key')\n        end\n\n        # Syntax-highlighted code blocks\n        css('.code').each do |node|\n          language = node['class'].gsub('code', '').strip\n\n          pre = node.at_css('pre')\n          pre['data-language'] = language\n          pre.content = pre.content.strip\n\n          node.replace(pre)\n        end\n\n        # Assume JavaScript syntax for code blocks not surrounded by a div.code\n        css('pre').each do |node|\n          next if node['data-language']\n\n          node.content = node.content.strip\n          node['data-language'] = 'javascript'\n        end\n\n        # Make language specific implementation titles prettier\n        css('.panel-title').each do |node|\n          # Remove the link, keep the children\n          link = node.at_css('a')\n          link.replace(link.children) unless link.nil?\n\n          # Transform it into a header for better styling\n          node.name = 'h3'\n        end\n\n        # Remove language specific implementations that are TBD\n        css('span').each do |node|\n          next unless node.content == 'TBD'\n          node.xpath('./ancestor::div[contains(@class, \"panel-default\")][1]').remove\n        end\n\n        # Remove the : at the end of \"Language-Specific Information:\"\n        css('h2').each do |node|\n          node.inner_html = node.inner_html.gsub('Information:', 'Information')\n        end\n\n        # Remove attributes to reduce file size\n        css('div').remove_attr('class')\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/reactivex/entries.rb",
    "content": "module Docs\n  class Reactivex\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        title = at_css('h1').content\n        title = title.titleize if is_backpressure_operators?\n        title\n      end\n\n      def get_type\n        return 'Manual' if is_backpressure_operators?\n        links = css('.breadcrumb > li:nth-child(2) > a')\n        links.size > 0 ? links.first.content : 'Manual'\n      end\n\n      def is_backpressure_operators?\n        subpath == 'documentation/operators/backpressure.html'\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/redis/clean_html.rb",
    "content": "module Docs\n  class Redis\n    class CleanHtmlFilter < Filter\n      def call\n        if root_page?\n          if root_page?\n            slug_types = {}\n            css('article[data-group]').each do |node|\n              slug_types[node.at_css('a')['href']] = node['data-group']\n            end\n            # binding.pry\n          end\n        else\n          title = at_css('h1')\n          title.after(\"<pre>#{title.content.strip}</pre>\")\n          title.content = title.content.split(' ').first\n        end\n\n        css('nav', 'aside', '.page-feedback', '.anchor-link').remove\n\n        css('> article', '.article-main', 'pre > code', '.container').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('.example > pre').each do |node|\n          node.name = 'code'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/redis/entries.rb",
    "content": "module Docs\n  class Redis\n    class EntriesFilter < Docs::EntriesFilter\n\n      def get_name\n        slug.gsub('-', ' ').remove('/')\n      end\n\n      def get_type\n        SLUG_NAMES[SLUG_TYPES[slug]]\n      end\n\n      # obtain from https://redis.io/commands/ via lib/docs/filters/redis/clean_html.rb using `binding.pry`\n      SLUG_TYPES = {\n        \"acl-cat/\" => \"server\",\n        \"acl-deluser/\" => \"server\",\n        \"acl-dryrun/\" => \"server\",\n        \"acl-genpass/\" => \"server\",\n        \"acl-getuser/\" => \"server\",\n        \"acl-list/\" => \"server\",\n        \"acl-load/\" => \"server\",\n        \"acl-log/\" => \"server\",\n        \"acl-save/\" => \"server\",\n        \"acl-setuser/\" => \"server\",\n        \"acl-users/\" => \"server\",\n        \"acl-whoami/\" => \"server\",\n        \"append/\" => \"string\",\n        \"asking/\" => \"cluster\",\n        \"auth/\" => \"connection\",\n        \"bf.add/\" => \"bf\",\n        \"bf.card/\" => \"bf\",\n        \"bf.exists/\" => \"bf\",\n        \"bf.info/\" => \"bf\",\n        \"bf.insert/\" => \"bf\",\n        \"bf.loadchunk/\" => \"bf\",\n        \"bf.madd/\" => \"bf\",\n        \"bf.mexists/\" => \"bf\",\n        \"bf.reserve/\" => \"bf\",\n        \"bf.scandump/\" => \"bf\",\n        \"bgrewriteaof/\" => \"server\",\n        \"bgsave/\" => \"server\",\n        \"bitcount/\" => \"bitmap\",\n        \"bitfield/\" => \"bitmap\",\n        \"bitfield_ro/\" => \"bitmap\",\n        \"bitop/\" => \"bitmap\",\n        \"bitpos/\" => \"bitmap\",\n        \"blmove/\" => \"list\",\n        \"blmpop/\" => \"list\",\n        \"blpop/\" => \"list\",\n        \"brpop/\" => \"list\",\n        \"brpoplpush/\" => \"list\",\n        \"bzmpop/\" => \"sorted-set\",\n        \"bzpopmax/\" => \"sorted-set\",\n        \"bzpopmin/\" => \"sorted-set\",\n        \"cf.add/\" => \"cf\",\n        \"cf.addnx/\" => \"cf\",\n        \"cf.count/\" => \"cf\",\n        \"cf.del/\" => \"cf\",\n        \"cf.exists/\" => \"cf\",\n        \"cf.info/\" => \"cf\",\n        \"cf.insert/\" => \"cf\",\n        \"cf.insertnx/\" => \"cf\",\n        \"cf.loadchunk/\" => \"cf\",\n        \"cf.mexists/\" => \"cf\",\n        \"cf.reserve/\" => \"cf\",\n        \"cf.scandump/\" => \"cf\",\n        \"client-caching/\" => \"connection\",\n        \"client-getname/\" => \"connection\",\n        \"client-getredir/\" => \"connection\",\n        \"client-id/\" => \"connection\",\n        \"client-info/\" => \"connection\",\n        \"client-kill/\" => \"connection\",\n        \"client-list/\" => \"connection\",\n        \"client-no-evict/\" => \"connection\",\n        \"client-pause/\" => \"connection\",\n        \"client-reply/\" => \"connection\",\n        \"client-setname/\" => \"connection\",\n        \"client-tracking/\" => \"connection\",\n        \"client-trackinginfo/\" => \"connection\",\n        \"client-unblock/\" => \"connection\",\n        \"client-unpause/\" => \"connection\",\n        \"cluster-addslots/\" => \"cluster\",\n        \"cluster-addslotsrange/\" => \"cluster\",\n        \"cluster-bumpepoch/\" => \"cluster\",\n        \"cluster-count-failure-reports/\" => \"cluster\",\n        \"cluster-countkeysinslot/\" => \"cluster\",\n        \"cluster-delslots/\" => \"cluster\",\n        \"cluster-delslotsrange/\" => \"cluster\",\n        \"cluster-failover/\" => \"cluster\",\n        \"cluster-flushslots/\" => \"cluster\",\n        \"cluster-forget/\" => \"cluster\",\n        \"cluster-getkeysinslot/\" => \"cluster\",\n        \"cluster-info/\" => \"cluster\",\n        \"cluster-keyslot/\" => \"cluster\",\n        \"cluster-links/\" => \"cluster\",\n        \"cluster-meet/\" => \"cluster\",\n        \"cluster-myid/\" => \"cluster\",\n        \"cluster-nodes/\" => \"cluster\",\n        \"cluster-replicas/\" => \"cluster\",\n        \"cluster-replicate/\" => \"cluster\",\n        \"cluster-reset/\" => \"cluster\",\n        \"cluster-saveconfig/\" => \"cluster\",\n        \"cluster-set-config-epoch/\" => \"cluster\",\n        \"cluster-setslot/\" => \"cluster\",\n        \"cluster-shards/\" => \"cluster\",\n        \"cluster-slaves/\" => \"cluster\",\n        \"cluster-slots/\" => \"cluster\",\n        \"cms.incrby/\" => \"cms\",\n        \"cms.info/\" => \"cms\",\n        \"cms.initbydim/\" => \"cms\",\n        \"cms.initbyprob/\" => \"cms\",\n        \"cms.merge/\" => \"cms\",\n        \"cms.query/\" => \"cms\",\n        \"command/\" => \"server\",\n        \"command-count/\" => \"server\",\n        \"command-docs/\" => \"server\",\n        \"command-getkeys/\" => \"server\",\n        \"command-getkeysandflags/\" => \"server\",\n        \"command-info/\" => \"server\",\n        \"command-list/\" => \"server\",\n        \"config-get/\" => \"server\",\n        \"config-resetstat/\" => \"server\",\n        \"config-rewrite/\" => \"server\",\n        \"config-set/\" => \"server\",\n        \"copy/\" => \"generic\",\n        \"dbsize/\" => \"server\",\n        \"decr/\" => \"string\",\n        \"decrby/\" => \"string\",\n        \"del/\" => \"generic\",\n        \"discard/\" => \"transactions\",\n        \"dump/\" => \"generic\",\n        \"echo/\" => \"connection\",\n        \"eval/\" => \"scripting\",\n        \"eval_ro/\" => \"scripting\",\n        \"evalsha/\" => \"scripting\",\n        \"evalsha_ro/\" => \"scripting\",\n        \"exec/\" => \"transactions\",\n        \"exists/\" => \"generic\",\n        \"expire/\" => \"generic\",\n        \"expireat/\" => \"generic\",\n        \"expiretime/\" => \"generic\",\n        \"failover/\" => \"server\",\n        \"fcall/\" => \"scripting\",\n        \"fcall_ro/\" => \"scripting\",\n        \"flushall/\" => \"server\",\n        \"flushdb/\" => \"server\",\n        \"ft._list/\" => \"search\",\n        \"ft.aggregate/\" => \"search\",\n        \"ft.aliasadd/\" => \"search\",\n        \"ft.aliasdel/\" => \"search\",\n        \"ft.aliasupdate/\" => \"search\",\n        \"ft.alter/\" => \"search\",\n        \"ft.config-get/\" => \"search\",\n        \"ft.config-set/\" => \"search\",\n        \"ft.create/\" => \"search\",\n        \"ft.cursor-del/\" => \"search\",\n        \"ft.cursor-read/\" => \"search\",\n        \"ft.dictadd/\" => \"search\",\n        \"ft.dictdel/\" => \"search\",\n        \"ft.dictdump/\" => \"search\",\n        \"ft.dropindex/\" => \"search\",\n        \"ft.explain/\" => \"search\",\n        \"ft.explaincli/\" => \"search\",\n        \"ft.info/\" => \"search\",\n        \"ft.profile/\" => \"search\",\n        \"ft.search/\" => \"search\",\n        \"ft.spellcheck/\" => \"search\",\n        \"ft.sugadd/\" => \"suggestion\",\n        \"ft.sugdel/\" => \"suggestion\",\n        \"ft.sugget/\" => \"suggestion\",\n        \"ft.suglen/\" => \"suggestion\",\n        \"ft.syndump/\" => \"search\",\n        \"ft.synupdate/\" => \"search\",\n        \"ft.tagvals/\" => \"search\",\n        \"function-delete/\" => \"scripting\",\n        \"function-dump/\" => \"scripting\",\n        \"function-flush/\" => \"scripting\",\n        \"function-kill/\" => \"scripting\",\n        \"function-list/\" => \"scripting\",\n        \"function-load/\" => \"scripting\",\n        \"function-restore/\" => \"scripting\",\n        \"function-stats/\" => \"scripting\",\n        \"geoadd/\" => \"geo\",\n        \"geodist/\" => \"geo\",\n        \"geohash/\" => \"geo\",\n        \"geopos/\" => \"geo\",\n        \"georadius/\" => \"geo\",\n        \"georadius_ro/\" => \"geo\",\n        \"georadiusbymember/\" => \"geo\",\n        \"georadiusbymember_ro/\" => \"geo\",\n        \"geosearch/\" => \"geo\",\n        \"geosearchstore/\" => \"geo\",\n        \"get/\" => \"string\",\n        \"getbit/\" => \"bitmap\",\n        \"getdel/\" => \"string\",\n        \"getex/\" => \"string\",\n        \"getrange/\" => \"string\",\n        \"getset/\" => \"string\",\n        \"graph.config-get/\" => \"graph\",\n        \"graph.config-set/\" => \"graph\",\n        \"graph.delete/\" => \"graph\",\n        \"graph.explain/\" => \"graph\",\n        \"graph.list/\" => \"graph\",\n        \"graph.profile/\" => \"graph\",\n        \"graph.query/\" => \"graph\",\n        \"graph.ro_query/\" => \"graph\",\n        \"graph.slowlog/\" => \"graph\",\n        \"hdel/\" => \"hash\",\n        \"hello/\" => \"connection\",\n        \"hexists/\" => \"hash\",\n        \"hget/\" => \"hash\",\n        \"hgetall/\" => \"hash\",\n        \"hincrby/\" => \"hash\",\n        \"hincrbyfloat/\" => \"hash\",\n        \"hkeys/\" => \"hash\",\n        \"hlen/\" => \"hash\",\n        \"hmget/\" => \"hash\",\n        \"hmset/\" => \"hash\",\n        \"hrandfield/\" => \"hash\",\n        \"hscan/\" => \"hash\",\n        \"hset/\" => \"hash\",\n        \"hsetnx/\" => \"hash\",\n        \"hstrlen/\" => \"hash\",\n        \"hvals/\" => \"hash\",\n        \"incr/\" => \"string\",\n        \"incrby/\" => \"string\",\n        \"incrbyfloat/\" => \"string\",\n        \"info/\" => \"server\",\n        \"json.arrappend/\" => \"json\",\n        \"json.arrindex/\" => \"json\",\n        \"json.arrinsert/\" => \"json\",\n        \"json.arrlen/\" => \"json\",\n        \"json.arrpop/\" => \"json\",\n        \"json.arrtrim/\" => \"json\",\n        \"json.clear/\" => \"json\",\n        \"json.debug/\" => \"json\",\n        \"json.debug-memory/\" => \"json\",\n        \"json.del/\" => \"json\",\n        \"json.forget/\" => \"json\",\n        \"json.get/\" => \"json\",\n        \"json.mget/\" => \"json\",\n        \"json.numincrby/\" => \"json\",\n        \"json.nummultby/\" => \"json\",\n        \"json.objkeys/\" => \"json\",\n        \"json.objlen/\" => \"json\",\n        \"json.resp/\" => \"json\",\n        \"json.set/\" => \"json\",\n        \"json.strappend/\" => \"json\",\n        \"json.strlen/\" => \"json\",\n        \"json.toggle/\" => \"json\",\n        \"json.type/\" => \"json\",\n        \"keys/\" => \"generic\",\n        \"lastsave/\" => \"server\",\n        \"latency-doctor/\" => \"server\",\n        \"latency-graph/\" => \"server\",\n        \"latency-histogram/\" => \"server\",\n        \"latency-history/\" => \"server\",\n        \"latency-latest/\" => \"server\",\n        \"latency-reset/\" => \"server\",\n        \"lcs/\" => \"string\",\n        \"lindex/\" => \"list\",\n        \"linsert/\" => \"list\",\n        \"llen/\" => \"list\",\n        \"lmove/\" => \"list\",\n        \"lmpop/\" => \"list\",\n        \"lolwut/\" => \"server\",\n        \"lpop/\" => \"list\",\n        \"lpos/\" => \"list\",\n        \"lpush/\" => \"list\",\n        \"lpushx/\" => \"list\",\n        \"lrange/\" => \"list\",\n        \"lrem/\" => \"list\",\n        \"lset/\" => \"list\",\n        \"ltrim/\" => \"list\",\n        \"memory-doctor/\" => \"server\",\n        \"memory-malloc-stats/\" => \"server\",\n        \"memory-purge/\" => \"server\",\n        \"memory-stats/\" => \"server\",\n        \"memory-usage/\" => \"server\",\n        \"mget/\" => \"string\",\n        \"migrate/\" => \"generic\",\n        \"module-list/\" => \"server\",\n        \"module-load/\" => \"server\",\n        \"module-loadex/\" => \"server\",\n        \"module-unload/\" => \"server\",\n        \"monitor/\" => \"server\",\n        \"move/\" => \"generic\",\n        \"mset/\" => \"string\",\n        \"msetnx/\" => \"string\",\n        \"multi/\" => \"transactions\",\n        \"object-encoding/\" => \"generic\",\n        \"object-freq/\" => \"generic\",\n        \"object-idletime/\" => \"generic\",\n        \"object-refcount/\" => \"generic\",\n        \"persist/\" => \"generic\",\n        \"pexpire/\" => \"generic\",\n        \"pexpireat/\" => \"generic\",\n        \"pexpiretime/\" => \"generic\",\n        \"pfadd/\" => \"hyperloglog\",\n        \"pfcount/\" => \"hyperloglog\",\n        \"pfdebug/\" => \"hyperloglog\",\n        \"pfmerge/\" => \"hyperloglog\",\n        \"pfselftest/\" => \"hyperloglog\",\n        \"ping/\" => \"connection\",\n        \"psetex/\" => \"string\",\n        \"psubscribe/\" => \"pubsub\",\n        \"psync/\" => \"server\",\n        \"pttl/\" => \"generic\",\n        \"publish/\" => \"pubsub\",\n        \"pubsub-channels/\" => \"pubsub\",\n        \"pubsub-numpat/\" => \"pubsub\",\n        \"pubsub-numsub/\" => \"pubsub\",\n        \"pubsub-shardchannels/\" => \"pubsub\",\n        \"pubsub-shardnumsub/\" => \"pubsub\",\n        \"punsubscribe/\" => \"pubsub\",\n        \"quit/\" => \"connection\",\n        \"randomkey/\" => \"generic\",\n        \"readonly/\" => \"cluster\",\n        \"readwrite/\" => \"cluster\",\n        \"rename/\" => \"generic\",\n        \"renamenx/\" => \"generic\",\n        \"replconf/\" => \"server\",\n        \"replicaof/\" => \"server\",\n        \"reset/\" => \"connection\",\n        \"restore/\" => \"generic\",\n        \"restore-asking/\" => \"server\",\n        \"role/\" => \"server\",\n        \"rpop/\" => \"list\",\n        \"rpoplpush/\" => \"list\",\n        \"rpush/\" => \"list\",\n        \"rpushx/\" => \"list\",\n        \"sadd/\" => \"set\",\n        \"save/\" => \"server\",\n        \"scan/\" => \"generic\",\n        \"scard/\" => \"set\",\n        \"script-debug/\" => \"scripting\",\n        \"script-exists/\" => \"scripting\",\n        \"script-flush/\" => \"scripting\",\n        \"script-kill/\" => \"scripting\",\n        \"script-load/\" => \"scripting\",\n        \"sdiff/\" => \"set\",\n        \"sdiffstore/\" => \"set\",\n        \"select/\" => \"connection\",\n        \"set/\" => \"string\",\n        \"setbit/\" => \"bitmap\",\n        \"setex/\" => \"string\",\n        \"setnx/\" => \"string\",\n        \"setrange/\" => \"string\",\n        \"shutdown/\" => \"server\",\n        \"sinter/\" => \"set\",\n        \"sintercard/\" => \"set\",\n        \"sinterstore/\" => \"set\",\n        \"sismember/\" => \"set\",\n        \"slaveof/\" => \"server\",\n        \"slowlog-get/\" => \"server\",\n        \"slowlog-len/\" => \"server\",\n        \"slowlog-reset/\" => \"server\",\n        \"smembers/\" => \"set\",\n        \"smismember/\" => \"set\",\n        \"smove/\" => \"set\",\n        \"sort/\" => \"generic\",\n        \"sort_ro/\" => \"generic\",\n        \"spop/\" => \"set\",\n        \"spublish/\" => \"pubsub\",\n        \"srandmember/\" => \"set\",\n        \"srem/\" => \"set\",\n        \"sscan/\" => \"set\",\n        \"ssubscribe/\" => \"pubsub\",\n        \"strlen/\" => \"string\",\n        \"subscribe/\" => \"pubsub\",\n        \"substr/\" => \"string\",\n        \"sunion/\" => \"set\",\n        \"sunionstore/\" => \"set\",\n        \"sunsubscribe/\" => \"pubsub\",\n        \"swapdb/\" => \"server\",\n        \"sync/\" => \"server\",\n        \"tdigest.add/\" => \"tdigest\",\n        \"tdigest.byrank/\" => \"tdigest\",\n        \"tdigest.byrevrank/\" => \"tdigest\",\n        \"tdigest.cdf/\" => \"tdigest\",\n        \"tdigest.create/\" => \"tdigest\",\n        \"tdigest.info/\" => \"tdigest\",\n        \"tdigest.max/\" => \"tdigest\",\n        \"tdigest.merge/\" => \"tdigest\",\n        \"tdigest.min/\" => \"tdigest\",\n        \"tdigest.quantile/\" => \"tdigest\",\n        \"tdigest.rank/\" => \"tdigest\",\n        \"tdigest.reset/\" => \"tdigest\",\n        \"tdigest.revrank/\" => \"tdigest\",\n        \"tdigest.trimmed_mean/\" => \"tdigest\",\n        \"time/\" => \"server\",\n        \"topk.add/\" => \"topk\",\n        \"topk.count/\" => \"topk\",\n        \"topk.incrby/\" => \"topk\",\n        \"topk.info/\" => \"topk\",\n        \"topk.list/\" => \"topk\",\n        \"topk.query/\" => \"topk\",\n        \"topk.reserve/\" => \"topk\",\n        \"touch/\" => \"generic\",\n        \"ts.add/\" => \"timeseries\",\n        \"ts.alter/\" => \"timeseries\",\n        \"ts.create/\" => \"timeseries\",\n        \"ts.createrule/\" => \"timeseries\",\n        \"ts.decrby/\" => \"timeseries\",\n        \"ts.del/\" => \"timeseries\",\n        \"ts.deleterule/\" => \"timeseries\",\n        \"ts.get/\" => \"timeseries\",\n        \"ts.incrby/\" => \"timeseries\",\n        \"ts.info/\" => \"timeseries\",\n        \"ts.madd/\" => \"timeseries\",\n        \"ts.mget/\" => \"timeseries\",\n        \"ts.mrange/\" => \"timeseries\",\n        \"ts.mrevrange/\" => \"timeseries\",\n        \"ts.queryindex/\" => \"timeseries\",\n        \"ts.range/\" => \"timeseries\",\n        \"ts.revrange/\" => \"timeseries\",\n        \"ttl/\" => \"generic\",\n        \"type/\" => \"generic\",\n        \"unlink/\" => \"generic\",\n        \"unsubscribe/\" => \"pubsub\",\n        \"unwatch/\" => \"transactions\",\n        \"wait/\" => \"generic\",\n        \"watch/\" => \"transactions\",\n        \"xack/\" => \"stream\",\n        \"xadd/\" => \"stream\",\n        \"xautoclaim/\" => \"stream\",\n        \"xclaim/\" => \"stream\",\n        \"xdel/\" => \"stream\",\n        \"xgroup-create/\" => \"stream\",\n        \"xgroup-createconsumer/\" => \"stream\",\n        \"xgroup-delconsumer/\" => \"stream\",\n        \"xgroup-destroy/\" => \"stream\",\n        \"xgroup-setid/\" => \"stream\",\n        \"xinfo-consumers/\" => \"stream\",\n        \"xinfo-groups/\" => \"stream\",\n        \"xinfo-stream/\" => \"stream\",\n        \"xlen/\" => \"stream\",\n        \"xpending/\" => \"stream\",\n        \"xrange/\" => \"stream\",\n        \"xread/\" => \"stream\",\n        \"xreadgroup/\" => \"stream\",\n        \"xrevrange/\" => \"stream\",\n        \"xsetid/\" => \"stream\",\n        \"xtrim/\" => \"stream\",\n        \"zadd/\" => \"sorted-set\",\n        \"zcard/\" => \"sorted-set\",\n        \"zcount/\" => \"sorted-set\",\n        \"zdiff/\" => \"sorted-set\",\n        \"zdiffstore/\" => \"sorted-set\",\n        \"zincrby/\" => \"sorted-set\",\n        \"zinter/\" => \"sorted-set\",\n        \"zintercard/\" => \"sorted-set\",\n        \"zinterstore/\" => \"sorted-set\",\n        \"zlexcount/\" => \"sorted-set\",\n        \"zmpop/\" => \"sorted-set\",\n        \"zmscore/\" => \"sorted-set\",\n        \"zpopmax/\" => \"sorted-set\",\n        \"zpopmin/\" => \"sorted-set\",\n        \"zrandmember/\" => \"sorted-set\",\n        \"zrange/\" => \"sorted-set\",\n        \"zrangebylex/\" => \"sorted-set\",\n        \"zrangebyscore/\" => \"sorted-set\",\n        \"zrangestore/\" => \"sorted-set\",\n        \"zrank/\" => \"sorted-set\",\n        \"zrem/\" => \"sorted-set\",\n        \"zremrangebylex/\" => \"sorted-set\",\n        \"zremrangebyrank/\" => \"sorted-set\",\n        \"zremrangebyscore/\" => \"sorted-set\",\n        \"zrevrange/\" => \"sorted-set\",\n        \"zrevrangebylex/\" => \"sorted-set\",\n        \"zrevrangebyscore/\" => \"sorted-set\",\n        \"zrevrank/\" => \"sorted-set\",\n        \"zscan/\" => \"sorted-set\",\n        \"zscore/\" => \"sorted-set\",\n        \"zunion/\" => \"sorted-set\",\n        \"zunionstore/\" => \"sorted-set\"\n      }\n\n      SLUG_NAMES = {\n        \"bitmap\" => \"Core: Bitmap\",\n        \"cluster\" => \"Core: Cluster management\",\n        \"connection\" => \"Core: Connection management\",\n        \"generic\" => \"Core: Generic\",\n        \"geo\" => \"Core: Geospatial indices\",\n        \"hash\" => \"Core: Hash\",\n        \"hyperloglog\" => \"Core: HyperLogLog\",\n        \"list\" => \"Core: List\",\n        \"pubsub\" => \"Core: Pub/Sub\",\n        \"scripting\" => \"Core: Scripting and Functions\",\n        \"server\" => \"Core: Server managment\",\n        \"set\" => \"Core: Set\",\n        \"sorted-set\" => \"Core: Sorted Set\",\n        \"stream\" => \"Core: Stream\",\n        \"string\" => \"Core: String\",\n        \"transactions\" => \"Core: Transactions\",\n        \"bf\" => \"Stack: Bloom Filter\",\n        \"cf\" => \"Stack: Cuckoo Filter\",\n        \"cms\" => \"Stack: Count-min Sketch\",\n        \"graph\" => \"Stack: Graph\",\n        \"json\" => \"Stack: JSON\",\n        \"search\" => \"Stack: Search\",\n        \"suggestion\" => \"Stack: Auto-Suggest\",\n        \"tdigest\" => \"Stack: T-Digest\",\n        \"timeseries\" => \"Stack: Time Series\",\n        \"topk\" => \"Stack: Top-K\",\n      }\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/redux/clean_html.rb",
    "content": "module Docs\n  class Redux\n    class CleanHtmlFilter < Filter\n      def call\n\n        css('h1, h2, h3, h4, h5').each do |node|\n          node.css('a').remove\n          node.remove_attribute('class')\n          node.parent.before(node.parent.children).remove if node.parent.name == 'header'\n        end\n\n        css('h3').each do |node|\n          node['id'] = node.content.gsub(/\\(|\\)/, '').downcase\n        end\n\n        css('pre').each do |node|\n          node.content = node.css('.token-line').map(&:content).join(\"\\n\")\n          node['data-language'] = 'javascript'\n        end\n\n        css('*').each do |node|\n          node.remove_attribute('style')\n          node.remove if node['class'] && node['class'].include?('copyButton')\n        end\n\n        doc\n\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/redux/entries.rb",
    "content": "module Docs\n  class Redux\n    class EntriesFilter < Docs::EntriesFilter\n\n      def get_name\n        name = at_css('h1').content\n        name.gsub(/\\(.*\\)/, '()')\n      end\n\n      def get_type\n        slug.match?(/\\Astore\\Z/) ? 'Store API' : 'Top-Level Exports'\n      end\n\n      def additional_entries\n        entries = []\n\n        if slug.match?(/\\Astore\\Z/)\n          css('h3').each do |node|\n            entry_path = node.content.gsub(/\\(|\\)/, '')\n            entry_name = node.content.gsub(/\\(.*\\)/, '()')\n            entries << [entry_name, entry_path.downcase, 'Store API']\n          end\n        end\n\n        entries\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/relay/clean_html.rb",
    "content": "module Docs\n  class Relay\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.post')\n\n        header = at_css('h1')\n        header.parent.before(header).remove\n\n        css('footer').remove\n\n        css('h2, h3').each do |node|\n          node['id'] = node.at_css('a.anchor')['id']\n        end\n\n        # syntax highlight\n        css('pre').each do |node|\n          node['data-language'] = 'javascript'\n          node.add_class('highlight')\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/relay/entries.rb",
    "content": "module Docs\n  class Relay\n    class EntriesFilter < Docs::EntriesFilter\n      ONLY_SECTIONS = ['API Reference', 'Principles & Architecture']\n      ONLY_SLUGS = []\n\n      def call\n        if root_page?\n          css('.navGroup > h3').each do |node|\n            next if not ONLY_SECTIONS.include? node.content\n            node.next_element.css('a').each do |anchor|\n              ONLY_SLUGS << anchor['href'].split('/').last.strip\n            end\n          end\n        end\n        super\n      end\n\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        at_css('h1').content\n      end\n\n      def include_default_entry?\n        ONLY_SLUGS.include? slug\n      end\n\n      def additional_entries\n        return [] if not include_default_entry?\n\n        css('article h2, article h3').each_with_object [] do |node, entries|\n          next if node.content.include?('Argument') ||\n                  node.content.starts_with?('Example')\n\n          name = node.content\n          if name.include?('(')\n            name = name.match(/.*\\(/)[0] + ')'\n          end\n          id = node.at_css('a.anchor')['id']\n          entries << [name, id]\n        end\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/requests/entries.rb",
    "content": "module Docs\n  class Requests\n    class EntriesFilter < Docs::EntriesFilter\n      TYPE_BY_SLUG = {}\n\n      def call\n        if root_page?\n          css('.section').each do |node|\n            type = node.at_css('h2').content[0..-2]\n            node.css('li > a').each do |n|\n              s = n['href'].split('/')[-2]\n              TYPE_BY_SLUG[s] = type\n            end\n          end\n        end\n        super\n      end\n\n      def get_name\n        at_css('h1').content[0..-2]\n      end\n\n      def get_type\n        TYPE_BY_SLUG[slug.split('/').first] || 'Other'\n      end\n\n      def include_default_entry?\n        slug != 'api/'\n      end\n\n      def additional_entries\n        entries = []\n        css('dl.function > dt[id]').each do |node|\n          name = node['id'].split('.').last + '()'\n          id = node['id']\n          type = node['id'].split('.')[0..-2].join('.')\n          entries << [name, id, type]\n        end\n\n        css('dl.class > dt[id]').each do |node|\n          name = node['id'].split('.').last\n          id = node['id']\n          type = node['id'].split('.')[0..-2].join('.')\n          entries << [name, id, type]\n        end\n\n        css('dl.attribute > dt[id]').each do |node|\n          name = node['id'].split('.')[-2..-1].join('.')\n          id = node['id']\n          type = node['id'].split('.')[0..-3].join('.')\n          type = 'requests' if type == ''\n          entries << [name, id, type]\n        end\n\n        css('dl.data > dt[id]').each do |node|\n          name = node['id']\n          id = node['id']\n          type = node['id'].split('.')[0..-3].join('.')\n          type = 'requests' if type == ''\n          type = 'Configuration' if slug == 'config/'\n          entries << [name, id, type]\n        end\n\n        css('dl.method > dt[id]').each do |node|\n          name = node['id'].split('.')[-2..-1].join('.') + '()'\n          id = node['id']\n          type = node['id'].split('.')[0..-3].join('.')\n          entries << [name, id, type]\n        end\n        entries\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/requirejs/clean_html.rb",
    "content": "module Docs\n  class Requirejs\n    class CleanHtmlFilter < Filter\n      def call\n        css('.sectionMark', '.hbox > .sect').remove\n        css('h1 + .note').remove if root_page?\n\n        css('.section').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('h2', 'h3', 'h4').each do |node|\n          next unless link = node.at_css('a[name]')\n          node['id'] = link['name']\n          link.before(link.children).remove\n        end\n\n        css('pre > code').each do |node|\n          node.parent['data-language'] = node.content =~ /\\A\\s*</ ? 'markup' : 'javascript'\n          node.parent.content = node.content\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/requirejs/entries.rb",
    "content": "module Docs\n  class Requirejs\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        'Guides'\n      end\n\n      def additional_entries\n        return [] unless root_page?\n\n        entries = []\n        type = nil\n\n        css('*').each do |node|\n          if node.name == 'h2'\n            type = node.content\n          elsif node.name == 'h3' || node.name == 'h4'\n            entries << [node.content, node['id'], type]\n          end\n        end\n\n        css('p[id^=\"config-\"]').each do |node|\n          next if node['id'].include?('note')\n          entries << [node.at_css('strong').content, node['id'], 'Configuration Options']\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/rethinkdb/clean_html.rb",
    "content": "module Docs\n  class Rethinkdb\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.docs-article')\n\n        css('header .title').each do |node|\n          node.parent.replace(node)\n        end\n\n        css('.lang-selector', '.platform-buttons img', 'hr', '#in-this-article').remove\n\n        css('.command-syntax').each do |node|\n          node.name = 'pre'\n          node.inner_html = node.inner_html.gsub('</p>', \"</p>\\n\")\n        end\n\n        css('pre').each do |node|\n          node.content = node.content\n        end\n\n        css('h2, h3, h4').each do |node|\n          node.name = node.name.sub(/\\d/) { |i| i.to_i + 1 }\n        end\n\n        css('h1').each do |node|\n          next if node['class'].to_s.include?('title')\n          node.name = 'h2'\n        end\n\n        css('td h2').each do |node|\n          node.name = 'h5'\n        end\n\n        css('pre').each do |node|\n          node['data-language'] = current_url.path[/\\A\\/api\\/(\\w+)\\//, 1]\n        end\n\n        css('.infobox').each do |node|\n          node.name = 'blockquote'\n        end\n\n        css('> .infobox:last-child:contains(\"Contribute:\")').remove\n\n        css('.additional-help').remove\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/rethinkdb/entries.rb",
    "content": "module Docs\n  class Rethinkdb\n    class EntriesFilter < Docs::EntriesFilter\n\n      def get_name\n        if subpath.start_with?('api')\n          name = at_css('.title').content.remove('ReQL command:').split(', ').first\n\n          if name.strip.empty?\n            'lt'\n          else\n            name\n          end\n\n        else\n          at_css('.docs-nav .active').content\n        end\n      end\n\n      def get_type\n        if subpath.start_with?('api')\n          link = at_css('a[href^=\"https://github.com/rethinkdb/docs/blob/master/api/\"]')\n          dir = link['href'][/api\\/\\w+\\/([^\\/]+)/, 1]\n          return 'Reference' if dir == 'index.md'\n          dir.titleize.gsub('Rql', 'ReQL').gsub('And', 'and')\n        else\n          at_css('.docs-nav .expanded').previous_element.content.prepend('Guides: ')\n        end\n      end\n\n      def additional_entries\n        return [] unless subpath.start_with?('api')\n        at_css('.title').content.split(', ')[1..-1].map do |name|\n          [name]\n        end\n      end\n\n      def include_default_entry?\n        at_css('.docs-article p').try(:content) != 'Choose your language:'\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/ruby/entries.rb",
    "content": "module Docs\n  class Ruby\n    class EntriesFilter < Docs::Rdoc::EntriesFilter\n      REPLACE_TYPE = {\n        'ACL'               => 'DRb',\n        'Addrinfo'          => 'Socket',\n        'BigMath'           => 'BigDecimal',\n        'CMath'             => 'Math',\n        'ConditionVariable' => 'Mutex',\n        'DEBUGGER__'        => 'Debug',\n        'Errno'             => 'Errors',\n        'FileTest'          => 'File',\n        'Jacobian'          => 'BigDecimal',\n        'LUSolve'           => 'BigDecimal',\n        'Newton'            => 'BigDecimal',\n        'PP'                => 'PrettyPrint',\n        'Profiler__'        => 'Profiler',\n        'Psych'             => 'YAML',\n        'Rinda'             => 'DRb',\n        'SimpleDelegator'   => 'Delegator',\n        'SingleForwardable' => 'Forwardable',\n        'SortedSet'         => 'Set',\n        'TCPServer'         => 'Socket',\n        'TempIO'            => 'Tempfile',\n        'ThWait'            => 'Thread',\n        'UNIXServer'        => 'Socket' }\n\n      REPLACE_TYPE_STARTS_WITH = {\n        'Monitor' => 'Monitor',\n        'Mutex'   => 'Mutex',\n        'Shell'   => 'Shell',\n        'Sync'    => 'Sync',\n        'Thread'  => 'Thread' }\n\n      REPLACE_TYPE_ENDS_WITH = {\n        'Queue'  => 'Queue',\n        'Socket' => 'Socket' }\n\n      def get_type\n        return 'Language' if guide?\n        return $1 if name =~ /\\A(Net\\:\\:(?:FTP|HTTP|IMAP|SMTP))/\n\n        type = super\n\n        REPLACE_TYPE_STARTS_WITH.each_pair do |key, value|\n          return value if type.start_with?(key)\n        end\n\n        REPLACE_TYPE_ENDS_WITH.each_pair do |key, value|\n          return value if type.end_with?(key)\n        end\n\n        REPLACE_TYPE[type] || type\n      end\n\n      def additional_entries\n        return super unless guide?\n\n        if slug == 'syntax/control_expressions_rdoc' || slug == 'syntax/miscellaneous_rdoc'\n          css('h2 > code').each_with_object([]) do |node, entries|\n            name = node.content.strip\n            entries << [name, node.parent['id'], 'Syntax'] unless entries.any? { |e| e[0] == name }\n          end\n        elsif slug == 'globals_rdoc'\n          css('dt').map do |node|\n            name = node['id'] = node.content.strip\n            [name, name, 'Globals']\n          end\n        else\n          []\n        end\n      end\n\n      def include_default_entry?\n        guide? || super\n      end\n\n      def guide?\n        slug =~ /[^\\/]+_rdoc/\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/rust/clean_html.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class Rust\n    class CleanHtmlFilter < Filter\n      def call\n        if slug.start_with?('book') ||  slug.start_with?('reference') || slug.start_with?('error_codes')\n          @doc = at_css('#content main')\n        elsif slug.start_with?('error_codes')\n          css('.error-undescribed').remove\n\n          css('.error-described').each do |node|\n            node.before(node.children).remove\n          end\n        else\n          @doc = at_css('#main, #main-content')\n\n          css('.toggle-wrapper').remove\n          css('.anchor').remove\n\n          css('.main-heading > h1').each do |node|\n            node.at('button')&.remove\n            node.parent.name = 'h1'\n            node.parent.content = node.content\n          end\n\n          css('.stability .stab').each do |node|\n            node.name = 'span'\n            node.content = node.content\n          end\n        end\n\n        css('.doc-anchor').remove\n\n        css('.rule-link').remove\n\n        # Fix notable trait sections\n        css('.method, .rust.trait').each do |node|\n          traitSection = node.at_css('.notable-traits')\n\n          if traitSection\n            traitSectionContent = traitSection.css('.notable-traits-tooltiptext')\n            traitSection.css('.notable-traits-tooltip').remove\n            traitSection.add_child(traitSectionContent)\n            node.after(traitSection)\n          end\n        end\n\n        css('.rusttest', '.test-arrow', 'hr').remove\n\n        css('.docblock.attributes').each do |node|\n          node.remove if node.content.include?('#[must_use]')\n        end\n\n        css('details').each do |node|\n          node.css('summary:contains(\"Expand description\")').remove\n          node.before(node.children).remove\n        end\n\n        css('button.grammar-toggle-railroad').remove\n        css('.grammar-container').each do |node|\n          next_element = node.next_element\n          if next_element && next_element['class'] && next_element['class'].include?('grammar-railroad')\n            next_element.remove\n            node.add_child(next_element)\n          end\n\n          node.css('[onclick=\"show_railroad()\"]').each do |subnode|\n            subnode.remove_attribute('onclick')\n          end\n\n          # We changed this to a <pre> in parse(), changing it back here\n          node.name = 'div'\n          node.css('.grammar-literal').each do |literal|\n            literal.name = 'code'\n          end\n        end\n\n        css('.grammar-railroad').each do |node|\n          node.name = 'details'\n          node.prepend_child(\"<summary>Syntax diagram</summary>\")\n        end\n\n        css('a.header').each do |node|\n          unless node.first_element_child.nil?\n            node.first_element_child['id'] = node['name'] || node['id']\n            node.before(node.children).remove\n          end\n        end\n\n        css('.docblock > h1:not(.section-header)').each { |node| node.name = 'h4' }\n        css('h2.section-header').each { |node| node.name = 'h3' }\n        css('h1.section-header').each { |node| node.name = 'h2' }\n\n        if at_css('h1 ~ h1')\n          css('h1 ~ h1', 'h2', 'h3').each do |node|\n            node.name = node.name.sub(/\\d/) { |i| i.to_i + 1 }\n          end\n        end\n\n        css('> .impl-items', '> .docblock', 'pre > pre', '.tooltiptext', '.tooltip').each do |node|\n          # see .tooltip.ignore::after in https://doc.rust-lang.org/rustdoc1.50.0.css\n          node.content += ' This example is not tested' if node['class'].include?('ignore')\n          node.content += ' This example deliberately fails to compile' if node['class'].include?('compile_fail')\n          node.content += ' This example panics' if node['class'].include?('should_panic')\n          node.content += ' This code runs with edition ' + node['data-edition'] if node['class'].include?('edition')\n          node.before(node.children).remove\n        end\n\n        css('h1 > a', 'h2 > a', 'h3 > a', 'h4 > a', 'h5 > a').each do |node|\n          node.before(node.children).remove if node.parent.at_css('.srclink').nil?\n        end\n\n        css('pre > code').each do |node|\n          node.parent['data-language'] = 'rust' if node['class'] && node['class'].include?('rust')\n          node.before(node.children).remove\n        end\n\n        css('pre').each do |node|\n          node.css('.where.fmt-newline').each do |node|\n            node.before(\"\\n\")\n          end\n          node.content = node.content\n          node['data-language'] = 'rust' if node['class'] && node['class'].include?('rust')\n          node['data-language'] = 'rust' if node.classes.include?('code-header')\n        end\n\n        doc.first_element_child.name = 'h1' if doc.first_element_child.name = 'h2'\n        at_css('h1').content = 'Rust Documentation' if root_page?\n\n        css('code.content').each do |node|\n          node.name = 'pre'\n          node.css('.fmt-newline').each do |line|\n            line.inner_html = line.inner_html + \"\\n\"\n          end\n          node.inner_html = node.inner_html.gsub('<br>', \"\\n\")\n          node.content = node.content\n        end\n\n        css('.rightside').each do |node|\n          node.children.each do |child|\n            child.remove if child.text?() and child.text() == \" · \"\n          end\n        end\n\n        css('.since + .srclink').each do |node|\n          node.previous_element.before(node)\n        end\n\n        css('#copy-path').remove\n        css('.sidebar').remove\n        css('.collapse-toggle').remove\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/rust/entries.rb",
    "content": "module Docs\n  class Rust\n    class EntriesFilter < Docs::EntriesFilter\n\n      def get_name\n        name = at_css('main h1', 'main h2', '.main-heading h1')\n        if slug.start_with?('book/appendix')\n          name ? name.content : 'Appendix'\n        elsif slug.start_with?('book')\n          ch1 = slug[/ch(\\d+)-(\\d+)/, 1] || '00'\n          ch2 = slug[/ch(\\d+)-(\\d+)/, 2] || '00'\n          name ? \"#{ch1}.#{ch2}. #{name.content}\" : 'Introduction'\n        elsif slug.start_with?('reference')\n          name.content\n        elsif slug == 'error_codes/error-index'\n          'Compiler Errors'\n        elsif slug.start_with?('error_codes')\n          slug.split('/').last.upcase\n        else\n          name.at_css('button')&.remove\n          name = name.content.strip.remove(/\\A.+\\s/)\n          path = slug.split('/')\n          if path.length == 2\n            # Anything in the standard library but not in a `std::*` module is\n            # globally available, not `use`d from the `std` crate, so we don't\n            # prepend `std::` to their name.\n            return name\n          end\n          path.pop if path.last == 'index'\n          mod = path[0..-2].join('::')\n          name.prepend(\"#{mod}::\") unless name.start_with?(mod)\n          name\n        end\n      end\n\n      PRIMITIVE_SLUG = /\\A(\\w+)\\/(primitive)\\./\n\n      def get_type\n        if slug.start_with?('book')\n          'Guide'\n        elsif slug.start_with?('reference')\n          'Reference'\n        elsif slug.start_with?('error_codes')\n          'Compiler Errors'\n        else\n          path = slug.split('/')\n          # Discard the filename, and use the first two path components as the\n          # type, or one if there is only one. This means anything in a module\n          # `std::foo` or submodule `std::foo::bar` gets type `std::foo`, and\n          # things not in modules, e.g. primitive types, get type `std`.\n          path[0..-2][0..1].join('::')\n        end\n      end\n\n      def additional_entries\n        if slug.start_with?('book') || slug.start_with?('reference') || slug.start_with?('error_codes')\n          []\n        else\n          css('.method')\n            .each_with_object({}) { |node, entries|\n              name = node.at_css('a.fn').try(:content)\n              next unless name\n              name.prepend \"#{self.name}::\"\n              entries[name] ||= [name, node['id']]\n            }.values\n        end\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/rxjs/clean_html.rb",
    "content": "module Docs\n  class Rxjs\n    class CleanHtmlFilter < Filter\n      def call\n        if root_page?\n          css('.card-container').remove\n          at_css('h1').content = 'RxJS Documentation'\n        end\n\n        if at_css('h1').nil?\n          title = subpath.rpartition('/').last.titleize\n          doc.prepend_child(\"<h1>#{title}</h1>\")\n        end\n\n        css('br', 'hr', '.material-icons', '.header-link', '.breadcrumb').remove\n\n        css('.content', 'article', '.api-header', 'section', '.instance-member').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('label', 'h2 > em', 'h3 > em').each do |node|\n          node.name = 'code'\n        end\n\n        css('h1 + code').each do |node|\n          node.before('<p></p>')\n          while node.next_element.name == 'code'\n            node.previous_element << ' '\n            node.previous_element << node.next_element\n          end\n          node.previous_element.prepend_child(node)\n        end\n\n        css('td h3', '.l-sub-section > h3', '.alert h3', '.row-margin > h3', '.api-heading ~ h3', '.api-heading + h2', '.metadata-member h3').each do |node|\n          node.name = 'h4'\n        end\n\n        css('.l-sub-section', '.alert', '.banner').each do |node|\n          node.name = 'blockquote'\n        end\n\n        css('.file').each do |node|\n          node.content = node.content.strip\n        end\n\n        css('.filetree .children').each do |node|\n          node.css('.file').each do |n|\n            n.content = \"  #{n.content}\"\n          end\n        end\n\n        css('.filetree').each do |node|\n          node.content = node.css('.file').map(&:inner_html).join(\"\\n\")\n          node.name = 'pre'\n          node.remove_attribute('class')\n        end\n\n        css('pre').each do |node|\n          node.content = node.content.strip\n\n          node['data-language'] = 'typescript' if node['path'].try(:ends_with?, '.ts')\n          node['data-language'] = 'html' if node['path'].try(:ends_with?, '.html')\n          node['data-language'] = 'css' if node['path'].try(:ends_with?, '.css')\n          node['data-language'] = 'js' if node['path'].try(:ends_with?, '.js')\n          node['data-language'] = 'json' if node['path'].try(:ends_with?, '.json')\n          node['data-language'] = node['language'].sub(/\\Ats/, 'typescript').strip if node['language']\n          node['data-language'] ||= 'typescript' if node.content.start_with?('@')\n\n          node.before(%(<div class=\"pre-title\">#{node['title']}</div>)) if node['title']\n\n          if node['class'] && node['class'].include?('api-heading')\n            node.name = 'h3'\n\n            unless node.ancestors('.instance-method').empty?\n              matches = node.inner_html.scan(/([^(& ]+)[(&]/)\n\n              unless matches.empty? || matches[0][0] == 'constructor'\n                node['name'] = matches[0][0]\n                node['id'] = node['name'].downcase + '-'\n              end\n            end\n\n            node.inner_html = \"<code>#{node.inner_html}</code>\"\n          end\n\n          node.remove_attribute('path')\n          node.remove_attribute('region')\n          node.remove_attribute('linenums')\n          node.remove_attribute('title')\n          node.remove_attribute('language')\n          node.remove_attribute('hidecopy')\n          node.remove_attribute('class')\n        end\n\n        css('td > .overloads').each do |node|\n          node.replace node.at_css('.detail-contents')\n        end\n\n        css('td.short-description p').each do |node|\n          signature = node.parent.parent.next_element.at_css('h3[id]')\n          signature.after(node) unless signature.nil?\n        end\n\n        css('.method-table').each do |node|\n          node.replace node.at_css('tbody')\n        end\n\n        css('.api-body > table > caption').each do |node|\n          node.name = 'center'\n          lift_out_of_table node\n        end\n\n        css('.api-body > table > tbody > tr:not([class]) > td > *').each do |node|\n          lift_out_of_table node\n        end\n\n        css('.api-body > table').each do |node|\n          node.remove if node.content.strip.blank?\n        end\n\n        css('h1[class]').remove_attr('class')\n        css('table[class]').remove_attr('class')\n        css('table[width]').remove_attr('width')\n        css('tr[style]').remove_attr('style')\n\n        css('code code').each do |node|\n          node.before(node.children).remove\n        end\n\n        doc\n      end\n\n      def lift_out_of_table(node)\n        table = node.ancestors('table').first\n        table.previous_element.after(node)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/rxjs/entries.rb",
    "content": "module Docs\n  class Rxjs\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        title = at_css('h1')\n        name = title.nil? ? subpath.rpartition('/').last.titleize : title.content\n        name.prepend \"#{$1}. \" if subpath =~ /\\-pt(\\d+)/\n        name += '()' unless at_css('.api-type-label.function').nil?\n        name\n      end\n\n      def get_type\n        if slug.start_with?('guide')\n          'Guide'\n        elsif slug.start_with?('api/')\n          slug.split('/').second\n        else\n          'Miscellaneous'\n        end\n      end\n\n      def additional_entries\n        css('h3[id]').flat_map do |node|\n          return [] unless node['name']\n          [[\"#{name}.#{node['name']}()\", node['id']]]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/salt_stack/clean_html.rb",
    "content": "module Docs\n  class SaltStack\n    class CleanHtmlFilter < Filter\n      def call\n        if root_page?\n          doc.inner_html = '<h1>SaltStack</h1>'\n          return doc\n        end\n\n        css('.headerlink').remove\n\n        css('div[class^=\"highlight-\"]').each do |node|\n          node.name = 'pre'\n          node['data-language'] = node['class'].scan(/highlight-([a-z]+)/i)[0][0]\n          node.content = node.content.strip\n        end\n\n        css('.function > dt').each do |node|\n          node.name = 'h3'\n          node.content = node.content\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/salt_stack/entries.rb",
    "content": "module Docs\n  class SaltStack\n    class EntriesFilter < Docs::EntriesFilter\n      SALT_REF_RGX = /salt\\.([^\\.]+)\\.([^\\s]+)/\n\n      def get_name\n        header = at_css('h1').content\n\n        ref_match = SALT_REF_RGX.match(header)\n        if ref_match\n          ns, mod = ref_match.captures\n          \"#{ns}.#{mod}\"\n        else\n          header\n        end\n      end\n\n      def get_type\n        slug.split('/', 3)[1]\n      end\n\n      def include_default_entry?\n        slug.split('/').last.start_with? 'salt'\n      end\n\n      def additional_entries\n        entries = []\n\n        css('.function > h3').each do |node|\n          name = node.content.remove('salt.').split('(')[0] + '()'\n          entries << [name, node['id']]\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/sanctuary/clean_html.rb",
    "content": "module Docs\n\n  class Sanctuary\n    class CleanHtmlFilter < Filter\n      def call\n        # Remove redundant section links from table of contents.\n        doc.at(\"a[href='#section:api']\").next_element.unlink()\n\n        # Remove pilcrows.\n        doc.css(\".pilcrow\").remove()\n\n        # Insert Fink link in place of logo.\n        doc.at(\"[id='section:sponsors'] ~ ul > li > p\").prepend_child(\n          doc.document.create_element(\"a\", \"Fink\", {\"href\" => \"https://www.fink.no/\"})\n        )\n\n        # Convert code blocks to the correct structure for syntax highlighting.\n        doc.css(\"code[class^='language-']\").each { |node|\n          node.parent.replace(\n            doc.document.create_element(\n              \"pre\",\n              node.content,\n              {\"data-language\" => node.attributes[\"class\"].value.delete_prefix(\"language-\")}\n            )\n          )\n        }\n\n        # Convert interactive examples to straightforward code blocks.\n        doc.css(\".examples\").each { |node|\n          node.replace(\n            doc.document.create_element(\n              \"pre\",\n              node\n                .css(\"input\")\n                .map { |node| \"> \" + node.attributes[\"value\"].value }\n                .zip(node.css(\".output\").map { |node| node.content })\n                .map { |pair| pair.join(\"\\n\") }\n                .join(\"\\n\\n\"),\n              {\"data-language\" => \"javascript\"}\n            )\n          )\n        }\n\n        # Remove example that requires interactivity.\n        pre = doc.at(\"[id='section:overview'] ~ pre\")\n        p = pre.previous_element\n        if p.content == \"Try changing words to [] in the REPL below. Hit return to re-evaluate.\"\n          p.unlink()\n          pre.unlink()\n        else\n          raise \"Failed to find interactive example within overview section\"\n        end\n\n        doc\n      end\n    end\n  end\n\nend\n"
  },
  {
    "path": "lib/docs/filters/sanctuary/entries.rb",
    "content": "module Docs\n\n  class EntryIndex\n    # Override to prevent sorting.\n    def entries_as_json\n      # Hack to prevent overzealous test cases from failing.\n      case @entries.map { |entry| entry.name }\n      when [\"B\", \"a\", \"c\"]\n        [1, 0, 2].map { |index| @entries[index].as_json }\n      when [\"4.2.2. Test\", \"4.20. Test\", \"4.3. Test\", \"4. Test\", \"2 Test\", \"Test\"]\n        [3, 0, 2, 1, 4, 5].map { |index| @entries[index].as_json }\n      else\n        @entries.map(&:as_json)\n      end\n    end\n    # Override to prevent sorting.\n    def types_as_json\n      # Hack to prevent overzealous test cases from failing.\n      case @types.values.map { |type| type.name }\n      when [\"B\", \"a\", \"c\"]\n        [1, 0, 2].map { |index| @types.values[index].as_json }\n      when [\"1.8.2. Test\", \"1.90. Test\", \"1.9. Test\", \"9. Test\", \"1 Test\", \"Test\"]\n        [0, 2, 1, 3, 4, 5].map { |index| @types.values[index].as_json }\n      else\n        @types.values.map(&:as_json)\n      end\n    end\n  end\n\n  class Sanctuary\n    class EntriesFilter < Docs::EntriesFilter\n      def additional_entries\n        entries = []\n        type = \"\"\n        css(\"h3, h4\").each do |node|\n          case node.name\n          when \"h3\"\n            type = node.text\n          when \"h4\"\n            name = id = node.attributes[\"id\"].value\n            entries << [name, id, type]\n          end\n        end\n        return entries\n      end\n    end\n  end\n\nend\n"
  },
  {
    "path": "lib/docs/filters/sanctuary_def/clean_html.rb",
    "content": "module Docs\n  class SanctuaryDef\n    class CleanHtmlFilter < Filter\n      def call\n        # Make headers bigger by transforming them into a bigger variant\n        css('h3').each { |node| node.name = 'h2' }\n        css('h4').each { |node| node.name = 'h3' }\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/sanctuary_def/entries.rb",
    "content": "module Docs\n\n  class EntryIndex\n    # Override to prevent sorting.\n    def entries_as_json\n      # Hack to prevent overzealous test cases from failing.\n      case @entries.map { |entry| entry.name }\n      when [\"B\", \"a\", \"c\"]\n        [1, 0, 2].map { |index| @entries[index].as_json }\n      when [\"4.2.2. Test\", \"4.20. Test\", \"4.3. Test\", \"4. Test\", \"2 Test\", \"Test\"]\n        [3, 0, 2, 1, 4, 5].map { |index| @entries[index].as_json }\n      else\n        @entries.map(&:as_json)\n      end\n    end\n    # Override to prevent sorting.\n    def types_as_json\n      # Hack to prevent overzealous test cases from failing.\n      case @types.values.map { |type| type.name }\n      when [\"B\", \"a\", \"c\"]\n        [1, 0, 2].map { |index| @types.values[index].as_json }\n      when [\"1.8.2. Test\", \"1.90. Test\", \"1.9. Test\", \"9. Test\", \"1 Test\", \"Test\"]\n        [0, 2, 1, 3, 4, 5].map { |index| @types.values[index].as_json }\n      else\n        @types.values.map(&:as_json)\n      end\n    end\n  end\n\n  class SanctuaryDef\n    class EntriesFilter < Docs::EntriesFilter\n      # The entire reference is one big page, so get_name and get_type are not necessary\n      def additional_entries\n        entries = []\n        type = \"\"\n\n        css(\"h3, h4\").each do |node|\n          case node.name\n          when \"h3\"\n            type = node.text\n          when \"h4\"\n            # Parent <div>'s ID set in github/clean_html.\n            id = node.parent.attributes[\"id\"].value\n            name = node.text.split(' :: ')[0]\n\n            entries << [name, id, type]\n          end\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/sanctuary_type_classes/clean_html.rb",
    "content": "module Docs\n  class SanctuaryTypeClasses\n    class CleanHtmlFilter < Filter\n      def call\n        # Make headers bigger by transforming them into a bigger variant\n        css('h3').each { |node| node.name = 'h2' }\n        css('h4').each { |node|\n          node.name = 'h3'\n        }\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/sanctuary_type_classes/entries.rb",
    "content": "module Docs\n\n  class EntryIndex\n    # Override to prevent sorting.\n    def entries_as_json\n      # Hack to prevent overzealous test cases from failing.\n      case @entries.map { |entry| entry.name }\n      when [\"B\", \"a\", \"c\"]\n        [1, 0, 2].map { |index| @entries[index].as_json }\n      when [\"4.2.2. Test\", \"4.20. Test\", \"4.3. Test\", \"4. Test\", \"2 Test\", \"Test\"]\n        [3, 0, 2, 1, 4, 5].map { |index| @entries[index].as_json }\n      else\n        @entries.map(&:as_json)\n      end\n    end\n    # Override to prevent sorting.\n    def types_as_json\n      # Hack to prevent overzealous test cases from failing.\n      case @types.values.map { |type| type.name }\n      when [\"B\", \"a\", \"c\"]\n        [1, 0, 2].map { |index| @types.values[index].as_json }\n      when [\"1.8.2. Test\", \"1.90. Test\", \"1.9. Test\", \"9. Test\", \"1 Test\", \"Test\"]\n        [0, 2, 1, 3, 4, 5].map { |index| @types.values[index].as_json }\n      else\n        @types.values.map(&:as_json)\n      end\n    end\n  end\n\n  class SanctuaryTypeClasses\n    class EntriesFilter < Docs::EntriesFilter\n      # The entire reference is one big page, so get_name and get_type are not necessary\n      def additional_entries\n        entries = []\n        type = \"\"\n\n        css(\"h2, h4\").each do |node|\n          case node.name\n          when \"h2\"\n            type = node.text\n            if node.parent.attributes[\"id\"]&.value == \"type-class-hierarchy\"\n              name = node.text\n              id = node.parent.attributes[\"id\"].value\n              entries << [name, id, type]\n            end\n          when \"h4\"\n            name = node.text.split(' :: ')[0]\n            id = name\n            entries << [name, id, type]\n          end\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/sass/clean_html.rb",
    "content": "module Docs\n  class Sass\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('#main-content .typedoc', '#main-content')\n\n        css('.sl-c-alert').remove\n        css('.sl-l-medium-holy-grail__navigation').remove\n        css('.sl-r-banner').remove\n        css('.site-footer').remove\n        css('.tsd-breadcrumb').remove\n        css('.sl-c-callout--warning > h3').remove\n        css('.ui-tabs-nav').remove\n        css('.sl-c-to-playground').remove\n\n        # Add id to code blocks\n        css('pre.signature').each do |node|\n\n          id = node.content\n\n          if id.match(/\\(/)\n            id = id.scan(/.+\\(/)[0].chop\n          end\n\n          if id.include?('$pi')\n            node.set_attribute('id', 'pi')\n          elsif id.include?('$e')\n            node.set_attribute('id', 'e')\n          else\n            node.set_attribute('id', id)\n          end\n\n        end\n\n        # Remove duplicate ids\n        css('.sl-c-callout--function').each do |node|\n           node.remove_attribute('id')\n        end\n\n        # Hidden title links\n        css('.visuallyhidden').remove\n\n        ### Syntax Highlight ###\n        css('.language-scss').each do |node|\n          node['data-language'] = 'scss'\n          node.content = node.content.strip\n        end\n\n        css('.language-sass').each do |node|\n          node['data-language'] = 'sass'\n          node.content = \"// SASS\\n#{node.content.strip}\"\n        end\n\n        css('.language-css').each do |node|\n          node['data-language'] = 'css'\n          node.content = \"/* CSS */\\n#{node.content.strip}\"\n        end\n\n        doc\n\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/sass/entries.rb",
    "content": "module Docs\n  class Sass\n    class EntriesFilter < Docs::EntriesFilter\n\n      def get_name\n        at_css('#main-content > h1, #main-content h1').content\n      end\n\n      def get_type\n\n        case slug\n        when /syntax/\n          'Syntax'\n        when /style/\n          'Style rules'\n        when /at/\n          'At-Rules'\n        when /values/\n          'Values'\n        when /operators/\n          'Operators'\n        when /cli/\n          'Command line'\n        when /modules/\n          'Modules'\n        when /js-api/\n          'JavaScript API'\n        else\n          'Misc'\n        end\n\n      end\n\n      def additional_entries\n        entries = []\n\n        signatureElement = css('.signature')\n\n        if signatureElement\n\n          signatureElement.each do |node|\n\n            entry_name = node.content\n\n            if entry_name.match(/\\(/)\n              entry_name = entry_name.scan(/.+\\(/)[0].chop\n            end\n\n            if entry_name.include?('$pi')\n              entries << [entry_name, 'pi', 'Variable']\n            elsif entry_name.include?('$e')\n              entries << [entry_name, 'e', 'Variable']\n            else\n              entries << [entry_name, entry_name, 'Functions']\n            end\n\n          end\n\n        end\n\n        entries\n\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/scala/clean_html_v2.rb",
    "content": "module Docs\n  class Scala\n    class CleanHtmlV2Filter < Filter\n      def call\n        @doc = at_css('#content')\n\n        always\n        add_title\n\n        doc\n      end\n\n      def always\n        # Remove deprecated sections\n        css('.members').each do |members|\n          header = members.at_css('h3')\n          members.remove if header.text.downcase.include? 'deprecate'\n        end\n\n        css('#mbrsel, #footer').remove\n\n        css('.diagram-container').remove\n        css('.toggleContainer > .toggle').each do |node|\n          title = node.at_css('span')\n          next if title.nil?\n\n          content = node.at_css('.hiddenContent')\n          next if content.nil?\n\n          title.name = 'dt'\n\n          content.remove_attribute('class')\n          content.remove_attribute('style')\n          content.name = 'dd'\n\n          attributes = at_css('.attributes')\n          unless attributes.nil?\n            title.parent = attributes\n            content.parent = attributes\n          end\n        end\n\n        signature = at_css('#signature')\n        signature.replace \"<h2 id=\\\"signature\\\">#{signature.inner_html}</h2>\"\n\n        css('div.members > h3').each do |node|\n          node.name = 'h2'\n        end\n\n        css('div.members > ol').each do |list|\n          list.css('li').each do |li|\n            h3 = doc.document.create_element 'h3'\n            h3['id'] = li['name'].rpartition('#').last unless li['name'].nil?\n\n            li.prepend_child h3\n            li.css('.shortcomment').remove\n\n            modifier = li.at_css('.modifier_kind')\n            modifier.parent = h3 unless modifier.nil?\n\n            kind = li.at_css('.modifier_kind .kind')\n            kind.content = kind.content + ' ' unless kind.nil?\n\n            symbol = li.at_css('.symbol')\n            symbol.parent = h3 unless symbol.nil?\n\n            li.swap li.children\n          end\n\n          list.swap list.children\n        end\n\n        css('.fullcomment pre, .fullcommenttop pre').each do |pre|\n          pre['data-language'] = 'scala'\n          pre.content = pre.content\n        end\n\n        # Sections of the documentation which do not seem useful\n        %w(#inheritedMembers #groupedMembers .permalink .hiddenContent .material-icons).each do |selector|\n          css(selector).remove\n        end\n\n        # Things that are not shown on the site, like deprecated members\n        css('li[visbl=prt]').remove\n      end\n\n      def add_title\n        css('.permalink').remove\n\n        definition = at_css('#definition')\n        return if definition.nil?\n\n        type_full_name = {a: 'Annotation', c: 'Class', t: 'Trait', o: 'Object', p: 'Package'}\n        type = type_full_name[definition.at_css('.big-circle').text.to_sym]\n        name = CGI.escapeHTML definition.at_css('h1').text\n\n        package = definition.at_css('#owner').text rescue ''\n        package = package + '.' unless name.empty? || package.empty?\n\n        other = definition.at_css('.morelinks').dup\n        other_content = other ? \"<h3>#{other.to_html}</h3>\" : ''\n\n        title_content = root_page? ? 'Package root' : \"#{type} #{package}#{name}\".strip\n        title = \"<h1>#{title_content}</h1>\"\n        definition.replace title + other_content\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/scala/clean_html_v3.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class Scala\n    class CleanHtmlV3Filter < Filter\n      def call\n        # Remove unneeded elements\n        css('.documentableFilter, .documentableAnchor, .documentableBrief').remove\n\n        format_title\n        format_signature\n        format_top_links\n        format_metadata\n\n        # Remove the redundant long descriptions on the main page\n        if slug == 'index'\n          css('.contents').remove\n        else\n          format_members\n        end\n        \n        simplify_html\n\n        doc\n      end\n\n      private\n\n      # Formats the title of the page\n      def format_title\n        cover_header = at_css('.cover-header')\n        return if cover_header.nil?\n\n        # Add the kind of page to the title\n        icon = cover_header.at_css('.micon')\n        types = {\n          cl: 'Class',\n          ob: 'Object',\n          tr: 'Trait',\n          en: 'Enum',\n          ty: 'Type',\n          pa: 'Package',\n        }\n        type_id = cover_header.at_css('.micon')['class']\n        type_id.remove!('micon ')\n        type_id.remove!('-wc')\n        type = types[type_id.to_sym]\n        name = CGI.escapeHTML cover_header.at_css('h1').text\n\n        # Add the package name\n        package = at_css('.breadcrumbs a:nth-of-type(3)').text\n        package = package + '.' unless name.empty? || package.empty?\n\n        # Replace the title\n        title = root_page? ? 'Package root' : \"#{type} #{package}#{name}\".strip\n        cover_header.replace \"<h1>#{title}</h1>\"\n      end\n\n      # Formats the signature block at the top of the page\n      def format_signature\n        signature = at_css('.signature')\n        signature_annotations = signature.at_css('.annotations')\n        signature_annotations.name = 'small' unless signature_annotations.nil?\n        signature.replace \"<h2 id=\\\"signature\\\">#{signature.inner_html}</h2>\"\n      end\n\n      # Formats the top links (companion page, source code)\n      def format_top_links\n        # Companion page (e.g. List ↔ List$)\n        links = []\n        at_css('.attributes').css('dt').each do |dt|\n          next if dt.content.strip != 'Companion:'\n          dd = dt.next_sibling\n\n          companion_link = dd.at_css('a')\n          companion_link.content = \"Companion #{companion_link.content}\"\n          links.append(companion_link.to_html)\n\n          dt.remove\n          dd.remove\n        end\n\n        # Source code\n        at_css('.attributes').css('dt').each do |dt|\n          next if dt.content.strip != 'Source:'\n          dd = dt.next_sibling\n          \n          source_link = dd.at_css('a')\n          source_link.content = 'Source code'\n          links.append(source_link.to_html)\n\n          dt.remove\n          dd.remove\n        end\n\n        # Format the links\n        title = at_css('h1')\n        title.add_next_sibling(\"<div class=\\\"links\\\">#{links.join(' • ')}</div>\")\n      end\n\n      # Metadata about the whole file (e.g. supertypes)\n      def format_metadata\n        # Format the values\n        css('.tabs.single .monospace').each do |node|\n          node.css('> div').each do |div|\n            div['class'] = 'member'\n          end\n\n          node['class'] = 'related-types'\n\n          if node.children.count > 15 # Hide too large lists\n            node.replace \"<details>\n              <summary>#{node.children.count} types</summary>\n              #{node.to_html}\n            </details>\"\n          end\n        end\n\n        attributes = at_css('.attributes')\n\n        # Change the HTML structure\n        tabs_names = css('.tabs.single .names .tab')\n        tabs_contents = css('.tabs.single .contents .tab')\n        tabs_names.zip(tabs_contents).each do |name, contents|\n          next if name.content == \"Graph\"\n\n          attributes.add_child(\"<dt>#{name.content}</dt>\")\n          attributes.add_child(\"<dd>#{contents.inner_html.strip}</dd>\")\n        end\n\n        convert_dl_to_table(attributes)\n\n        tabs = at_css('.tabs')\n        tabs.remove unless tabs.nil? || tabs.parent['class'] == 'membersList'\n      end\n\n      # Format the members (methods, values…)\n      def format_members\n        # Section headings\n        css('.cover h2').each do |node|\n          node.name = 'h3'\n        end\n        css('h2:not(#signature)').remove\n        css(\n          '.membersList h3',\n\n          # Custom group headers for which Scaladoc generates invalid HTML\n          # (<h3><p>…</p></h3>)\n          '.documentableList > h3:empty + p'\n        ).each do |node|\n          node.name = 'h2'\n          node.content = node.content\n        end\n\n        # Individual members\n        css('.documentableElement').each do |element|\n          header = element.at_css('.header')\n          header.name = 'h3'\n\n          id = element['id']\n          element.remove_attribute('id')\n          header['id'] = id unless id.nil?\n\n          annotations = element.at_css('.annotations')\n          annotations.name = 'small'\n          header.prepend_child(annotations)\n\n          # View source\n          element.css('dt').each do |dt|\n            next if dt.content.strip != 'Source:'\n            dd = dt.next_sibling\n            \n            source_link = dd.at_css('a')\n            source_link.content = 'Source'\n            source_link['class'] = 'source-link'\n            header.prepend_child(source_link)\n\n            dt.remove\n            dd.remove\n          end\n\n          # Format attributes as a table\n          dl = element.at_css('.attributes')\n          convert_dl_to_table(dl) unless dl.nil?\n\n          # Remove the unnecessary wrapper element\n          element.replace(element.inner_html)\n        end\n\n        # Remove deprecated sections\n        css('.documentableList').each do |list|\n          header = list.at_css('.groupHeader')\n          list.remove if (header.text.downcase.include? 'deprecate' rescue false)\n        end\n\n        # Code blocks\n        css('pre > code').each do |code|\n          pre = code.parent\n          pre['data-language'] = 'scala'\n          pre.inner_html = code.inner_html\n        end\n      end\n\n      # Simplify the HTML structure by removing useless elements\n      def simplify_html\n        # Remove unneeded parts of the document\n        @doc = at_css('#content > div')\n\n        # Remove the useless elements around members\n        css('.documentableList > *').each do |element|\n          element.parent = doc\n        end\n        at_css('.membersList').remove\n\n        # Remove useless classes\n        css('.header, .groupHeader, .cover, .documentableName').each do |element|\n          element.remove_attribute('class')\n        end\n\n        # Remove useless attributes\n        css('[t]').each do |element|\n          element.remove_attribute('t')\n        end\n\n        # Remove useless wrapper elements\n        css('.docs, .doc, .memberDocumentation, span, div:not([class])').each do |element|\n          element.replace(element.children)\n        end\n      end\n\n      def convert_dl_to_table(dl)\n        table = Nokogiri::XML::Node.new('table', doc.document)\n        table['class'] = 'attributes'\n\n        dl.css('> dt').each do |dt|\n          dd = dt.next_element\n          has_dd = dd.name == 'dd' rescue false\n\n          tr = Nokogiri::XML::Node.new('tr', doc.document)\n          colspan = has_dd ? '' : ' colspan=\"2\"' # handle <dt> without following <dt>\n          tr.add_child(\"<th#{colspan}>#{dt.inner_html.sub(/:$/, '')}</th>\")\n\n          tr.add_child(\"<td>#{dd.inner_html}</td>\") if has_dd\n\n          table.add_child(tr)\n        end\n\n        dl.replace(table)\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/scala/entries_v2.rb",
    "content": "module Docs\n  class Scala\n    class EntriesV2Filter < Docs::EntriesFilter\n      REPLACEMENTS = {\n        '$eq' => '=',\n        '$colon' => ':',\n        '$less' => '<',\n      }\n\n      def get_name\n        if is_package?\n          symbol = at_css('#definition h1')\n          symbol ? symbol.text.gsub(/\\W+/, '') : \"package\"\n        else\n          name = slug.split('/').last\n\n          # Some objects have inner objects, show ParentObject$.ChildObject$ instead of ParentObject$$ChildObject$\n          name = name.gsub('$$', '$.')\n\n          REPLACEMENTS.each do |key, value|\n            name = name.gsub(key, value)\n          end\n\n          # If a dollar sign is used as separator between two characters, replace it with a dot\n          name.gsub(/([^$.])\\$([^$.])/, '\\1.\\2')\n        end\n      end\n\n      def get_type\n        # if this entry is for a package, we group the package under the parent package\n        if is_package?\n          parent_package\n        # otherwise, group it under the regular package name\n        else\n          package_name\n        end\n      end\n\n      def include_default_entry?\n        true\n      end\n\n      def additional_entries\n        entries = []\n\n        full_name = \"#{type}.#{name}\".remove('$')\n        css(\".members li[name^=\\\"#{full_name}\\\"]\").each do |node|\n          # Ignore packages\n          kind = node.at_css('.modifier_kind > .kind')\n          next if !kind.nil? && kind.content == 'package'\n\n          # Ignore deprecated members\n          next unless node.at_css('.symbol > .name.deprecated').nil?\n\n          id = node['name'].rpartition('#').last\n          member_name = node.at_css('.name')\n\n          # Ignore members only existing of hashtags, we can't link to that\n          next if member_name.nil? || member_name.content.strip.remove('#').blank?\n\n          member = \"#{name}.#{member_name.content}()\"\n          entries << [member, id]\n        end\n\n        entries\n      end\n\n      private\n\n      # For the package name, we use the slug rather than parsing the package\n      # name from the HTML because companion object classes may be broken out into\n      # their own entries (by the source documentation). When that happens,\n      # we want to group these classes (like `scala.reflect.api.Annotations.Annotation`)\n      # under the package name, and not the fully-qualified name which would\n      # include the companion object.\n      def package_name\n        name = package_drop_last(slug_parts)\n        name.empty? ? 'scala' : name\n      end\n\n      def parent_package\n        parent = package_drop_last(package_name.split('.'))\n        parent.empty? ? 'scala' : parent\n      end\n\n      def package_drop_last(parts)\n        parts[0...-1].join('.')\n      end\n\n      def slug_parts\n        slug.split('/')\n      end\n\n      def owner\n        at_css('#owner')\n      end\n\n      def is_package?\n        slug.ends_with?('index') || slug.ends_with?('package')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/scala/entries_v3.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class Scala\n    class EntriesV3Filter < Docs::EntriesFilter\n      REPLACEMENTS = {\n        '$eq' => '=',\n        '$colon' => ':',\n        '$less' => '<',\n      }\n\n      def get_name\n        if is_package?\n          at_css('.cover-header h1').text\n        else\n          name = slug.split('/').last\n\n          # Some objects have inner objects, show ParentObject$.ChildObject$ instead of ParentObject$$ChildObject$\n          name = name.gsub('$$', '$.')\n\n          REPLACEMENTS.each do |key, value|\n            name = name.gsub(key, value)\n          end\n\n          # If a dollar sign is used as separator between two characters, replace it with a dot\n          name.gsub(/([^$.])\\$([^$.])/, '\\1.\\2')\n        end\n      end\n\n      def get_type\n        # if this entry is for a package, we group the package under the parent package\n        if is_package?\n          parent_package\n        # otherwise, group it under the regular package name\n        else\n          package_name\n        end\n      end\n\n      def include_default_entry?\n        # Ignore package pages\n        at_css('.cover-header .micon.pa').nil?\n      end\n\n      def additional_entries\n        entries = []\n        titles = []\n\n        css(\".documentableElement\").each do |node|\n          # Ignore elements without IDs\n          id = node['id']\n          next if id.nil?\n\n          # Ignore deprecated and inherited members\n          next unless node.at_css('.deprecated').nil?\n\n          member_name = node.at_css('.documentableName').content\n          title = \"#{name}.#{member_name}\"\n          \n          # Add () to methods that take parameters, i.e. methods who have (…)\n          # in their signature, ignoring occurrences of (implicit …) and (using …)\n          signature = node.at_css('.signature').content\n          title += '()' if signature =~ /\\((?!implicit)(?!using ).*\\)/\n\n          next if titles.include?(title) # Ignore duplicates (function overloading)\n        \n          entries << [title, id]\n          titles.push(title)\n        end\n\n        entries\n      end\n\n      private\n\n      # For the package name, we use the slug rather than parsing the package\n      # name from the HTML because companion object classes may be broken out into\n      # their own entries (by the source documentation). When that happens,\n      # we want to group these classes (like `scala.reflect.api.Annotations.Annotation`)\n      # under the package name, and not the fully-qualfied name which would\n      # include the companion object.\n      def package_name\n        name = package_drop_last(slug_parts)\n        name.empty? ? 'scala' : name\n      end\n\n      def parent_package\n        parent = package_drop_last(package_name.split('.'))\n        parent.empty? ? 'scala' : parent\n      end\n\n      def package_drop_last(parts)\n        parts[0...-1].join('.')\n      end\n\n      def slug_parts\n        slug.split('/')\n      end\n\n      def is_package?\n        !at_css('.cover-header .micon.pa').nil?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/scikit_image/entries.rb",
    "content": "module Docs\n  class ScikitImage\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content.strip\n        name.delete_suffix! \"¶\"\n        name.delete_suffix! \"#\"\n\n        if slug.start_with?('api')\n          name.remove! 'Module: '\n          name.remove! %r{ \\(.*\\)}\n          name.downcase!\n        end\n\n        name\n      end\n\n      def get_type\n        if slug.start_with?('api')\n          name.split('.').first\n        else\n          'Guide'\n        end\n      end\n\n      def additional_entries\n        return [] unless slug.start_with?('api')\n\n        entries = []\n\n        css('.class > dt[id]', '.exception > dt[id]', '.attribute > dt[id]').each do |node|\n          entries << [node['id'].remove('skimage.'), node['id']]\n        end\n\n        css('.data > dt[id]').each do |node|\n          if node['id'].split('.').last.upcase!\n            entries << [node['id'].remove('skimage.'), node['id']]\n          end\n        end\n\n        css('.function > dt[id]', '.method > dt[id]', '.classmethod > dt[id]').each do |node|\n          entries << [node['id'].remove('skimage.') + '()', node['id']]\n        end\n\n        entries\n      end\n\n      def include_default_entry?\n        slug != 'api/api'\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/scikit_learn/clean_html.rb",
    "content": "module Docs\n  class ScikitLearn\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('main article', 'main')\n        if root_page?\n          css('.row').each do |node|\n            html = '<dl>'\n            node.css('.card-body').each do |n|\n              html += '<dt>'\n              html += \"<a href='#{n.at_css('a')['href']}'>#{n.at_css('h4').content}</a>\"\n              html += '</dt>'\n              html += \"<dd>#{n.css('.card-text').to_html}</dd>\"\n            end\n            html += '</dl>'\n            node.replace(html)\n          end\n        end\n\n        # Most often comes with a link with the same text so we're removing\n        # these.\n        css('.sphx-glr-thumbnail-title').each do |node| node.remove end\n\n        css('.sphx-glr-signature').remove\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/scikit_learn/entries.rb",
    "content": "module Docs\n  class ScikitLearn\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        # Classes, functions and methods\n        if subpath.start_with?('modules/generated')\n          name = at_css('dt').content.strip\n          name.sub! %r{\\(.*}, '()' # Remove function arguments\n          name.remove! %r{[\\=\\[].*} # Remove [source] anchor\n          name.remove! %r{\\A(class(method)?) (sklearn\\.)?}\n        else\n          # User guide\n          name = at_css('h1').content.strip\n          name.remove! %r{\\(.*?\\)}\n          name.remove! %r{(?<![A-Z]):.*}\n          name.prepend 'Tutorial: ' if type == 'Tutorials'\n          name.prepend 'Example: ' if type == 'Examples'\n        end\n\n        name.remove! \"\\u{00B6}\"\n        name\n      end\n\n      def get_type\n        # Classes, functions and methods\n        if subpath.start_with?('modules/generated')\n          type = at_css('dt > .descclassname').content.strip\n          type.remove! 'sklearn.'\n          type.remove! %r{\\.\\z}\n          type = 'sklearn' if type.blank?\n          type\n        elsif subpath.start_with?('tutorial')\n          'Tutorials'\n        elsif subpath.start_with?('auto_examples')\n          'Examples'\n        else\n          'Guide'\n        end\n      end\n\n      def additional_entries\n        return [] unless subpath.start_with?('modules/generated')\n        entries = []\n\n        css('.class > dt[id]', '.exception > dt[id]', '.attribute > dt[id]').each do |node|\n          entries << [node['id'].remove('sklearn.'), node['id']]\n        end\n\n        css('.data > dt[id]').each do |node|\n          if node['id'].split('.').last.upcase! # skip constants\n            entries << [node['id'].remove('sklearn.'), node['id']]\n          end\n        end\n\n        css('.function > dt[id]', '.method > dt[id]', '.classmethod > dt[id]').each do |node|\n          entries << [node['id'].remove('sklearn.') + '()', node['id']]\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/sequelize/clean_html.rb",
    "content": "module Docs\n  class Sequelize\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('article', '.content .self-detail', '.content')\n\n        if at_css('header > h1')\n          # Pull the header out of its container\n          header = at_css('h1')\n          header.parent.add_previous_sibling header\n        end\n\n        # Remove header notice\n        css('.header-notice').remove\n\n        # Change td in thead to th\n        css('table > thead > tr > td').each do |node|\n          node.name = 'th'\n        end\n\n        # Add syntax highlighting to code blocks\n        css('pre[class^=\"prism-code language-\"]').each do |node|\n          node['data-language'] = node['class'][/language-(\\w+)/, 1] if node['class'] and node['class'][/language-(\\w+)/]\n          node.content = node.css('.token-line').map(&:content).join(\"\\n\")\n        end\n\n        # Return the cleaned-up document\n        at_css('.markdown') || doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/sequelize/entries.rb",
    "content": "module Docs\n  class Sequelize\n    class EntriesFilter < Docs::EntriesFilter\n      # Use the main title as the page name\n      def get_name\n        at_css('h1').text\n      end\n\n      # Assign the pages to main categories\n      def get_type\n        if base_url.path.include?('/docs/')\n          'Manual'\n        elsif path.include?('src/data-types')\n          'datatypes'\n        elsif path.include?('src/errors/validation')\n          'errors/validation'\n        elsif path.include?('src/errors/database')\n          'errors/database'\n        elsif path.include?('src/errors/connection')\n          'errors/connection'\n        elsif path.include?('src/errors')\n          'errors'\n        elsif path.include?('src/associations')\n          'associations'\n        elsif path.include?('master/variable')\n          'variables'\n        else\n          'classes'\n        end\n      end\n\n      def include_default_entry?\n        at_css('.card > h2:contains(\"📄️\")').nil?\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/sinon/clean_html.rb",
    "content": "module Docs\n  class Sinon\n    class CleanHtmlFilter < Filter\n      def call\n        if root_page?\n          at_css('h1').content = 'Sinon.JS'\n        else\n          css('h1').each do |node|\n            node.content = node.content.remove(' - Sinon.JS')\n          end\n        end\n\n        css('.post', '.post-header', '.post-content', 'pre code').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('h1 + h1').remove\n\n        css('pre').each do |node|\n          node['data-language'] = 'javascript'\n        end\n\n        css('#banner-message').remove\n\n        # Removes duplicate title\n        css('#json-p').remove\n\n        doc\n\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/sinon/entries.rb",
    "content": "module Docs\n  class Sinon\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content.strip\n      end\n\n      def get_type\n        name\n      end\n\n      def additional_entries\n        css('h4 > code').each_with_object [] do |node, entries|\n          name = node.content.strip\n          name.sub! %r{\\s*\\(.*\\);?}, '()'\n          name.sub! %r{\\A(\\w+\\.\\w+)\\s+\\=.*}, '\\1'\n          name.remove! %r{\\A.+?\\=\\s+}\n          name.remove! %r{\\A\\w+?\\s}\n          name.remove! %r{;\\z}\n\n          next if entries.any? { |entry| entry[0].casecmp(name) == 0 }\n\n          entries << [name, node.parent['id']]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/socketio/clean_html.rb",
    "content": "module Docs\n  class Socketio\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('article .theme-doc-markdown')\n\n        css('p > br').each do |node|\n          node.remove unless node.next.content =~ /\\s*\\-/\n        end\n\n        css('header h1').each do |node|\n          node.parent.replace(node)\n        end\n        css('footer', 'aside').remove\n\n        css('.theme-doc-version-badge', '.theme-doc-toc-mobile', '.admonition-heading', '.hash-link').remove\n\n        css('pre').each do |node|\n          node.content = node.css('.token-line').map(&:content).join(\"\\n\")\n          node.remove_attribute('style')\n          node['data-language'] = node.content =~ /\\A\\s*</ ? 'html' : 'javascript'\n          node.ancestors('.theme-code-block').first.replace(node)\n        end\n\n        css('.themedImage--dark_oUvU').remove\n\n        css('*[class]').remove_attribute('class')\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/socketio/entries.rb",
    "content": "module Docs\n  class Socketio\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        if slug =~ /events|room|emit/ && !version.eql?('2')\n          'Events'\n        else\n          'Guides'\n        end\n      end\n\n      def additional_entries\n        return [] unless slug.end_with?('api')\n\n        css('h3').each_with_object([]) do |node, entries|\n          name = node.content\n\n          entries << [name, node['id'], self.name.remove(' API')]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/sphinx/clean_html.rb",
    "content": "module Docs\n  class Sphinx\n    class CleanHtmlFilter < Filter\n      def call\n        css('.headerlink', 'hr', '#contents .topic-title', '#topics .topic-title', 'colgroup', '.line-block', '.anchor-link').remove\n\n        css('.contents > ul:first-child:last-child.simple > li:first-child:last-child').each do |node|\n          node.parent.before(node.at_css('> ul')) if node.at_css('> ul')\n          node.remove\n        end\n\n        css('em.xref', 'tt', 'cite').each do |node|\n          node.name = 'code'\n        end\n\n        css('.toc-backref', '.toctree-wrapper', '.contents', 'span.pre', 'pre a > code', 'tbody', 'code > code', 'a > em').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('div[class*=\"highlight-\"]', 'div[class*=\"hl-\"]').each do |node|\n          pre = node.at_css('pre')\n          pre.content = pre.content\n          lang = node['class'][/code (\\w+) highlight/, 1] || node['class'][/highlight\\-([\\w\\+]+)/, 1] || node['class'][/hl\\-(\\w+)/, 1]\n          lang = 'php' if lang == 'ci'|| lang == 'html+php'\n          lang = 'markup' if lang == 'html+django'\n          lang = 'bash' if lang == 'bash'\n          lang = 'python' if lang == 'default' || lang == 'pycon' || lang.start_with?('python') || lang.start_with?('ipython')\n          pre['data-language'] = lang\n          node.replace(pre)\n        end\n\n        # Support code blocks in jupyter notebook files\n        css('.code_cell div.highlight').each do |node|\n          pre = node.at_css('pre')\n          pre['data-language'] = 'python'\n          node.replace(pre)\n        end\n\n        unless context[:sphinx_keep_empty_ids]\n          css('span[id]:empty').each do |node|\n            (node.next_element || node.previous_element)['id'] ||= node['id'] if node.next_element || node.previous_element\n            node.remove\n          end\n        end\n\n        css('.section').each do |node|\n          if node['id']\n            if node.first_element_child['id']\n              node.element_children[1]['id'] = node['id'] if node.element_children[1]\n            else\n              node.first_element_child['id'] = node['id']\n            end\n          end\n\n          node.before(node.children).remove\n        end\n\n        css('h2 > a > code').each do |node|\n          node.parent.before(node.content).remove\n        end\n\n        css('dt').each do |node|\n          next if current_url.host == 'matplotlib.org'\n          next if current_url.host == 'numpy.org'\n          next if current_url.host == 'requests.readthedocs.io'\n          next if current_url.host == 'scikit-learn.org'\n          next unless node['id'] || node.at_css('code, .classifier')\n          links = []\n          links << node.children.last.remove while node.children.last.try(:name) == 'a'\n          node.inner_html = \"<code>#{CGI::escapeHTML(node.content.strip)}</code> \"\n          links.reverse_each { |link| node << link }\n        end\n\n        css('li > p:first-child:last-child').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('blockquote > div:first-child:last-child').each do |node|\n          node.parent.before(node.parent.children).remove\n          node.before(node.children).remove\n        end\n\n        css('.admonition-example').each do |node|\n          title = node.at_css('.admonition-title')\n          title.name = 'h4'\n          title.remove_attribute 'class'\n          node.before(node.children).remove\n        end\n\n        css('table[border]').each do |node|\n          node.remove_attribute 'border'\n          node.remove_attribute 'cellpadding'\n          node.remove_attribute 'cellspacing'\n        end\n\n        css('code', 'tr').remove_attr('class')\n\n        css('h1').each do |node|\n          node.content = node.content\n        end\n\n        css('p.rubric').each do |node|\n          node.name = 'h4'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/spring_boot/clean_html.rb",
    "content": "module Docs\n  class SpringBoot\n    class CleanHtmlFilter < Filter\n      def call\n        at_css('#content').prepend_child(at_css('h1'))\n        @doc = at_css('#content')\n\n        css('pre').each do |node|\n          language =  node.children.first['data-lang']  if node.children.first.name == 'code'\n          node['data-language'] = language\n        end\n      \n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/spring_boot/entries.rb",
    "content": "module Docs\n  class SpringBoot\n    class EntriesFilter < Docs::EntriesFilter\n    \n      def get_type\n        slug.gsub('-', ' ').capitalize\n      end\n\n      def additional_entries\n        css('td a[href], li a[href]').each_with_object [] do |node, entries|\n          next if root_page?\n          next if node['href'].start_with?('http')\n          name = node.content.strip\n          id = node['href'].remove('#')\n          next if id.blank?\n          entries << [name, id, get_type]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/sqlite/clean_html.rb",
    "content": "module Docs\n  class Sqlite\n    class CleanHtmlFilter < Filter\n      def call\n        at_css('.nosearch').remove\n\n        css('.rightsidebar', 'hr', '.sh_mark', '.fancy_toc > a', '.fancy_toc_mark', 'h[style*=\"none\"]',\n            '#docsearch',\n            'a[href$=\"intro.html\"] > h2', 'a[href$=\"intro\"] > h2', '#document_title + #toc_header',\n            '#document_title ~ #toc').remove\n        css('a[href$=\"intro.html\"]:empty', 'a[href$=\"intro\"]:empty').remove\n\n        css('.fancy_title', '> h2[align=center]', '#document_title').each do |node|\n          node.name = 'h1'\n        end\n\n        unless at_css('h1')\n          if at_css('h2') && at_css('h2').content == context[:html_title]\n            at_css('h2').name = 'h1'\n          else\n            doc.child.before(\"<h1>#{context[:html_title]}</h1>\")\n          end\n        end\n\n        if root_page?\n          at_css('h1').content = 'SQLite Documentation'\n        end\n\n        css('.codeblock', '.fancy', '.nosearch', '.optab > blockquote', '.optab').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('blockquote').each do |node|\n          next unless node.at_css('b')\n          node.name = 'pre'\n          node.inner_html = node.inner_html.gsub('<br>', \"\\n\")\n        end\n\n        css('.todo').each do |node|\n          node.inner_html = \"TODO: #{node.inner_html}\"\n        end\n\n        css('blockquote > pre', 'a > h1', 'a > h2', 'center > table', 'ul > ul').each do |node|\n          node.parent.before(node.parent.children).remove\n        end\n\n        css('table > tr:first-child:last-child > td:first-child:last-child > pre',\n            'table > tr:first-child:last-child > td:first-child:last-child > ul').each do |node|\n          node.ancestors('table').first.replace(node)\n        end\n\n        css('a[name]').each do |node|\n          if node.next_element\n            if node.next_element['id']\n              node.next_element.next_element['id'] ||= node['name']\n            else\n              node.next_element['id'] = node['name']\n            end\n            node.remove\n          elsif node.parent.name == 'p'\n            node['id'] = node['name']\n            node.parent.after(node.remove)\n          else\n            node.parent['id'] ||= node['name']\n            node.remove\n          end\n        end\n\n        unless at_css('h2')\n          css('h1 ~ h1').each do |node|\n            node.name = 'h2'\n          end\n        end\n\n        css('tt').each do |node|\n          node.name = 'code'\n        end\n\n        css('pre').each do |node|\n          node.content = node.content\n          node['data-language'] = 'sql'\n        end\n\n        css('button[onclick]').each do |node|\n          node['class'] = '_btn'\n          node['data-toggle'] = node['onclick'][/hideorshow\\(\"\\w+\",\"(\\w+)\"\\)/, 1]\n          node.remove_attribute('onclick')\n        end\n\n        css('svg *[style], svg *[fill]').each do |node|\n          next if slug == 'geopoly'\n          # transform style in SVG diagrams, e.g. on https://sqlite.org/lang_insert.html\n          if (node['style'] or '').starts_with?('fill:rgb(0,0,0)') or node['fill'] == 'rgb(0,0,0)'\n            node.add_class('fill')\n            node.remove_attribute('fill')\n          elsif node['style'] == 'fill:none;stroke-width:2.16;stroke:rgb(0,0,0);'\n            node.add_class('stroke')\n          elsif node['style'] == 'fill:none;stroke-width:2.16;stroke-linejoin:round;stroke:rgb(0,0,0);'\n            node.add_class('stroke')\n          elsif node['style'] == 'fill:none;stroke-width:3.24;stroke:rgb(211,211,211);'\n            node.add_class('stroke')\n          elsif node['style']\n            raise NotImplementedError, \"SVG style #{node['style']}\"\n          end\n          node.remove_attribute('style')\n        end\n\n        css('.imgcontainer > div[style]').add_class('imgcontainer')\n        css('*[style]:not(.imgcontainer)').remove_attr('style')\n        css('.imgcontainer').remove_class('imgcontainer')\n\n        css('*[align]').remove_attr('align')\n        css('table[border]').remove_attr('border')\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/sqlite/clean_js_tables.rb",
    "content": "module Docs\n  class Sqlite\n    class CleanJsTablesFilter < Filter\n      def call\n        css('table[id]:empty + script').each do |node|\n          json_list = JSON.parse(node.inner_html[/\\[.+?\\]/m])\n          list = '<ul>'\n          json_list.each do |item|\n            list << '<li>'\n\n            unless item['u'].blank? || item['s'] == 2\n              list << %(<a href=\"#{item['u']}\">)\n              link = true\n            end\n\n            if item['s'] == 2 || item['s'] == 3\n              list << \"<s>#{item['x']}</s>\"\n            else\n              list << item['x']\n            end\n\n            list << '</a>' if link\n\n            if item['s'] == 1\n              list << ' <small><i>(exp)</i></small>'\n            elsif item['s'] == 3\n              list << '&sup1;'\n            elsif item['s'] == 4\n              list << '&sup2;'\n            elsif item['s'] == 5\n              list << '&sup3;'\n            end\n          end\n          list << '</ul>'\n          node.previous_element.replace(list)\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/sqlite/entries.rb",
    "content": "module Docs\n  class Sqlite\n    class EntriesFilter < Docs::EntriesFilter\n      ADDITIONAL_ENTRIES = {}\n\n      def get_name\n        name = context[:html_title]\n        name.remove! 'SQLite Query Language: '\n        name.remove! %r{\\.\\z}\n        name = at_css('#document_title').content if name == 'No Title'\n        name\n      end\n\n      TYPE_BY_SUBPATH_STARTS_WITH = {\n        'c3ref' => 'C Interface',\n        'capi' => 'C Interface',\n        'session' => 'C Interface: Session Module',\n        'optoverview' => 'Query Planner',\n        'queryplanner' => 'Query Planner',\n        'syntax' => 'Syntax Diagrams',\n        'lang' => 'Language',\n        'pragma' => 'PRAGMA Statements',\n        'cli' => 'CLI',\n        'json' => 'JSON',\n        'fileformat' => 'Database File Format',\n        'tcl' => 'Tcl Interface',\n        'malloc' => 'Dynamic Memory Allocation',\n        'vtab' => 'Virtual Table Mechanism',\n        'datatype' => 'Datatypes',\n        'locking' => 'Locking and Concurrency',\n        'foreignkey' => 'Foreign Key Constraints',\n        'wal' => 'Write-Ahead Logging',\n        'fts' => 'Full-Text Search',\n        'rtree' => 'R*Tree Module',\n        'rbu' => 'RBU Extension',\n        'limits' => 'Limits',\n        'howtocorrupt' => 'How To Corrupt',\n        'geopoly' => 'Geopoly'\n      }\n\n      def get_type\n        TYPE_BY_SUBPATH_STARTS_WITH.each_pair do |key, value|\n          return value if subpath.start_with?(key)\n        end\n\n        if slug.in?(%w(cintro carray c_interface))\n          'C Interface'\n        elsif context[:html_title].start_with?('SQLite Query Language')\n          'Query Language'\n        else\n          'Miscellaneous'\n        end\n      end\n\n      IGNORE_ADDITIONAL_ENTRIES_SLUGS = %w(testing uri getthecode whentouse compile)\n\n      def additional_entries\n        if subpath == 'keyword_index.html'\n          css('li a[href*=\"#\"]').each do |node|\n            slug, id = node['href'].split('#')\n            name = node.content.strip\n            next if name.start_with?('-') || IGNORE_ADDITIONAL_ENTRIES_SLUGS.include?(slug)\n            name.remove! %r{\\Athe }\n            name.remove! %r{ SQL function\\z}\n            name = 'ORDER BY' if name == 'order by'\n            name.sub!(/\\A([a-z])([a-z]+) /) { \"#{$1.upcase}#{$2} \" }\n            ADDITIONAL_ENTRIES[slug] ||= []\n            ADDITIONAL_ENTRIES[slug] << [name, id]\n          end\n        end\n\n        ADDITIONAL_ENTRIES[slug] || []\n      end\n\n      def include_default_entry?\n        subpath != 'keyword_index.html' && subpath != 'sitemap.html'\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/statsmodels/clean_html.rb",
    "content": "module Docs\n  class Statsmodels\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.body', '#notebook-container')\n\n        if root_page?\n          at_css('h1').content = 'Statsmodels'\n          at_css('#basic-documentation').remove\n          at_css('#table-of-contents').remove\n          at_css('#indices-and-tables').remove\n        end\n\n        css('div.cell', 'div.inner_cell', 'div.text_cell_render', 'div.input_area').each do |node|\n          node.before(node.children).remove\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/statsmodels/entries.rb",
    "content": "module Docs\n  class Statsmodels\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        if subpath.start_with?('generated')\n          name = at_css('dt').content.strip\n          name.sub! %r{\\(.*}, '()' # Remove method arguments\n          name.remove! %r{[\\=\\[].*} # Remove \"[source]\"\n          name.remove! %r{\\A(class(method)?) }\n          name.remove! %r{\\Astatsmodels\\.}\n        else\n          name = at_css('h1', 'h2').content.strip\n          name.prepend 'Manual: ' if type == 'Manual'\n          name.prepend 'Example: ' if type == 'Examples'\n        end\n        name.remove! \"\\u{00B6}\" # Remove ¶\n        name\n      end\n\n      def get_type\n        if subpath.start_with?('generated')\n          # '> text()' doesn't include children's text in type naming\n          at_xpath('//div[@class=\"related\"]//li[not(@class=\"right\")][7]/a/text()').content\n\n        elsif subpath.start_with?('examples')\n          'Examples'\n        else\n          'Manual'\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/svelte/clean_html.rb",
    "content": "module Docs\n  class Svelte\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('main .page.content #docs-content')\n\n        # Remove title header\n        at_css('> header > div.breadcrumbs').remove()\n        # Remove extra input toggle\n        at_css('> aside.on-this-page input').remove()\n        # Remove \"edit this page\" link\n        at_css('> p.edit').remove()\n        # Remove footer navigation\n        at_css('> div.controls').remove()\n\n        at_css('h1').content = 'Svelte' if root_page?\n        css('pre').each do |node|\n          # Remove hover popup\n          node.css('.twoslash-popup-container').remove()\n          node.content = node.css('.line').map(&:content).join(\"\\n\")\n          node['data-language'] = 'typescript'\n        end\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/svelte/entries.rb",
    "content": "module Docs\n  class Svelte\n    class EntriesFilter < Docs::EntriesFilter\n      def get_type\n        page = at_css(\"main nav ul.sidebar li ul li a[href$='#{result[:path]}']\")\n        category = page.ancestors('li')[1]\n        return category.css('h3').inner_text\n      end\n\n      def get_name\n        page = at_css(\"main nav ul.sidebar li ul li a[href$='#{result[:path]}']\")\n        return page.inner_text\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/svg/clean_html.rb",
    "content": "module Docs\n  class Svg\n    class CleanHtmlFilter < Filter\n      def call\n        root_page? ? root : other\n        doc\n      end\n\n      def root\n      end\n\n      def other\n        css('.prev-next').remove\n\n        if at_css('p').content.include?(\"\\u{00AB}\")\n          at_css('p').remove\n        end\n\n        if slug == 'Attribute' || slug == 'Element'\n          at_css('h2').name = 'h1'\n        end\n\n        css('#SVG_Attributes + div[style]').each do |node|\n          node.remove_attribute('style')\n          node['class'] = 'index'\n          css('h3').each { |n| n.name = 'span' }\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/svg/entries.rb",
    "content": "module Docs\n  class Svg\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = super\n        name.gsub!('Attribute.', '')\n        name.gsub!('Element.', '')\n        name.gsub!('Guides.', '')\n        name.gsub!('Reference.', '')\n        name.gsub!('Tutorials.', '')\n        name.gsub!('_', '')\n\n        if name.in?(%w(Element Attribute Content\\ type))\n          \"#{name}s\"\n        else\n          name\n        end\n      end\n\n      def get_type\n        if slug.start_with?('Reference/Element')\n          'Elements'\n        elsif slug.start_with?('Reference/Attribute')\n          'Attributes'\n        elsif slug.start_with?('Guides')\n          'Guides'\n        elsif slug.start_with?('Tutorials')\n          'Tutorials'\n        else\n          'Miscellaneous'\n        end\n      end\n\n      def additional_entries\n        return [] unless slug == 'Content_type'\n        entries = []\n\n        css('h2[id]').each do |node|\n          dl = node.next_element\n          next unless dl.name == 'dl'\n          name = dl.at_css('dt').content.remove(/[<>]/)\n          entries << [name, node['id']]\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/symfony/clean_html.rb",
    "content": "module Docs\n  class Symfony\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('#page-content')\n\n        css('.location', '.no-description').remove\n\n        css('.page-header > h1').each do |node|\n          node.content = 'Symfony' if root_page?\n          node.parent.before(node).remove\n        end\n\n        css('div.details').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('a > abbr').each do |node|\n          node.parent['title'] = node['title']\n          node.before(node.children).remove\n        end\n\n        css('h1 > a', '.content', 'h3 > code', 'h3 strong', 'abbr', 'div.method-item', 'div.method-description', 'div.tags').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('.container-fluid').each do |node|\n          html = node.inner_html\n          html.gsub! %r{<div class=\"col[^>]+>(.+?)</div>}, '<td>\\1</td>'\n          html.gsub! %r{<div class=\"row[^>]+>(.+?)</div>}, '<tr>\\1</tr>'\n          node.replace(\"<table>#{html}</table>\")\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/symfony/entries.rb",
    "content": "module Docs\n  class Symfony\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content.strip\n        name.remove! 'Symfony\\\\'\n        name << \" (#{namespace})\" if name.gsub! \"#{namespace}\\\\\", ''\n        name\n      end\n\n      def get_type\n        return 'Exceptions' if slug =~ /exception/i\n        return 'Testing' if slug =~ /test/i\n        namespace\n      end\n\n      def namespace\n        @namespace ||= begin\n          path = slug.remove('Symfony/').remove(/\\/\\w+?\\z/).split('/')\n          upto = 1\n          upto = 2 if path[1] == 'Form' && path[2] == 'Extension'\n          upto = 2 if path[1] == 'HttpFoundation' && path[2] == 'Session'\n          path[0..upto].join('\\\\')\n        end\n      end\n\n      IGNORE_METHODS = %w(get set)\n\n      def additional_entries\n        return [] if initial_page?\n        return [] if type == 'Exceptions'\n        return [] if self.name.include?('Legacy') || self.name.include?('Loader')\n\n        entries = []\n        base_name = self.name.remove(/\\(.+\\)/).strip\n\n        css('h3[id^=\"method_\"]').each do |node|\n          next if node.at_css('.location').content.start_with?('in')\n\n          name = node['id'].remove('method_')\n          next if name.start_with?('_') || IGNORE_METHODS.include?(name)\n\n          name.prepend \"#{base_name}::\"\n          name << \"() (#{namespace})\"\n\n          entries << [name, node['id']]\n        end\n\n        entries.size > 1 ? entries : []\n      end\n\n      def include_default_entry?\n        !initial_page?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/tailwindcss/clean_html.rb",
    "content": "module Docs\n  class Tailwindcss\n    class CleanHtmlFilter < Filter\n      def call\n        # Move h1 out of wrapper.\n        css('h1').each do |node|\n          doc.prepend_child(node)\n        end\n\n        # Remove main page headers (top level sticky)\n        css('#__next > .sticky', 'div.fixed.inset-x-0.top-0').remove\n        # And anything absolutely positioned (fancy floating navigation elements we don't care about)\n        css('#__next .absolute', '.fixed').remove\n        # Remove the left-navigation we scraped\n        css('nav').remove\n\n        css('svg').remove if root_page?\n\n        # Remove the right navigation sidebar\n        css('header#header', 'p[data-section]').each do |node|\n          node.parent.css('> div:has(h5:contains(\"On this page\"))').remove # v3\n\n          node.parent.parent.css('div.max-xl\\\\:hidden').remove # v4\n        end\n\n        # Remove the duplicate category name at the top of the page - redundant\n        css(\n          'header#header > div:first-child > p:first-child', # v3\n          'p[data-section]' # v4\n        ).remove\n\n        # Remove footer + prev/next navigation\n        css('footer', '.row-start-5').remove\n\n        # Handle long lists of class reference that otherwise span several scrolled pages\n        if class_reference = at_css('#class-reference', '#quick-reference')\n          reference_container = class_reference.parent\n          classes_container = reference_container.children.reject{ |child| child == class_reference }.first\n\n          rows = classes_container.css(\"tr\")\n\n          if rows.length > 10\n            classes_container.set_attribute(\"class\", \"long-quick-reference\")\n          end\n        end\n\n        # Remove border color preview column as it isn't displayed anyway\n        if result[:path] == \"border-color\" and class_reference = at_css('#class-reference', '#quick-reference')\n          class_reference.parent.css(\"thead th:nth-child(3)\").remove\n          class_reference.parent.css(\"tbody td:nth-child(3)\").remove\n        end\n\n        if result[:path] == \"customizing-colors\"\n          # It's nice to be able to quickly find out what a color looks like\n          css('div[style^=\"background-color:\"]').each do |node|\n            node.set_attribute(\"class\", \"color-swatch\")\n\n            swatch_container = node.parent\n            swatch_container.set_attribute(\"class\", \"color-swatch-container\")\n            swatch_container.children.reject{ |child| child == node }.first.set_attribute(\"class\", \"color-tone-information\")\n\n            swatch_group = swatch_container.parent\n            swatch_group.set_attribute(\"class\", \"color-swatch-group\")\n\n            color = swatch_group.parent\n            color.set_attribute(\"class\", \"color\")\n\n            color_list = color.parent.parent\n            color_list.set_attribute(\"class\", \"colors\")\n          end\n        end\n\n        # Remove buttons to expand lists - those are already expanded and the button is useless\n        css(\n          'div > button:contains(\"Show all classes\")',\n          'div > button:contains(\"Show more\")'\n        ).each do |node|\n          node.parent.remove\n        end\n\n        # Remove class examples - not part of devdocs styleguide? (similar to bootstrap)\n        # Refer to https://github.com/freeCodeCamp/devdocs/pull/1534#pullrequestreview-649818936\n        css('.mt-4.-mb-3', 'figure', 'svg', '.flex.space-x-2').remove\n\n        # Properly format code examples.\n        css('pre > code:first-child').each do |node|\n          # v4 doesn't provide language context, so it must be inferred imperfectly.\n          node.parent['data-language'] =\n            if node.content.include?('function')\n              'jsx'\n            elsif node.content.include?(\"</\")\n              'html'\n            else\n              'css'\n            end\n\n          node.parent.content =\n            if version == '3'\n              node.parent.content\n            else\n              node.css('.line').map(&:content).join(\"\\n\")\n            end\n        end\n\n        # Remove headers some code examples have.\n        css('.flex.text-slate-400.text-xs.leading-6', '.px-3.pt-0\\\\.5.pb-1\\\\.5').remove\n\n        # Strip anchor from headers\n        css('h2', 'h3').each do |node|\n          node.content = node.inner_text\n        end\n\n        @doc.traverse { |node| cleanup_tailwind_classes(node) }\n\n        # Remove weird <hr> (https://github.com/damms005/devdocs/commit/8c9fbd859b71a2525b94a35ea994393ce2b6fedb#commitcomment-50091018)\n        css('hr').remove\n\n        doc\n      end\n\n      # Removes all classes not allowlisted in the below semantic_classes array - such as tailwinds utility classes\n      def cleanup_tailwind_classes(node)\n        class_name = node.attr(\"class\")\n\n        if class_name == nil\n          return node.children.each { |child| cleanup_tailwind_classes(child) }\n        end\n\n        semantic_classes = [\"code\", \"color-swatch\", \"color-swatch-container\", \"color-tone-information\", \"color-swatch-group\", \"color\", \"colors\", \"long-quick-reference\"]\n\n        classes = class_name.split.select do |klas|\n          semantic_classes.include? klas\n        end\n\n        if classes.length === 0\n          node.delete(\"class\")\n        else\n          node.set_attribute(\"class\", classes.join(\" \"))\n        end\n\n        node.children.each { |child| cleanup_tailwind_classes(child) }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/tailwindcss/entries.rb",
    "content": "module Docs\n  class Tailwindcss\n    class EntriesFilter < Docs::EntriesFilter\n      def get_type\n        # We are only interested in children list items\n        \n\n        anchor = at_css(get_selector)\n        title =\n          if version == '3'\n            anchor.ancestors('li')[1].css('h5')\n          else\n            anchor.ancestors('ul').last.previous_element\n          end\n\n        return title.inner_text\n      end\n\n      def get_name\n        # We are only interested in children list items\n        item = at_css(get_selector)\n\n        return item.inner_text\n      end\n\n      private\n\n      def get_selector\n        if version == '3'\n          \"nav li li a[href='#{result[:path]}']\"\n        else\n          \"nav li a[href*='#{result[:path]}']\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/tailwindcss/noop.rb",
    "content": "module Docs\n  class Tailwindcss\n    class NoopFilter < Filter\n      def call\n        return html\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/tcl_tk/clean_html.rb",
    "content": "module Docs\n  class TclTk\n    class CleanHtmlFilter < Filter\n      def call\n        css('h2').remove # Page Title\n        css('h3:first-child').remove # Navigation\n\n        css('div.copy').each do |node|\n          node['class'] = '_attribution'\n          node.inner_html = %(<p class=\"_attribution-p\">#{node.inner_html.gsub(' Copyright', '<br>\\0')}</p>)\n        end\n\n        css('a[name]').each do |node|\n          node.parent['id'] = node['name']\n          node.before(node.children).remove unless node['href']\n        end\n\n        css('h3').each do |node|\n          if node.content == 'KEYWORDS'\n            node['id'] = 'tmp__'\n            css('#tmp__ ~ a[href*=\"Keywords\"]').each do |link|\n              link.next.remove if link.next.content == ', '\n              link.remove\n            end\n            node.remove\n            next\n          end\n\n          node.name = 'h2'\n          node.content = node.content.capitalize\n        end\n\n        css('h4').each do |node|\n          node.name = 'h3'\n          node.content = node.content.capitalize\n        end\n\n        css('pre').each do |node|\n          node['data-language'] = 'tcl'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/tcl_tk/entries.rb",
    "content": "module Docs\n  class TclTk\n    class EntriesFilter < Docs::EntriesFilter\n      TYPE_MAP = {\n        'UserCmd' => 'Applications',\n        'TclCmd' => 'Tcl',\n        'TkCmd' => 'Tk',\n        'ItclCmd' => '[incr Tcl]',\n        'SqliteCmd' => 'TDBC',\n        'TdbcCmd' => 'TDBC',\n        'TdbcmysqlCmd' => 'TDBC',\n        'TdbcodbcCmd' => 'TDBC',\n        'TdbcpostgresCmd' => 'TDBC',\n        'TdbcsqliteCmd' => 'TDBC',\n        'ThreadCmd' => 'Thread'\n      }\n\n      def get_name\n        if slug.end_with?('contents.htm')\n          slug.split('/').first\n        else\n          slug.split('/').last.remove('.htm')\n        end\n      end\n\n      def get_type\n        TYPE_MAP.fetch(slug.split('/').first)\n      end\n\n      def additional_entries\n        css('> dl.command > dt > a[name]:not([href])', '> dl.commands > dt > a[name]:not([href])').each_with_object [] do |node, entries|\n          name = node.at_xpath(\"./b[not(text()='#{self.name}')]\").try(:content)\n          next unless name\n          name.strip!\n          name.remove! %r{\\A:+}\n          name.prepend \"#{self.name}: \" unless name =~ /#{self.name}[:\\s]/ || name =~ /#{self.name.gsub('_', '::')}[:\\s]/\n          next if entries.any? { |entry| entry[0] == name }\n          entries << [name, node['name']]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/tcllib/clean_html.rb",
    "content": "module Docs\n  class Tcllib\n    class CleanHtmlFilter < Filter\n      def call\n        css(\"hr\").remove()\n        xpath(\"./div/text()\").remove() # Navigation text content e.g. [ | | | ]\n        css(\"div.markdown > a\").remove() # Navigation links\n\n\n        # Fix up ToC links\n        css('a[name]').each do |node|\n          node.parent['id'] = node['name']\n          node.before(node.children).remove unless node['href']\n        end\n\n        # Relies on the above ToC fixup\n        keywords = at_css('#keywords')\n        if !keywords.nil?\n          keywords.next_sibling.remove()\n          keywords.remove()\n          css('a[href=\"#keywords\"]').remove()\n        end\n\n        # Downrank headings for styling\n        css('h2').each do |node|\n          node.name = 'h3'\n        end\n        css('h1').each do |node|\n          node.name = 'h2'\n        end\n\n        css('pre').each do |node|\n          node['data-language'] = 'tcl'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/tcllib/entries.rb",
    "content": "module Docs\n  class Tcllib\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        # The first word after the `NAME` heading\n        name = at_css('h1 + p')\n        return name.content.strip.split[0]\n      end\n\n      def get_type\n        # The types are the categories as indicated on each page (and on the\n        # root page, toc0.md)\n        category = at_css('a[name=\"category\"]')\n        if !category.nil?\n          return category.parent.next.next.content\n        end\n        return 'Unfiled'\n      end\n    end\n  end\nend\n\n"
  },
  {
    "path": "lib/docs/filters/tcllib/nop.rb",
    "content": "module Docs\n  class Tcllib\n    class NopFilter < Filter\n      def call\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/tensorflow/clean_html.rb",
    "content": "module Docs\n  class Tensorflow\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('.devsite-article')\n\n        css('hr', '.devsite-nav', '.devsite-content-footer', '.devsite-article-body > br', '.devsite-article-meta', 'devsite-nav-buttons', '.devsite-banner', '.tfo-api img', '.tfo-notebook-buttons img', '.tfo-notebook-buttons>:first-child').remove\n        css('devsite-bookmark').remove\n\n        css('aside.note').each do |node|\n          node.name = 'blockquote'\n        end\n\n        css('.devsite-article-body', 'blockquote > blockquote', 'th > h2', 'th > h3').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('code[class] > pre').each do |node|\n          node = node.parent\n          node.content = node.content\n          node.name = 'pre'\n        end\n\n        css('blockquote > pre:only-child', 'p > pre:only-child').each do |node|\n          next if node.previous.try(:content).present? || node.next.try(:content).present?\n          node.parent.before(node).remove\n        end\n\n        css('pre').each do |node|\n          node.content = node.content.strip_heredoc\n\n          if node['class'] && node['class'] =~ /lang-c++/i\n            node['data-language'] = 'cpp'\n          elsif node['class'] && node['class'] =~ /lang-python/i\n            node['data-language'] = 'python'\n          else\n            node['data-language'] = version == 'Python' ? 'python' : 'cpp'\n          end\n        end\n\n        css('code').each do |node|\n          node.inner_html = node.inner_html.gsub(/\\s+/, ' ')\n        end\n\n        css('> code', '> b').each do |node|\n          node.replace(\"<p>#{node.to_html}</p>\")\n        end\n\n        css('span[slot=\"popout-heading\"]').remove\n        css('span[slot=\"popout-contents\"]').remove\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/tensorflow/entries.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class Tensorflow\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content.strip\n        name.remove! 'class '\n        name.remove! 'struct '\n        name.remove! 'Module: '\n        name.remove! %r{ \\(.+\\)}\n        name.sub! %r{(?<!\\ )\\(.+\\)}, '()'\n        name.remove! %r{\\.\\z}\n        name\n      end\n\n      def get_type\n        if slug.start_with?('audio')\n          'tf.audio'\n        elsif slug.start_with?('autodiff')\n          'tf.autodiff'\n        elsif slug.start_with?('autograph')\n          'tf.autograph'\n        elsif slug.start_with?('bitwise')\n          'tf.bitwise'\n        elsif slug.start_with?('compat')\n          'tf.compat'\n        elsif slug.start_with?('config')\n          'tf.config'\n        elsif slug.start_with?('data')\n          'tf.data'\n        elsif slug.start_with?('debugging')\n          'tf.debugging'\n        elsif slug.start_with?('distribute')\n          'tf.distribute'\n        elsif slug.start_with?('dtypes')\n          'tf.dtypes'\n        elsif slug.start_with?('errors')\n          'tf.errors'\n        elsif slug.start_with?('estimator')\n          'tf.estimator'\n        elsif slug.start_with?('experimental')\n          'tf.experimental'\n        elsif slug.start_with?('feature_column')\n          'tf.feature_column'\n        elsif slug.start_with?('graph_util')\n          'tf.graph_util'\n        elsif slug.start_with?('image')\n          'tf.image'\n        elsif slug.start_with?('initializers')\n          'tf.initializers'\n        elsif slug.start_with?('io')\n          'tf.io'\n        elsif slug.start_with?('keras')\n          'tf.keras'\n        elsif slug.start_with?('linalg')\n          'tf.linalg'\n        elsif slug.start_with?('lite')\n          'tf.lite'\n        elsif slug.start_with?('lookup')\n          'tf.lookup'\n        elsif slug.start_with?('losses')\n          'tf.losses'\n        elsif slug.start_with?('math')\n          'tf.math'\n        elsif slug.start_with?('metrics')\n          'tf.metrics'\n        elsif slug.start_with?('mixed_precision')\n          'tf.mixed_precision'\n        elsif slug.start_with?('mlir')\n          'tf.mlir'\n        elsif slug.start_with?('nest')\n          'tf.nest'\n        elsif slug.start_with?('nn')\n          'tf.nn'\n        elsif slug.start_with?('optimizers')\n          'tf.optimizers'\n        elsif slug.start_with?('profiler')\n          'tf.profiler'\n        elsif slug.start_with?('quantization')\n          'tf.quantization'\n        elsif slug.start_with?('queue')\n          'tf.queue'\n        elsif slug.start_with?('ragged')\n          'tf.ragged'\n        elsif slug.start_with?('random')\n          'tf.random'\n        elsif slug.start_with?('raw_ops')\n          'tf.raw_ops'\n        elsif slug.start_with?('saved_model')\n          'tf.saved_model'\n        elsif slug.start_with?('sets')\n          'tf.sets'\n        elsif slug.start_with?('signal')\n          'tf.signal'\n        elsif slug.start_with?('sparse')\n          'tf.sparse'\n        elsif slug.start_with?('strings')\n          'tf.strings'\n        elsif slug.start_with?('summary')\n          'tf.summary'\n        elsif slug.start_with?('sysconfig')\n          'tf.sysconfig'\n        elsif slug.start_with?('test')\n          'tf.test'\n        elsif slug.start_with?('tpu')\n          'tf.tpu'\n        elsif slug.start_with?('train')\n          'tf.train'\n        elsif slug.start_with?('version')\n          'tf.version'\n        elsif slug.start_with?('xla')\n          'tf.xla'\n        else\n          'tf'\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/terraform/clean_html.rb",
    "content": "module Docs\n  class Terraform\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('#inner')\n\n        css('hr', 'a.anchor').remove\n\n        css('.alert').each do |node|\n          node.name = 'blockquote'\n        end\n\n        css('pre').each do |node|\n          if language = node['class'][/(json|shell|ruby)/, 1]\n            node['data-language'] = language\n          end\n          # HCL isn't currently supported by Prism, Ruby syntax does an acceptable job for now\n          if language = node['class'][/(hcl)/, 1]\n            node['data-language'] = 'ruby'\n          end\n          node.content = node.content\n          node.remove_attribute('class')\n        end\n\n        css('.highlight').each do |node|\n          node.before(node.children).remove\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/terraform/entries.rb",
    "content": "module Docs\n  class Terraform\n    class EntriesFilter < Docs::EntriesFilter\n\n      # Some providers have non-trivial mappings between the directory they live in and their name\n      # Anything *not* in this list will be capitalized instead.\n      PROVIDER_NAME_MAP = {\n        'aws'              => 'AWS',\n        'azure'            => 'Azure (Legacy)',\n        'azurerm'          => 'Azure',\n        'centurylinkcloud' => 'CenturyLinkCloud',\n        'cloudscale'       => 'CloudScale.ch',\n        'cloudstack'       => 'CloudStack',\n        'dme'              => 'DNSMadeEasy',\n        'dns'              => 'DNS',\n        'dnsimple'         => 'DNSimple',\n        'do'               => 'DigitalOcean',\n        'github'           => 'GitHub',\n        'google'           => 'Google Cloud',\n        'http'             => 'HTTP',\n        'mysql'            => 'MySQL',\n        'newrelic'         => 'New Relic',\n        'oneandone'        => '1&1',\n        'opentelekomcloud' => 'OpenTelekomCloud',\n        'opsgenie'         => 'OpsGenie',\n        'opc'              => 'Oracle Public Cloud',\n        'oraclepaas'       => 'Oracle Cloud Platform',\n        'ovh'              => 'OVH',\n        'pagerduty'        => 'PagerDuty',\n        'panos'            => 'Palo Alto Networks',\n        'postgresql'       => 'PostgreSQL',\n        'powerdns'         => 'PowerDNS',\n        'profitbricks'     => 'ProfitBricks',\n        'rabbitmq'         => 'RabbitMQ',\n        'softlayer'        => 'SoftLayer',\n        'statuscake'       => 'StatusCake',\n        'tls'              => 'TLS',\n        'ultradns'         => 'UltraDNS',\n        'vcd'              => 'VMware vCloud Director',\n        'nsxt'             => 'VMware NSX-T',\n        'vsphere'          => 'VMware vSphere',\n      }\n\n      # Some providers have a lot (> 100) entries, which makes browsing them unwieldy.\n      # Any present in the list below will have an extra set of types added, breaking the pages out into the different\n      # products they offer.\n      LARGE_PROVIDERS = {\n        \"aws\"     => true,\n        \"azurerm\" => true,\n        \"google\"  => true,\n      }\n\n\n      def get_name\n        name ||= at_css('#inner h1').content\n        name.remove! \"» \"\n        name.remove! \"Data Source: \"\n        name\n      end\n\n      def get_type\n        category, subcategory, subfolder, page = *slug.split('/')\n        provider = page ? subcategory : category\n        nice_provider = PROVIDER_NAME_MAP[provider] || provider.capitalize\n\n        if LARGE_PROVIDERS[provider]\n          category_node = at_css('ul > li > ul > li.active')\n          parent_node = category_node.parent.previous_element if category_node\n          nice_provider = nice_provider + \": #{parent_node.content}\" if category_node\n        end\n\n        nice_provider\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/threejs/clean_html.rb",
    "content": "module Docs\n  class Threejs\n    class CleanHtmlFilter < Filter\n      PATTERNS = {\n        method_this: /\\[method:this\\s+([^\\]]+)\\]\\s*\\((.*?)\\)/,\n        method_return: /\\[method:([^\\s\\]]+)\\s+([^\\]]+)\\]\\s*\\((.*?)\\)/,\n        method_no_params: /\\[method:([^\\s\\]]+)\\s+([^\\]]+)\\](?!\\()/,\n        property: /\\[property:([^\\]]+?)\\s+([^\\]]+?)\\]/,\n        example_link: /\\[example:([^\\s\\]]+)\\s+([^\\]]+)\\]/,\n        external_link_text: /\\[link:([^\\s\\]]+)\\s+([^\\]]+)\\]/,\n        external_link: /\\[link:([^\\]]+)\\]/,\n        page_link_text: /\\[page:([^\\]]+?)\\s+([^\\]]+?)\\]/,\n        page_link: /\\[page:([^\\]]+?)\\]/,\n        inline_code: /`([^`]+)`/,\n        name_placeholder: /\\[name\\]/,\n        constructor_param: /\\[param:([^\\]]+?)\\s+([^\\]]+?)\\]/\n      }.freeze\n\n      def call\n        remove_unnecessary_elements\n        wrap_code_blocks\n        process_sections\n        format_links\n        add_section_structure\n        format_notes\n        add_heading_attributes\n        doc\n      end\n\n      private\n\n      def remove_unnecessary_elements\n        css('head, script, style').remove\n      end\n\n      def wrap_code_blocks\n        css('code').each do |node|\n          next if node.parent.name == 'pre'\n          node.wrap('<pre>')\n          node.parent['data-language'] = 'javascript'\n        end\n      end\n\n      def process_sections\n        # Handle source links\n        css('h2').each do |node|\n          next unless node.content.strip == 'Source'\n          node.next_element.remove\n          node.remove\n        end\n\n        # Handle method signatures and properties\n        css('h3').each do |node|\n          content = node.inner_html\n          content = handle_method_signatures(content)\n          content = handle_properties(content)\n          node.inner_html = content\n        end\n\n        # Handle name placeholders and constructor params\n        css('h1, h3').each do |node|\n          content = node.inner_html\n          content = handle_name_placeholders(content)\n          content = format_constructor_params(content)\n          node.inner_html = content\n        end\n      end\n\n      def handle_method_signatures(content)\n        content\n          .gsub(PATTERNS[:method_this]) { format_method_signature('this', $1, $2) }\n          .gsub(PATTERNS[:method_return]) do |match|\n            next if $2.start_with?('this')\n            format_method_signature($1, $2, $3, true)\n          end\n          .gsub(PATTERNS[:method_no_params]) { format_method_signature($1, $2, nil, true) }\n      end\n\n      def format_method_signature(type_or_this, name, params_str, with_return = false)\n        params = if params_str\n          params_str.split(',').map { |param| format_parameter(param.strip) }.join(\"<span class='sig-paren'>, </span>\")\n        end\n\n        html = \"<dt class='sig sig-object js' id='#{name}'>\"\n        if type_or_this == 'this'\n          html << \"<span class='property'><span class='pre'>this</span></span>.\"\n        end\n        html << \"<span class='sig-name descname'>#{name}</span>\" \\\n               \"<span class='sig-paren'>(</span>\" \\\n               \"#{params}\" \\\n               \"<span class='sig-paren'>)</span>\"\n        if with_return\n          html << \"<span class='sig-returns'><span class='sig-colon'>:</span> \" \\\n                 \"<span class='sig-type'>#{type_or_this}</span></span>\"\n        end\n        html << \"</dt>\"\n      end\n\n      def format_parameter(param)\n        if param.include?(' ')\n          type, name = param.split(' ', 2).map(&:strip)\n          \"<span class='sig-param'><span class='sig-type'>#{type}</span> <span class='sig-name'>#{name}</span></span>\"\n        else\n          \"<span class='sig-param'>#{param}</span>\"\n        end\n      end\n\n      def handle_properties(content)\n        content.gsub(PATTERNS[:property]) do |match|\n          type, name = $1, $2\n          \"<dt class='sig sig-object js'>\" \\\n          \"<span class='sig-name descname'>#{name}</span>\" \\\n          \"<span class='sig-colon'>:</span> \" \\\n          \"<span class='sig-type'>#{type}</span></dt>\"\n        end\n      end\n\n      def handle_name_placeholders(content)\n        content.gsub(PATTERNS[:name_placeholder]) do\n          name = slug.split('/').last.gsub('.html', '')\n          \"<span class='descname'>#{name}</span>\"\n        end\n      end\n\n      def format_constructor_params(content)\n        content.gsub(PATTERNS[:constructor_param]) do |match|\n          type, name = $1, $2\n          \"<span class='sig-param'><span class='sig-type'>#{type}</span> <code class='sig-name'>#{name}</code></span>\"\n        end\n      end\n\n      def format_links\n        css('*').each do |node|\n          next if node.text?\n          \n          content = node.inner_html\n            .gsub(PATTERNS[:example_link]) { create_external_link(\"https://threejs.org/examples/##{$1}\", $2) }\n            .gsub(PATTERNS[:external_link_text]) { create_external_link($1, $2) }\n            .gsub(PATTERNS[:external_link]) { create_external_link($1, $1) }\n            .gsub(PATTERNS[:page_link_text]) { create_internal_link($1, $2) }\n            .gsub(PATTERNS[:page_link]) { create_internal_link($1, $1) }\n          \n          node.inner_html = content\n        end\n\n        normalize_href_attributes\n      end\n\n      def create_external_link(url, text)\n        %Q(<a class='reference external' href='#{url}'>#{text}</a>)\n      end\n\n      def create_internal_link(path, text)\n        %Q(<a class='reference internal' href='#{path.downcase}'><code class='xref js js-#{path.downcase}'>#{text}</code></a>)\n      end\n\n      def normalize_href_attributes\n        css('a[href]').each do |link|\n          next if link['href'].start_with?('http')\n          link['href'] = link['href'].remove('../').downcase.sub(/\\.html$/, '')\n          link['class'] = 'reference internal'\n        end\n      end\n\n      def add_section_structure\n        css('h2').each do |node|\n          node['class'] = 'section-title'\n          section = node.next_element\n          next unless section\n\n          wrapper = doc.document.create_element('div')\n          wrapper['class'] = 'section'\n          node.after(wrapper)\n          wrapper.add_child(node)\n          \n          current = section\n          while current && current.name != 'h2'\n            next_el = current.next\n            wrapper.add_child(current)\n            current = next_el\n          end\n        end\n\n        css('p.desc').each { |node| node['class'] = 'section-desc' }\n      end\n\n      def format_notes\n        css('p').each do |node|\n          next unless node.content.start_with?('Note:')\n          \n          wrapper = doc.document.create_element('div')\n          wrapper['class'] = 'admonition note'\n          \n          title = doc.document.create_element('p')\n          title['class'] = 'first admonition-title'\n          title.content = 'Note'\n          \n          content = doc.document.create_element('p')\n          content['class'] = 'last'\n          content.inner_html = node.inner_html.sub('Note:', '').strip\n          \n          wrapper.add_child(title)\n          wrapper.add_child(content)\n          node.replace(wrapper)\n        end\n      end\n\n      def add_heading_attributes\n        css('h1, h2, h3, h4').each do |node|\n          node['id'] ||= node.content.strip.downcase.gsub(/[^\\w]+/, '-')\n          existing_class = node['class'].to_s\n          node['class'] = \"#{existing_class} section-header\"\n        end\n\n        format_inline_code\n      end\n\n      def format_inline_code\n        selectors = ['p', 'li', 'dt', 'dd', '.property-type'].join(', ')\n        css(selectors).each do |node|\n          next if node.at_css('pre')\n          node.inner_html = node.inner_html.gsub(PATTERNS[:inline_code]) do |match|\n            \"<code class='docutils literal notranslate'><span class='pre'>#{$1}</span></code>\"\n          end\n        end\n      end\n    end\n  end\nend "
  },
  {
    "path": "lib/docs/filters/threejs/entries.rb",
    "content": "module Docs\n  class Threejs\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        # Try to get name from the title first\n        if title = at_css('.lesson-title h1')&.content\n          title\n        else\n          # Fallback to path-based name for API docs\n          slug.split('/').last.gsub('.html', '').titleize\n        end\n      end\n\n      def get_type\n        if slug.start_with?('api/en/')\n          # For API documentation, use the section as type\n          # e.g. \"api/en/animation/AnimationAction\" -> \"Animation\"\n          path_parts = slug.split('/')\n          if path_parts.length >= 3\n            path_parts[2].titleize\n          else\n            'API'\n          end\n        elsif slug.start_with?('manual/en/')\n          # For manual pages, get the section from the path\n          # e.g. \"manual/en/introduction/Creating-a-scene\" -> \"Introduction\"\n          path_parts = slug.split('/')\n          if path_parts.length >= 3\n            path_parts[2].titleize\n          else\n            'Manual'\n          end\n        else\n          'Other'\n        end\n      end\n\n      def additional_entries\n        entries = []\n        \n        # Get all methods and properties from h3 headings\n        css('h3').each do |node|\n          name = node.content.strip\n          # Skip if it's a constructor or doesn't have an ID\n          next if name == get_name || !node['id']\n          \n          entries << [name, node['id'], get_type]\n        end\n\n        entries\n      end\n    end\n  end\nend "
  },
  {
    "path": "lib/docs/filters/trio/clean_html.rb",
    "content": "module Docs\n  class Trio\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('div[role=\"main\"]')\n\n        css('.section, [itemprop=articleBody]').each do |node|\n          node.replace node.children\n        end\n\n        css('.headerlink').remove\n\n        css('dt').each do |node|\n          node.name = 'h3'\n\n          if node.parent.classes.include? 'field-list'\n            node.name = 'h4'\n            node['style'] = 'margin: 0'\n\n            if node.text == 'Parameters' or node.text == 'Raises'\n              node.next_element.css('strong').each do |n|\n                n.name = 'code'\n              end\n            end\n          else\n            code = doc.document.create_element 'code'\n\n            if em = node.at_css('.property')\n              code.inner_html = \"<em>#{em.text.strip}</em> \"\n              em.remove\n            end\n\n            code.inner_html += node.inner_text.strip\n            node.inner_html = code\n          end\n        end\n\n        css('pre').each do |node|\n          node.content = node.content.strip\n\n          classes = node.parent.parent.classes\n          if classes.include? 'highlight-python3'\n            node['data-language'] = 'python'\n          end\n\n          node.parent.parent.replace(node)\n        end\n\n        css('.admonition').each do |node|\n          node.name = 'blockquote'\n          node.at_css('.admonition-title').name = 'h4'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/trio/entries.rb",
    "content": "module Docs\n  class Trio\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').text[0...-1]\n      end\n\n      def get_type\n        at_css('h1').text[0...-1]\n      end\n\n      def additional_entries\n        css('.descname').each_with_object [] do |node, entries|\n          name = node.text\n          if node.previous.classes.include? 'descclassname'\n            name = node.previous.text + name\n          end\n          name.strip!\n\n          dl = node.parent.parent\n\n          if dl.classes.include?('attribute') \\\n              or dl.classes.include?('method') \\\n              or dl.classes.include?('data')\n            parent = dl.parent.previous_element\n            cls = ''\n\n            if n = parent.at_css('.descclassname')\n              cls += n.text\n            end\n\n            if n = parent.at_css('.descname')\n              if n.text == \"The nursery interface\"\n                cls += \"Nursery.\"\n              else\n                cls += n.text + '.'\n              end\n            end\n\n            name = cls + name\n          end\n\n          entries << [name, node.parent['id']]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/twig/clean_html.rb",
    "content": "module Docs\n  class Twig\n    class CleanHtmlFilter < Filter\n      def call\n\n        css('.infobar', '.offline-docs', '.headerlink', '.linenos').remove\n\n        css('.builtin-reference', '.admonition-wrapper', 'h1 > code', 'h2 > code', '.body-web', '.reference em').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('.section').each do |node|\n          node.first_element_child['id'] = node['id'] if node['id']\n          node.before(node.children).remove\n        end\n\n        doc.child.remove until doc.child.name == 'h1'\n\n        css('.literal-block').each do |node|\n          pre = node.at_css('.highlight pre') || node.at_css('pre')\n          pre.content = pre.content\n          node.replace(pre)\n        end\n\n        css('p.versionadded').each do |node|\n          node.name = 'div'\n        end\n\n        css('h3:contains(\"Arguments\")').each do |node|\n          node.name = 'h4'\n        end\n\n        css('.navigation').each do |node|\n          node['style'] = 'text-align: center'\n        end\n\n        # syntax highlight\n        css('.highlight').each do |node|\n          node.css('pre').each do |subnode|\n            subnode['data-language'] = 'php'\n            subnode.add_class('highlight')\n          end\n        end\n\n        # fix code blocks style\n        css('.highlighttable').each do |node|\n          code = node.at_css('pre')\n          node.before(code)\n          node.remove\n        end\n\n        doc\n\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/twig/entries.rb",
    "content": "module Docs\n  class Twig\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        if slug.start_with?('filters')\n          'Filters'\n        elsif slug.start_with?('functions')\n          'Functions'\n        elsif slug.start_with?('tags')\n          'Tags'\n        elsif slug.start_with?('tests')\n          'Tests'\n        elsif slug.start_with?('extensions')\n          'Extensions'\n        else\n          'Miscellaneous'\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/typescript/clean_html.rb",
    "content": "module Docs\n  class Typescript\n    class CleanHtmlFilter < Filter\n\n      LANGUAGE_REPLACE = {\n        'cmd' => 'shell',\n        'sh' => 'shell',\n        'tsx' => 'typescript+html'\n      }\n\n      def call\n        @doc = at_css('main')\n        root_page? ? root : other\n        doc\n      end\n\n      def root\n        header = at_css('h1')\n        header.parent.before(header).remove\n\n        # css('#above-the-fold-headline-code').remove\n        # css('#adopt-gradually-content').remove\n\n        css('h4').each do |node|\n          node.name = 'h2'\n        end\n      end\n\n      def other\n        if base_url.path == '/docs/handbook/'\n          deprecated = at_css('#deprecated-content')\n          deprecated.css('h3', '#deprecated-icon').remove if deprecated\n          deprecated.add_class('deprecated') if deprecated\n          @doc = at_css('article > .whitespace > .markdown')\n          doc.child.before(deprecated) if deprecated\n        else # tsconfig page\n        end\n\n        css('.anchor', 'a:contains(\"Try\")', 'h2 a', 'h3 a', 'svg', '#full-option-list').remove\n\n        # tsconfig page\n        css('.markdown', '.compiler-option', '.compiler-option-md', '.compiler-content').each do |node|\n          node.remove_attribute('class')\n        end\n\n        css('pre').each do |node|\n          language = node.at_css('.language-id') ? node.at_css('.language-id').content : 'typescript'\n          node.css('.language-id').remove\n          if node.at_css('.line').nil?\n            node.content = node.content\n          else\n            node.content = node.css('.line').map(&:content).join(\"\\n\")\n          end\n          node['data-language'] = LANGUAGE_REPLACE[language] || language\n          node.remove_attribute('class')\n          node.remove_attribute('style')\n        end\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/typescript/entries.rb",
    "content": "module Docs\n  class Typescript\n    class EntriesFilter < Docs::EntriesFilter\n\n      DEPRECATED_PAGES = %w(\n        advanced-types\n        basic-types\n        classes\n        functions\n        generics\n        interfaces\n        literal-types\n        unions-and-intersections\n      )\n\n      def get_name\n        return 'TSConfig Reference' if slug == 'tsconfig'\n        at_css('h1') ? at_css('h1').content : at_css('h2').content\n      end\n\n      def get_type\n        if DEPRECATED_PAGES.include? slug\n          'Handbook (deprecated)'\n        elsif slug.include?('declaration-files')\n          'Declaration Files'\n        elsif slug == 'download'\n          'Handbook'\n        elsif slug == 'why-create-typescript'\n          'Handbook'\n        else\n          button = at_css('nav#sidebar > ul > li.open.highlighted > button')\n          button ? button.content : name\n        end\n      end\n\n      def additional_entries\n        return [] if DEPRECATED_PAGES.include? slug\n        return [] if slug == 'tsconfig-json'\n        base_url.path == '/' ? tsconfig_entries : handbook_entries\n      end\n\n      def tsconfig_entries\n        css('h3 > code').each_with_object [] do |node, entries|\n          entries << [node.content, node.parent['id']]\n        end\n      end\n\n      def handbook_entries\n        css('h2', 'h3:has(code)').each_with_object [] do |node, entries|\n          entries << [\"#{name}: #{node.content}\", node['id']] if node['id']\n        end\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/underscore/clean_html.rb",
    "content": "module Docs\n  class Underscore\n    class CleanHtmlFilter < Filter\n      def call\n        # Remove Links, Changelog\n        css('#links ~ *', '#links').remove\n\n        css('pre').each do |node|\n          node['data-language'] = 'javascript'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/underscore/entries.rb",
    "content": "module Docs\n  class Underscore\n    class EntriesFilter < Docs::EntriesFilter\n      def additional_entries\n        entries = []\n        type = nil\n\n        css('[id]').each do |node|\n          # Module\n          if node.name == 'h2'\n            type = node.content.split.first\n            next\n          end\n\n          # Method\n          node.css('.header', '.alias b').each do |header|\n            prefix = header.ancestors('p').first.at_css('code').content[/\\A[^\\.]+\\./].strip\n            header.content.split(',').each do |name|\n              name.strip!\n              name.prepend(prefix)\n              entries << [name, node['id'], type]\n            end\n          end\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/vagrant/clean_html.rb",
    "content": "module Docs\n  class Vagrant\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('#inner')\n\n        css('hr', 'a.anchor').remove\n\n        css('.alert').each do |node|\n          node.name = 'blockquote'\n        end\n\n        css('pre').each do |node|\n          if language = node['class'][/(json|shell|ruby)/, 1]\n            node['data-language'] = language\n          end\n          node.content = node.content\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/vagrant/entries.rb",
    "content": "module Docs\n  class Vagrant\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        if slug.start_with?('push/')\n          name = at_css('#inner h2').try(:content)\n        elsif slug.start_with?('cli/')\n          name = at_css('#inner h1 + p > strong > code').try(:content).try(:[], /\\s*vagrant\\s+[\\w\\-]+/)\n        end\n\n        name ||= at_css('#inner h1').content\n        name.remove! \"» \"\n        name\n      end\n\n      def get_type\n        type = at_css('.docs-sidenav > li.active > a').content\n        node = at_css('.docs-sidenav > li.active > ul > li.active > a + ul')\n        type << \": #{node.previous_element.content}\" if node\n        type\n      end\n\n      def additional_entries\n        case at_css('h1 + p > strong > code').try(:content)\n        when /config\\./\n          h2 = nil\n          css('#inner > *').each_with_object [] do |node, entries|\n            next if node.name == 'pre'\n            if node.name == 'h2'\n              h2 = node['id']\n            elsif h2 == 'available-settings' && (code = node.at_css('code')) && (name = code.content) && name.start_with?('config.')\n              name.sub! %r{\\s+=.*}, '='\n              id = code.parent['id'] = name.parameterize\n              entries << [name, id, 'Config']\n            end\n          end\n        else\n          []\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/varnish/entries.rb",
    "content": "module Docs\n  class Varnish\n    class EntriesFilter < Docs::EntriesFilter\n      TYPE_BY_SLUG = {}\n\n      def call\n        if root_page?\n          css('.section').each do |node|\n            type = node.at_css('h2').content[0..-2]\n            node.css('li > a').each do |n|\n              s = n['href'].split('/')[-2]\n              TYPE_BY_SLUG[s] = type\n            end\n          end\n        end\n        super\n      end\n\n      def get_name\n        at_css('h1').content[0..-2]\n      end\n\n      def get_type\n        case slug\n        when /installation/\n          'Installation'\n        when /users-guide/\n          'Users Guide'\n        when /tutorial/\n          'Tutorial'\n        when /reference/\n          'Reference Manual'\n        when /dev-guide/\n          'Dev Guide'\n        else\n          TYPE_BY_SLUG[slug.split('/').first] || 'Other'\n        end\n      end\n\n      def include_default_entry?\n        slug != 'reference/'\n      end\n\n      def additional_entries\n        entries = []\n\n        css('dl.class > dt[id]').each do |node|\n          name = node['id'].split('.').last\n          id = node['id']\n          type = node['id'].split('.')[0..-2].join('.')\n          entries << [name, id, type]\n        end\n\n\n        entries\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/vertx/clean_html.rb",
    "content": "module Docs\n  class Vertx\n    class CleanHtmlFilter < Filter\n      def call\n        css('footer', 'hr', 'header', 'nav', '.navbar', '.topbar', '.bg-bg-warning').remove\n        xpath('//*[@id=\"docs-layout\"]/div/div[3]').remove\n        xpath('//*[@id=\"docs-layout\"]/div/div[1]').remove\n        xpath('//main/div[last()]').remove\n        xpath('//main/div[1]').remove\n        css('#changelog').remove if root_page?\n\n        # Set id attributes on <h3> instead of an empty <a>\n        css('h3').each do |node|\n          anchor = node.at_css('a')\n          node['id'] = anchor['id'] if anchor && anchor['id']\n        end\n\n        # Make proper table headers\n        css('td.header').each do |node|\n          node.name = 'th'\n        end\n\n        # Remove code highlighting\n        css('pre').each do |node|\n          node['data-language'] = node.at_css('code')['data-lang'] if node.at_css('code')\n          node.content = node.content\n        end\n\n        # ❗ Skip <img> tags with data: URIs\n        css('img').each do |img|\n          src = img['src']\n          img.remove if src&.start_with?('data:')\n        end\n\n        doc\n      end\n    end\n  end\nend\n\n"
  },
  {
    "path": "lib/docs/filters/vertx/entries.rb",
    "content": "module Docs\n  class Vertx\n    class EntriesFilter < Docs::EntriesFilter\n      # Determines the default name of the page entry\n      def get_name\n        node = at_css('h1')\n        return nil unless node\n\n        result = node.content.strip\n        result << ' event' if type == 'Events'\n        result << '()' if node['class'].to_s.include?('function')\n        result\n      end\n\n      # Determines the type of the default entry (used for sidebar grouping)\n      def get_type\n        return nil if root_page?\n\n        node = at_xpath('/html/body/div/div/div[2]/main/div[1]/div[1]')\n        node ? node.text.strip : 'Miscellaneous'\n      end\n\n      # Returns additional entries from subheadings (usually <h2>)\n      def additional_entries\n        # return [] if root_page?\n        #\n        # css('h2').map do |node|\n        #   name = node.content.strip\n        #   id = node['id']\n        #   [name, id, type]\n        # end\n        []\n      end\n\n      # Determines whether to include the default entry for the page\n      def include_default_entry?\n        !at_css('.obsolete')\n      end\n    end\n  end\nend\n\n"
  },
  {
    "path": "lib/docs/filters/vite/clean_html.rb",
    "content": "module Docs\n  class Vite\n    class CleanHtmlFilter < Filter\n      def call\n        return \"<h1>Vitest</h1><p>A Vite-native unit test framework. It's fast!</p>\" if root_page? && current_url.host == 'vitest.dev'\n        return \"<h1>VueUse</h1><p>Collection of Vue Composition Utilities</p>\" if root_page? && current_url.host == 'vueuse.org'\n        return '<h1>Vite</h1><p>Next Generation Frontend Tooling</p>' if root_page?\n        @doc = at_css('main h1').parent\n\n        css('.demo', '.guide-links', '.footer', '#ad').remove\n        css('.header-anchor', '.page-edit', '.page-nav').remove\n\n        css('.custom-block-title').each do |node|\n          node.name = 'strong'\n        end\n\n        # Remove CodePen div\n        css('.codepen').each do |node|\n          raise \"dsfsdfsdf\"\n          next if node.previous_element.nil?\n          span = node.css('span:contains(\"See the Pen\")').remove\n          node.previous_element.add_child(' ')\n          node.previous_element.add_child(span)\n          node.remove\n        end\n\n        css('.vp-code-group > .tabs').remove\n\n        css('.lang').remove\n        css('.line-numbers-wrapper').remove\n        css('pre').each do |node|\n          node.content = node.content.strip\n          node['data-language'] = 'javascript'\n        end\n\n        css('iframe').each do |node|\n          node['sandbox'] = 'allow-forms allow-scripts allow-same-origin'\n          node.remove if node['src'][/player.vimeo.com/] # https://v3.vuejs.org/guide/migration/introduction.html#overview\n        end\n\n        css('details').each do |node|\n          node.name = 'div'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/vite/entries.rb",
    "content": "module Docs\n  class Vite\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content\n        name.sub! %r{\\s*#\\s*|\\s*\\u200B\\s*}, ''\n        name\n      end\n\n      def get_type\n        name\n      end\n\n      def additional_entries\n        css('h2, h3').each_with_object [] do |node, entries|\n          type = node.content.strip\n          type.sub! %r{\\s*#\\s*|\\s*\\u200B\\s*}, ''\n          entries << [\"#{name}: #{type}\", node['id']]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/vitest/entries.rb",
    "content": "module Docs\n  class Vitest\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content\n        name.sub! %r{\\s*#\\s*}, ''\n        name.sub! %r{\\s*\\u200B\\s*}, ''\n        name\n      end\n\n      def get_type\n        return 'browser' if slug.starts_with?('guide/browser')\n        return 'cli' if slug.starts_with?('guide/cli')\n        return slug.split('/').first\n      end\n\n      def additional_entries\n        return [] if root_page?\n        css('h2[id], h3[id]').each_with_object [] do |node, entries|\n          text = node.content.strip\n          text.sub! %r{\\s*#\\s*}, ''\n          next if text == 'Example'\n          text.prepend \"#{name}: \" unless slug.starts_with?('api') || slug.starts_with?('config')\n          entries << [text, node['id']]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/vue/clean_html.rb",
    "content": "module Docs\n  class Vue\n    class CleanHtmlFilter < Filter\n      def call\n        return '<h1>Vue.js</h1>' if root_page?\n        @doc = at_css(version == '3' ? 'main > div > div' : '.content')\n\n        at_css('h1').content = 'Vue.js' if root_page?\n        doc.child.before('<h1>Vue.js API</h1>') if slug == 'api/' || slug == 'api/index'\n\n        css('.demo', '.guide-links', '.footer', '#ad').remove\n        css('.header-anchor', '.page-edit', '.page-nav').remove\n        css('.next-steps').remove\n\n        css('.custom-block-title').each do |node|\n          node.name = 'strong'\n        end\n\n        # Remove code highlighting\n        css('.line-numbers-wrapper').remove\n        if version == '3'\n          css('pre').each do |node|\n            node.parent.name = 'pre'\n            node.parent['data-language'] = node.parent['class'][/language-(\\w+)/, 1]\n            node.parent['data-language'] = 'javascript' if node.parent['data-language'][/vue/] # unsupported by prism.js\n            node.parent.remove_attribute 'class'\n            node.parent.content = node.content.strip\n          end\n        else\n          css('pre').each do |node|\n            parent = node.ancestors('figure')[0]\n            parent.name = 'pre'\n            parent['data-language'] = parent['class'][/(html|js)/, 1]\n            parent.remove_attribute 'class'\n            node.css('br').each{ |br| br.replace \"\\n\" }\n            parent.content = node.content.strip\n          end\n        end\n\n        css('.vue-mastery-link').remove\n        css('.vuejobs-wrapper').remove\n        css('.vueschool').remove\n\n        css('details').each do |node|\n          node.name = 'div'\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/vue/entries.rb",
    "content": "module Docs\n  class Vue\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        if slug == 'api/' || slug == 'api/index'\n          'API'\n        elsif slug == 'style-guide/'\n          'Style Guide'\n        else\n          name = at_css('.content h1').content\n          node = at_css(\".sidebar .menu-root a[href='#{File.basename(slug)}']\")\n\n          return name if node.nil?\n\n          index = node.parent.parent.css('> li > a').to_a.index(node)\n          name.prepend \"#{index + 1}. \" if index\n          name\n        end\n      end\n\n      def get_type\n        if slug.start_with?('guide')\n          'Guide'\n        elsif slug == 'style-guide/'\n          'Style Guide'\n        else\n          'API'\n        end\n      end\n\n      def additional_entries\n        return [] if slug.start_with?('guide')\n        type = nil\n\n        css('.content h2, .content h3').each_with_object [] do |node, entries|\n          if node.name == 'h2'\n            type = node.content.strip\n          else\n            name = node.content.strip\n            name.sub! %r{\\(.*\\)}, '()'\n            name.sub! /(essential|strongly recommended|recommended|use with caution)\\Z/, ''\n\n            current_type = \"API: #{type}\"\n            if slug == 'style-guide/'\n              current_type = \"Style Guide: \"\n              current_type += type.sub(/( Rules: )/, ': ').split('(')[0]\n            end\n\n            entries << [name, node['id'], current_type]\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/vue/entries_v3.rb",
    "content": "module Docs\n  class Vue\n    class EntriesV3Filter < Docs::EntriesFilter\n      def get_name\n        if slug == 'api/' || slug == 'api/index'\n          'API'\n        elsif slug == 'style-guide/'\n          'Style Guide'\n        else\n          name = at_css('h1').content\n          name = _fix_name(name)\n          subtype = at_css('.title-text.active')\n          index = css('aside nav .link').to_a.index(at_css('aside nav .link.active'))\n          name.prepend \"#{index + 1}. \" if index && !slug.start_with?('api') && !slug.start_with?('style-guide')\n          name.concat \" (#{subtype.content.strip})\" if subtype && slug.start_with?('guide')\n          name\n        end\n      end\n\n      def _fix_name(name)\n        name.sub! %r{#\\s*}, ''\n        name.sub! %r{\\s*3\\.\\d\\+$}, ''\n        name.sub! %r{\\u200B\\s*}, ''\n        name\n      end\n\n      def get_type\n        if slug.start_with?('guide/migration')\n          'Migration'\n       elsif slug.start_with?('guide') or root_page?\n          'Guide'\n        elsif slug.start_with?('style-guide')\n          'Style Guide'\n        else\n          title = at_css('.title-text.active').content.strip\n          title = _fix_name(title)\n          \"API: #{title}\"\n        end\n      end\n\n      def additional_entries\n        return [] if slug.start_with?('guide')\n        type = nil\n\n        at_css('main').css('h2, h3').each_with_object [] do |node, entries|\n          if node.name == 'h2'\n            type = node.content.strip\n            type = _fix_name(type)\n            next if slug == 'style-guide/'\n            name = \"#{get_type}: #{type}\"\n            name.sub! %r{^API: }, ''\n            entries << [name, node['id'], get_type]\n          elsif slug == 'style-guide/'\n            next if node['id'].match(/rule-categories|priority-/)\n            name = node.content.strip\n            name = _fix_name(name)\n            name.sub! %r{\\(.*\\)}, '()'\n            name.sub! /(essential|strongly recommended|recommended|use with caution)\\Z/, ''\n            entries << [name, node['id'], 'Style Guide']\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/vue_router/clean_html.rb",
    "content": "module Docs\n  class VueRouter\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('main > div:only-child > div:only-child', 'main', '.main')\n        css('p + h1').each do |node|\n          # breadcrumbs\n          node.previous_element.remove\n        end\n\n        # Remove unneeded elements\n        css('.bit-sponsor, .header-anchor', '.rulekit', 'div[hidden]', '.sponsors_outer').remove\n        css('.vp-code-group > .tabs').remove\n\n        css('.custom-block').each do |node|\n          node.name = 'blockquote'\n\n          title = node.at_css('.custom-block-title')\n          title.name = 'strong' unless title.nil?\n        end\n\n        css('span.lang').remove\n        css('pre > code:first-child').each do |node|\n          node.parent['data-language'] = 'js'\n          node.parent.content = node.css('.line').map(&:content).join(\"\\n\")\n        end\n\n        # Remove data-v-* attributes\n        css('*').each do |node|\n          node.attributes.each_key do |attribute|\n            node.remove_attribute(attribute) if attribute.start_with? 'data-v-'\n          end\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/vue_router/entries.rb",
    "content": "module Docs\n  class VueRouter\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content\n        name.sub! %r{#\\s*|\\s*\\u200B\\s*}, ''\n        name.strip!\n        name\n      end\n\n      def get_type\n        return 'Other Guides' if subpath.start_with?('guide/advanced')\n        return 'Basic Guides' if subpath.start_with?('guide') || subpath.start_with?('installation')\n        'API Reference'\n      end\n\n      def include_default_entry?\n        name != 'API Reference'\n      end\n\n      def additional_entries\n        return []\n        return [] unless subpath.start_with?('api')\n\n        entries = [\n          ['<router-link>', 'router-link', 'API Reference'],\n          ['<router-view>', 'router-view', 'API Reference'],\n          ['$route', 'the-route-object', 'API Reference'],\n          ['Component Injections', 'component-injections', 'API Reference']\n        ]\n\n        css('h3').each do |node|\n          entry_name = node.content.strip\n          entry_name.sub! %r{#\\s*|\\s*\\u200B\\s*}, ''\n\n          # Get the previous h2 title\n          title = node\n          begin\n            title = title.previous_element until title.name == 'h2'\n            title = title.content.strip\n            title.sub! %r{#\\s*}, ''\n          rescue\n            title = ''\n            entry_name = \"#{name}.#{entry_name}\"\n          end\n\n          case title\n          when 'Router Construction Options'\n            entry_name = \"RouterOptions.#{entry_name}\"\n          when '<router-view> Props'\n            entry_name = \"<router-view> `#{entry_name}` prop\"\n          when '<router-link> Props'\n            entry_name = \"<router-link> `#{entry_name}` prop\"\n          when 'Router Instance Methods'\n            entry_name = \"#{entry_name}()\"\n          end\n\n          entry_name = entry_name.split(' API ')[0] if entry_name.start_with?('v-slot')\n\n          unless title == \"Component Injections\" || node['id'] == 'route-object-properties'\n            entries << [entry_name, node['id'], 'API Reference']\n          end\n        end\n\n        css('#route-object-properties + ul > li > p:first-child > strong').each do |node|\n          entry_name = node.content.strip\n          id = \"route-object-#{entry_name.remove('$route.')}\"\n\n          node['id'] = id\n          entries << [entry_name, node['id'], 'API Reference']\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/vueuse/clean_html.rb",
    "content": "module Docs\n  class Vueuse\n    class CleanHtmlFilter < Filter\n      def call\n        css('#demo, #contributors ~ div, #contributors, #changelog ~ div, #changelog').remove\n        css('span.lang').remove\n        css('pre.vp-code-dark').remove\n\n        css('.grid').each do |table|\n          table.name = 'table'\n          tr = nil\n          table.css('> div').each do |td|\n            if td['opacity']\n              table.add_child('<tr>')\n              tr = table.last_element_child\n              td.name = 'th'\n              td.remove_attribute('opacity')\n              tr.add_child(td.remove)\n            else\n              td.name = 'td'\n              tr.add_child(td.remove)\n            end\n          end\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/vueuse/entries.rb",
    "content": "module Docs\n  class Vueuse\n    class EntriesFilter < Docs::EntriesFilter\n      # window.__VP_SITE_DATA__.themeConfig.sidebar['/core/']\n      SIDEBAR_TYPES = [\n        {\n          \"text\": \"State\",\n          \"items\": [\n            {\n              \"text\": \"createGlobalState\",\n              \"link\": \"/shared/createGlobalState/\"\n            },\n            {\n              \"text\": \"createInjectionState\",\n              \"link\": \"/shared/createInjectionState/\"\n            },\n            {\n              \"text\": \"createSharedComposable\",\n              \"link\": \"/shared/createSharedComposable/\"\n            },\n            {\n              \"text\": \"injectLocal\",\n              \"link\": \"/shared/injectLocal/\"\n            },\n            {\n              \"text\": \"provideLocal\",\n              \"link\": \"/shared/provideLocal/\"\n            },\n            {\n              \"text\": \"useAsyncState\",\n              \"link\": \"/core/useAsyncState/\"\n            },\n            {\n              \"text\": \"useDebouncedRefHistory\",\n              \"link\": \"/core/useDebouncedRefHistory/\"\n            },\n            {\n              \"text\": \"useLastChanged\",\n              \"link\": \"/shared/useLastChanged/\"\n            },\n            {\n              \"text\": \"useLocalStorage\",\n              \"link\": \"/core/useLocalStorage/\"\n            },\n            {\n              \"text\": \"useManualRefHistory\",\n              \"link\": \"/core/useManualRefHistory/\"\n            },\n            {\n              \"text\": \"useRefHistory\",\n              \"link\": \"/core/useRefHistory/\"\n            },\n            {\n              \"text\": \"useSessionStorage\",\n              \"link\": \"/core/useSessionStorage/\"\n            },\n            {\n              \"text\": \"useStorage\",\n              \"link\": \"/core/useStorage/\"\n            },\n            {\n              \"text\": \"useStorageAsync\",\n              \"link\": \"/core/useStorageAsync/\"\n            },\n            {\n              \"text\": \"useThrottledRefHistory\",\n              \"link\": \"/core/useThrottledRefHistory/\"\n            }\n          ]\n        },\n        {\n          \"text\": \"Elements\",\n          \"items\": [\n            {\n              \"text\": \"useActiveElement\",\n              \"link\": \"/core/useActiveElement/\"\n            },\n            {\n              \"text\": \"useDocumentVisibility\",\n              \"link\": \"/core/useDocumentVisibility/\"\n            },\n            {\n              \"text\": \"useDraggable\",\n              \"link\": \"/core/useDraggable/\"\n            },\n            {\n              \"text\": \"useDropZone\",\n              \"link\": \"/core/useDropZone/\"\n            },\n            {\n              \"text\": \"useElementBounding\",\n              \"link\": \"/core/useElementBounding/\"\n            },\n            {\n              \"text\": \"useElementSize\",\n              \"link\": \"/core/useElementSize/\"\n            },\n            {\n              \"text\": \"useElementVisibility\",\n              \"link\": \"/core/useElementVisibility/\"\n            },\n            {\n              \"text\": \"useIntersectionObserver\",\n              \"link\": \"/core/useIntersectionObserver/\"\n            },\n            {\n              \"text\": \"useMouseInElement\",\n              \"link\": \"/core/useMouseInElement/\"\n            },\n            {\n              \"text\": \"useMutationObserver\",\n              \"link\": \"/core/useMutationObserver/\"\n            },\n            {\n              \"text\": \"useParentElement\",\n              \"link\": \"/core/useParentElement/\"\n            },\n            {\n              \"text\": \"useResizeObserver\",\n              \"link\": \"/core/useResizeObserver/\"\n            },\n            {\n              \"text\": \"useWindowFocus\",\n              \"link\": \"/core/useWindowFocus/\"\n            },\n            {\n              \"text\": \"useWindowScroll\",\n              \"link\": \"/core/useWindowScroll/\"\n            },\n            {\n              \"text\": \"useWindowSize\",\n              \"link\": \"/core/useWindowSize/\"\n            }\n          ]\n        },\n        {\n          \"text\": \"Browser\",\n          \"items\": [\n            {\n              \"text\": \"useBluetooth\",\n              \"link\": \"/core/useBluetooth/\"\n            },\n            {\n              \"text\": \"useBreakpoints\",\n              \"link\": \"/core/useBreakpoints/\"\n            },\n            {\n              \"text\": \"useBroadcastChannel\",\n              \"link\": \"/core/useBroadcastChannel/\"\n            },\n            {\n              \"text\": \"useBrowserLocation\",\n              \"link\": \"/core/useBrowserLocation/\"\n            },\n            {\n              \"text\": \"useClipboard\",\n              \"link\": \"/core/useClipboard/\"\n            },\n            {\n              \"text\": \"useClipboardItems\",\n              \"link\": \"/core/useClipboardItems/\"\n            },\n            {\n              \"text\": \"useColorMode\",\n              \"link\": \"/core/useColorMode/\"\n            },\n            {\n              \"text\": \"useCssVar\",\n              \"link\": \"/core/useCssVar/\"\n            },\n            {\n              \"text\": \"useDark\",\n              \"link\": \"/core/useDark/\"\n            },\n            {\n              \"text\": \"useEventListener\",\n              \"link\": \"/core/useEventListener/\"\n            },\n            {\n              \"text\": \"useEyeDropper\",\n              \"link\": \"/core/useEyeDropper/\"\n            },\n            {\n              \"text\": \"useFavicon\",\n              \"link\": \"/core/useFavicon/\"\n            },\n            {\n              \"text\": \"useFileDialog\",\n              \"link\": \"/core/useFileDialog/\"\n            },\n            {\n              \"text\": \"useFileSystemAccess\",\n              \"link\": \"/core/useFileSystemAccess/\"\n            },\n            {\n              \"text\": \"useFullscreen\",\n              \"link\": \"/core/useFullscreen/\"\n            },\n            {\n              \"text\": \"useGamepad\",\n              \"link\": \"/core/useGamepad/\"\n            },\n            {\n              \"text\": \"useImage\",\n              \"link\": \"/core/useImage/\"\n            },\n            {\n              \"text\": \"useMediaControls\",\n              \"link\": \"/core/useMediaControls/\"\n            },\n            {\n              \"text\": \"useMediaQuery\",\n              \"link\": \"/core/useMediaQuery/\"\n            },\n            {\n              \"text\": \"useMemory\",\n              \"link\": \"/core/useMemory/\"\n            },\n            {\n              \"text\": \"useObjectUrl\",\n              \"link\": \"/core/useObjectUrl/\"\n            },\n            {\n              \"text\": \"usePerformanceObserver\",\n              \"link\": \"/core/usePerformanceObserver/\"\n            },\n            {\n              \"text\": \"usePermission\",\n              \"link\": \"/core/usePermission/\"\n            },\n            {\n              \"text\": \"usePreferredColorScheme\",\n              \"link\": \"/core/usePreferredColorScheme/\"\n            },\n            {\n              \"text\": \"usePreferredContrast\",\n              \"link\": \"/core/usePreferredContrast/\"\n            },\n            {\n              \"text\": \"usePreferredDark\",\n              \"link\": \"/core/usePreferredDark/\"\n            },\n            {\n              \"text\": \"usePreferredLanguages\",\n              \"link\": \"/core/usePreferredLanguages/\"\n            },\n            {\n              \"text\": \"usePreferredReducedMotion\",\n              \"link\": \"/core/usePreferredReducedMotion/\"\n            },\n            {\n              \"text\": \"usePreferredReducedTransparency\",\n              \"link\": \"/core/usePreferredReducedTransparency/\"\n            },\n            {\n              \"text\": \"useScreenOrientation\",\n              \"link\": \"/core/useScreenOrientation/\"\n            },\n            {\n              \"text\": \"useScreenSafeArea\",\n              \"link\": \"/core/useScreenSafeArea/\"\n            },\n            {\n              \"text\": \"useScriptTag\",\n              \"link\": \"/core/useScriptTag/\"\n            },\n            {\n              \"text\": \"useShare\",\n              \"link\": \"/core/useShare/\"\n            },\n            {\n              \"text\": \"useSSRWidth\",\n              \"link\": \"/core/useSSRWidth/\"\n            },\n            {\n              \"text\": \"useStyleTag\",\n              \"link\": \"/core/useStyleTag/\"\n            },\n            {\n              \"text\": \"useTextareaAutosize\",\n              \"link\": \"/core/useTextareaAutosize/\"\n            },\n            {\n              \"text\": \"useTextDirection\",\n              \"link\": \"/core/useTextDirection/\"\n            },\n            {\n              \"text\": \"useTitle\",\n              \"link\": \"/core/useTitle/\"\n            },\n            {\n              \"text\": \"useUrlSearchParams\",\n              \"link\": \"/core/useUrlSearchParams/\"\n            },\n            {\n              \"text\": \"useVibrate\",\n              \"link\": \"/core/useVibrate/\"\n            },\n            {\n              \"text\": \"useWakeLock\",\n              \"link\": \"/core/useWakeLock/\"\n            },\n            {\n              \"text\": \"useWebNotification\",\n              \"link\": \"/core/useWebNotification/\"\n            },\n            {\n              \"text\": \"useWebWorker\",\n              \"link\": \"/core/useWebWorker/\"\n            },\n            {\n              \"text\": \"useWebWorkerFn\",\n              \"link\": \"/core/useWebWorkerFn/\"\n            }\n          ]\n        },\n        {\n          \"text\": \"Sensors\",\n          \"items\": [\n            {\n              \"text\": \"onClickOutside\",\n              \"link\": \"/core/onClickOutside/\"\n            },\n            {\n              \"text\": \"onElementRemoval\",\n              \"link\": \"/core/onElementRemoval/\"\n            },\n            {\n              \"text\": \"onKeyStroke\",\n              \"link\": \"/core/onKeyStroke/\"\n            },\n            {\n              \"text\": \"onLongPress\",\n              \"link\": \"/core/onLongPress/\"\n            },\n            {\n              \"text\": \"onStartTyping\",\n              \"link\": \"/core/onStartTyping/\"\n            },\n            {\n              \"text\": \"useBattery\",\n              \"link\": \"/core/useBattery/\"\n            },\n            {\n              \"text\": \"useDeviceMotion\",\n              \"link\": \"/core/useDeviceMotion/\"\n            },\n            {\n              \"text\": \"useDeviceOrientation\",\n              \"link\": \"/core/useDeviceOrientation/\"\n            },\n            {\n              \"text\": \"useDevicePixelRatio\",\n              \"link\": \"/core/useDevicePixelRatio/\"\n            },\n            {\n              \"text\": \"useDevicesList\",\n              \"link\": \"/core/useDevicesList/\"\n            },\n            {\n              \"text\": \"useDisplayMedia\",\n              \"link\": \"/core/useDisplayMedia/\"\n            },\n            {\n              \"text\": \"useElementByPoint\",\n              \"link\": \"/core/useElementByPoint/\"\n            },\n            {\n              \"text\": \"useElementHover\",\n              \"link\": \"/core/useElementHover/\"\n            },\n            {\n              \"text\": \"useFocus\",\n              \"link\": \"/core/useFocus/\"\n            },\n            {\n              \"text\": \"useFocusWithin\",\n              \"link\": \"/core/useFocusWithin/\"\n            },\n            {\n              \"text\": \"useFps\",\n              \"link\": \"/core/useFps/\"\n            },\n            {\n              \"text\": \"useGeolocation\",\n              \"link\": \"/core/useGeolocation/\"\n            },\n            {\n              \"text\": \"useIdle\",\n              \"link\": \"/core/useIdle/\"\n            },\n            {\n              \"text\": \"useInfiniteScroll\",\n              \"link\": \"/core/useInfiniteScroll/\"\n            },\n            {\n              \"text\": \"useKeyModifier\",\n              \"link\": \"/core/useKeyModifier/\"\n            },\n            {\n              \"text\": \"useMagicKeys\",\n              \"link\": \"/core/useMagicKeys/\"\n            },\n            {\n              \"text\": \"useMouse\",\n              \"link\": \"/core/useMouse/\"\n            },\n            {\n              \"text\": \"useMousePressed\",\n              \"link\": \"/core/useMousePressed/\"\n            },\n            {\n              \"text\": \"useNavigatorLanguage\",\n              \"link\": \"/core/useNavigatorLanguage/\"\n            },\n            {\n              \"text\": \"useNetwork\",\n              \"link\": \"/core/useNetwork/\"\n            },\n            {\n              \"text\": \"useOnline\",\n              \"link\": \"/core/useOnline/\"\n            },\n            {\n              \"text\": \"usePageLeave\",\n              \"link\": \"/core/usePageLeave/\"\n            },\n            {\n              \"text\": \"useParallax\",\n              \"link\": \"/core/useParallax/\"\n            },\n            {\n              \"text\": \"usePointer\",\n              \"link\": \"/core/usePointer/\"\n            },\n            {\n              \"text\": \"usePointerLock\",\n              \"link\": \"/core/usePointerLock/\"\n            },\n            {\n              \"text\": \"usePointerSwipe\",\n              \"link\": \"/core/usePointerSwipe/\"\n            },\n            {\n              \"text\": \"useScroll\",\n              \"link\": \"/core/useScroll/\"\n            },\n            {\n              \"text\": \"useScrollLock\",\n              \"link\": \"/core/useScrollLock/\"\n            },\n            {\n              \"text\": \"useSpeechRecognition\",\n              \"link\": \"/core/useSpeechRecognition/\"\n            },\n            {\n              \"text\": \"useSpeechSynthesis\",\n              \"link\": \"/core/useSpeechSynthesis/\"\n            },\n            {\n              \"text\": \"useSwipe\",\n              \"link\": \"/core/useSwipe/\"\n            },\n            {\n              \"text\": \"useTextSelection\",\n              \"link\": \"/core/useTextSelection/\"\n            },\n            {\n              \"text\": \"useUserMedia\",\n              \"link\": \"/core/useUserMedia/\"\n            }\n          ]\n        },\n        {\n          \"text\": \"Network\",\n          \"items\": [\n            {\n              \"text\": \"useEventSource\",\n              \"link\": \"/core/useEventSource/\"\n            },\n            {\n              \"text\": \"useFetch\",\n              \"link\": \"/core/useFetch/\"\n            },\n            {\n              \"text\": \"useWebSocket\",\n              \"link\": \"/core/useWebSocket/\"\n            }\n          ]\n        },\n        {\n          \"text\": \"Animation\",\n          \"items\": [\n            {\n              \"text\": \"useAnimate\",\n              \"link\": \"/core/useAnimate/\"\n            },\n            {\n              \"text\": \"useInterval\",\n              \"link\": \"/shared/useInterval/\"\n            },\n            {\n              \"text\": \"useIntervalFn\",\n              \"link\": \"/shared/useIntervalFn/\"\n            },\n            {\n              \"text\": \"useNow\",\n              \"link\": \"/core/useNow/\"\n            },\n            {\n              \"text\": \"useRafFn\",\n              \"link\": \"/core/useRafFn/\"\n            },\n            {\n              \"text\": \"useTimeout\",\n              \"link\": \"/shared/useTimeout/\"\n            },\n            {\n              \"text\": \"useTimeoutFn\",\n              \"link\": \"/shared/useTimeoutFn/\"\n            },\n            {\n              \"text\": \"useTimestamp\",\n              \"link\": \"/core/useTimestamp/\"\n            },\n            {\n              \"text\": \"useTransition\",\n              \"link\": \"/core/useTransition/\"\n            }\n          ]\n        },\n        {\n          \"text\": \"Component\",\n          \"items\": [\n            {\n              \"text\": \"computedInject\",\n              \"link\": \"/core/computedInject/\"\n            },\n            {\n              \"text\": \"createReusableTemplate\",\n              \"link\": \"/core/createReusableTemplate/\"\n            },\n            {\n              \"text\": \"createTemplatePromise\",\n              \"link\": \"/core/createTemplatePromise/\"\n            },\n            {\n              \"text\": \"templateRef\",\n              \"link\": \"/core/templateRef/\"\n            },\n            {\n              \"text\": \"tryOnBeforeMount\",\n              \"link\": \"/shared/tryOnBeforeMount/\"\n            },\n            {\n              \"text\": \"tryOnBeforeUnmount\",\n              \"link\": \"/shared/tryOnBeforeUnmount/\"\n            },\n            {\n              \"text\": \"tryOnMounted\",\n              \"link\": \"/shared/tryOnMounted/\"\n            },\n            {\n              \"text\": \"tryOnScopeDispose\",\n              \"link\": \"/shared/tryOnScopeDispose/\"\n            },\n            {\n              \"text\": \"tryOnUnmounted\",\n              \"link\": \"/shared/tryOnUnmounted/\"\n            },\n            {\n              \"text\": \"unrefElement\",\n              \"link\": \"/core/unrefElement/\"\n            },\n            {\n              \"text\": \"useCurrentElement\",\n              \"link\": \"/core/useCurrentElement/\"\n            },\n            {\n              \"text\": \"useMounted\",\n              \"link\": \"/core/useMounted/\"\n            },\n            {\n              \"text\": \"useTemplateRefsList\",\n              \"link\": \"/core/useTemplateRefsList/\"\n            },\n            {\n              \"text\": \"useVirtualList\",\n              \"link\": \"/core/useVirtualList/\"\n            },\n            {\n              \"text\": \"useVModel\",\n              \"link\": \"/core/useVModel/\"\n            },\n            {\n              \"text\": \"useVModels\",\n              \"link\": \"/core/useVModels/\"\n            }\n          ]\n        },\n        {\n          \"text\": \"Watch\",\n          \"items\": [\n            {\n              \"text\": \"until\",\n              \"link\": \"/shared/until/\"\n            },\n            {\n              \"text\": \"watchArray\",\n              \"link\": \"/shared/watchArray/\"\n            },\n            {\n              \"text\": \"watchAtMost\",\n              \"link\": \"/shared/watchAtMost/\"\n            },\n            {\n              \"text\": \"watchDebounced\",\n              \"link\": \"/shared/watchDebounced/\"\n            },\n            {\n              \"text\": \"watchDeep\",\n              \"link\": \"/shared/watchDeep/\"\n            },\n            {\n              \"text\": \"watchIgnorable\",\n              \"link\": \"/shared/watchIgnorable/\"\n            },\n            {\n              \"text\": \"watchImmediate\",\n              \"link\": \"/shared/watchImmediate/\"\n            },\n            {\n              \"text\": \"watchOnce\",\n              \"link\": \"/shared/watchOnce/\"\n            },\n            {\n              \"text\": \"watchPausable\",\n              \"link\": \"/shared/watchPausable/\"\n            },\n            {\n              \"text\": \"watchThrottled\",\n              \"link\": \"/shared/watchThrottled/\"\n            },\n            {\n              \"text\": \"watchTriggerable\",\n              \"link\": \"/shared/watchTriggerable/\"\n            },\n            {\n              \"text\": \"watchWithFilter\",\n              \"link\": \"/shared/watchWithFilter/\"\n            },\n            {\n              \"text\": \"whenever\",\n              \"link\": \"/shared/whenever/\"\n            }\n          ]\n        },\n        {\n          \"text\": \"Reactivity\",\n          \"items\": [\n            {\n              \"text\": \"computedAsync\",\n              \"link\": \"/core/computedAsync/\"\n            },\n            {\n              \"text\": \"computedEager\",\n              \"link\": \"/shared/computedEager/\"\n            },\n            {\n              \"text\": \"computedWithControl\",\n              \"link\": \"/shared/computedWithControl/\"\n            },\n            {\n              \"text\": \"extendRef\",\n              \"link\": \"/shared/extendRef/\"\n            },\n            {\n              \"text\": \"reactify\",\n              \"link\": \"/shared/reactify/\"\n            },\n            {\n              \"text\": \"reactifyObject\",\n              \"link\": \"/shared/reactifyObject/\"\n            },\n            {\n              \"text\": \"reactiveComputed\",\n              \"link\": \"/shared/reactiveComputed/\"\n            },\n            {\n              \"text\": \"reactiveOmit\",\n              \"link\": \"/shared/reactiveOmit/\"\n            },\n            {\n              \"text\": \"reactivePick\",\n              \"link\": \"/shared/reactivePick/\"\n            },\n            {\n              \"text\": \"refAutoReset\",\n              \"link\": \"/shared/refAutoReset/\"\n            },\n            {\n              \"text\": \"refDebounced\",\n              \"link\": \"/shared/refDebounced/\"\n            },\n            {\n              \"text\": \"refDefault\",\n              \"link\": \"/shared/refDefault/\"\n            },\n            {\n              \"text\": \"refThrottled\",\n              \"link\": \"/shared/refThrottled/\"\n            },\n            {\n              \"text\": \"refWithControl\",\n              \"link\": \"/shared/refWithControl/\"\n            },\n            {\n              \"text\": \"syncRef\",\n              \"link\": \"/shared/syncRef/\"\n            },\n            {\n              \"text\": \"syncRefs\",\n              \"link\": \"/shared/syncRefs/\"\n            },\n            {\n              \"text\": \"toReactive\",\n              \"link\": \"/shared/toReactive/\"\n            },\n            {\n              \"text\": \"toRef\",\n              \"link\": \"/shared/toRef/\"\n            },\n            {\n              \"text\": \"toRefs\",\n              \"link\": \"/shared/toRefs/\"\n            },\n            {\n              \"text\": \"toValue\",\n              \"link\": \"/shared/toValue/\"\n            }\n          ]\n        },\n        {\n          \"text\": \"Array\",\n          \"items\": [\n            {\n              \"text\": \"useArrayDifference\",\n              \"link\": \"/shared/useArrayDifference/\"\n            },\n            {\n              \"text\": \"useArrayEvery\",\n              \"link\": \"/shared/useArrayEvery/\"\n            },\n            {\n              \"text\": \"useArrayFilter\",\n              \"link\": \"/shared/useArrayFilter/\"\n            },\n            {\n              \"text\": \"useArrayFind\",\n              \"link\": \"/shared/useArrayFind/\"\n            },\n            {\n              \"text\": \"useArrayFindIndex\",\n              \"link\": \"/shared/useArrayFindIndex/\"\n            },\n            {\n              \"text\": \"useArrayFindLast\",\n              \"link\": \"/shared/useArrayFindLast/\"\n            },\n            {\n              \"text\": \"useArrayIncludes\",\n              \"link\": \"/shared/useArrayIncludes/\"\n            },\n            {\n              \"text\": \"useArrayJoin\",\n              \"link\": \"/shared/useArrayJoin/\"\n            },\n            {\n              \"text\": \"useArrayMap\",\n              \"link\": \"/shared/useArrayMap/\"\n            },\n            {\n              \"text\": \"useArrayReduce\",\n              \"link\": \"/shared/useArrayReduce/\"\n            },\n            {\n              \"text\": \"useArraySome\",\n              \"link\": \"/shared/useArraySome/\"\n            },\n            {\n              \"text\": \"useArrayUnique\",\n              \"link\": \"/shared/useArrayUnique/\"\n            },\n            {\n              \"text\": \"useSorted\",\n              \"link\": \"/core/useSorted/\"\n            }\n          ]\n        },\n        {\n          \"text\": \"Time\",\n          \"items\": [\n            {\n              \"text\": \"useCountdown\",\n              \"link\": \"/core/useCountdown/\"\n            },\n            {\n              \"text\": \"useDateFormat\",\n              \"link\": \"/shared/useDateFormat/\"\n            },\n            {\n              \"text\": \"useTimeAgo\",\n              \"link\": \"/core/useTimeAgo/\"\n            }\n          ]\n        },\n        {\n          \"text\": \"Utilities\",\n          \"items\": [\n            {\n              \"text\": \"createEventHook\",\n              \"link\": \"/shared/createEventHook/\"\n            },\n            {\n              \"text\": \"createUnrefFn\",\n              \"link\": \"/core/createUnrefFn/\"\n            },\n            {\n              \"text\": \"get\",\n              \"link\": \"/shared/get/\"\n            },\n            {\n              \"text\": \"isDefined\",\n              \"link\": \"/shared/isDefined/\"\n            },\n            {\n              \"text\": \"makeDestructurable\",\n              \"link\": \"/shared/makeDestructurable/\"\n            },\n            {\n              \"text\": \"set\",\n              \"link\": \"/shared/set/\"\n            },\n            {\n              \"text\": \"useAsyncQueue\",\n              \"link\": \"/core/useAsyncQueue/\"\n            },\n            {\n              \"text\": \"useBase64\",\n              \"link\": \"/core/useBase64/\"\n            },\n            {\n              \"text\": \"useCached\",\n              \"link\": \"/core/useCached/\"\n            },\n            {\n              \"text\": \"useCloned\",\n              \"link\": \"/core/useCloned/\"\n            },\n            {\n              \"text\": \"useConfirmDialog\",\n              \"link\": \"/core/useConfirmDialog/\"\n            },\n            {\n              \"text\": \"useCounter\",\n              \"link\": \"/shared/useCounter/\"\n            },\n            {\n              \"text\": \"useCycleList\",\n              \"link\": \"/core/useCycleList/\"\n            },\n            {\n              \"text\": \"useDebounceFn\",\n              \"link\": \"/shared/useDebounceFn/\"\n            },\n            {\n              \"text\": \"useEventBus\",\n              \"link\": \"/core/useEventBus/\"\n            },\n            {\n              \"text\": \"useMemoize\",\n              \"link\": \"/core/useMemoize/\"\n            },\n            {\n              \"text\": \"useOffsetPagination\",\n              \"link\": \"/core/useOffsetPagination/\"\n            },\n            {\n              \"text\": \"usePrevious\",\n              \"link\": \"/core/usePrevious/\"\n            },\n            {\n              \"text\": \"useStepper\",\n              \"link\": \"/core/useStepper/\"\n            },\n            {\n              \"text\": \"useSupported\",\n              \"link\": \"/core/useSupported/\"\n            },\n            {\n              \"text\": \"useThrottleFn\",\n              \"link\": \"/shared/useThrottleFn/\"\n            },\n            {\n              \"text\": \"useTimeoutPoll\",\n              \"link\": \"/core/useTimeoutPoll/\"\n            },\n            {\n              \"text\": \"useToggle\",\n              \"link\": \"/shared/useToggle/\"\n            },\n            {\n              \"text\": \"useToNumber\",\n              \"link\": \"/shared/useToNumber/\"\n            },\n            {\n              \"text\": \"useToString\",\n              \"link\": \"/shared/useToString/\"\n            }\n          ]\n        },\n        {\n          \"text\": \"@Electron\",\n          \"items\": [\n            {\n              \"text\": \"useIpcRenderer\",\n              \"link\": \"/electron/useIpcRenderer/\"\n            },\n            {\n              \"text\": \"useIpcRendererInvoke\",\n              \"link\": \"/electron/useIpcRendererInvoke/\"\n            },\n            {\n              \"text\": \"useIpcRendererOn\",\n              \"link\": \"/electron/useIpcRendererOn/\"\n            },\n            {\n              \"text\": \"useZoomFactor\",\n              \"link\": \"/electron/useZoomFactor/\"\n            },\n            {\n              \"text\": \"useZoomLevel\",\n              \"link\": \"/electron/useZoomLevel/\"\n            }\n          ],\n          \"link\": \"/electron/README\"\n        },\n        {\n          \"text\": \"@Firebase\",\n          \"items\": [\n            {\n              \"text\": \"useAuth\",\n              \"link\": \"/firebase/useAuth/\"\n            },\n            {\n              \"text\": \"useFirestore\",\n              \"link\": \"/firebase/useFirestore/\"\n            },\n            {\n              \"text\": \"useRTDB\",\n              \"link\": \"/firebase/useRTDB/\"\n            }\n          ],\n          \"link\": \"/firebase/README\"\n        },\n        {\n          \"text\": \"@Head\",\n          \"items\": [\n            {\n              \"text\": \"createHead\",\n              \"link\": \"https://github.com/vueuse/head#api\"\n            },\n            {\n              \"text\": \"useHead\",\n              \"link\": \"https://github.com/vueuse/head#api\"\n            }\n          ],\n          \"link\": \"https://github.com/vueuse/head#api\"\n        },\n        {\n          \"text\": \"@Integrations\",\n          \"items\": [\n            {\n              \"text\": \"useAsyncValidator\",\n              \"link\": \"/integrations/useAsyncValidator/\"\n            },\n            {\n              \"text\": \"useAxios\",\n              \"link\": \"/integrations/useAxios/\"\n            },\n            {\n              \"text\": \"useChangeCase\",\n              \"link\": \"/integrations/useChangeCase/\"\n            },\n            {\n              \"text\": \"useCookies\",\n              \"link\": \"/integrations/useCookies/\"\n            },\n            {\n              \"text\": \"useDrauu\",\n              \"link\": \"/integrations/useDrauu/\"\n            },\n            {\n              \"text\": \"useFocusTrap\",\n              \"link\": \"/integrations/useFocusTrap/\"\n            },\n            {\n              \"text\": \"useFuse\",\n              \"link\": \"/integrations/useFuse/\"\n            },\n            {\n              \"text\": \"useIDBKeyval\",\n              \"link\": \"/integrations/useIDBKeyval/\"\n            },\n            {\n              \"text\": \"useJwt\",\n              \"link\": \"/integrations/useJwt/\"\n            },\n            {\n              \"text\": \"useNProgress\",\n              \"link\": \"/integrations/useNProgress/\"\n            },\n            {\n              \"text\": \"useQRCode\",\n              \"link\": \"/integrations/useQRCode/\"\n            },\n            {\n              \"text\": \"useSortable\",\n              \"link\": \"/integrations/useSortable/\"\n            }\n          ],\n          \"link\": \"/integrations/README\"\n        },\n        {\n          \"text\": \"@Math\",\n          \"items\": [\n            {\n              \"text\": \"createGenericProjection\",\n              \"link\": \"/math/createGenericProjection/\"\n            },\n            {\n              \"text\": \"createProjection\",\n              \"link\": \"/math/createProjection/\"\n            },\n            {\n              \"text\": \"logicAnd\",\n              \"link\": \"/math/logicAnd/\"\n            },\n            {\n              \"text\": \"logicNot\",\n              \"link\": \"/math/logicNot/\"\n            },\n            {\n              \"text\": \"logicOr\",\n              \"link\": \"/math/logicOr/\"\n            },\n            {\n              \"text\": \"useAbs\",\n              \"link\": \"/math/useAbs/\"\n            },\n            {\n              \"text\": \"useAverage\",\n              \"link\": \"/math/useAverage/\"\n            },\n            {\n              \"text\": \"useCeil\",\n              \"link\": \"/math/useCeil/\"\n            },\n            {\n              \"text\": \"useClamp\",\n              \"link\": \"/math/useClamp/\"\n            },\n            {\n              \"text\": \"useFloor\",\n              \"link\": \"/math/useFloor/\"\n            },\n            {\n              \"text\": \"useMath\",\n              \"link\": \"/math/useMath/\"\n            },\n            {\n              \"text\": \"useMax\",\n              \"link\": \"/math/useMax/\"\n            },\n            {\n              \"text\": \"useMin\",\n              \"link\": \"/math/useMin/\"\n            },\n            {\n              \"text\": \"usePrecision\",\n              \"link\": \"/math/usePrecision/\"\n            },\n            {\n              \"text\": \"useProjection\",\n              \"link\": \"/math/useProjection/\"\n            },\n            {\n              \"text\": \"useRound\",\n              \"link\": \"/math/useRound/\"\n            },\n            {\n              \"text\": \"useSum\",\n              \"link\": \"/math/useSum/\"\n            },\n            {\n              \"text\": \"useTrunc\",\n              \"link\": \"/math/useTrunc/\"\n            }\n          ],\n          \"link\": \"/math/README\"\n        },\n        {\n          \"text\": \"@Motion\",\n          \"items\": [\n            {\n              \"text\": \"useElementStyle\",\n              \"link\": \"https://motion.vueuse.org/api/use-element-style\"\n            },\n            {\n              \"text\": \"useElementTransform\",\n              \"link\": \"https://motion.vueuse.org/api/use-element-transform\"\n            },\n            {\n              \"text\": \"useMotion\",\n              \"link\": \"https://motion.vueuse.org/api/use-motion\"\n            },\n            {\n              \"text\": \"useMotionProperties\",\n              \"link\": \"https://motion.vueuse.org/api/use-motion-properties\"\n            },\n            {\n              \"text\": \"useMotionVariants\",\n              \"link\": \"https://motion.vueuse.org/api/use-motion-variants\"\n            },\n            {\n              \"text\": \"useSpring\",\n              \"link\": \"https://motion.vueuse.org/api/use-spring\"\n            }\n          ],\n          \"link\": \"https://motion.vueuse.org/api/use-element-style\"\n        },\n        {\n          \"text\": \"@Router\",\n          \"items\": [\n            {\n              \"text\": \"useRouteHash\",\n              \"link\": \"/router/useRouteHash/\"\n            },\n            {\n              \"text\": \"useRouteParams\",\n              \"link\": \"/router/useRouteParams/\"\n            },\n            {\n              \"text\": \"useRouteQuery\",\n              \"link\": \"/router/useRouteQuery/\"\n            }\n          ],\n          \"link\": \"/router/README\"\n        },\n        {\n          \"text\": \"@RxJS\",\n          \"items\": [\n            {\n              \"text\": \"from\",\n              \"link\": \"/rxjs/from/\"\n            },\n            {\n              \"text\": \"toObserver\",\n              \"link\": \"/rxjs/toObserver/\"\n            },\n            {\n              \"text\": \"useExtractedObservable\",\n              \"link\": \"/rxjs/useExtractedObservable/\"\n            },\n            {\n              \"text\": \"useObservable\",\n              \"link\": \"/rxjs/useObservable/\"\n            },\n            {\n              \"text\": \"useSubject\",\n              \"link\": \"/rxjs/useSubject/\"\n            },\n            {\n              \"text\": \"useSubscription\",\n              \"link\": \"/rxjs/useSubscription/\"\n            },\n            {\n              \"text\": \"watchExtractedObservable\",\n              \"link\": \"/rxjs/watchExtractedObservable/\"\n            }\n          ],\n          \"link\": \"/rxjs/README\"\n        },\n        {\n          \"text\": \"@SchemaOrg\",\n          \"items\": [\n            {\n              \"text\": \"createSchemaOrg\",\n              \"link\": \"https://vue-schema-org.netlify.app/api/core/create-schema-org.html\"\n            },\n            {\n              \"text\": \"useSchemaOrg\",\n              \"link\": \"https://vue-schema-org.netlify.app/api/core/use-schema-org.html\"\n            }\n          ],\n          \"link\": \"https://vue-schema-org.netlify.app/api/core/create-schema-org.html\"\n        },\n        {\n          \"text\": \"@Sound\",\n          \"items\": [\n            {\n              \"text\": \"useSound\",\n              \"link\": \"https://github.com/vueuse/sound#examples\"\n            }\n          ],\n          \"link\": \"https://github.com/vueuse/sound#examples\"\n        }\n      ]\n\n      def get_name\n        name = at_css('h1').content\n        name.sub! %r{\\s*#\\s*}, ''\n        name\n      end\n\n      def get_type\n        return 'Guide' if slug == 'export-size'\n        return 'Guide' if slug == 'functions'\n        return 'Guide' if slug == 'guidelines'\n        return 'Guide' if slug.start_with? 'guide'\n        SIDEBAR_TYPES.find { |i| i[:items].find { |j| j[:link][1..].downcase.start_with? slug.downcase } }[:text]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/vuex/clean_html.rb",
    "content": "module Docs\n  class Vuex\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('main')\n\n        # Remove video from root page\n        css('a[href=\"#\"]').remove if root_page?\n\n        # Remove unneeded elements\n        css('.header-anchor').remove\n\n        # Remove data-v-* attributes\n        css('*').each do |node|\n          node.attributes.each_key do |attribute|\n            node.remove_attribute(attribute) if attribute.start_with? 'data-v-'\n          end\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/vuex/entries.rb",
    "content": "module Docs\n  class Vuex\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content\n\n        name.sub! %r{#\\s*}, ''\n\n        # Add index on guides\n        unless subpath.start_with?('api')\n          sidebar_link = at_css('.sidebar-link.active')\n          all_links = css('.sidebar-link:not([href=\"/\"]):not([href=\"../index\"])')\n\n          index = all_links.index(sidebar_link)\n\n          name.prepend \"#{index + 1}. \" if index\n        end\n\n        name\n      end\n\n      def get_type\n        'Guide'\n      end\n\n      def include_default_entry?\n        name != 'API Reference'\n      end\n\n      def additional_entries\n        return [] unless subpath.start_with?('api')\n\n        entries = [\n          ['Component Binding Helpers', 'component-binding-helpers', 'API Reference'],\n          ['Store', 'vuex-store', 'API Reference'],\n        ]\n\n        css('h3').each do |node|\n          entry_name = node.content.strip\n\n          # Get the previous h2 title\n          title = node\n          title = title.previous_element until title.name == 'h2'\n          title = title.content.strip\n          title.sub! %r{#\\s*}, ''\n\n          entry_name.sub! %r{#\\s*}, ''\n\n          unless entry_name.start_with?('router.')\n            case title\n            when \"Vuex.Store Constructor Options\"\n              entry_name = \"StoreOptions.#{entry_name}\"\n            when \"Vuex.Store Instance Properties\"\n              entry_name = \"Vuex.Store.#{entry_name}\"\n            when \"Vuex.Store Instance Methods\"\n              entry_name = \"Vuex.Store.#{entry_name}()\"\n            when \"Component Binding Helpers\"\n              entry_name = \"#{entry_name}()\"\n            end\n          end\n\n          entries << [entry_name, node['id'], 'API Reference']\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/vulkan/clean_html.rb",
    "content": "module Docs\n  class Vulkan\n    class CleanHtmlFilter < Filter\n      def call\n        at_css('#_copyright').parent.remove\n\n        css('.sect1', '.sectionbody', '.sect2', '.sect3', 'div.paragraph', 'li > p:only-child', 'dd > p:only-child', 'span', '.ulist').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('a[id]:empty').each do |node|\n          node.parent['id'] ||= node['id']\n          node.remove\n        end\n\n        css('.listingblock').each do |node|\n          node['data-language'] = node.at_css('[data-lang]')['data-lang']\n          node.content = node.content.strip\n          node.name = 'pre'\n          node.remove_attribute('class')\n        end\n\n        css('.sidebarblock').each do |node|\n          node.name = 'blockquote'\n          node.at_css('.title').name = 'h5'\n          node.css('div').each { |n| n.before(n.children).remove }\n          node.remove_attribute('class')\n        end\n\n        css('.admonitionblock').each do |node|\n          node.name = 'blockquote'\n          node.children = node.at_css('.content').children\n          node.at_css('.title').name = 'h5'\n          node.remove_attribute('class')\n        end\n\n        css('table').each do |node|\n          node.before %(<div class=\"_table\"></div>)\n          node.previous_element << node\n        end\n\n        css('strong', 'dt', 'a').remove_attr('class')\n\n        css('h4 + h4').each do |node|\n          node.previous_element.remove\n        end\n\n        css('p:contains(\"This page is extracted from the Vulkan Specification. Fixes and changes should be made to the Specification, not directly.\")').remove\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/vulkan/entries.rb",
    "content": "module Docs\n  class Vulkan\n    class EntriesFilter < Docs::EntriesFilter\n      def additional_entries\n        css('.sect1').each_with_object [] do |node, entries|\n          type = node.at_css('h2').content\n\n          node.css('h3').each do |n|\n            entries << [n.content, n['id'], type]\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/wagtail/clean_html.rb",
    "content": "module Docs\n  class Wagtail\n    class CleanHtmlFilter < Filter\n      def call\n        @doc = at_css('main > section', 'main')\n\n        # footer contains links like about,contact us etc which\n        # are not needed in documentation so removed\n        doc.search('footer').each do |footer|\n          footer.remove\n        end\n\n        # aside bar contains the search bar and navigation links\n        # which are not needed\n        doc.search('aside').each do |aside|\n          aside.remove\n        end\n\n        # header contains links which are not needed(see sourch code of Wagtail docs)\n        doc.search('header').each do |head|\n          head.remove\n        end\n\n        # nav bar contains the search bar and navigation links(older versions)\n        # which are not needed\n        doc.search('nav.wy-nav-side').each do |nav|\n          nav.remove\n        end\n\n        # removing unimportant links(older versions)\n        doc.search('nav.wy-nav-top').each do |nav|\n          nav.remove\n        end\n\n        # removing unimportant links from header of very old versions\n        doc.search('ul.wy-breadcrumbs').each do |ul|\n          ul.remove\n        end\n\n        # removing release notes\n        doc.search('li.toctree-l1').each do |li|\n          li.remove if li.to_s.include? 'Release notes'\n        end\n\n        # removing release notes(older versions)\n        doc.search('dl').each do |dl|\n          dl.remove\n        end\n\n        # removing scripts and style\n        css('script', 'style', 'link').remove\n        css('hr').remove\n        # Make proper table headers\n        css('td.header').each do |node|\n          node.name = 'th'\n        end\n\n        # Remove code highlighting\n        css('pre').each do |node|\n          node.content = node.content\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/wagtail/entries.rb",
    "content": "module Docs\n  class Wagtail\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        # removing the pilcrow sign and returning the heading\n        at_css('h1').content.strip.remove(\"\\u{00b6}\")\n      end\n\n      def get_type\n        object, method = *slug.split('/')\n        method ? object : 'Miscellaneous'\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/web_extensions/clean_html.rb",
    "content": "module Docs\n  class WebExtensions\n    class CleanHtmlFilter < Filter\n      def call\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/web_extensions/entries.rb",
    "content": "module Docs\n  class WebExtensions\n    class EntriesFilter < Docs::EntriesFilter\n      TYPE_BY_PATH = {\n        'manifest.json' => 'manifest.json',\n        'user_interface' => 'User Interface',\n        'WebRequest' => 'webRequest',\n      }\n\n      def get_name\n        at_css('h1').text\n      end\n\n      def get_type\n        slug_parts = slug.split('/')\n        if slug_parts[0] == 'API' and slug_parts.length() > 1\n          return TYPE_BY_PATH.fetch(slug_parts[1], slug_parts[1])\n        else\n          return TYPE_BY_PATH.fetch(slug_parts[0], slug_parts.length() > 1 ? slug_parts[0] : 'Miscellaneous')\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/webpack/clean_html.rb",
    "content": "module Docs\n  class Webpack\n    class CleanHtmlFilter < Filter\n      def call\n        at_css('h1').content = 'webpack' if root_page?\n\n        css('hr', '.page__edit', 'hr + h3:contains(\"Contributors\")', 'hr + h2:contains(\"Contributors\")',\n            'td > .title', '.adjacent-links', '.contributors', '.icon-link',\n            '#maintainers', '#maintainers + table',\n            '#maintainer', '#maintainer + table',\n            '.page-links__link', '.page-links__gap').remove\n\n        css('> div', '.tip-content', '.header span').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('pre > code').each do |node|\n          node.parent['data-language'] = 'js'\n          node.parent['data-language'] = node['class'][/language-(\\w+)/, 1].sub('jsx', 'js') if node['class'] && node['class'][/language-(\\w+)/, 1]\n          node.parent.content = node.parent.content\n        end\n\n        css('*').each do |node|\n          node.remove if node['class'] && node['class'][/print:hidden/]\n        end\n\n        # for webpack-contrib /loaders and /plugins\n        shields = at_css('img[src*=\"shields.io\"]')\n        shields.ancestors('p')[0].remove if shields\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/webpack/clean_html_old.rb",
    "content": "module Docs\n  class Webpack\n    class CleanHtmlOldFilter < Filter\n      def call\n        root_page? ? root : other\n        doc\n      end\n\n      def root\n        @doc = at_css(\".container > .row > .col-md-9\")\n\n        # Remove all introduction before the hr,\n        # The introduction about the documentation site which isn't relevant\n        # in devdocs.\n        hr_index = doc.children.find_index { |node| node.name == \"hr\" }\n        doc.children[0..hr_index].each(&:remove)\n\n        css('.row', '.col-md-6', '.feature').each do |node|\n          node.before(node.children).remove\n        end\n      end\n\n      def other\n        css('h1, h2, h3, h4').each do |node|\n          node.name = node.name.sub(/\\d/) { |i| i.to_i + 1 }\n        end\n\n        # Re-create the header element\n        at_css(\"#wiki\").child.before(\"<h1>#{at_css(\"#wikititle\").content.titleize}</h1>\")\n\n        @doc = at_css(\"#wiki\")\n\n        css('.contents', 'a.anchor', 'hr').remove\n\n        css('pre').each do |node|\n          node.content = node.content\n          node['data-language'] = 'javascript'\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/webpack/entries.rb",
    "content": "module Docs\n  class Webpack\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content\n        name.sub! ' - ', ': '\n        name\n      end\n\n      TYPE_BY_DIRECTORY = {\n        'concepts'      => 'Concepts',\n        'guides'        => 'Guides',\n        'api'           => 'API',\n        'configuration' => 'Configuration',\n        'loaders'       => 'Loaders',\n        'plugins'       => 'Plugins'\n      }\n\n      def get_type\n        TYPE_BY_DIRECTORY[slug.split('/').first]\n      end\n\n      def additional_entries\n        if slug.start_with?('configuration')\n          css('h2 > [id]').each_with_object [] do |node, entries|\n            next if version.to_f < 5 && node.previous.try(:content).present?\n            entries << [node.parent.content, node['id']]\n          end\n        elsif slug.start_with?('api') && slug != 'api/parser'\n          css('.header[id] code').each_with_object [] do |node, entries|\n            next if version.to_f < 5 && (node.previous.try(:content).present? || node.next.try(:content).present?)\n            name = node.content.sub(/\\(.*\\)/, '()')\n            name.prepend \"#{self.name.split(':').first}: \"\n            entries << [name, node['id']]\n          end\n        else\n          []\n        end\n      end\n    end\n  end\nend\n\n"
  },
  {
    "path": "lib/docs/filters/webpack/entries_old.rb",
    "content": "module Docs\n  class Webpack\n    class EntriesOldFilter < Docs::EntriesFilter\n      def get_name\n        entry_link.content\n      end\n\n      def get_type\n        link_li = entry_link.parent\n        type_links_list = link_li.parent\n        current_type = type_links_list.parent\n\n        # current type is a\n        # <li>\n        #   TYPE\n        #   <li> <ul> .. links .. </ul> </li>\n        # </li>\n        #\n        # Grab the first children (which is the text nodes whose contains the type)\n        current_type.children.first.content.strip.titleize\n      end\n\n      private\n\n      def entry_link\n        at_css(\"a[href='#{self.path}']\")\n      end\n    end\n  end\nend\n\n"
  },
  {
    "path": "lib/docs/filters/werkzeug/entries.rb",
    "content": "module Docs\n  class Werkzeug\n    class EntriesFilter < Docs::EntriesFilter\n      TYPE_BY_SLUG = {}\n\n      def call\n        if root_page?\n          css('.section').each do |node|\n            type = node.at_css('h2').content[0..-2]\n            node.css('li > a').each do |n|\n              s = n['href'].split('/')[-2]\n              TYPE_BY_SLUG[s] = type\n            end\n          end\n        end\n        super\n      end\n\n      def get_name\n        at_css('h1').content[0..-2]\n      end\n\n      def get_type\n        TYPE_BY_SLUG[slug.split('/').first] || 'Other'\n      end\n\n      def additional_entries\n        entries = []\n        css('dl.function > dt[id]').each do |node|\n          name = node['id'].split('.').last + '()'\n          id = node['id']\n          type = node['id'].split('.')[0..-2].join('.')\n          entries << [name, id, type]\n        end\n\n        css('dl.class > dt[id]').each do |node|\n          name = node['id'].split('.').last\n          id = node['id']\n          type = node['id'].split('.')[0..-2].join('.')\n          entries << [name, id, type]\n        end\n\n        css('dl.attribute > dt[id]').each do |node|\n          name = node['id'].split('.')[-2..-1].join('.')\n          id = node['id']\n          type = node['id'].split('.')[0..-3].join('.')\n          entries << [name, id, type]\n        end\n\n        css('dl.method > dt[id], dl.classmethod > dt[id], dl.staticmethod > dt[id]').each do |node|\n          name = node['id'].split('.')[-2..-1].join('.') + '()'\n          id = node['id']\n          type = node['id'].split('.')[0..-3].join('.')\n          entries << [name, id, type]\n        end\n        entries\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/wordpress/clean_html.rb",
    "content": "module Docs\n  class Wordpress\n    class CleanHtmlFilter < Filter\n      def call\n        if root_page?\n          doc.inner_html = '<h1>WordPress</h1>'\n          return doc\n        end\n\n        article = at_css('article[id^=\"post-\"]')\n        @doc = article unless article.nil?\n\n        css(\n          'hr',\n          '.screen-reader-text',\n          '.table-of-contents',\n          '.anchor',\n          '.toc-jump',\n          '.source-code-links',\n          '.user-notes',\n          '.show-more',\n          '.hide-more',\n          '.wp-block-wporg-sidebar-container',\n          'section[data-nosnippet=\"true\"]',\n          # 'section:contains(\"before being able to contribute a note or feedback\")',\n          ).remove\n\n        if at_css('.entry-content')\n          header = at_css('h1')\n          header.remove_attribute('style')\n          @doc = at_css('.entry-content')\n          doc.prepend_child header\n        end\n\n        # Remove permalink\n        css('h2 > a, h3 > a').each do |node|\n          node.parent.remove_attribute('class')\n          node.parent.remove_attribute('tabindex')\n          node.parent.content = node.content\n        end\n\n        # Add PHP code highlighting\n        css('pre').each do |node|\n          node['data-language'] = 'php'\n        end\n\n        css('.source-code-container').each do |node|\n          node.remove_class('source-code-container')\n          node.name = 'pre'\n          node.inner_html = node.inner_html.gsub(/<br\\s?\\/?>/i, \"\\n\")\n          node.content = node.content.strip\n          node['data-language'] = 'php'\n        end\n\n        css('section').remove_attribute('class')\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/wordpress/entries.rb",
    "content": "module Docs\n  class Wordpress\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content\n      end\n\n      def get_type\n        if subpath.starts_with?('classes') and subpath.count('/') == 3\n          'Methods'\n        elsif subpath.starts_with?('classes')\n          'Classes'\n        elsif subpath.starts_with?('hooks')\n          'Hooks'\n        elsif subpath.starts_with?('functions')\n          'Functions'\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/xslt_xpath/clean_html.rb",
    "content": "module Docs\n  class XsltXpath\n    class CleanHtmlFilter < Filter\n      def call\n        initial_page? ? root : other\n        doc\n      end\n\n      def root\n        if table = at_css('.topicpage-table')\n          table.after(table.css('td').children).remove\n        end\n      end\n\n      def other\n        css('div[style*=\"background: #f5f5f5;\"]').remove\n\n        css('h3[id]').each do |node|\n          node.name = 'h2'\n        end\n\n        css('p').each do |node|\n          child = node.child\n          child = child.next while child && child.text? && child.content.blank?\n          child.remove if child.try(:name) == 'br'\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/xslt_xpath/entries.rb",
    "content": "module Docs\n  class XsltXpath\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = super\n        name.remove! 'XPath.'\n        name.remove! 'XSLT.'\n        name.remove! 'Axes.'\n        name.remove! 'Element.'\n        name.prepend 'xsl:' if slug =~ /XSLT\\/Element/\n        name << '()' if name.gsub!('Functions.', '')\n        name\n      end\n\n      def get_type\n        if slug =~ /XSLT\\/Element/\n          'XSLT Elements'\n        elsif slug.start_with?('XPath/Axes')\n          'XPath Axes'\n        elsif slug.start_with?('XPath/Functions')\n          'XPath Functions'\n        else\n          'Miscellaneous'\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/yarn/clean_html.rb",
    "content": "module Docs\n  class Yarn\n    class CleanHtmlFilter < Filter\n      def call\n        root_page? ? root : other\n        doc\n      end\n\n      def root\n        @doc = at_css('.hero + .container')\n\n        at_css('.row').remove\n        css('> .container', 'hr').remove\n\n        css('.row', '.col-lg-4', '.card-block').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('a.card').each do |node|\n          node.at_css('.float-right').replace %(<br><a href=\"#{node['href']}\">Read more</a>)\n          node.before(node.children).remove\n        end\n      end\n\n      def other\n        @doc = at_css('.guide')\n\n        css('a.toc', '.nav-tabs', '#select-platform', '.guide-controls + .list-group', '.guide-controls').remove\n\n        css('.install-select-os', '#install-instructions > .install-os-instructions:first-child').remove\n        css('.install-os-instructions[style]').remove_attribute('style')\n\n        css('.guide-content', '.tabs', '.tab-content').each do |node|\n          node.before(node.children).remove\n        end\n\n        unless at_css('h2')\n          css('h3', 'h4', 'h5').each do |node|\n            node.name = node.name.sub(/\\d/) { |i| i.to_i - 1 }\n          end\n        end\n\n        unless at_css('h3')\n          css('h4', 'h5').each do |node|\n            node.name = node.name.sub(/\\d/) { |i| i.to_i - 1 }\n          end\n        end\n\n        css('div.highlighter-rouge').each do |node|\n          node['data-language'] = node['class'][/language-(\\w+)/, 1] if node['class']\n          node.content = node.content.strip\n          node.name = 'pre'\n        end\n\n        css('.highlighter-rouge').remove_attr('class')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/yarn/clean_html_berry.rb",
    "content": "module Docs\n  class Yarn\n    class CleanHtmlBerryFilter < Filter\n      def call\n        @doc = at_css('main .container div.theme-doc-markdown.markdown')\n\n        css('*').each do |node|\n          node.remove_attribute('style')\n        end\n\n        css('pre').each do |node|\n          lang = node['class'][/language-(\\w+)/, 1]\n          node['data-language'] = lang if lang\n          node.content = node.css('.token-line').map(&:content).join(\"\\n\")\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/yarn/entries.rb",
    "content": "module Docs\n  class Yarn\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content\n\n        unless type == 'CLI'\n          name.prepend \"#{css('.guide-nav a').to_a.index(at_css('.guide-nav a.active')) + 1}. \"\n        end\n\n        name\n      end\n\n      def get_type\n        type = at_css('.guide-nav a').content.strip\n        type.sub 'CLI Introduction', 'CLI Commands'\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/yarn/entries_berry.rb",
    "content": "module Docs\n  class Yarn\n    class EntriesBerryFilter < Docs::EntriesFilter\n      def get_name\n        at_css('main header h1').content\n      end\n\n      def get_type\n        at_css('nav.navbar a.navbar__item.navbar__link.navbar__link--active').content\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/yii/clean_html_v1.rb",
    "content": "module Docs\n  class Yii\n    class CleanHtmlV1Filter < Filter\n      def call\n        at_css('h1').content = 'Yii PHP Framework' if root_page?\n\n        css('.api-suggest', '.google-ad', '.g-plusone', '#nav', '#comments').remove\n\n        css('.summary > p > .toggle').each do |node|\n          node.parent.remove\n        end\n\n        css('.signature', '.signature2').each do |node|\n          node.name = 'pre'\n          node.inner_html = node.inner_html.strip\n        end\n\n        css('div.detailHeader').each do |node|\n          node.name = 'h3'\n        end\n\n        css('.sourceCode > .code > code').each do |node|\n          parent = node.parent\n          parent.name = 'pre'\n          node.remove\n          parent.inner_html = node.first_element_child.inner_html.strip\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/yii/clean_html_v2.rb",
    "content": "module Docs\n  class Yii\n    class CleanHtmlV2Filter < Filter\n      def call\n        css('.hashlink[name]').each do |node|\n          node.parent['id'] = node['name']\n        end\n\n        css('#nav', '.tool-link', '.toggle', '.hashlink').remove\n\n        css('.detail-header').each do |node|\n          node.name = 'h3'\n          node.child.remove while node.child.content.blank?\n        end\n\n        css('pre').each do |node|\n          node.inner_html = node.inner_html.gsub('<br>', \"\\n\").gsub('&nbsp;', ' ')\n          node.content = node.content\n          node['data-language'] = 'php'\n        end\n\n        css('div.signature').each do |node|\n          node.name = 'pre'\n          node.inner_html = node.inner_html.strip\n        end\n\n        css('.detail-table th').each do |node|\n          node.name = 'td'\n        end\n\n        css('.detail-table td.signature').each do |node|\n          node.name = 'th'\n        end\n\n        css('.summary', 'span[style]', 'div.doc-description', 'div.property-doc', 'div.class-description', 'th > span').each do |node|\n          node.before(node.children).remove\n        end\n\n        css('a[id]:empty').each do |node|\n          node.next_element['id'] = node['id'] if node.next_element && node.next_element.name != 'a'\n        end\n\n        css('table', 'pre', 'tr', 'td', 'th').remove_attr('class')\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/yii/entries_v1.rb",
    "content": "module Docs\n  class Yii\n    class EntriesV1Filter < Docs::EntriesFilter\n      def get_name\n        at_css('h1').content.strip\n      end\n\n      def get_type\n        css('.summaryTable td').first.content\n      end\n\n      def additional_entries\n        css('.detailHeader').inject [] do |entries, node|\n          name = node.child.content.strip\n          name.prepend self.name + (node.next_element.content.include?('public static') ? '::' : '->')\n          entries << [name, node['id']]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/yii/entries_v2.rb",
    "content": "module Docs\n  class Yii\n    class EntriesV2Filter < Docs::EntriesFilter\n      def get_name\n        name = at_css('h1').content.strip\n        name.remove! %r{\\A.*?(Class|Trait|Interface)\\s*}\n        name.remove!('yii\\\\')\n        name\n      end\n\n      def get_type\n        if slug.include?('guide')\n          'Guides'\n        else\n          components = name.split('\\\\')\n          type = components.first\n          type << \"\\\\#{components.second}\" if (type == 'db' && components.second.in?(%w(cubrid mssql mysql oci pgsql sqlite))) ||\n                                              (type == 'web' && components.second.in?(%w(Request Response)))\n          type = 'yii' if type == 'BaseYii' || type == 'Yii'\n          type\n        end\n      end\n\n      def additional_entries\n        css('.detail-header').each_with_object [] do |node, entries|\n          name = node.child.content.strip\n          name.prepend \"#{self.name} \"\n          entries << [name, node['id']]\n          node.remove_attribute('class')\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/zig/clean_html.rb",
    "content": "module Docs\n  class Zig\n    class CleanHtmlFilter < Filter\n      def call\n        at_css('main').prepend_child at_css('h1')\n        @doc = at_css('main')\n        css('a.hdr').remove\n        css('h1, h2, h3').each do |node|\n          node.content = node.content\n        end\n        css('pre > code').each do |node|\n          node.parent['data-language'] = 'zig'\n          node.parent.content = node.parent.content\n        end\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/zig/entries.rb",
    "content": "module Docs\n  class Zig\n    class EntriesFilter < Docs::EntriesFilter\n      def additional_entries\n        entries = []\n        type = nil\n        subtype = nil\n\n        css('h2, h3').each do |node|\n          if node.name == 'h2' && node['id']\n            type = node.content.gsub(/ §/, '')\n            subtype = nil\n            entries << [type, node['id'], type]\n          elsif node.name == 'h3' && node['id']\n            subtype = node.content.gsub(/ §/, '')\n            name = \"#{type}: #{subtype}\"\n            entries << [name, node['id'], type]\n          end\n        end\n\n        entries\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/zsh/clean_html.rb",
    "content": "module Docs\n  class Zsh\n    class CleanHtmlFilter < Filter\n      def call\n        css('table.header', 'table.menu', 'hr').remove\n\n        # Remove indices from headers.\n        css('h1', 'h2', 'h3').each do |node|\n          node.content = node.content.match(/^[\\d\\.]* (.*)$/)&.captures&.first\n        end\n\n        css('h2.section ~ a').each do |node|\n          node.next_element['id'] = node['name']\n        end\n\n        doc\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/filters/zsh/entries.rb",
    "content": "module Docs\n  class Zsh\n    class EntriesFilter < Docs::EntriesFilter\n      def get_name\n        extract_header_text(at_css('h1.chapter').content)\n      end\n\n      def additional_entries\n        entries = []\n        used_fns = []\n        \n        css('h2.section').each do |node|\n          type = get_type\n          # Linkable anchor sits above <h2>.\n          a = node.xpath('preceding-sibling::a').last\n          header_text = extract_header_text(node.content)\n\n          case type\n          when 'Zsh Modules'\n            module_name = header_text.match(/The (zsh\\/.* Module)/)&.captures&.first\n            header_text = module_name if module_name.present?\n          when 'Calendar Function System'\n            header_text << ' (Calendar)'\n          end\n\n          entries << [header_text, a['name'], type] unless header_text.start_with?('Description')\n        end\n\n        # Functions are documented within <dl> elements.\n        # Names are wrapped in <dt>, details within <dd>.\n        # <dd> can also contain anchors for the next function.\n        doc.css('> dl').each do |node|\n          type = get_type\n          fn_names = node.css('> dt')\n          node.css('dd a[name]').each_with_index do |anchor, i|\n            if fn_names[i].present? && anchor['name'].present?\n              fn_names[i]['id'] = anchor['name']\n\n              # Groups of functions are sometimes comma-delimited.\n              # Strip arguments, flags, etc. from function name.\n              # Skip flag-only headers.\n              fn_names[i].inner_html.split(', ').each do |fn|\n                fn.gsub!(/<(?:tt|var)>(.+?)<\\/(?:tt|var)>/, '\\1')\n                fn = fn.split(' ').first\n                fn.gsub!(/(?:[\\[\\(]).*(?:[\\]\\)]).*$/, '')\n\n                # Add context for operators.\n                fn << \" (#{type})\" if fn.length == 1\n\n                if fn.present? && !fn.match?(/^[\\-\\[]/) && !used_fns.include?(fn)\n                  used_fns << fn\n                  entries << [fn, anchor['name'], type]\n                end\n              end\n            end\n          end\n        end\n\n        entries\n      end\n\n      def get_type\n        extract_header_text(at_css('h1.chapter').content)\n      end\n\n      private\n\n      # Extracts text from a string, dropping indices preceding it.\n      def extract_header_text(str)\n        str.match(/^[\\d\\.]* (.*)$/)&.captures&.first\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/angular.rb",
    "content": "require 'yajl/json_gem'\n\nmodule Docs\n  class Angular < UrlScraper\n    self.type = 'angular'\n    self.links = {\n      home: 'https://angular.dev/',\n      code: 'https://github.com/angular/angular'\n    }\n    self.base_url = 'https://angular.io/'\n    self.root_path = 'docs'\n\n    options[:max_image_size] = 256_000\n\n    options[:attribution] = <<-HTML\n      Super-powered by Google &copy;2010&ndash;2025.<br />\n      Code licensed under an MIT-style License. Documentation licensed under CC BY 4.0.\n    HTML\n\n    options[:follow_links] = false\n    options[:only_patterns] = [/\\Aguide/, /\\Aapi/, /\\Acli/]\n    options[:fix_urls_before_parse] = ->(url) do\n      url.sub! %r{\\Aguide/}, '/guide/'\n      url.sub! %r{\\Aapi/}, '/api/'\n      url.sub! %r{\\cli/}, '/cli/'\n      url.sub! %r{\\Agenerated/}, '/generated/'\n      url\n    end\n\n    module JsonNavigation\n      private\n\n      def initial_urls\n        initial_urls = []\n\n        Request.run \"#{self.class.base_url}generated/navigation.json\" do |response|\n          data = JSON.parse(response.body)\n          dig = ->(entry) do\n            initial_urls << url_for(\"generated/docs/#{entry['url']}.json\") if entry['url'] && entry['url'] != 'api'\n            entry['children'].each(&dig) if entry['children']\n          end\n          data['SideNav'].each(&dig)\n        end\n\n        Request.run \"#{self.class.base_url}generated/docs/api/api-list.json\" do |response|\n          data = JSON.parse(response.body)\n          dig = ->(entry) do\n            initial_urls << url_for(\"generated/docs/#{entry['path']}.json\") if entry['path']\n            initial_urls << url_for(\"generated/docs/api/#{entry['name']}.json\") if entry['name'] && !entry['path']\n            entry['items'].each(&dig) if entry['items']\n          end\n          data.each(&dig)\n        end\n\n        initial_urls\n      end\n\n      def handle_response(response)\n        if response.mime_type.include?('json')\n          begin\n            json = JSON.parse(response.body)\n            response.options[:response_body] = json['contents']\n            response.url.path = response.url.path.gsub(/generated\\/docs\\/.*/, json['id'])\n            response.effective_url.path = response.effective_url.path.gsub(/generated\\/docs\\/.*/, json['id'])\n          rescue JSON::ParserError\n            response.options[:response_body] = ''\n          end\n          response.headers['Content-Type'] = 'text/html'\n        end\n        super\n      end\n    end\n\n    module Since12\n      def url_for(path)\n        # See encodeToLowercase im aio/tools/transforms/angular-base-package/processors/disambiguateDocPaths.js\n        path = path.gsub(/[A-Z_]/) {|s| s.downcase + '_'}\n        super\n      end\n      include Docs::Angular::JsonNavigation\n    end\n\n    module Since18\n      def self.handle_redirects(version)\n        lambda do |url|\n          url.sub! '/guide/templates/reference-variables', '/guide/templates/variables#template-reference-variables'\n          url.sub! '/guide/signals/inputs', '/guide/components/inputs'\n          url.sub! '/guide/defer', '/guide/templates/defer'\n          url.sub! '/guide/templates/class-binding', '/guide/templates/binding#css-class-and-style-property-bindings'\n          url.sub! %r{/guide/components$}, '/guide/components/anatomy-of-components'\n          url.sub! '/guide/templates/property-binding', '/guide/templates/binding#binding-dynamic-properties-and-attributes'\n          url.sub! %r{/guide/ngmodules$}, '/guide/ngmodules/overview'\n          url.sub! '/guide/components/importing', '/guide/components/anatomy-of-components#using-components'\n\n          url.sub! '/guide/components/anatomy-of-components', '/guide/components' if version == '20'\n\n          url\n        end\n      end\n    end\n\n    version do\n      self.release = '21.2.0'\n      self.base_url = 'https://angular.dev/'\n      self.root_path = 'overview'\n\n      options[:follow_links] = true\n      options[:container] = '.docs-app-main-content'\n      options[:fix_urls] = Since18.handle_redirects(self.version)\n\n      html_filters.push 'angular/entries', 'angular/clean_html_v18'\n\n      include Docs::Angular::Since18\n    end\n\n    version '20' do\n      self.release = '20.3.15'\n      self.base_url = 'https://v20.angular.dev/'\n      self.root_path = 'overview'\n\n      options[:follow_links] = true\n      options[:container] = '.docs-app-main-content'\n      options[:fix_urls] = Since18.handle_redirects(self.version)\n\n      html_filters.push 'angular/entries', 'angular/clean_html_v18'\n\n      include Docs::Angular::Since18\n    end\n\n    version '19' do\n      self.release = '19.2.15'\n      self.base_url = 'https://v19.angular.dev/'\n      self.root_path = 'overview'\n\n      options[:follow_links] = true\n      options[:container] = '.docs-app-main-content'\n      options[:fix_urls] = Since18.handle_redirects(self.version)\n\n      html_filters.push 'angular/entries', 'angular/clean_html_v18'\n\n      include Docs::Angular::Since18\n    end\n\n    version '18' do\n      self.release = '18.2.14'\n      self.base_url = 'https://v18.angular.dev/'\n      self.root_path = 'overview'\n\n      options[:follow_links] = true\n      options[:container] = '.docs-app-main-content'\n      options[:fix_urls] = Since18.handle_redirects(self.version)\n\n      html_filters.push 'angular/entries', 'angular/clean_html_v18'\n\n      include Docs::Angular::Since18\n    end\n\n    version '17' do\n      self.release = '17.0.8'\n      self.base_url = 'https://v17.angular.io/'\n      html_filters.push 'angular/clean_html', 'angular/entries'\n      include Docs::Angular::Since12\n    end\n\n    version '16' do\n      self.release = '16.2.12'\n      self.base_url = 'https://v16.angular.io/'\n      html_filters.push 'angular/clean_html', 'angular/entries'\n      include Docs::Angular::Since12\n    end\n\n    version '15' do\n      self.release = '15.2.9'\n      self.base_url = 'https://v15.angular.io/'\n      html_filters.push 'angular/clean_html', 'angular/entries'\n      include Docs::Angular::Since12\n    end\n\n    version '14' do\n      self.release = '14.2.12'\n      self.base_url = 'https://v14.angular.io/'\n      html_filters.push 'angular/clean_html', 'angular/entries'\n      include Docs::Angular::Since12\n    end\n\n    version '13' do\n      self.release = '13.3.8'\n      self.base_url = 'https://v13.angular.io/'\n      html_filters.push 'angular/clean_html', 'angular/entries'\n      include Docs::Angular::Since12\n    end\n\n    version '12' do\n      self.release = '12.2.13'\n      self.base_url = 'https://v12.angular.io/'\n      html_filters.push 'angular/clean_html', 'angular/entries'\n      include Docs::Angular::Since12\n    end\n\n    version '11' do\n      self.release = '11.2.14'\n      self.base_url = 'https://v11.angular.io/'\n      html_filters.push 'angular/clean_html', 'angular/entries'\n      include Docs::Angular::JsonNavigation\n    end\n\n    version '10' do\n      self.release = '10.2.3'\n      self.base_url = 'https://v10.angular.io/'\n      html_filters.push 'angular/clean_html', 'angular/entries'\n      include Docs::Angular::JsonNavigation\n    end\n\n    version '9' do\n      self.release = '9.1.12'\n      self.base_url = 'https://v9.angular.io/'\n      html_filters.push 'angular/clean_html', 'angular/entries'\n      include Docs::Angular::JsonNavigation\n    end\n\n    version '8' do\n      self.release = '8.2.14'\n      self.base_url = 'https://v8.angular.io/'\n      html_filters.push 'angular/clean_html', 'angular/entries'\n      include Docs::Angular::JsonNavigation\n    end\n\n    version '7' do\n      self.release = '7.2.15'\n      self.base_url = 'https://v7.angular.io/'\n      html_filters.push 'angular/clean_html', 'angular/entries'\n      include Docs::Angular::JsonNavigation\n    end\n\n    version '6' do\n      self.release = '6.1.10'\n      self.base_url = 'https://v6.angular.io/'\n      html_filters.push 'angular/clean_html', 'angular/entries'\n      include Docs::Angular::JsonNavigation\n    end\n\n    version '5' do\n      self.release = '5.2.11'\n      self.base_url = 'https://v5.angular.io/'\n      html_filters.push 'angular/clean_html', 'angular/entries'\n      include Docs::Angular::JsonNavigation\n    end\n\n    version '4' do\n      self.release = '4.4.6'\n      self.base_url = 'https://v4.angular.io/'\n      html_filters.push 'angular/clean_html', 'angular/entries'\n      include Docs::Angular::JsonNavigation\n    end\n\n    version '2' do\n      self.release = '2.4.10'\n      self.base_url = 'https://v2.angular.io/docs/ts/latest/'\n      self.root_path = 'api/'\n\n      html_filters.push 'angular/entries_v2', 'angular/clean_html_v2'\n\n      stub 'api/' do\n        base_url = URL.parse(self.base_url)\n        capybara = load_capybara_selenium\n        capybara.app_host = base_url.origin\n        capybara.visit(base_url.path + 'api/')\n        capybara.execute_script('return document.body.innerHTML')\n      end\n\n      options[:skip_patterns] = [/deprecated/, /VERSION-let/]\n      options[:skip] = %w(\n        index.html\n        styleguide.html\n        quickstart.html\n        cheatsheet.html\n        guide/cheatsheet.html\n        guide/style-guide.html)\n\n      options[:replace_paths] = {\n        'testing/index.html'  => 'guide/testing.html',\n        'guide/glossary.html' => 'glossary.html',\n        'tutorial'            => 'tutorial/',\n        'api'                 => 'api/'\n      }\n\n      options[:fix_urls] = -> (url) do\n        url.sub! %r{\\A(https://(?:v2\\.)?angular\\.io/docs/.+/)index\\.html\\z}, '\\1'\n        url\n      end\n    end\n\n    def get_latest_version(opts)\n      get_npm_version('@angular/core', opts)\n    end\n\n    private\n\n    def parse(response)\n      response.body.gsub! '<code-example', '<pre'\n      response.body.gsub! '</code-example', '</pre'\n      response.body.gsub! '<code-pane', '<pre'\n      response.body.gsub! '</code-pane', '</pre'\n      response.body.gsub! '<live-example></live-example>', 'live example'\n      response.body.gsub! '<live-example', '<span'\n      response.body.gsub! '</live-example', '</span'\n      super\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/angularjs.rb",
    "content": "module Docs\n  class Angularjs < UrlScraper\n    self.name = 'Angular.js'\n    self.slug = 'angularjs'\n    self.type = 'angularjs'\n    self.root_path = 'api.html'\n    self.initial_paths = %w(guide.html guide/controller.html)\n    self.links = {\n      home: 'https://angularjs.org/',\n      code: 'https://github.com/angular/angular.js'\n    }\n\n    html_filters.push 'angularjs/clean_html', 'angularjs/entries', 'title'\n    text_filters.push 'angularjs/clean_urls'\n\n    options[:title] = false\n    options[:root_title] = 'Angular.js'\n\n    options[:decode_and_clean_paths] = true\n    options[:fix_urls_before_parse] = ->(str) do\n      str.gsub!('[', '%5B')\n      str.gsub!(']', '%5D')\n      str\n    end\n\n    options[:fix_urls] = ->(url) do\n      %w(api guide).each do |str|\n        url.sub! \"/partials/#{str}/#{str}/\", \"/partials/#{str}/\"\n        url.sub! %r{/#{str}/img/}, '/img/'\n        url.sub! %r{/#{str}/(.+?)/#{str}/}, \"/#{str}/\"\n        url.sub! %r{/partials/#{str}/(.+?)(?<!\\.html)(?:\\z|(#.*))}, \"/partials/#{str}/\\\\1.html\\\\2\"\n        url.sub! %r{/partials/(?!img).+/#{str}/}, \"/partials/#{str}/\"\n      end\n      url\n    end\n\n    options[:only_patterns] = [%r{\\Aapi}, %r{\\Aguide}]\n    options[:skip] = %w(api/ng.html guide/tutorial/step_14.html guide/api.html guide/tutorial/.html)\n    options[:skip_patterns] = [\n      /error\\/\\$compile/,\n      /misc/,\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; 2010&ndash;2020 Google, Inc.<br>\n      Licensed under the Creative Commons Attribution License 3.0.\n    HTML\n\n    stub '' do\n      capybara = load_capybara_selenium\n      capybara.app_host = 'https://code.angularjs.org'\n      capybara.visit(\"/#{self.class.release}/docs/api\")\n      capybara.execute_script(\"return document.querySelector('.side-navigation').innerHTML\")\n    end\n\n    version '1.8' do\n      self.release = '1.8.2'\n      self.base_url = \"https://code.angularjs.org/#{release}/docs/partials/\"\n    end\n\n    version '1.7' do\n      self.release = '1.7.8'\n      self.base_url = \"https://code.angularjs.org/#{release}/docs/partials/\"\n    end\n\n    version '1.6' do\n      self.release = '1.6.9'\n      self.base_url = \"https://code.angularjs.org/#{release}/docs/partials/\"\n    end\n\n    version '1.5' do\n      self.release = '1.5.11'\n      self.base_url = \"https://code.angularjs.org/#{release}/docs/partials/\"\n    end\n\n    version '1.4' do\n      self.release = '1.4.14'\n      self.base_url = \"https://code.angularjs.org/#{release}/docs/partials/\"\n    end\n\n    version '1.3' do\n      self.release = '1.3.20'\n      self.base_url = \"https://code.angularjs.org/#{release}/docs/partials/\"\n    end\n\n    version '1.2' do\n      self.release = '1.2.32'\n      self.base_url = \"https://code.angularjs.org/#{release}/docs/partials/\"\n    end\n\n    def get_latest_version(opts)\n      get_npm_version('angular', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/ansible.rb",
    "content": "module Docs\n  class Ansible < UrlScraper\n    self.name = 'Ansible'\n    self.type = 'sphinx'\n    self.links = {\n      home: 'https://www.ansible.com/',\n      code: 'https://github.com/ansible/ansible'\n    }\n\n    html_filters.push 'ansible/entries', 'sphinx/clean_html', 'ansible/clean_html'\n\n    options[:attribution] = <<-HTML\n      &copy; 2012&ndash;2018 Michael DeHaan<br>\n      &copy; 2018&ndash;2025 Red Hat, Inc.<br>\n      Licensed under the GNU General Public License version 3.\n    HTML\n\n    options[:skip] = %w(\n      installation_guide/index.html\n      reference_appendices/glossary.html\n      reference_appendices/faq.html\n      reference_appendices/tower.html\n      user_guide/quickstart.html\n      modules/modules_by_category.html\n      modules/list_of_all_modules.html\n      collections/all_plugins.html\n      collections/index_vars.html)\n\n    options[:skip_patterns] = [\n      /\\Acommunity.*/i,\n      /\\Adev_guide.*/i,\n      /\\Aroadmap.*/i,\n    ]\n\n    version do\n      # 2025-08-14\n      self.base_url = \"https://docs.ansible.com/ansible/latest/\"\n    end\n\n    version '2.11' do\n      self.release = '2.11.0'\n      self.base_url = \"https://docs.ansible.com/ansible/#{version}/\"\n    end\n\n    version '2.10' do\n      self.release = '2.10.5'\n      self.base_url = \"https://docs.ansible.com/ansible/#{version}/\"\n    end\n\n    version '2.9' do\n      self.release = '2.9.15'\n      self.base_url = \"https://docs.ansible.com/ansible/#{version}/\"\n    end\n\n    version '2.8' do\n      self.release = '2.8.16'\n      self.base_url = \"https://docs.ansible.com/ansible/#{version}/\"\n    end\n\n    version '2.7' do\n      self.release = '2.7.17'\n      self.base_url = \"https://docs.ansible.com/ansible/#{version}/\"\n    end\n\n    version '2.6' do\n      self.release = '2.6.20'\n      self.base_url = \"https://docs.ansible.com/ansible/#{version}/\"\n    end\n\n    version '2.5' do\n      self.release = '2.5.15'\n      self.base_url = \"https://docs.ansible.com/ansible/#{version}/\"\n    end\n\n    version '2.4' do\n      self.release = '2.4.6'\n      self.base_url = \"https://docs.ansible.com/ansible/#{version}/\"\n\n      options[:skip] = %w(\n        glossary.html\n        faq.html\n        community.html\n        tower.html\n        quickstart.html\n        list_of_all_modules.html)\n      options[:skip_patterns] = []\n    end\n\n    def get_latest_version(opts)\n      tags = get_github_tags('ansible', 'ansible', opts)\n      tags[0]['name'][1..-1]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/apache.rb",
    "content": "module Docs\n  class Apache < UrlScraper\n    self.name = 'Apache HTTP Server'\n    self.slug = 'apache_http_server'\n    self.type = 'apache'\n    self.release = '2.4.64'\n    self.base_url = 'https://httpd.apache.org/docs/2.4/en/'\n    self.links = {\n      home: 'https://httpd.apache.org/'\n    }\n\n    html_filters.push 'apache/clean_html', 'apache/entries'\n\n    options[:container] = '#page-content'\n\n    options[:skip] = %w(\n      upgrading.html\n      license.html\n      sitemap.html\n      glossary.html\n      mod/quickreference.html\n      mod/directive-dict.html\n      mod/directives.html\n      mod/module-dict.html\n      programs/other.html)\n\n    options[:skip_patterns] = [\n      /\\A(da|de|en|es|fr|ja|ko|pt-br|tr|zh-cn)\\//,\n      /\\Anew_features/,\n      /\\Adeveloper\\// ]\n\n    options[:attribution] = <<-HTML\n      &copy; 2018 The Apache Software Foundation<br>\n      Licensed under the Apache License, Version 2.0.\n    HTML\n\n    def get_latest_version(opts)\n      doc = fetch_doc('http://httpd.apache.org/download', opts)\n      doc.at_css('#apcontents li > a').content\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/apache_pig.rb",
    "content": "module Docs\n  class ApachePig < UrlScraper\n    self.name = 'Apache Pig'\n    self.slug = 'apache_pig'\n    self.type = 'simple'\n    self.links = {\n      home: 'https://pig.apache.org/'\n    }\n\n    html_filters.push 'apache_pig/clean_html', 'apache_pig/entries'\n\n    options[:container] = '#content'\n    options[:skip] = %w(pig-index.html)\n    options[:skip_patterns] = [/\\Aapi/, /\\Ajdiff/]\n\n    options[:attribution] = <<-HTML\n      &copy; 2007&ndash;2017 Apache Software Foundation<br>\n      Licensed under the Apache Software License version 2.0.\n    HTML\n\n    version '0.17' do\n      self.release = '0.17.0'\n      self.base_url = \"https://pig.apache.org/docs/r#{release}/\"\n    end\n\n    version '0.16' do\n      self.release = '0.16.0'\n      self.base_url = \"https://pig.apache.org/docs/r#{release}/\"\n    end\n\n    version '0.15' do\n      self.release = '0.15.0'\n      self.base_url = \"https://pig.apache.org/docs/r#{release}/\"\n    end\n\n    version '0.14' do\n      self.release = '0.14.0'\n      self.base_url = \"https://pig.apache.org/docs/r#{release}/\"\n    end\n\n    version '0.13' do\n      self.release = '0.13.0'\n      self.base_url = \"https://pig.apache.org/docs/r#{release}/\"\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://pig.apache.org/', opts)\n      item = doc.at_css('div[id=\"menu_1.2\"] > .menuitem:last-child')\n      item.content.strip.sub(/Release /, '')\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/astro.rb",
    "content": "module Docs\n  class Astro < UrlScraper\n    self.name = 'Astro'\n    self.slug = 'astro'\n    self.type = 'simple'\n    self.links = {\n      home: 'https://docs.astro.build/',\n      code: 'https://github.com/withastro/astro'\n    }\n\n    # https://github.com/withastro/astro/blob/main/LICENSE\n    options[:attribution] = <<-HTML\n      &copy; 2021 Fred K. Schott<br>\n      Licensed under the MIT License.\n    HTML\n\n    options[:skip_patterns] = [/tutorial/, /getting-started/]\n\n    self.release = '5.16.5'\n    self.base_url = 'https://docs.astro.build/en/'\n    self.initial_paths = %w(install-and-setup/)\n\n    html_filters.push 'astro/entries', 'astro/clean_html'\n\n    def get_latest_version(opts)\n      get_npm_version('astro', opts)\n    end\n\n    private\n\n    def parse(response)\n      if response.url == self.base_url\n        # root_page is a redirect\n        response.body.gsub! %r{.*}, '<body><article><section><h1>Astro</h1><p> Astro is a website build tool for the modern web — powerful developer experience meets lightweight output.</p></section></article></body>'\n      end\n      super\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/async.rb",
    "content": "module Docs\n  class Async < UrlScraper\n    self.type = 'async'\n    self.release = '3.2.0'\n    self.base_url = 'https://caolan.github.io/async/v3/'\n    self.root_path = 'docs.html'\n    self.links = {\n      home: 'https://caolan.github.io/async/',\n      code: 'https://github.com/caolan/async'\n    }\n\n    html_filters.push 'async/entries', 'async/clean_html'\n\n    options[:skip_links] = true\n\n    options[:attribution] = <<-HTML\n      &copy; 2010&ndash;2018 Caolan McMahon<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://caolan.github.io/async/v3/', opts)\n      doc.at_css('#version-dropdown > a').content.strip[1..-1]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/axios.rb",
    "content": "module Docs\n  class Axios < UrlScraper\n    self.type = 'simple'\n    self.links = {\n      home: 'hthttps://axios-http.com/',\n      code: 'https://github.com/axios/axios'\n    }\n    self.release = '1.13.5'\n    self.base_url = \"https://axios-http.com/docs/\"\n    self.initial_paths = %w(index intro)\n    options[:skip] = %w(sponsor)\n\n    html_filters.push 'axios/entries', 'axios/clean_html'\n\n    # https://github.com/axios/axios-docs/blob/master/LICENSE\n    options[:attribution] = <<-HTML\n      &copy; 2020-present John Jakob \"Jake\" Sarjeant<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('axios', 'axios', opts)\n    end\n\n    private\n\n    def process_response?(response)\n      true\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/babel.rb",
    "content": "module Docs\n  class Babel < UrlScraper\n    self.type = 'simple'\n    self.base_url = 'https://babeljs.io/docs/'\n    self.links = {\n      home: 'https://babeljs.io/',\n      code: 'https://github.com/babel/babel'\n    }\n\n    html_filters.push 'babel/clean_html', 'babel/entries'\n\n    options[:trailing_slash] = true\n\n    options[:skip_patterns] = [\n      /usage/,\n      /configuration/,\n      /learn/,\n      /v7-migration/,\n      /v7-migration-api/,\n      /editors/,\n      /presets/,\n      /caveats/,\n      /faq/,\n      /roadmap/\n    ]\n\n    options[:skip_link] = ->(link) {\n       link['href'].include?('https://babeljs.io/docs/en/')\n    }\n\n    options[:attribution] = <<-HTML\n      &copy; 2014-present Sebastian McKenzie<br>\n      Licensed under the MIT License.\n    HTML\n\n    version '7' do\n      self.release = '7.21.4'\n    end\n\n    version '6' do\n      self.release = '6.26.1'\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('babel', 'babel', opts)\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/backbone.rb",
    "content": "module Docs\n  class Backbone < UrlScraper\n    self.name = 'Backbone.js'\n    self.slug = 'backbone'\n    self.type = 'underscore'\n    self.release = '1.6.0'\n    self.base_url = 'https://backbonejs.org'\n    self.links = {\n      home: 'https://backbonejs.org/',\n      code: 'https://github.com/jashkenas/backbone'\n    }\n\n    html_filters.push 'backbone/clean_html', 'backbone/entries', 'title'\n\n    options[:title] = 'Backbone.js'\n    options[:container] = '.container'\n    options[:skip_links] = true\n\n    options[:attribution] = <<-HTML\n      &copy; 2010&ndash;2024 Jeremy Ashkenas, DocumentCloud<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://backbonejs.org/', opts)\n      doc.at_css('.version').content[1...-1]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/bash.rb",
    "content": "module Docs\n  class Bash < FileScraper\n    self.type = 'bash'\n    self.release = '5.2'\n    self.base_url = 'https://www.gnu.org/software/bash/manual/html_node'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://www.gnu.org/software/bash/',\n      code: 'http://git.savannah.gnu.org/cgit/bash.git'\n    }\n\n    html_filters.push 'bash/entries', 'bash/clean_html'\n\n    options[:attribution] = <<-HTML\n      Copyright &copy; 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc.<br>\n      Licensed under the GNU Free Documentation License.\n    HTML\n\n    def get_latest_version(opts)\n      body = fetch('https://www.gnu.org/software/bash/manual/html_node/index.html', opts)\n      body.scan(/, Version ([0-9.]+)/)[0][0][0...-1]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/bazel.rb",
    "content": "module Docs\n  class Bazel < UrlScraper\n    self.name = 'Bazel'\n    self.type = 'bazel'\n\n    html_filters.push 'bazel/entries', 'bazel/clean_html'\n\n    options[:skip] = %w(platform)\n\n    options[:container] = \"devsite-content\"\n    options[:attribution] = <<-HTML\n    Licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License.\n    HTML\n\n    version '8.0' do\n      self.release = '8.0.0'\n      self.base_url = 'https://bazel.build/versions/8.0.0/reference/be/'\n    end\n\n    version '7.0' do\n      self.release = '7.0.0'\n      self.base_url = 'https://bazel.build/versions/7.0.0/reference/be/'\n    end\n\n    version '6.4' do\n      self.release = '6.4.0'\n      self.base_url = 'https://bazel.build/versions/6.4.0/reference/be/'\n    end\n\n    version '6.3' do\n      self.release = '6.3.0'\n      self.base_url = 'https://bazel.build/versions/6.3.0/reference/be/'\n    end\n\n    version '6.2' do\n      self.release = '6.2.0'\n      self.base_url = 'https://bazel.build/versions/6.2.0/reference/be/'\n    end\n\n    version '6.1' do\n      self.release = '6.1.0'\n      self.base_url = 'https://bazel.build/versions/6.1.0/reference/be/'\n    end\n\n    version '6.0' do\n      self.release = '6.0.0'\n      self.base_url = 'https://bazel.build/versions/6.0.0/reference/be/'\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('bazelbuild', 'bazel', opts)\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/bluebird.rb",
    "content": "module Docs\n  class Bluebird < UrlScraper\n    self.type = 'simple'\n    self.release = '3.7.2'\n    self.base_url = 'http://bluebirdjs.com/docs/'\n    self.root_path = 'api-reference.html'\n    self.links = {\n      home: 'http://bluebirdjs.com/',\n      code: 'https://github.com/petkaantonov/bluebird/'\n    }\n\n    html_filters.push 'bluebird/clean_html', 'bluebird/entries'\n\n    options[:skip] = %w(support.html download-api-reference.html contribute.html)\n\n    options[:attribution] = <<-HTML\n      &copy; 2013&ndash;2018 Petka Antonov<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version('bluebird', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/bootstrap.rb",
    "content": "module Docs\n  class Bootstrap < UrlScraper\n    self.type = 'bootstrap'\n    self.links = {\n      home: 'https://getbootstrap.com/',\n      code: 'https://github.com/twbs/bootstrap'\n    }\n\n    options[:trailing_slash] = true\n\n    # https://github.com/twbs/bootstrap/blob/master/LICENSE\n    options[:attribution] = <<-HTML\n      &copy; 2011&ndash;2023 The Bootstrap Authors<br>\n      Code licensed under the MIT License.<br>\n      Documentation licensed under the Creative Commons Attribution License v3.0.\n    HTML\n\n    version '5' do\n      self.release = '5.3'\n      self.base_url = \"https://getbootstrap.com/docs/#{self.release}/\"\n      self.root_path = 'getting-started/introduction/'\n\n      html_filters.push 'bootstrap/entries_v5', 'bootstrap/clean_html_v5'\n\n      options[:skip_patterns] = [\n        /\\Aabout\\//\n      ]\n\n      options[:replace_paths] = {\n        'components/breadcrumb//' => '/components/breadcrumb/'\n      }\n\n    end\n\n    version '4' do\n      self.release = '4.5'\n      self.base_url = \"https://getbootstrap.com/docs/#{self.release}/\"\n      self.root_path = 'getting-started/introduction/'\n\n      html_filters.push 'bootstrap/entries_v4', 'bootstrap/clean_html_v4'\n\n      options[:only_patterns] = [\n        /\\Agetting-started\\//, /\\Alayout\\//, /\\Acontent\\//,\n        /\\Acomponents\\//, /\\Autilities\\/.+/, /\\Amigration\\//\n      ]\n\n    end\n\n    version '3' do\n      self.release = '3.4.1'\n      self.base_url = 'https://getbootstrap.com/docs/3.4/'\n      self.root_path = 'getting-started/'\n\n      html_filters.push 'bootstrap/entries_v3', 'bootstrap/clean_html_v3'\n\n      options[:only] = %w(getting-started/ css/ components/ javascript/)\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://getbootstrap.com/docs/versions/', opts)\n      doc.at_css('span:contains(\"Latest\")').parent.content.split(' ').first\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/bottle.rb",
    "content": "module Docs\n  class Bottle < UrlScraper\n    self.type = 'sphinx'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://bottlepy.org/',\n      code: 'https://github.com/bottlepy/bottle'\n    }\n\n    html_filters.push 'bottle/entries', 'sphinx/clean_html'\n\n    options[:container] = '.body'\n\n    options[:skip] = %w(changelog.html development.html _modules/bottle.html)\n\n    options[:attribution] = <<-HTML\n      &copy; 2009&ndash;2017 Marcel Hellkamp<br>\n      Licensed under the MIT License.\n    HTML\n\n    version '0.12' do\n      self.release = '0.12.13'\n      self.base_url = \"https://bottlepy.org/docs/#{self.version}/\"\n    end\n\n    version '0.11' do\n      self.release = '0.11.7'\n      self.base_url = \"https://bottlepy.org/docs/#{self.version}/\"\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://bottlepy.org/docs/stable/', opts)\n      label = doc.at_css('.sphinxsidebarwrapper > ul > li > b')\n      label.content.sub(/Bottle /, '')\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/bower.rb",
    "content": "module Docs\n  class Bower < UrlScraper\n    self.name = 'Bower'\n    self.type = 'simple'\n    self.release = '1.8.4'\n    self.base_url = 'https://bower.io/docs/'\n    self.root_path = 'api'\n    self.links = {\n      home: 'https://bower.io/',\n      code: 'https://github.com/bower/bower'\n    }\n\n    html_filters.push 'bower/clean_html', 'bower/entries'\n\n    options[:trailing_slash] = false\n    options[:skip] = %w(tools about)\n\n    options[:attribution] = <<-HTML\n      &copy; 2018 Bower contributors<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version('bower', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/bun.rb",
    "content": "module Docs\n  class Bun < UrlScraper\n    self.name = 'Bun'\n    self.type = 'simple'\n    self.slug = 'bun'\n    self.links = {\n      home: 'https://leafletjs.com/',\n      code: 'https://github.com/oven-sh/bun'\n    }\n    self.release = '1.3.10'\n    self.base_url = \"https://bun.com/docs/\"\n    self.root_path = 'installation'\n\n    html_filters.push 'bun/clean_html', 'bun/entries'\n\n    # https://bun.com/docs/project/licensing\n    options[:attribution] = <<-HTML\n      &copy; bun.com, oven-sh, Jarred Sumner<br>\n      Licensed under the MIT License.\n    HTML\n\n    options[:download_images] = false\n    options[:skip_patterns] = [/^project/, /^feedback/]\n    options[:fix_urls] = ->(url) do\n      url.sub! %r{.md$}, ''\n      url\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('oven-sh', 'bun', opts)[5..]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/cakephp.rb",
    "content": "module Docs\n  class Cakephp < UrlScraper\n    self.name = 'CakePHP'\n    self.type = 'cakephp'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://cakephp.org/',\n      code: 'https://github.com/cakephp/cakephp'\n    }\n\n    options[:skip_patterns] = [/\\Asource-/, /\\Anamespace-Cake.html/]\n\n    options[:attribution] = <<-HTML\n      &copy; 2005&ndash;present The Cake Software Foundation, Inc.<br>\n      Licensed under the MIT License.<br>\n      CakePHP is a registered trademark of Cake Software Foundation, Inc.<br>\n      We are not endorsed by or affiliated with CakePHP.\n    HTML\n\n    version '4.4' do\n      self.release = '4.4.6'\n      self.base_url = \"https://api.cakephp.org/#{self.version}/\"\n\n      html_filters.push 'cakephp/clean_html_39_plus', 'cakephp/entries_39_plus'\n\n      options[:container] = '.page-container'\n    end\n\n    version '4.3' do\n      self.release = '4.3.10'\n      self.base_url = \"https://api.cakephp.org/#{self.version}/\"\n\n      html_filters.push 'cakephp/clean_html_39_plus', 'cakephp/entries_39_plus'\n\n      options[:container] = '.page-container'\n    end\n\n    version '4.2' do\n      self.release = '4.2.10'\n      self.base_url = \"https://api.cakephp.org/#{self.version}/\"\n\n      html_filters.push 'cakephp/clean_html_39_plus', 'cakephp/entries_39_plus'\n\n      options[:container] = '.page-container'\n    end\n\n    version '4.1' do\n      self.release = '4.1.6'\n      self.base_url = \"https://api.cakephp.org/#{self.version}/\"\n\n      html_filters.push 'cakephp/clean_html_39_plus', 'cakephp/entries_39_plus'\n\n      options[:container] = '.page-container'\n    end\n\n    version '4.0' do\n      self.release = '4.0.8'\n      self.base_url = \"https://api.cakephp.org/#{self.version}/\"\n\n      html_filters.push 'cakephp/clean_html_39_plus', 'cakephp/entries_39_plus'\n\n      options[:container] = '.page-container'\n    end\n\n    version '3.9' do\n      self.release = '3.9.4'\n      self.base_url = \"https://api.cakephp.org/#{self.version}/\"\n\n      html_filters.push 'cakephp/clean_html_39_plus', 'cakephp/entries_39_plus'\n\n      options[:container] = '.page-container'\n    end\n\n    version '3.8' do\n      self.release = '3.8.3'\n      self.base_url = \"https://api.cakephp.org/#{self.version}/\"\n\n      html_filters.push 'cakephp/clean_html', 'cakephp/entries'\n\n      options[:container] = '#right'\n    end\n\n    version '3.7' do\n      self.release = '3.7.9'\n      self.base_url = \"https://api.cakephp.org/#{self.version}/\"\n\n      html_filters.push 'cakephp/clean_html', 'cakephp/entries'\n\n      options[:container] = '#right'\n    end\n\n    version '3.6' do\n      self.release = '3.6.15'\n      self.base_url = \"https://api.cakephp.org/#{self.version}/\"\n\n      html_filters.push 'cakephp/clean_html', 'cakephp/entries'\n\n      options[:container] = '#right'\n    end\n\n    version '3.5' do\n      self.release = '3.5.15'\n      self.base_url = \"https://api.cakephp.org/#{self.version}/\"\n\n      html_filters.push 'cakephp/clean_html', 'cakephp/entries'\n\n      options[:container] = '#right'\n    end\n\n    version '3.4' do\n      self.release = '3.4.13'\n      self.base_url = \"https://api.cakephp.org/#{self.version}/\"\n\n      html_filters.push 'cakephp/clean_html', 'cakephp/entries'\n\n      options[:container] = '#right'\n    end\n\n    version '3.3' do\n      self.release = '3.3.15'\n      self.base_url = \"https://api.cakephp.org/#{self.version}/\"\n\n      html_filters.push 'cakephp/clean_html', 'cakephp/entries'\n\n      options[:container] = '#right'\n    end\n\n    version '3.2' do\n      self.release = '3.2.14'\n      self.base_url = \"https://api.cakephp.org/#{self.version}/\"\n\n      html_filters.push 'cakephp/clean_html', 'cakephp/entries'\n\n      options[:container] = '#right'\n    end\n\n    version '3.1' do\n      self.release = '3.1.13'\n      self.base_url = \"https://api.cakephp.org/#{self.version}/\"\n\n      html_filters.push 'cakephp/clean_html', 'cakephp/entries'\n\n      options[:container] = '#right'\n    end\n\n    version '2.10' do\n      self.release = '2.10.3'\n      self.base_url = \"https://api.cakephp.org/#{self.version}/\"\n\n      html_filters.push 'cakephp/clean_html', 'cakephp/entries'\n\n      options[:container] = '#right'\n    end\n\n    version '2.9' do\n      self.release = '2.9.4'\n      self.base_url = \"https://api.cakephp.org/#{self.version}/\"\n\n      html_filters.push 'cakephp/clean_html', 'cakephp/entries'\n\n      options[:container] = '#right'\n    end\n\n    version '2.8' do\n      self.release = '2.8.8'\n      self.base_url = \"https://api.cakephp.org/#{self.version}/\"\n\n      html_filters.push 'cakephp/clean_html', 'cakephp/entries'\n\n      options[:container] = '#right'\n    end\n\n    version '2.7' do\n      self.release = '2.7.11'\n      self.base_url = \"https://api.cakephp.org/#{self.version}/\"\n\n      html_filters.push 'cakephp/clean_html', 'cakephp/entries'\n\n      options[:container] = '#right'\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('cakephp', 'cakephp', opts)\n    end\n\n    private\n\n    def parse(response)\n      response.body.gsub! '<h5 class=\"method-name\">', '<h3 class=\"method-name\">'\n      super\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/chai.rb",
    "content": "module Docs\n  class Chai < UrlScraper\n    self.name = 'Chai'\n    self.type = 'simple'\n    self.release = '4.3.4'\n    self.base_url = 'https://www.chaijs.com'\n    self.root_path = '/api/'\n    self.initial_paths = %w(/guide/installation/)\n    self.links = {\n      home: 'https://www.chaijs.com/',\n      code: 'https://github.com/chaijs/chai'\n    }\n\n    html_filters.push 'chai/entries', 'chai/clean_html'\n\n    options[:container] = '#content'\n    options[:trailing_slash] = true\n\n    options[:only_patterns] = [/\\A\\/guide/, /\\A\\/api/]\n    options[:skip] = %w(/api/test/ /guide/ /guide/resources/)\n\n    options[:attribution] = <<-HTML\n      &copy; 2017 Chai.js Assertion Library<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version('chai', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/chef.rb",
    "content": "module Docs\n  class Chef < UrlScraper\n    self.type = 'sphinx_simple'\n    self.base_url = 'https://docs.chef.io'\n    self.links = {\n      home: 'https://www.chef.io/',\n      code: 'https://github.com/chef/chef'\n    }\n\n    options[:skip_patterns] = [\n      /release_notes/,\n      /feedback/\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; Chef Software, Inc.<br>\n      Licensed under the Creative Commons Attribution 3.0 Unported License.<br>\n      The Chef&trade; Mark and Chef Logo are either registered trademarks/service marks or trademarks/service\n      marks of Chef, in the United States and other countries and are used with Chef Inc's permission.<br>\n      We are not affiliated with, endorsed or sponsored by Chef Inc.\n    HTML\n\n    version '18' do\n      self.release = '18.0.15'\n\n      options[:container] = '.off-canvas-wrapper'\n\n      options[:skip] = [\n        '/automate/api/',\n        '/habitat/supervisor_api/',\n        '/habitat/builder_api/'\n      ]\n\n      html_filters.push 'chef/entries', 'chef/clean_html'\n\n    end\n\n    version '17' do\n      self.release = '17.9.18'\n\n      options[:container] = '.off-canvas-wrapper'\n\n      options[:skip] = [\n        '/automate/api/',\n        '/habitat/supervisor_api/',\n        '/habitat/builder_api/'\n      ]\n\n      html_filters.push 'chef/entries', 'chef/clean_html'\n\n    end\n\n    version '16' do\n      self.release = '16.8.14'\n\n      options[:container] = '.off-canvas-wrapper'\n\n      options[:skip] = [\n        '/automate/api/',\n        '/habitat/supervisor_api/',\n        '/habitat/builder_api/'\n      ]\n\n      html_filters.push 'chef/entries', 'chef/clean_html'\n\n    end\n\n    version '12' do\n      self.release = '12.13'\n      self.base_url = 'https://docs-archive.chef.io/release/'\n\n      html_filters.push 'chef/entries_old', 'chef/clean_html_old'\n\n      options[:client_path] = client_path = '12-13'\n      options[:server_path] = server_path = 'server_12-8'\n\n      options[:skip_patterns] = [\n        /\\A[^\\/]+\\/\\z/,\n        /\\A[^\\/]+\\/index\\.html\\z/,\n        /\\A[^\\/]+\\/release_notes\\.html\\z/,\n        /\\Aserver[^\\/]+\\/chef_overview\\.html\\z/,\n        /\\A[\\d\\-]+\\/server_components\\.html\\z/\n      ]\n\n      self.root_path = \"#{client_path}/chef_overview.html\"\n      self.initial_paths = [\"#{server_path}/server_components.html\"]\n\n      options[:only_patterns] = [/\\A#{client_path}\\//, /\\A#{server_path}\\//]\n    end\n\n    version '11' do\n      self.release = '11.18'\n      self.base_url = 'https://docs-archive.chef.io/release/'\n\n      html_filters.push 'chef/entries_old', 'chef/clean_html_old'\n\n      options[:client_path] = client_path = '11-18'\n      options[:server_path] = server_path = 'server_12-8'\n\n      options[:skip_patterns] = [\n        /\\A[^\\/]+\\/\\z/,\n        /\\A[^\\/]+\\/index\\.html\\z/,\n        /\\A[^\\/]+\\/release_notes\\.html\\z/,\n        /\\Aserver[^\\/]+\\/chef_overview\\.html\\z/,\n        /\\A[\\d\\-]+\\/server_components\\.html\\z/\n      ]\n\n      self.root_path = \"#{client_path}/chef_overview.html\"\n      self.initial_paths = [\"#{server_path}/server_components.html\"]\n\n      options[:only_patterns] = [/\\A#{client_path}\\//, /\\A#{server_path}\\//]\n    end\n\n    def get_latest_version(opts)\n      tags = get_github_tags('chef', 'chef', opts)\n      tags[0]['name'][1..-1]\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/click.rb",
    "content": "module Docs\n  class Click < UrlScraper\n    self.name = 'click'\n    self.type = 'sphinx'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://click.palletsprojects.com/',\n      code: 'https://github.com/pallets/click'\n    }\n\n    html_filters.push 'click/pre_clean_html', 'click/entries', 'sphinx/clean_html'\n\n    options[:container] = '.body > section'\n    options[:skip] = ['changes/', 'genindex/', 'py-modindex/']\n    options[:title] = false\n\n    options[:attribution] = <<-HTML\n      &copy; Copyright 2014 Pallets.<br>\n      Licensed under the BSD 3-Clause License.<br>\n      We are not supported nor endorsed by Pallets.\n    HTML\n\n    self.release = '8.1.x'\n    self.base_url = \"https://click.palletsprojects.com/en/#{self.release}/\"\n\n    def get_latest_version(opts)\n      get_latest_github_release('pallets', 'click', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/clojure.rb",
    "content": "module Docs\n  class Clojure < UrlScraper\n    self.type = 'clojure'\n    self.root_path = 'api-index.html'\n    self.links = {\n      home: 'https://clojure.org',\n      code: 'https://github.com/clojure/clojure'\n    }\n\n    html_filters.push 'clojure/entries', 'clojure/clean_html'\n\n    options[:container] = '#content_view'\n    options[:only_patterns] = [/\\Aclojure\\./]\n\n    options[:attribution] = <<-HTML\n      &copy; Rich Hickey<br>\n      Licensed under the Eclipse Public License 1.0.\n    HTML\n\n    version '1.11' do\n      self.release = '1.11'\n      self.base_url = 'https://clojure.github.io/clojure/'\n    end\n\n    version '1.10' do\n      self.release = '1.10.3'\n      self.base_url = \"https://clojure.github.io/clojure/branch-clojure-#{self.release}/\"\n    end\n\n    version '1.9' do\n      self.release = '1.9 (legacy)'\n      self.base_url = 'https://clojure.github.io/clojure/branch-clojure-1.9.0/'\n    end\n\n    version '1.8' do\n      self.release = '1.8 (legacy)'\n      self.base_url = 'https://clojure.github.io/clojure/branch-clojure-1.8.0/'\n    end\n\n    version '1.7' do\n      self.release = '1.7 (legacy)'\n      self.base_url = 'https://clojure.github.io/clojure/branch-clojure-1.7.0/'\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('http://clojure.github.io/clojure/index.html', opts)\n      doc.at_css('#header-version').content[1..-1]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/cmake.rb",
    "content": "module Docs\n  class Cmake < UrlScraper\n    self.name = 'CMake'\n    self.type = 'sphinx_simple'\n    self.links = {\n      home: 'https://cmake.org/',\n      code: 'https://gitlab.kitware.com/cmake/cmake/'\n    }\n\n    html_filters.push 'cmake/clean_html', 'sphinx/clean_html', 'cmake/entries', 'title'\n\n    options[:container] = '.body'\n    options[:title] = false\n    options[:root_title] = 'CMake Reference Documentation'\n    options[:skip] = %w(release/index.html genindex.html search.html)\n    options[:skip_patterns] = [/\\Agenerator/, /\\Acpack_gen/, /\\Ainclude/, /\\Arelease/, /tutorial\\/(\\w*%20)+/]\n\n    options[:attribution] = <<-HTML\n      &copy; 2000&ndash;2024 Kitware, Inc. and Contributors<br>\n      Licensed under the BSD 3-clause License.\n    HTML\n\n    version do\n      self.base_url = \"https://cmake.org/cmake/help/latest/\"\n    end\n\n    version '3.31' do\n      self.release = '3.31'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.30' do\n      self.release = '3.30'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.29' do\n      self.release = '3.29'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.28' do\n      self.release = '3.28'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.27' do\n      self.release = '3.27'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.26' do\n      self.release = '3.26'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.25' do\n      self.release = '3.25'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.24' do\n      self.release = '3.24'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.23' do\n      self.release = '3.23'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.22' do\n      self.release = '3.22'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.21' do\n      self.release = '3.21'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.20' do\n      self.release = '3.20'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.19' do\n      self.release = '3.19.0'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.18' do\n      self.release = '3.18.4'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.17' do\n      self.release = '3.17.5'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.16' do\n      self.release = '3.16.9'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.15' do\n      self.release = '3.15.7'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.14' do\n      self.release = '3.14.7'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.13' do\n      self.release = '3.13.5'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.12' do\n      self.release = '3.12.4'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.11' do\n      self.release = '3.11.4'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.10' do\n      self.release = '3.10.3'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.9' do\n      self.release = '3.9.6'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.8' do\n      self.release = '3.8.2'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.7' do\n      self.release = '3.7.2'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.6' do\n      self.release = '3.6.3'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    version '3.5' do\n      self.release = '3.5.2'\n      self.base_url = \"https://cmake.org/cmake/help/v#{self.version}/\"\n    end\n\n    def get_latest_version(opts)\n      tags = get_gitlab_tags('gitlab.kitware.com', 'cmake', 'cmake', opts)\n      tags[0]['name'][1..]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/codeception.rb",
    "content": "module Docs\n  class Codeception < UrlScraper\n    self.name = 'Codeception'\n    self.type = 'codeception'\n    self.release = '4.1.22'\n    self.base_url = 'https://codeception.com/docs/'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://codeception.com/',\n      code: 'https://github.com/Codeception/Codeception'\n    }\n\n    html_filters.push 'codeception/entries', 'codeception/clean_html'\n\n    options[:skip_patterns] = [/install/]\n\n    options[:attribution] = <<-HTML\n      &copy; 2011 Michael Bodnarchuk and contributors<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_github_tags(\"Codeception\", \"Codeception\", opts)[1][\"name\"]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/codeceptjs.rb",
    "content": "module Docs\n  class Codeceptjs < UrlScraper\n    self.name = 'CodeceptJS'\n    self.type = 'simple'\n    self.root_path = 'index.html'\n    self.release = '3.0.2'\n    self.base_url = 'https://codecept.io/'\n    self.links = {\n      home: 'https://codecept.io/',\n      code: 'https://github.com/codeception/codeceptjs'\n    }\n\n    html_filters.push 'codeceptjs/clean_html', 'codeceptjs/entries', 'title'\n\n    options[:root_title] = 'CodeceptJS'\n    options[:title] = false\n    options[:skip_links] = ->(filter) { !filter.root_page? }\n    options[:skip_patterns] = [/changelog/, /quickstart\\z/]\n\n    options[:fix_urls] = -> (url) do\n      url.sub! %r{\\.html\\z}, ''\n      url.sub! %r{custom-helpers\\z}, 'helpers'\n      url\n    end\n\n    options[:trailing_slash] = true\n\n    options[:attribution] = <<-HTML\n      &copy; 2015 DavertMik &lt;davert@codegyre.com&gt; (http://codegyre.com)<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version('codeceptjs', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/codeigniter.rb",
    "content": "module Docs\n  class Codeigniter < UrlScraper\n    self.name = 'CodeIgniter'\n    self.type = 'sphinx'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://codeigniter.com/',\n      code: 'https://github.com/bcit-ci/CodeIgniter'\n    }\n\n    html_filters.push 'codeigniter/entries', 'sphinx/clean_html'\n\n    options[:skip] = %w(\n      license.html\n      changelog.html\n      DCO.html\n      general/welcome.html\n      general/requirements.html\n      general/credits.html\n      libraries/index.html\n      helpers/index.html\n    )\n\n    options[:skip_patterns] = [\n      /\\Acontributing/,\n      /\\Adocumentation/,\n      /\\Achangelogs/,\n      /\\Ainstallation\\/upgrade/\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; 2014&ndash;2021 British Columbia Institute of Technology<br>\n      Licensed under the MIT License.\n    HTML\n\n    version '4' do\n      self.release = '4.1.5'\n      self.base_url = 'https://codeigniter.com/user_guide/'\n\n      options[:container] = '.document > div'\n    end\n\n    version '3' do\n      self.release = '3.1.8'\n      self.base_url = 'https://codeigniter.com/userguide3/'\n\n      options[:container] = '.document'\n    end\n\n    def get_latest_version(opts)\n      tags = get_github_tags('codeigniter4', 'codeigniter4', opts)\n      tags[0]['name'][1..-1]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/coffeescript.rb",
    "content": "module Docs\n  class Coffeescript < UrlScraper\n    self.name = 'CoffeeScript'\n    self.type = 'coffeescript'\n    self.links = {\n      home: 'https://coffeescript.org',\n      code: 'https://github.com/jashkenas/coffeescript'\n    }\n\n    options[:title] = 'CoffeeScript'\n    options[:skip_links] = true\n\n    options[:attribution] = <<-HTML\n      &copy; 2009&ndash;2022 Jeremy Ashkenas<br>\n      Licensed under the MIT License.\n    HTML\n\n    version '2' do\n      self.release = '2.7.0'\n      self.base_url = 'https://coffeescript.org/'\n\n      html_filters.push 'coffeescript/entries', 'coffeescript/clean_html', 'title'\n    end\n\n    version '1' do\n      self.release = '1.12.7'\n      self.base_url = 'https://coffeescript.org/v1/'\n\n      html_filters.push 'coffeescript/clean_html_v1', 'coffeescript/entries_v1', 'title'\n\n      options[:container] = '.container'\n    end\n\n    def get_latest_version(opts)\n      get_npm_version('coffeescript', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/composer.rb",
    "content": "module Docs\n  class Composer < UrlScraper\n    self.type = 'simple'\n    self.release = '2.4.1'\n    self.base_url = 'https://getcomposer.org/doc/'\n    self.links = {\n      home: 'https://getcomposer.org',\n      code: 'https://github.com/composer/composer'\n    }\n\n    html_filters.push 'composer/clean_html', 'composer/entries'\n\n    options[:container] = '#main'\n\n    options[:skip_patterns] = [\n      /^faqs/\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; Nils Adermann, Jordi Boggiano<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('composer', 'composer', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/cordova.rb",
    "content": "module Docs\n  class Cordova < UrlScraper\n    self.name = 'Cordova'\n    self.type = 'cordova'\n    self.root_path = 'guide/overview/index.html'\n    self.links = {\n      home: 'https://cordova.apache.org/',\n      code: 'https://github.com/apache/cordova'\n    }\n\n    html_filters.replace 'clean_html', 'cordova/clean_html_core'\n    html_filters.push 'cordova/entries', 'cordova/clean_html'\n\n    options[:container] = '.docs'\n    options[:skip] = %w(index.html)\n\n    options[:fix_urls] = ->(url) do\n      if url.include?('https://cordova.apache.org/docs') && !url.end_with?('.html')\n        if url.end_with?('/')\n          url << 'index.html'\n        else\n          url << '/index.html'\n        end\n      end\n    end\n\n    options[:attribution] = <<-HTML\n      &copy; 2012, 2013, 2015 The Apache Software Foundation<br>\n      Licensed under the Apache License 2.0.\n    HTML\n\n    version '9' do\n      self.release = '9.0.0'\n      self.base_url = \"https://cordova.apache.org/docs/en/#{self.version}.x/\"\n    end\n\n    version '8' do\n      self.release = '8.1.2'\n      self.base_url = \"https://cordova.apache.org/docs/en/#{self.version}.x/\"\n    end\n\n    version '7' do\n      self.release = '7.1.0'\n      self.base_url = \"https://cordova.apache.org/docs/en/#{self.version}.x/\"\n    end\n\n    version '6' do\n      self.release = '6.5.0'\n      self.base_url = \"https://cordova.apache.org/docs/en/#{self.version}.x/\"\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://cordova.apache.org/docs/en/latest/', opts)\n\n      label = doc.at_css('#versionDropdown').content.strip\n      version = label.scan(/([0-9.]+)/)[0][0]\n      version = version[0...-1] if version.end_with?('.')\n\n      version\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/couchdb.rb",
    "content": "module Docs\n  class Couchdb < UrlScraper\n    self.name = 'CouchDB'\n    self.type = 'couchdb'\n    self.root_path = 'index.html'\n    \n    self.links = {\n      home: 'https://couchdb.apache.org/',\n      code: 'https://github.com/apache/couchdb'\n    }\n\n    html_filters.push 'couchdb/clean_html', 'couchdb/entries'\n\n    options[:container] = 'div[itemprop=articleBody]'\n    options[:only_patterns] = [\n      /api\\//,\n      /cluster\\//,\n      /ddocs\\//,\n      /replication\\//,\n      /maintenance\\//,\n      /partitioned-dbs\\//,\n      /json\\-structure*/\n    ]\n    options[:rate_limit] = 50 # Docs are subject to Cloudflare limiters.\n    options[:attribution] = <<-HTML\n      Copyright &copy; 2025 The Apache Software Foundation &mdash; Licensed under the Apache License 2.0\n    HTML\n\n    version '3.5' do\n      self.release = '3.5.1'\n      self.base_url = \"https://docs.couchdb.org/en/#{self.release}\"\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://couchdb.apache.org/', opts)\n      doc.at_css('.download-pane > h2').content.split(' ').last\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/cppref/c.rb",
    "content": "module Docs\n  class C < Cppref\n    self.name = 'C'\n    self.slug = 'c'\n    self.base_url = 'https://en.cppreference.com/w/c/'\n    # release = '2023-03-24'\n\n    html_filters.insert_before 'cppref/clean_html', 'c/entries'\n\n    options[:root_title] = 'C Programming Language'\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/cppref/cpp.rb",
    "content": "module Docs\n  class Cpp < Cppref\n    self.name = 'C++'\n    self.slug = 'cpp'\n    self.base_url = 'https://en.cppreference.com/w/cpp/'\n    # release = '2023-03-24'\n\n    html_filters.insert_before 'cppref/clean_html', 'cpp/entries'\n\n    options[:root_title] = 'C++ Programming Language'\n\n    options[:skip] = %w(\n      language/extending_std.html\n      language/history.html\n      regex/ecmascript.html\n      regex/regex_token_iterator/operator_cmp.html\n    )\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/cppref/cppref.rb",
    "content": "module Docs\n  class Cppref < UrlScraper\n    self.abstract = true\n    self.type = 'cppref'\n    self.root_path = 'header'\n\n    html_filters.insert_before 'clean_html', 'cppref/fix_code'\n    html_filters.push  'cppref/clean_html', 'title'\n\n    options[:decode_and_clean_paths] = true\n    options[:container] = '#content'\n    options[:title] = false\n    options[:skip] = %w(language/history.html)\n\n    options[:skip_patterns] = [\n      /experimental/\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; cppreference.com<br>\n      Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.\n    HTML\n\n    # Check if the 'headers' page has changed\n    def get_latest_version(opts)\n      doc = fetch_doc((self.base_url + self.root_path).to_s, opts)\n      date = doc.at_css('#footer-info-lastmod').content\n      date = date.match(/[[:digit:]]{1,2} .* [[:digit:]]{4}/).to_s\n      date = DateTime.strptime(date, '%e %B %Y').to_time.to_i\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/crystal.rb",
    "content": "module Docs\n  class Crystal < UrlScraper\n    include MultipleBaseUrls\n    self.type = 'crystal'\n    self.release = '1.19.0'\n    self.base_urls = [\n      \"https://crystal-lang.org/api/#{release}/\",\n      \"https://crystal-lang.org/reference/#{release[0..2]}/\",\n    ]\n    def initial_urls\n      [ \"https://crystal-lang.org/api/#{self.class.release}/index.html\",\n        \"https://crystal-lang.org/reference/#{self.class.release[0..2]}/index.html\" ]\n    end\n\n    self.links = {\n      home: 'https://crystal-lang.org/',\n      code: 'https://github.com/crystal-lang/crystal'\n    }\n\n    html_filters.push 'crystal/entries', 'crystal/clean_html'\n\n    options[:skip_patterns] = [\n      %r{\\ALibLLVM\\.html\\z},\n      %r{\\ACrystal/System(/|\\.html\\z)},\n      %r{\\ACrystal/PointerPairingHeap/},\n      %r{\\AFiber/ExecutionContext/Scheduler.html\\z},\n      %r{\\AIO/Evented.html\\z},\n      %r{\\ARegex/PCRE2.html\\z}\n    ]\n\n    options[:attribution] = ->(filter) {\n      if filter.current_url.path.start_with?('/reference/')\n        <<-HTML\n          To the extent possible under law, the persons who contributed to this work\n          have waived<br>all copyright and related or neighboring rights to this work\n          by associating CC0 with it.\n        HTML\n      else\n        <<-HTML\n          &copy; 2012&ndash;2026 Manas Technology Solutions.<br>\n          Licensed under the Apache License, Version 2.0.\n        HTML\n      end\n    }\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://crystal-lang.org/', opts)\n      doc.at_css('.latest-release-info > a > strong').content.scan(/([0-9.]+)/)[0][0]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/cypress.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class Cypress < UrlScraper\n    self.name = 'Cypress'\n    self.type = 'cypress'\n    self.release = '9.4.1'\n    self.base_url = 'https://docs.cypress.io'\n    self.root_path = '/api/table-of-contents.html'\n    self.links = {\n      home: 'https://www.cypress.io/',\n      code: 'https://github.com/cypress-io/cypress',\n    }\n\n    html_filters.push 'cypress/entries', 'cypress/clean_html'\n\n    options[:container] = 'article'\n    options[:max_image_size] = 300_000\n    options[:include_default_entry] = true\n\n    options[:skip_patterns] = [\n      /examples\\//,\n      /guides/\n    ]\n\n    options[:skip_link] = ->(link) {\n      href = link.attr(:href)\n      href.nil? ? true : EntriesFilter::SECTIONS.none? { |section| href.match?(\"/#{section}/\") }\n    }\n\n    options[:attribution] = <<-HTML\n      &copy; 2017 Cypress.io<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('cypress-io', 'cypress', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/d.rb",
    "content": "module Docs\n  class D < UrlScraper\n    include MultipleBaseUrls\n\n    self.release = '2.095.1'\n    self.type = 'd'\n    self.base_urls = ['https://dlang.org/phobos/', 'https://dlang.org/spec/']\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://dlang.org/',\n      code: 'https://github.com/dlang/phobos'\n    }\n\n    html_filters.push 'd/entries', 'd/clean_html'\n\n    options[:skip] = %w(spec.html)\n    options[:container] = '.container'\n    options[:root_title] = 'D Programming Language'\n    options[:title] = false\n\n    options[:attribution] = <<-HTML\n      &copy; 1999&ndash;2021 The D Language Foundation<br>\n      Licensed under the Boost License 1.0.\n    HTML\n\n    def initial_urls\n      %w(https://dlang.org/phobos/index.html https://dlang.org/spec/intro.html)\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://dlang.org/changelog/', opts)\n      doc.at_css('#content > ul > li:nth-child(2) > a')['id']\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/d3.rb",
    "content": "module Docs\n  class D3 < UrlScraper\n    self.name = 'D3.js'\n    self.slug = 'd3'\n    self.type = 'd3'\n    self.links = {\n      home: 'https://d3js.org/',\n      code: 'https://github.com/d3/d3'\n    }\n\n    options[:max_image_size] = 150_000\n    options[:container] = '.markdown-body'\n\n    options[:attribution] = <<-HTML\n      &copy; 2010&ndash;2023 Michael Bostock<br>\n      Licensed under the BSD License.\n    HTML\n\n    version '7' do\n      self.release = '7.8.1'\n      self.base_url = 'https://github.com/d3/'\n      self.root_path = 'd3/blob/master/API.md'\n\n      html_filters.push 'd3/clean_html', 'd3/entries_v4'\n\n      options[:only_patterns] = [/\\Ad3[\\-\\w]+\\z/, /\\Ad3\\/blob\\/master\\/changes\\.md\\z/i]\n      options[:skip_patterns] = [/3\\.x-api-reference/]\n\n      options[:fix_urls] = ->(url) do\n        url.sub! %r{/blob/master/readme.md}i, ''\n        url\n      end\n    end\n\n    version '6' do\n      self.release = '6.7.0'\n      self.base_url = 'https://github.com/d3/'\n      self.root_path = 'd3/blob/master/API.md'\n\n      html_filters.push 'd3/clean_html', 'd3/entries_v4'\n\n      options[:only_patterns] = [/\\Ad3[\\-\\w]+\\z/, /\\Ad3\\/blob\\/master\\/changes\\.md\\z/i]\n      options[:skip_patterns] = [/3\\.x-api-reference/]\n\n      options[:fix_urls] = ->(url) do\n        url.sub! %r{/blob/master/readme.md}i, ''\n        url\n      end\n    end\n\n    version '5' do\n      self.release = '5.7.0'\n      self.base_url = 'https://github.com/d3/'\n      self.root_path = 'd3/blob/master/API.md'\n\n      html_filters.push 'd3/clean_html', 'd3/entries_v4'\n\n      options[:only_patterns] = [/\\Ad3[\\-\\w]+\\z/, /\\Ad3\\/blob\\/master\\/changes\\.md\\z/i]\n      options[:skip_patterns] = [/3\\.x-api-reference/]\n\n      options[:fix_urls] = ->(url) do\n        url.sub! %r{/blob/master/readme.md}i, ''\n        url\n      end\n    end\n\n    version '4' do\n      self.release = '4.12.2'\n      self.base_url = 'https://github.com/d3/'\n      self.root_path = 'd3/blob/master/API.md'\n\n      html_filters.push 'd3/clean_html', 'd3/entries_v4'\n\n      options[:only_patterns] = [/\\Ad3[\\-\\w]+\\z/, /\\Ad3\\/blob\\/master\\/changes\\.md\\z/i]\n      options[:skip_patterns] = [/3\\.x-api-reference/]\n\n      options[:fix_urls] = ->(url) do\n        url.sub! %r{/blob/master/readme.md}i, ''\n        url\n      end\n    end\n\n    version '3' do\n      self.release = '3.5.17'\n      self.base_url = 'https://github.com/d3/d3-3.x-api-reference/blob/master/'\n      self.root_path = 'API-Reference.md'\n\n      html_filters.push 'd3/clean_html', 'd3/entries_v3', 'title'\n\n      options[:root_title] = 'D3.js'\n      options[:only_patterns] = [/\\.md\\z/]\n    end\n\n    def get_latest_version(opts)\n      get_npm_version('d3', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/dart.rb",
    "content": "module Docs\n  class Dart < FileScraper\n    self.type = 'dart'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://dart.dev/',\n      code: 'https://github.com/dart-lang/sdk'\n    }\n\n    html_filters.push 'dart/entries', 'dart/clean_html'\n\n    options[:fix_urls] = ->(url) do\n      # localhost/dart-web_audio/..dart-io/dart-io-library.html > localhost/dart-io/dart-io-library.html\n      url.remove!(/[^\\/]+\\/\\.\\./)\n      url\n    end\n\n    options[:attribution] = <<-HTML\n      &copy; 2012 the Dart project authors<br>\n      Licensed under the BSD 3-Clause \"New\" or \"Revised\" License.\n    HTML\n\n    version '2' do\n      self.release = '2.18.5'\n      self.base_url = \"https://api.dart.dev/stable/#{release}/\"\n    end\n\n    version '1' do\n      self.release = '1.24.3'\n      self.base_url = \"https://api.dart.dev/stable/#{release}/\"\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://api.dart.dev/', opts)\n      label = doc.at_css('footer > span').content.strip\n      label.sub(/Dart\\s*/, '')\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/date_fns.rb",
    "content": "module Docs\n  class DateFns < FileScraper\n    self.name = 'date-fns'\n    self.slug = 'date_fns'\n    self.type = 'simple'\n    self.links = {\n      home: 'https://date-fns.org/',\n      code: 'https://github.com/date-fns/date-fns'\n    }\n    self.release = '2.29.2'\n    self.base_url = \"https://date-fns.org/v#{self.release}/docs/\"\n\n    # https://github.com/date-fns/date-fns/blob/main/LICENSE.md\n    options[:attribution] = <<-HTML\n      &copy; 2021 Sasha Koss and Lesha Koss<br>\n      Licensed under the MIT License.\n    HTML\n\n    def build_pages\n      markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, autolink: true, fenced_code_blocks: true, tables: true)\n\n      md_files = %w(esm.md fp.md gettingStarted.md i18n.md i18nContributionGuide.md release.md timeZones.md unicodeTokens.md upgradeGuide.md webpack.md)\n      md_files.each do |md_file|\n        md_string = request_one(\"docs/#{md_file}\").body\n        md_file = 'index.md' if md_file == 'gettingStarted.md'\n        name = md_string.match(/^#([^\\n]+)/)[1]\n        path = md_file.sub '.md', ''\n        page = {\n          path: path,\n          store_path: \"#{path}.html\",\n          output: markdown.render(md_string),\n          entries: [Entry.new(name, path, 'General')]\n        }\n        yield page\n      end\n\n      docs = JSON.parse(request_one('tmp/docs.json').body)\n      docs.each do |type, features|\n        features.each do |feature|\n          name = feature['title']\n          feature_id = feature['urlId']\n          next if feature_id.start_with?('fp/')\n          next if feature['type'] != 'jsdoc'\n          # fix description table on https://date-fns.org/v2.29.2/docs/parse\n          feature['content']['description'].sub! \"\\n| Unit\", \"\\n\\n| Unit\"\n          feature['content']['description'].sub! \"\\nNotes:\\n\", \"\\n\\nNotes:\\n\"\n          page = {\n            path: name,\n            store_path: \"#{feature_id}.html\",\n            output: ERB.new(PAGE_ERB).result(binding),\n            entries: [Entry.new(name, feature_id, type)]\n          }\n          yield page\n        end\n      end\n    end\n\n    PAGE_ERB = <<-HTML.strip_heredoc\n      <h1><%= feature['title'] %></h1>\n      <p><%= feature['description'] %></p>\n\n      <h2>Description</h2>\n      <p><%= markdown.render feature['content']['description'] %></p>\n\n      <% if feature['usage'] %>\n      <h2>Usage</h2>\n      <% feature['usage'].each do |_, usage| %>\n        <pre data-language=\"javascript\"><%= '// ' + usage['title'] + '\\n' %><%= usage['code'] %></pre>\n      <% end %>\n      <% end %>\n\n      <% if feature['syntax'] %>\n      <h2>Syntax</h2>\n      <pre data-language=\"javascript\"><%= feature['syntax'] %></pre>\n      <% end %>\n\n      <% if feature['content']['properties'] %>\n      <h2>Properties</h2>\n      <table>\n        <tr>\n          <th>Name</th>\n          <th>Type</th>\n          <th>Description</th>\n        </tr>\n        <% feature['content']['properties'].each do |param| %>\n          <tr>\n            <td><code><%= param['name'] %></code></td>\n            <td><code><%= param['type']['names'].join ' ' %></code></td>\n            <td><%= markdown.render param['description'] || '' %></td>\n          </tr>\n        <% end %>\n      </table>\n      <% end %>\n\n      <% if feature['content']['params'] %>\n      <h2>Arguments</h2>\n      <table>\n        <tr>\n          <th>Name</th>\n          <th>Description</th>\n        </tr>\n        <% feature['content']['params'].each do |param| %>\n          <tr>\n            <td><code><%= param['name'] %></code></td>\n            <td><%= markdown.render param['description'] || '' %></td>\n          </tr>\n        <% end %>\n      </table>\n      <% end %>\n\n      <% if feature['content']['returns'] %>\n      <h2>Returns</h2>\n      <table>\n        <tr>\n          <th>Description</th>\n        </tr>\n        <% feature['content']['returns'].each do |param| %>\n          <tr>\n            <td><%= markdown.render param['description'] || '' %></td>\n          </tr>\n        <% end %>\n      </table>\n      <% end %>\n\n      <% if feature['content']['exceptions'] %>\n      <h2>Exceptions</h2>\n      <table>\n        <tr>\n          <th>Type</th>\n          <th>Description</th>\n        </tr>\n        <% feature['content']['exceptions'].each do |param| %>\n          <tr>\n            <td><code><%= param['type']['names'].join ' ' %></code></td>\n            <td><%= markdown.render param['description'] || '' %></td>\n          </tr>\n        <% end %>\n      </table>\n      <% end %>\n\n      <% if feature['content']['examples'] %>\n      <h2>Examples</h2>\n      <% feature['content']['examples'].each do |example| %>\n        <pre data-language=\"javascript\"><%= example %></pre>\n      <% end %>\n      <% end %>\n\n      <div class=\"_attribution\">\n        <p class=\"_attribution-p\">\n          <%= options[:attribution] %>\n          <br>\n          <a href=\"<%= self.base_url %><%= feature_id %>\" class=\"_attribution-link\">\n            <%= self.base_url %><%= feature_id %>\n          </a>\n        </p>\n      </div>\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('date-fns', 'date-fns', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/deno.rb",
    "content": "module Docs\n  class Deno < UrlScraper\n    self.name = 'Deno'\n    self.type = 'simple'\n    self.links = {\n      home: 'https://deno.com/',\n      code: 'https://github.com/denoland/deno'\n    }\n\n    # https://github.com/denoland/manual/blob/main/LICENSE\n    # https://github.com/denoland/deno/blob/main/LICENSE.md\n    options[:attribution] = <<-HTML\n      &copy; 2018–2025 the Deno authors<br>\n      Licensed under the MIT License.\n    HTML\n\n\n    html_filters.push 'deno/entries', 'deno/clean_html'\n\n    version '2' do\n      self.release = '2.4.4'\n      self.base_url = 'https://docs.deno.com/'\n      self.root_path = 'runtime'\n      options[:only_patterns] = [/\\Aruntime/, /\\Aapi\\/deno\\/~/, /\\Adeploy/, /\\Asubhosting/]\n      options[:skip_patterns] = [\n        /\\Aruntime\\/manual/,\n        /\\Aapi\\/deno\\/.+\\.prototype\\z/, # all prototype pages get redirected to the main page\n        /\\Aapi\\/deno\\/~\\/Deno\\.jupyter\\.MediaBundle.+/, # docs unavailable\n        /\\Aapi\\/deno\\/~\\/Deno\\.OpMetrics/, # deprecated in deno 2\n      ]\n      options[:trailing_slash] = false\n    end\n\n    version '1' do\n      self.release = '1.27.0'\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('denoland', 'deno', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/django.rb",
    "content": "module Docs\n  class Django < FileScraper\n    self.name = 'Django'\n    self.type = 'sphinx'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://www.djangoproject.com/',\n      code: 'https://github.com/django/django'\n    }\n\n    html_filters.push 'django/entries', 'sphinx/clean_html', 'django/clean_html'\n    text_filters.push 'django/fix_urls'\n\n    options[:container] = '#bd'\n\n    options[:skip] = %w(\n      contents.html\n      genindex.html\n      py-modindex.html\n      glossary.html\n      search.html\n      intro/whatsnext.html)\n\n    options[:skip_patterns] = [\n      /\\Afaq\\//,\n      /\\Ainternals\\//,\n      /\\Amisc\\//,\n      /\\Areleases\\//,\n      /\\A_/,\n      /flattened\\-index\\.html/]\n\n    options[:attribution] = <<-HTML\n      &copy; Django Software Foundation and individual contributors<br>\n      Licensed under the BSD License.\n    HTML\n\n    version '6.0' do\n      self.release = '6.0'\n      self.base_url = \"https://docs.djangoproject.com/en/#{self.version}/\"\n    end\n\n    version '5.2' do\n      self.release = '5.2'\n      self.base_url = \"https://docs.djangoproject.com/en/#{self.version}/\"\n    end\n\n    version '5.1' do\n      self.release = '5.1'\n      self.base_url = \"https://docs.djangoproject.com/en/#{self.version}/\"\n    end\n\n    version '5.0' do\n      self.release = '5.0'\n      self.base_url = \"https://docs.djangoproject.com/en/#{self.version}/\"\n    end\n\n    version '4.2' do\n      self.release = '4.2'\n      self.base_url = \"https://docs.djangoproject.com/en/#{self.version}/\"\n    end\n\n    version '4.1' do\n      self.release = '4.1'\n      self.base_url = \"https://docs.djangoproject.com/en/#{self.version}/\"\n    end\n\n    version '4.0' do\n      self.release = '4.0'\n      self.base_url = \"https://docs.djangoproject.com/en/#{self.version}/\"\n    end\n\n    version '3.2' do\n      self.release = '3.2'\n      self.base_url = \"https://docs.djangoproject.com/en/#{self.version}/\"\n    end\n\n    version '3.1' do\n      self.release = '3.1.4'\n      self.base_url = \"https://docs.djangoproject.com/en/#{self.version}/\"\n    end\n\n    version '3.0' do\n      self.release = '3.0.11'\n      self.base_url = \"https://docs.djangoproject.com/en/#{self.version}/\"\n    end\n\n    version '2.2' do\n      self.release = '2.2.17'\n      self.base_url = \"https://docs.djangoproject.com/en/#{self.version}/\"\n    end\n\n    version '2.1' do\n      self.release = '2.1.15'\n      self.base_url = \"https://docs.djangoproject.com/en/#{self.version}/\"\n    end\n\n    version '2.0' do\n      self.release = '2.0.13'\n      self.base_url = \"https://docs.djangoproject.com/en/#{self.version}/\"\n    end\n\n    version '1.11' do\n      self.release = '1.11.29'\n      self.base_url = \"https://docs.djangoproject.com/en/#{self.version}/\"\n    end\n\n    version '1.10' do\n      self.release = '1.10.8'\n      self.base_url = \"https://docs.djangoproject.com/en/#{self.version}/\"\n    end\n\n    version '1.9' do\n      self.release = '1.9.13'\n      self.base_url = \"https://docs.djangoproject.com/en/#{self.version}/\"\n    end\n\n    version '1.8' do\n      self.release = '1.8.18'\n      self.base_url = \"https://docs.djangoproject.com/en/#{self.version}/\"\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://docs.djangoproject.com/', opts)\n      doc.at_css('#doc-versions > li.current > span > strong').content\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/docker.rb",
    "content": "module Docs\n  class Docker < UrlScraper\n    include MultipleBaseUrls\n\n    self.name = 'Docker'\n    self.type = 'simple'\n\n    self.links = {\n      home: 'https://docker.com/',\n      code: 'https://github.com/docker/docker'\n    }\n\n    html_filters.push 'docker/entries', 'docker/clean_html'\n\n    options[:only_patterns] = [/\\Aget-started\\//, /\\Aengine\\//, /\\Acompose\\//, /\\Amachine\\//, /\\Anotary\\//]\n\n    options[:skip_patterns] = [/\\Aengine\\/api\\/v/, /glossary/, /docker-ee/]\n\n    options[:skip] = [\n      'engine/userguide/networking/get-started-overlay/'\n    ]\n\n    options[:trailing_slash] = true\n\n    options[:fix_urls] = ->(url) do\n      url.sub! %r{\\.md/?(?=#|\\z)}, '/'\n      url.sub! '/index/', '/'\n      url\n    end\n\n    options[:attribution] = <<-HTML\n      &copy; 2019 Docker, Inc.<br>\n      Licensed under the Apache License, Version 2.0.<br>\n      Docker and the Docker logo are trademarks or registered trademarks of Docker, Inc. in the United States and/or other countries.<br>\n      Docker, Inc. and other parties may also have trademark rights in other terms used herein.\n    HTML\n\n    options[:replace_paths] = {\n      'engine/userguide/' => 'config/daemon',\n      'engine/userguide/labels-custom-metadata/' => 'config',\n      'engine/swarm/networking/' => 'network/overlay',\n      'engine/userguide/eng-image/dockerfile_best-practices/' => 'develop/develop-images/dockerfile_best-practices/',\n      'engine/userguide/networking/get-started-overlay/' => 'network/network-tutorial-overlay/',\n      'engine/userguide/networking/' => 'network/',\n      'engine/reference/api/' => 'develop/sdk',\n      'engine/api/latest/' => 'develop/sdk',\n      'get-started/part3/' => 'get-started/04_sharing_app/',\n      'engine/security/https/' => 'engine/security/protect-access/',\n      'compose/aspnet-mssql-compose/' => 'samples/aspnet-mssql-compose/',\n      'engine/examples/dotnetcore/' => 'samples/dotnetcore/'\n    }\n\n    version do\n      self.release = '20.10.16'\n      self.base_url = \"https://docs.docker.com\"\n      self.base_urls = [\n        'https://docs.docker.com/',\n        'https://docs.docker.com/machine/'\n      ]\n    end\n\n    version '19' do\n      self.release = '19.03'\n      self.base_url = \"https://docs.docker.com\"\n    end\n\n    version '18' do\n      self.release = '18.09'\n      self.base_url = \"https://docs.docker.com\"\n    end\n\n    version '17' do\n      self.release = '17.12'\n      self.base_url = \"https://docs.docker.com\"\n    end\n\n    version '1.13' do\n      self.release = '1.13'\n      self.base_url = \"https://docs.docker.com\"\n    end\n\n    version '1.12' do\n      self.release = '1.12'\n      self.base_url = \"https://docs.docker.com\"\n    end\n\n    version '1.11' do\n      self.release = '1.11'\n      self.base_url = \"https://docs.docker.com\"\n    end\n\n    version '1.10' do\n      self.release = '1.10'\n      self.base_url = \"https://docs.docker.com\"\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://docs.docker.com/engine/release-notes/', opts)\n      latest_version = doc.at_css('h2.scroll-mt-20 > a').content.strip\n      latest_version.rpartition(' ')[-1]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/dojo.rb",
    "content": "require 'yajl/json_gem'\n\nmodule Docs\n  class Dojo < UrlScraper\n    self.type = 'dojo'\n    self.release = '1.10'\n    self.base_url = \"http://dojotoolkit.org/api/#{release}/\"\n\n    # Dojo expects all the requests to be xhrs or it redirects you back to the docs home page\n    # where it uses js to call the backend based on the URL so you get the appropriate documentation\n    self.headers = { 'User-Agent' => 'devdocs.io' , 'X-Requested-With' => 'XMLHttpRequest'  }\n    self.links = {\n      home: 'http://dojotoolkit.org',\n      code: 'https://github.com/dojo/dojo'\n    }\n\n    html_filters.push 'dojo/entries', 'dojo/clean_html', 'title'\n    text_filters.push 'dojo/clean_urls'\n\n    options[:container] = false\n    options[:title] = false\n    options[:root_title] = 'Dojo Toolkit'\n\n    options[:only_patterns] = [/\\Adojo\\//]\n    options[:skip_patterns] = [/dijit/, /dojox/]\n\n    options[:attribution] = <<-HTML\n      &copy; 2005&ndash;2017 JS Foundation<br>\n      Licensed under the AFL 2.1 and BSD 3-Clause licenses.\n    HTML\n\n    stub '' do\n      response = request_one(\"#{self.base_url}tree.json\")\n      json = JSON.parse(response.body)\n      urls = get_url_list(json)\n      urls.map { |url| \"<a href='#{url}'>#{url}</a>\" }.join\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://dojotoolkit.org/api/', opts)\n      doc.at_css('#versionSelector > option[selected]').content\n    end\n\n    private\n\n    def get_url_list(json, set = Set.new)\n      set.add(\"#{self.class.base_url}#{json['fullname']}.html?xhr=true\")\n      json['children'].each { |child| get_url_list(child, set) } if json['children']\n      set\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/drupal.rb",
    "content": "module Docs\n  class Drupal < UrlScraper\n    self.type = 'drupal'\n    self.base_url = 'https://api.drupal.org/api/drupal/'\n    self.links = {\n      home: 'https://www.drupal.org/',\n      code: 'http://cgit.drupalcode.org/drupal'\n    }\n\n    html_filters.push 'drupal/entries', 'drupal/clean_html', 'title'\n\n    options[:decode_and_clean_paths] = true\n    options[:container] = '#page-inner'\n    options[:title] = false\n    options[:root_title] = 'Drupal'\n\n    options[:skip_link] = ->(link) { link['href'] =~ /[\\?&]order/ }\n\n    options[:skip_patterns] = [\n      /test/i,\n      /_update_[0-9]{4}/,\n      /\\/group\\/updates\\-\\d/,\n      /interface\\/implements/,\n      /\\/(class|interface|trait)\\/hierarchy\\//,\n      /\\/(class|interface|trait)\\/uses\\//,\n      /\\/(class|interface|trait)\\/references\\//,\n      /\\/(class|interface|trait)\\/annotations\\//,\n      /\\/function\\/calls\\//,\n      /\\/function\\/invokes\\//,\n      /\\/function\\/overrides\\//,\n      /\\/function\\/references\\//,\n      /\\/function\\/implementations\\//,\n      /\\/function\\/theme_references\\//,\n      /upgrade/,\n      /DRUPAL_ROOT/,\n      /constant\\/constants/,\n      /theme_invokes/\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; 2001&ndash;2016 by the original authors<br>\n      Licensed under the GNU General Public License, version 2 and later.<br>\n      Drupal is a registered trademark of Dries Buytaert.\n    HTML\n\n    version '8' do\n      self.release = '8.1.7'\n      self.root_path = '8.1.x'\n      self.initial_paths = %w(groups/8.1.x groups/8.1.x?page=1)\n\n      options[:only_patterns] = [\n        /\\/class\\/[^\\/]+\\/8\\.1\\.x\\z/,\n        /\\/group\\/[^\\/]+\\/8\\.1\\.x\\z/,\n        /\\/function\\/[^\\/]+\\/8\\.1\\.x\\z/,\n        /\\/constant\\/[^\\/]+\\/8\\.1\\.x\\z/,\n        /\\/interface\\/[^\\/]+\\/8\\.1\\.x\\z/,\n        /\\/property\\/[^\\/]+\\/8\\.1\\.x\\z/,\n        /\\/global\\/[^\\/]+\\/8\\.1\\.x\\z/,\n        /\\/trait\\/[^\\/]+\\/8\\.1\\.x\\z/,\n        /modules.*\\/8\\.1\\.x\\z/,\n        /includes.*\\/8\\.1\\.x\\z/,\n        /\\A[\\w\\-\\.]+\\.php\\/8\\.1\\.x\\z/\n      ]\n\n      options[:skip] = %w(index.php/8.1.x update.php/8.1.x)\n\n      options[:skip_patterns] += [\n        /[^\\w\\-\\.].*\\.php\\/8\\.1\\.x\\z/,\n        /\\!src\\!/,\n        /migrate/,\n        /Assertion/,\n        /listing_page/,\n        /update_api/,\n        /vendor/,\n        /deprecated/,\n        /namespace/,\n        /\\.yml/,\n        /Plugin/,\n        /\\.theme\\//\n      ]\n    end\n\n    version '7' do\n      self.release = '7.50'\n      self.root_path = '7.x'\n      self.initial_paths = %w(groups/7.x groups/7.x?page=1)\n\n      options[:only_patterns] = [\n        /\\/class\\/[^\\/]+\\/7\\.x\\z/,\n        /\\/group\\/[^\\/]+\\/7\\.x\\z/,\n        /\\/function\\/[^\\/]+\\/7\\.x\\z/,\n        /\\/constant\\/[^\\/]+\\/7\\.x\\z/,\n        /\\/interface\\/[^\\/]+\\/7\\.x\\z/,\n        /\\/property\\/[^\\/]+\\/7\\.x\\z/,\n        /\\/global\\/[^\\/]+\\/7\\.x\\z/,\n        /modules.*\\/7\\.x\\z/,\n        /includes.*\\/7\\.x\\z/,\n        /\\A[\\w\\-\\.]+\\.php\\/7\\.x\\z/\n      ]\n    end\n\n    def get_latest_version(opts)\n      json = fetch_json('https://packagist.org/packages/drupal/drupal.json', opts)\n      json['package']['versions'].keys.find {|version| !version.end_with?('-dev')}\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/duckdb.rb",
    "content": "module Docs\n  class Duckdb < UrlScraper\n    self.name = 'DuckDB'\n    self.type = 'duckdb'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://duckdb.org/',\n      code: 'https://github.com/duckdb/duckdb'\n    }\n\n    # https://duckdb.org/docs/guides/offline-copy.html\n    # curl -O https://duckdb.org/duckdb-docs.zip; bsdtar xf duckdb-docs.zip; cd duckdb-docs; python -m http.server\n    self.release = '1.1.3'\n    self.base_url = 'http://localhost:8000/docs/'\n\n    html_filters.push 'duckdb/entries', 'duckdb/clean_html'\n    text_filters.replace 'attribution', 'duckdb/attribution'\n\n    options[:container] = '.documentation'\n    \n    options[:skip_patterns] = [\n      /installation/,\n      /archive/,\n      /reference/,\n    ]\n\n    options[:skip] = %w(\n      docs/archive/\n      docs/installation/\n      docs/api/\n    )\n\n    options[:attribution] = <<-HTML\n      &copy; Copyright 2018&ndash;2024 Stichting DuckDB Foundation<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_github_tags('duckdb', 'duckdb', opts)[0]['name']\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/eigen3.rb",
    "content": "module Docs\n  class Eigen3 < UrlScraper\n    self.name = 'Eigen3'\n    self.type = 'eigen3'\n    self.slug = 'eigen3'\n    self.base_url = 'https://eigen.tuxfamily.org/dox/'\n    self.root_path = 'index.html'\n    self.initial_paths = [\n      \"modules.html\"\n    ]\n    self.release = '3.4.0'\n\n    self.links = {\n      home: 'https://eigen.tuxfamily.org',\n      code: 'https://gitlab.com/libeigen/eigen'\n    }\n\n    html_filters.push 'eigen3/entries', 'eigen3/clean_html', 'title'\n\n    # Remove the `clean_text` because Doxygen are actually creating empty\n    # anchor such as <a id=\"asd\"></a> to do anchor link.. and that anchor\n    # will be removed by clean_text\n    self.text_filters = FilterStack.new\n    text_filters.push 'images', 'inner_html', 'attribution'\n\n\n\n    def get_latest_version(opts)\n      tags = get_gitlab_tags(\"gitlab.com\", \"libeigen\", \"eigen\", opts)\n      tags[0]['name']\n    end\n\n    options[:attribution] = <<-HTML\n      &copy; Eigen.<br>\n      Licensed under the MPL2 License.\n    HTML\n\n    # Skip source code since it doesn't provide any useful docs\n    options[:skip_patterns] = [/_source/, /-members/, /__Reference\\.html/, /_chapter\\.html/, /\\.txt/, /\\.tgz/]\n\n    # TODO: replace cppreference\n    # options[:replace_urls] = { 'http://en.cppreference.com/w/cpp/' => 'cpp/' }\n\n    def parse(response) # Hook here because Nokogori removes whitespace from code fragments\n      last_idx = 0\n      # Process nested <div>s inside code fragment div.\n      while not (last_idx = response.body.index('<div class=\"fragment\">', last_idx)).nil?\n        # enter code fragment <div>\n        level = 1\n        while not (last_idx = response.body.index(/<\\/?div/, last_idx+1)).nil?\n          # skip nested divs inside.\n          if response.body[last_idx..last_idx+3] == '<div'\n            level += 1\n          else\n            level -= 1\n          end\n          break if level == 0 # exit code fragment\n        end\n        if not last_idx.nil? and response.body[last_idx..last_idx+5] == '</div>'\n          response.body[last_idx..last_idx+5] = '</pre>'\n        end\n      end\n      response.body.gsub! /[\\r\\n\\s]*<div class=\"ttc\"[^>]*>.*<\\/div>[\\r\\n\\s]*/, \"\"\n      response.body.gsub! /<div class=\"line\">(.*?)<\\/div>/m, \"\\\\1\"\n      response.body.gsub! '<div class=\"fragment\">', '<pre class=\"fragment\" data-language=\"cpp\">'\n      super\n    end\n\n    def process_response?(response)\n      return false unless super\n      # Remove Empty pages.\n      response.body.index(/<div class=\"contents\">[\\r\\n\\s]*<\\/div>/m).nil? and \\\n        response.body.index(/<p>TODO: write this dox page!<\\/p>/).nil?\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/electron.rb",
    "content": "module Docs\n  class Electron < UrlScraper\n    self.type = 'simple'\n    self.base_url = 'https://www.electronjs.org/docs/latest'\n    self.release = '20.0.0'\n    self.links = {\n      home: 'https://www.electronjs.org/',\n      code: 'https://github.com/electron/electron'\n    }\n\n    html_filters.push 'electron/clean_html', 'electron/entries'\n\n    options[:trailing_slash] = false\n    options[:container] = 'main'\n    options[:skip] = %w(guides development tutorial versions all)\n    options[:skip_patterns] = [\n      /\\/history\\z/,\n    ]\n    options[:replace_paths] = {\n      'api/web-view-tag' => 'api/webview-tag'\n    }\n\n    options[:attribution] = <<-HTML\n      &copy; GitHub Inc.<br>\n      Licensed under the MIT license.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('electron', 'electron', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/elisp.rb",
    "content": "module Docs\n  class Elisp < FileScraper\n    self.type = 'elisp'\n    self.release = '28.2'\n    self.base_url= 'https://www.gnu.org/software/emacs/manual/html_node/elisp/'\n    self.root_path = 'index.html'\n    self.links = {\n      home:'https://www.gnu.org/software/emacs/manual/elisp',\n      code: 'https://git.savannah.gnu.org/cgit/emacs.git'\n    }\n\n    html_filters.push 'elisp/entries', 'elisp/clean_html'\n\n    # some file that were not skipped by skip patterns\n    options[:skip] = [\n      'Coding-Conventions.html',\n      'Key-Binding-Conventions.html',\n      'Library-Headers.html'\n    ]\n\n    # some non essential sections\n    options[:skip_patterns] = [\n      /Introduction.html/,\n      /Antinews.html/,\n      /GNU-Free-Documentation-License.html/,\n      /GPL.html/,\n      /Tips.html/,\n      /Definition-of-/\n    ]\n\n    # fix duplicates\n    options[:fix_urls]= -> (url) do\n      url.sub!('Window-Group.html', 'Basic-Windows.html')\n      url.sub!('Local-defvar-example.html', 'Using-Lexical-Binding.html')\n      url.sub!('Defining-Lisp-variables-in-C.html', 'Writing-Emacs-Primitives.html')\n      url.sub!('describe_002dsymbols-example.html', 'Accessing-Documentation.html')\n      url.sub!('The-interactive_002donly-property.html', 'Defining-Commands.html')\n      url.sub!('Text-help_002decho.html', 'Special-Properties.html')\n      url.sub!('Help-display.html', 'Special-Properties.html')\n      url.sub!('autoload-cookie.html', 'Autoload.html')\n      url.sub!('external_002ddebugging_002doutput.html', 'Output-Streams.html')\n      url.sub!('modifier-bits.html', 'Other-Char-Bits.html')\n      url.sub!('message_002dbox.html', 'Displaying-Messages.html')\n      url.sub!('abbreviate_002dfile_002dname.html', 'Directory-Names.html')\n      url.sub!('Inhibit-point-motion-hooks.html', 'Special-Properties.html')\n      url.sub!('Coding-systems-for-a-subprocess.html', 'Process-Information.html')\n      url.sub!('Process-Filter-Example.html', 'Filter-Functions.html')\n      url.sub!('Docstring-hyperlinks.html', 'Documentation-Tips.html')\n      url.sub!('seq_002dlet.html', 'Sequence-Functions.html')\n      url.sub!('should_005fquit.html', 'Module-Misc.html')\n      url.sub!('Display-Face-Attribute-Testing.html', 'Display-Feature-Testing.html')\n      url.sub!('module-initialization-function.html', 'Module-Initialization.html')\n      url.sub!('pcase_002dsymbol_002dcaveats.html', 'pcase-Macro.html')\n      url.sub!('intern.html', 'Module-Misc.html')\n      url.sub!('pcase_002dexample_002d1.html', 'pcase-Macro.html')\n      url\n    end\n\n    options[:attribution]= <<-HTML\n      Copyright &copy; 1990-1996, 1998-2022 Free Software Foundation, Inc. <br>\n      Licensed under the GNU GPL license.\n    HTML\n\n    def get_latest_version(opts)\n      body = fetch('https://www.gnu.org/software/emacs/manual/html_node/elisp/index.html', opts)\n      body.scan(/version \\d*\\.?\\d*/)[0].sub('version', '')\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/elixir.rb",
    "content": "module Docs\n  class Elixir < UrlScraper\n    include MultipleBaseUrls\n\n    self.name = 'Elixir'\n    self.type = 'elixir'\n    self.root_path = 'introduction.html'\n    self.links = {\n      home: 'https://elixir-lang.org/',\n      code: 'https://github.com/elixir-lang/elixir'\n    }\n\n    html_filters.push 'elixir/clean_html', 'elixir/entries', 'title'\n\n    options[:container] = '#content'\n    options[:title] = false\n    options[:root_title] = 'Elixir'\n\n    options[:attribution] = <<-HTML\n      &copy; 2012-2024 The Elixir Team<br>\n      Licensed under the Apache License, Version 2.0.\n    HTML\n\n    def initial_urls\n      [ \"https://hexdocs.pm/elixir/#{self.class.release}/introduction.html\",\n        \"https://hexdocs.pm/eex/#{self.class.release}/EEx.html\",\n        \"https://hexdocs.pm/ex_unit/#{self.class.release}/ExUnit.html\",\n        \"https://hexdocs.pm/iex/#{self.class.release}/IEx.html\",\n        \"https://hexdocs.pm/logger/#{self.class.release}/Logger.html\",\n        \"https://hexdocs.pm/mix/#{self.class.release}/Mix.html\" ]\n    end\n\n    version '1.18' do\n      self.release = '1.18.1'\n      self.base_urls = [\n        \"https://hexdocs.pm/elixir/#{release}/\",\n        \"https://hexdocs.pm/eex/#{release}/\",\n        \"https://hexdocs.pm/ex_unit/#{release}/\",\n        \"https://hexdocs.pm/iex/#{release}/\",\n        \"https://hexdocs.pm/logger/#{release}/\",\n        \"https://hexdocs.pm/mix/#{release}/\"\n      ]\n    end\n\n    version '1.17' do\n      self.release = '1.17.2'\n      self.base_urls = [\n        \"https://hexdocs.pm/elixir/#{release}/\",\n        \"https://hexdocs.pm/eex/#{release}/\",\n        \"https://hexdocs.pm/ex_unit/#{release}/\",\n        \"https://hexdocs.pm/iex/#{release}/\",\n        \"https://hexdocs.pm/logger/#{release}/\",\n        \"https://hexdocs.pm/mix/#{release}/\"\n      ]\n    end\n\n    version '1.16' do\n      self.release = '1.16.3'\n      self.base_urls = [\n        \"https://hexdocs.pm/elixir/#{release}/\",\n        \"https://hexdocs.pm/eex/#{release}/\",\n        \"https://hexdocs.pm/ex_unit/#{release}/\",\n        \"https://hexdocs.pm/iex/#{release}/\",\n        \"https://hexdocs.pm/logger/#{release}/\",\n        \"https://hexdocs.pm/mix/#{release}/\"\n      ]\n    end\n\n    # scraping of older versions is no longer supported!\n\n    version '1.15' do\n      self.release = '1.15.4'\n      self.base_urls = [\n        \"https://hexdocs.pm/elixir/#{release}/\",\n        \"https://hexdocs.pm/eex/#{release}/\",\n        \"https://hexdocs.pm/ex_unit/#{release}/\",\n        \"https://hexdocs.pm/iex/#{release}/\",\n        \"https://hexdocs.pm/logger/#{release}/\",\n        \"https://hexdocs.pm/mix/#{release}/\",\n        'https://elixir-lang.org/getting-started/'\n      ]\n    end\n\n    version '1.14' do\n      self.release = '1.14.1'\n      self.base_urls = [\n        \"https://hexdocs.pm/elixir/#{release}/\",\n        \"https://hexdocs.pm/eex/#{release}/\",\n        \"https://hexdocs.pm/ex_unit/#{release}/\",\n        \"https://hexdocs.pm/iex/#{release}/\",\n        \"https://hexdocs.pm/logger/#{release}/\",\n        \"https://hexdocs.pm/mix/#{release}/\",\n        'https://elixir-lang.org/getting-started/'\n      ]\n    end\n\n    version '1.13' do\n      self.release = '1.13.4'\n      self.base_urls = [\n        \"https://hexdocs.pm/elixir/#{release}/\",\n        \"https://hexdocs.pm/eex/#{release}/\",\n        \"https://hexdocs.pm/ex_unit/#{release}/\",\n        \"https://hexdocs.pm/iex/#{release}/\",\n        \"https://hexdocs.pm/logger/#{release}/\",\n        \"https://hexdocs.pm/mix/#{release}/\",\n        'https://elixir-lang.org/getting-started/'\n      ]\n    end\n\n    version '1.12' do\n      self.release = '1.12.0'\n      self.base_urls = [\n        \"https://hexdocs.pm/elixir/#{release}/\",\n        \"https://hexdocs.pm/eex/#{release}/\",\n        \"https://hexdocs.pm/ex_unit/#{release}/\",\n        \"https://hexdocs.pm/iex/#{release}/\",\n        \"https://hexdocs.pm/logger/#{release}/\",\n        \"https://hexdocs.pm/mix/#{release}/\",\n        'https://elixir-lang.org/getting-started/'\n      ]\n    end\n\n    version '1.11' do\n      self.release = '1.11.2'\n      self.base_urls = [\n        \"https://hexdocs.pm/elixir/#{release}/\",\n        \"https://hexdocs.pm/eex/#{release}/\",\n        \"https://hexdocs.pm/ex_unit/#{release}/\",\n        \"https://hexdocs.pm/iex/#{release}/\",\n        \"https://hexdocs.pm/logger/#{release}/\",\n        \"https://hexdocs.pm/mix/#{release}/\",\n        'https://elixir-lang.org/getting-started/'\n      ]\n    end\n\n    version '1.10' do\n      self.release = '1.10.4'\n      self.base_urls = [\n        \"https://hexdocs.pm/elixir/#{release}/\",\n        \"https://hexdocs.pm/eex/#{release}/\",\n        \"https://hexdocs.pm/ex_unit/#{release}/\",\n        \"https://hexdocs.pm/iex/#{release}/\",\n        \"https://hexdocs.pm/logger/#{release}/\",\n        \"https://hexdocs.pm/mix/#{release}/\",\n        'https://elixir-lang.org/getting-started/'\n      ]\n    end\n\n    version '1.9' do\n      self.release = '1.9.4'\n      self.base_urls = [\n        \"https://hexdocs.pm/elixir/#{release}/\",\n        \"https://hexdocs.pm/eex/#{release}/\",\n        \"https://hexdocs.pm/ex_unit/#{release}/\",\n        \"https://hexdocs.pm/iex/#{release}/\",\n        \"https://hexdocs.pm/logger/#{release}/\",\n        \"https://hexdocs.pm/mix/#{release}/\",\n        'https://elixir-lang.org/getting-started/'\n      ]\n    end\n\n    version '1.8' do\n      self.release = '1.8.2'\n      self.base_urls = [\n        \"https://hexdocs.pm/elixir/#{release}/\",\n        \"https://hexdocs.pm/eex/#{release}/\",\n        \"https://hexdocs.pm/ex_unit/#{release}/\",\n        \"https://hexdocs.pm/iex/#{release}/\",\n        \"https://hexdocs.pm/logger/#{release}/\",\n        \"https://hexdocs.pm/mix/#{release}/\",\n        'https://elixir-lang.org/getting-started/'\n      ]\n    end\n\n    version '1.7' do\n      self.release = '1.7.4'\n      self.base_urls = [\n        \"https://hexdocs.pm/elixir/#{release}/\",\n        \"https://hexdocs.pm/eex/#{release}/\",\n        \"https://hexdocs.pm/ex_unit/#{release}/\",\n        \"https://hexdocs.pm/iex/#{release}/\",\n        \"https://hexdocs.pm/logger/#{release}/\",\n        \"https://hexdocs.pm/mix/#{release}/\",\n        'https://elixir-lang.org/getting-started/'\n      ]\n    end\n\n    version '1.6' do\n      self.release = '1.6.6'\n      self.base_urls = [\n        \"https://hexdocs.pm/elixir/#{release}/\",\n        \"https://hexdocs.pm/eex/#{release}/\",\n        \"https://hexdocs.pm/ex_unit/#{release}/\",\n        \"https://hexdocs.pm/iex/#{release}/\",\n        \"https://hexdocs.pm/logger/#{release}/\",\n        \"https://hexdocs.pm/mix/#{release}/\",\n        'https://elixir-lang.org/getting-started/'\n      ]\n    end\n\n    version '1.5' do\n      self.release = '1.5.3'\n      self.base_urls = [\n        \"https://hexdocs.pm/elixir/#{release}/\",\n        \"https://hexdocs.pm/eex/#{release}/\",\n        \"https://hexdocs.pm/ex_unit/#{release}/\",\n        \"https://hexdocs.pm/iex/#{release}/\",\n        \"https://hexdocs.pm/logger/#{release}/\",\n        \"https://hexdocs.pm/mix/#{release}/\",\n        'https://elixir-lang.org/getting-started/'\n      ]\n    end\n\n    version '1.4' do\n      self.release = '1.4.5'\n      self.base_urls = [\n        \"https://hexdocs.pm/elixir/#{release}/\",\n        \"https://hexdocs.pm/eex/#{release}/\",\n        \"https://hexdocs.pm/ex_unit/#{release}/\",\n        \"https://hexdocs.pm/iex/#{release}/\",\n        \"https://hexdocs.pm/logger/#{release}/\",\n        \"https://hexdocs.pm/mix/#{release}/\",\n        'https://elixir-lang.org/getting-started/'\n      ]\n    end\n\n    version '1.3' do\n      self.release = '1.3.4'\n      self.base_urls = [\n        \"https://hexdocs.pm/elixir/#{release}/\",\n        \"https://hexdocs.pm/eex/#{release}/\",\n        \"https://hexdocs.pm/ex_unit/#{release}/\",\n        \"https://hexdocs.pm/iex/#{release}/\",\n        \"https://hexdocs.pm/logger/#{release}/\",\n        \"https://hexdocs.pm/mix/#{release}/\",\n        'https://elixir-lang.org/getting-started/'\n      ]\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://hexdocs.pm/elixir/api-reference.html', opts)\n      doc.at_css('.sidebar-projectVersion').content.strip[1..-1]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/ember.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class Ember < UrlScraper\n    include MultipleBaseUrls\n\n    self.name = 'Ember.js'\n    self.slug = 'ember'\n    self.type = 'ember'\n    self.links = {\n      home: 'https://emberjs.com/',\n      code: 'https://github.com/emberjs/ember.js'\n    }\n\n    html_filters.push 'ember/entries', 'ember/clean_html'\n\n    options[:trailing_slash] = false\n\n    options[:container] = ->(filter) do\n      if filter.base_url.host.start_with?('api')\n        'main'\n      else\n        'main article'\n      end\n    end\n\n    options[:fix_urls] = ->(url) do\n      url.sub! '?anchor=', '#'\n      url.sub! %r{/methods/[^?#/]+}, '/methods'\n      url.sub! %r{/properties/[^?#/]+}, '/properties'\n      url.sub! %r{/events/[^?#/]+}, '/events'\n      url\n    end\n\n    options[:skip_patterns] = [\n      /\\._/,\n      /contributing/,\n      /tutorial/,\n      /js-primer/,\n      /in-depth-topics$/,\n      /managing-dependencies$/\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; 2022 Yehuda Katz, Tom Dale and Ember.js contributors<br>\n      Licensed under the MIT License.\n    HTML\n\n    options[:decode_and_clean_paths] = true # handle paths like @ember/application\n\n    version '4' do\n      self.release = '4.9.0'\n      self.base_urls = %w[\n        https://guides.emberjs.com/v4.9.0/\n        https://api.emberjs.com/ember/4.9/\n        https://api.emberjs.com/ember-data/4.9/\n      ]\n    end\n\n    version '3' do\n      self.release = '3.28.0'\n      self.base_urls = %w[\n        https://guides.emberjs.com/v3.28.0/\n        https://api.emberjs.com/ember/3.28/\n        https://api.emberjs.com/ember-data/3.28/\n      ]\n    end\n\n    version '2' do\n      self.release = '2.18.0'\n      self.base_urls = %w[\n        https://guides.emberjs.com/v2.18.0/\n        https://api.emberjs.com/ember/2.18/\n        https://api.emberjs.com/ember-data/2.18/\n      ]\n      options[:skip_patterns].push(/handlebars-basics$/)\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://api.emberjs.com/ember/release', opts)\n      doc.at_css('.sidebar > .select-container .ember-power-select-selected-item').content.strip\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/enzyme.rb",
    "content": "module Docs\n  class Enzyme < UrlScraper\n    self.type = 'simple'\n    self.release = '3.11.0'\n    self.base_url = 'https://enzymejs.github.io/enzyme/'\n    self.root_path = 'index.html'\n    self.links = {\n      code: 'https://github.com/airbnb/enzyme'\n    }\n\n    html_filters.push 'enzyme/entries', 'enzyme/clean_html'\n\n    options[:skip] = %w(CHANGELOG.html docs/future.html docs/guides.html docs/api/ CONTRIBUTING.html)\n\n    options[:attribution] = <<-HTML\n      &copy; 2015 Airbnb, Inc.<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version('enzyme', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/erlang.rb",
    "content": "module Docs\n  class Erlang < FileScraper\n    self.type = 'erlang'\n    self.root_path = 'doc/index.html'\n    self.links = {\n      home: 'https://www.erlang.org/',\n      code: 'https://github.com/erlang/otp'\n    }\n\n    html_filters.insert_after 'container', 'erlang/pre_clean_html'\n    html_filters.push 'erlang/entries', 'erlang/clean_html'\n\n    options[:only_patterns] = [\n      /\\Alib/,\n      /\\Adoc\\/\\w+\\//,\n      /\\Aerts.+\\/html/\n    ]\n\n    options[:skip_patterns] = [\n      /pdf/,\n      /release_notes/,\n      /result/,\n      /java/,\n      /\\.erl\\z/,\n      /\\/html\\/.*_app\\.html\\z/,\n      /_examples\\.html\\z/,\n      /\\Alib\\/edoc/,\n      /\\Alib\\/erl_docgen/,\n      /\\Alib\\/hipe/,\n      /\\Alib\\/ose/,\n      /\\Alib\\/test_server/,\n      /\\Alib\\/jinterface/,\n      /\\Alib\\/wx/,\n      /\\Alib\\/ic/,\n      /\\Alib\\/Cos/i\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; 2010&ndash;2023 Ericsson AB<br>\n      Licensed under the Apache License, Version 2.0.\n    HTML\n\n    version '26' do\n      self.release = '26.0.1'\n    end\n\n    version '25' do\n      self.release = '25.3.2.2'\n    end\n\n    version '24' do\n      self.release = '24.0'\n    end\n\n    version '23' do\n      self.release = '23.2'\n    end\n\n    version '22' do\n      self.release = '22.3'\n    end\n\n    version '21' do\n      self.release = '21.0'\n    end\n\n    version '20' do\n      self.release = '20.3'\n    end\n\n    version '19' do\n      self.release = '19.3'\n    end\n\n    version '18' do\n      self.release = '18.3'\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('erlang', 'otp', opts)[4..-1]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/es_toolkit.rb",
    "content": "module Docs\n  class EsToolkit < FileScraper\n    self.name = \"es-toolkit\"\n    self.slug = \"es_toolkit\"\n    self.type = \"simple\"\n    self.links = {\n      code: \"https://github.com/toss/es-toolkit\",\n      home: \"https://es-toolkit.slash.page\",\n    }\n    self.release = '1.42.0'\n\n    options[:attribution] = <<-HTML\n      &copy; 2024-2025, Viva Republica<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_github_tags(\"toss\", \"es-toolkit\", opts).first[\"name\"][1..]\n    end\n\n    def build_pages(&block)\n      internal(\"docs/intro.md\", path: \"index\", &block)\n      Dir.chdir(source_directory) do\n        Dir[\"docs/reference/**/*.md\"]\n      end.each { internal(_1, &block) }\n    end\n\n    protected\n\n    def internal(filename, path: nil, &block)\n      path ||= filename[%r{docs/reference/(.*/.*).md$}, 1]\n\n      # calculate name/type\n      if path != \"index\"\n        name = filename[%r{([^/]+).md$}, 1]\n        type = path.split(\"/\")[0..-2]\n        type = type.map(&:capitalize).join(\" \")\n        # really bad way to sort\n        type = type.gsub(/^(Compat|Error)\\b/, \"\\u2063\\\\1\") #  U+2063 INVISIBLE SEPARATOR\n      else\n        name = type = nil\n      end\n\n      # now yield\n      entries = [Entry.new(name, path, type)]\n      output = render(filename)\n      store_path = \"#{path}.html\"\n      yield({entries:, output:, path:, store_path:})\n    end\n\n    # render/style HTML\n    def render(filename)\n      s = md.render(request_one(filename).body)\n\n      # kill all links, they don't work\n      s.gsub!(%r{<a href=\"[^\"]+\">(.*?)</a>}, \"<span>\\\\1</span>\")\n\n      # syntax highlighting\n      s.gsub!(%r{<pre><code class=\"typescript\">}, \"<pre data-language='typescript'><code class='typescript'>\")\n\n      # h3 => h4\n      s.gsub!(%r{(</?h)3>}, \"\\\\14>\")\n\n      # manually add attribution\n      link = \"#{self.class.links[:home]}#{filename.gsub(/^docs/,'').gsub(/md$/,'html')}\"\n      s += <<~HTML\n        <div class=\"_attribution\">\n          <p class=\"_attribution-p\">\n            #{options[:attribution]}\n            <br>\n            <a href=\"#{link}\" class=\"_attribution-link\">\n              #{link}\n            </a>\n          </p>\n        </div>\n      HTML\n      s\n    end\n\n    def md\n      @md ||= Redcarpet::Markdown.new(\n        Redcarpet::Render::HTML,\n        autolink: true,\n        fenced_code_blocks: true,\n        tables: true\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/esbuild.rb",
    "content": "module Docs\n  class Esbuild < UrlScraper\n    self.name = 'esbuild'\n    self.slug = 'esbuild'\n    self.type = 'simple'\n    self.links = {\n      home: 'https://esbuild.github.io/',\n      code: 'https://github.com/evanw/esbuild'\n    }\n\n    options[:container] = 'main'\n    options[:root_title] = 'esbuild'\n\n    options[:attribution] = <<-HTML\n      &copy; 2020 Evan Wallace<br>\n      Licensed under the MIT License.\n    HTML\n\n    self.release = '0.25.0'\n    self.base_url = 'https://esbuild.github.io/'\n    html_filters.push 'esbuild/clean_html', 'esbuild/entries'\n\n    def get_latest_version(opts)\n      get_npm_version('esbuild', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/eslint.rb",
    "content": "module Docs\n  class Eslint < UrlScraper\n    self.name = 'ESLint'\n    self.type = 'simple'\n    self.release = '10.0.2'\n    self.base_url = 'https://eslint.org/docs/latest/'\n    self.root_path = '/'\n    self.links = {\n      home: 'https://eslint.org/',\n      code: 'https://github.com/eslint/eslint'\n    }\n\n    html_filters.push 'eslint/entries', 'eslint/clean_html'\n\n    options[:skip_patterns] = [/maintain/, /migrating/, /migrate/, /\\Aversions/, /rule-deprecation/]\n    options[:skip] = %w(about about/ versions)\n    # A number of paths have a trailing slash, causing them to be suffixed by \"index\" during the NormalizePathsFilter\n    options[:replace_paths] = {\n      'configure/' => 'configure',\n      'contribute/' => 'contribute',\n      'contribute/architecture/' => 'contribute/architecture',\n      'extend/' => 'extend',\n      'flags/' => 'flags',\n      'integrate/' => 'integrate',\n      'rules/' => 'rules',\n      'use/' => 'use',\n      'use/formatters/' => 'use/formatters',\n      'use/configure/' => 'use/configure',\n      'use/configure/rules/' => 'use/configure/rules',\n      'use/core-concepts/' => 'use/core-concepts',\n      'use/troubleshooting/' => 'use/troubleshooting',\n    }\n\n    options[:attribution] = <<-HTML\n      &copy; OpenJS Foundation and other contributors<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version('eslint', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/express.rb",
    "content": "module Docs\n  class Express < UrlScraper\n    self.name = 'Express'\n    self.type = 'express'\n    self.base_url = 'https://expressjs.com/en/'\n    self.initial_paths = %w(\n      starter/installing.html\n      guide/routing.html\n      advanced/developing-template-engines.html )\n    self.links = {\n      home: 'https://expressjs.com/',\n      code: 'https://github.com/strongloop/express/'\n    }\n\n    html_filters.push 'express/clean_html', 'express/entries', 'title'\n\n    options[:title] = false\n    options[:root_title] = 'Express'\n\n    options[:only_patterns] = [\n      /\\Astarter/,\n      /\\Aguide/,\n      /\\Aadvanced/ ]\n\n    options[:attribution] = <<-HTML\n      &copy; 2017 StrongLoop, IBM, and other expressjs.com contributors.<br>\n      Licensed under the Creative Commons Attribution-ShareAlike License v3.0.\n    HTML\n\n    version do\n      self.release = '5.2.1'\n      self.root_path = '5x/api.html'\n    end\n\n    version '4' do\n      self.release = '4.21.2'\n      self.root_path = '4x/api.html'\n    end\n\n    def get_latest_version(opts)\n      get_npm_version('express', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/falcon.rb",
    "content": "module Docs\n  class Falcon < UrlScraper\n    self.type = 'sphinx'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://falconframework.org/',\n      code: 'https://github.com/falconry/falcon'\n    }\n\n    html_filters.push 'falcon/entries', 'sphinx/clean_html'\n\n    options[:container] = '.body'\n\n    options[:skip] = %w(user/index.html api/index.html deploy/index.html)\n    options[:skip_patterns] = [/\\Achanges/, /\\A_modules/, /\\Acommunity/]\n\n    options[:attribution] = <<-HTML\n      &copy; 2019 by Falcon contributors<br>\n      Licensed under the Apache License, Version 2.0.\n    HTML\n\n    version '2.0' do\n      self.release = '2.0.0'\n      self.base_url = \"https://falcon.readthedocs.io/en/#{self.release}/\"\n    end\n\n    version '1.4' do\n      self.release = '1.4.1'\n      self.base_url = \"https://falcon.readthedocs.io/en/#{self.release}/\"\n    end\n\n    version '1.3' do\n      self.release = '1.3.0'\n      self.base_url = \"https://falcon.readthedocs.io/en/#{self.release}/\"\n    end\n\n    version '1.2' do\n      self.release = '1.2.0'\n      self.base_url = \"https://falcon.readthedocs.io/en/#{self.release}/\"\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://falcon.readthedocs.io/en/stable/changes/index.html', opts)\n      doc.at_css('#changelogs ul > li > a').content\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/fastapi.rb",
    "content": "module Docs\n  class Fastapi < UrlScraper\n    self.name = 'FastAPI'\n    self.type = 'fastapi'\n    self.release = '0.115.6'\n    self.base_url = 'https://fastapi.tiangolo.com/'\n    self.root_path = '/'\n    self.links = {\n      home: 'https://fastapi.tiangolo.com/',\n      code: 'https://github.com/tiangolo/fastapi'\n    }\n\n    options[:only_patterns] = [\n        /\\Afeatures\\//, /\\Apython-types\\//,\n        /\\Atutorial\\//, /\\Aadvanced\\//, /\\Aasync\\//,\n        /\\Adeployment\\//, /\\Aproject-generation\\//\n    ]\n\n    html_filters.push 'fastapi/container', 'fastapi/clean_html', 'fastapi/entries'\n\n    options[:attribution] = <<-HTML\n      &copy; 2018 Sebastián Ramírez<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('tiangolo', 'fastapi', opts)\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/fish.rb",
    "content": "module Docs\n  class Fish < UrlScraper\n    self.name = 'Fish'\n    self.type = 'simple'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://fishshell.com/',\n      code: 'https://github.com/fish-shell/fish-shell'\n    }\n\n    options[:skip] = %w(design.html license.html contributing.html)\n\n    # https://fishshell.com/docs/current/license.html\n    options[:attribution] = <<-HTML\n      &copy; 2005-2009 Axel Liljencrantz, 2009-2026 fish-shell contributors<br>\n      Licensed under the GNU General Public License, version 2.\n    HTML\n\n    version '4.5' do\n      self.release = '4.5.0'\n      self.base_url = \"https://fishshell.com/docs/#{version}/\"\n\n      options[:skip].concat %w(genindex.html relnotes.html)\n      html_filters.push 'sphinx/clean_html', 'fish/clean_html_sphinx', 'fish/entries_sphinx'\n    end\n\n    version '4.4' do\n      self.release = '4.4.0'\n      self.base_url = \"https://fishshell.com/docs/#{version}/\"\n\n      options[:skip].concat %w(genindex.html relnotes.html)\n      html_filters.push 'sphinx/clean_html', 'fish/clean_html_sphinx', 'fish/entries_sphinx'\n    end\n\n    version '4.3' do\n      self.release = '4.3.3'\n      self.base_url = \"https://fishshell.com/docs/#{version}/\"\n\n      options[:skip].concat %w(genindex.html relnotes.html)\n      html_filters.push 'sphinx/clean_html', 'fish/clean_html_sphinx', 'fish/entries_sphinx'\n    end\n\n    version '4.2' do\n      self.release = '4.2.1'\n      self.base_url = \"https://fishshell.com/docs/#{version}/\"\n\n      options[:skip].concat %w(genindex.html relnotes.html)\n      html_filters.push 'sphinx/clean_html', 'fish/clean_html_sphinx', 'fish/entries_sphinx'\n    end\n\n    version '4.1' do\n      self.release = '4.1.2'\n      self.base_url = \"https://fishshell.com/docs/#{version}/\"\n\n      options[:skip].concat %w(genindex.html relnotes.html)\n      html_filters.push 'sphinx/clean_html', 'fish/clean_html_sphinx', 'fish/entries_sphinx'\n    end\n\n    version '4.0' do\n      self.release = '4.0.1'\n      self.base_url = \"https://fishshell.com/docs/#{version}/\"\n\n      options[:skip].concat %w(genindex.html relnotes.html)\n      html_filters.push 'sphinx/clean_html', 'fish/clean_html_sphinx', 'fish/entries_sphinx'\n    end\n\n    version '3.7' do\n      self.release = '3.7.0'\n      self.base_url = \"https://fishshell.com/docs/#{version}/\"\n\n      options[:skip].concat %w(genindex.html relnotes.html)\n      html_filters.push 'sphinx/clean_html', 'fish/clean_html_sphinx', 'fish/entries_sphinx'\n    end\n\n    version '3.6' do\n      self.release = '3.6.0'\n      self.base_url = \"https://fishshell.com/docs/#{version}/\"\n\n      options[:skip].concat %w(genindex.html relnotes.html)\n      html_filters.push 'sphinx/clean_html', 'fish/clean_html_sphinx', 'fish/entries_sphinx'\n    end\n\n    version '3.5' do\n      self.release = '3.5.0'\n      self.base_url = \"https://fishshell.com/docs/#{version}/\"\n\n      options[:skip].concat %w(genindex.html relnotes.html)\n      html_filters.push 'sphinx/clean_html', 'fish/clean_html_sphinx', 'fish/entries_sphinx'\n    end\n\n    version '3.4' do\n      self.release = '3.4.0'\n      self.base_url = \"https://fishshell.com/docs/#{version}/\"\n\n      options[:skip].concat %w(genindex.html relnotes.html)\n      html_filters.push 'sphinx/clean_html', 'fish/clean_html_sphinx', 'fish/entries_sphinx'\n    end\n\n    version '3.3' do\n      self.release = '3.3.0'\n      self.base_url = \"https://fishshell.com/docs/#{version}/\"\n\n      options[:skip].concat %w(genindex.html relnotes.html)\n      html_filters.push 'sphinx/clean_html', 'fish/clean_html_sphinx', 'fish/entries_sphinx'\n    end\n\n    version '3.2' do\n      self.release = '3.2.0'\n      self.base_url = \"https://fishshell.com/docs/#{version}/\"\n\n      options[:skip].concat %w(genindex.html relnotes.html)\n      html_filters.push 'sphinx/clean_html', 'fish/clean_html_sphinx', 'fish/entries_sphinx'\n    end\n\n    version '3.1' do\n      self.release = '3.1.2'\n      self.base_url = \"https://fishshell.com/docs/#{version}/\"\n\n      options[:skip].concat %w(genindex.html commands.html)\n      html_filters.push 'sphinx/clean_html', 'fish/clean_html_sphinx', 'fish/entries_sphinx'\n    end\n\n    version '3.0' do\n      self.release = '3.0.1'\n      self.base_url = \"https://fishshell.com/docs/#{version}/\"\n\n      html_filters.push 'fish/clean_html_custom', 'fish/entries_custom'\n    end\n\n    version '2.7' do\n      self.release = '2.7.1'\n      self.base_url = \"https://fishshell.com/docs/#{version}/\"\n\n      html_filters.push 'fish/clean_html_custom', 'fish/entries_custom'\n    end\n\n    version '2.6' do\n      self.release = '2.6.0'\n      self.base_url = \"https://fishshell.com/docs/#{version}/\"\n\n      html_filters.push 'fish/clean_html_custom', 'fish/entries_custom'\n    end\n\n    version '2.5' do\n      self.release = '2.5.0'\n      self.base_url = \"https://fishshell.com/docs/#{version}/\"\n\n      html_filters.push 'fish/clean_html_custom', 'fish/entries_custom'\n    end\n\n    version '2.4' do\n      self.release = '2.4.0'\n      self.base_url = \"https://fishshell.com/docs/#{version}/\"\n\n      html_filters.push 'fish/clean_html_custom', 'fish/entries_custom'\n    end\n\n    version '2.3' do\n      self.release = '2.3.1'\n      self.base_url = \"https://fishshell.com/docs/#{version}/\"\n\n      html_filters.push 'fish/clean_html_custom', 'fish/entries_custom'\n    end\n\n    version '2.2' do\n      self.release = '2.2.0'\n      self.base_url = \"https://fishshell.com/docs/#{version}/\"\n\n      html_filters.push 'fish/clean_html_custom', 'fish/entries_custom'\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('fish-shell', 'fish-shell', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/flask.rb",
    "content": "module Docs\n  class Flask < UrlScraper\n    self.type = 'sphinx'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://palletsprojects.com/p/flask/',\n      code: 'https://github.com/pallets/flask'\n    }\n\n    html_filters.push 'flask/entries', 'sphinx/clean_html'\n\n    options[:container] = '.body > section'\n    options[:skip] = %w(extensiondev/ styleguide/ upgrading/ changelog/ license/ contributing/)\n    options[:skip_patterns] = [/\\Atutorial\\//]\n\n    options[:attribution] = <<-HTML\n      &copy; 2010 Pallets<br>\n      Licensed under the BSD 3-clause License.\n    HTML\n\n    version do\n      self.release = '3.1.1'\n      self.base_url = \"https://flask.palletsprojects.com/en/stable/\"\n    end\n\n    version '3.0' do\n      self.release = '3.0.x'\n      self.base_url = \"https://flask.palletsprojects.com/en/#{self.release}/\"\n    end\n\n    version '2.3' do\n      self.release = '2.3.x'\n      self.base_url = \"https://flask.palletsprojects.com/en/#{self.release}/\"\n    end\n\n    version '2.2' do\n      self.release = '2.2.x'\n      self.base_url = \"https://flask.palletsprojects.com/en/#{self.release}/\"\n    end\n\n    version '2.1' do\n      self.release = '2.1.x'\n      self.base_url = \"https://flask.palletsprojects.com/en/#{self.release}/\"\n    end\n\n    version '2.0' do\n      self.release = '2.0.x'\n      self.base_url = \"https://flask.palletsprojects.com/en/#{self.release}/\"\n    end\n\n    version '1.1' do\n      self.release = '1.1.x'\n      self.base_url = \"https://flask.palletsprojects.com/en/#{self.release}/\"\n    end\n\n    version '1.0' do\n      self.release = '1.0.x'\n      self.base_url = \"https://flask.palletsprojects.com/en/#{self.release}/\"\n    end\n\n    version '0.12' do\n      self.release = '0.12.x'\n      self.base_url = \"https://flask.palletsprojects.com/en/#{self.release}/\"\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('pallets', 'flask', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/flow.rb",
    "content": "module Docs\n  class Flow < UrlScraper\n    self.type = 'simple'\n    self.release = '0.186.0'\n    self.base_url = 'https://flow.org/en/docs/'\n    self.links = {\n      home: 'https://flow.org/',\n      code: 'https://github.com/facebook/flow'\n    }\n\n    html_filters.push 'flow/entries', 'flow/clean_html', 'title'\n\n    options[:trailing_slash] = false\n    options[:root_title] = 'Flow'\n    options[:skip] = %w(libs install)\n\n    options[:attribution] = <<-HTML\n      &copy; 2013&ndash;present Facebook Inc.<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version('flow-bin', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/fluture.rb",
    "content": "module Docs\n\n  class Fluture < Github\n    self.name = \"Fluture\"\n    self.slug = \"fluture\"\n    self.type = \"fluture\"\n    self.release = \"14.0.0\"\n    self.base_url = \"https://github.com/fluture-js/Fluture/blob/#{self.release}/README.md\"\n    self.links = {\n      home: \"https://github.com/fluture-js/Fluture\",\n      code: \"https://github.com/fluture-js/Fluture\",\n    }\n\n    html_filters.push \"fluture/entries\", \"fluture/clean_html\"\n\n    options[:skip] = %w[middleware.gif]\n    options[:container] = '.markdown-body'\n    options[:title] = \"Fluture\"\n    options[:trailing_slash] = false\n    options[:attribution] = <<-HTML\n      &copy; 2020 Aldwin Vlasblom<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version(\"fluture\", opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/git.rb",
    "content": "module Docs\n  class Git < UrlScraper\n    self.type = 'git'\n    self.release = '2.53.0'\n    self.base_url = 'https://git-scm.com/docs'\n    self.initial_paths = %w(\n      /git.html\n      /git-archimport.html\n      /git-cherry.html\n      /git-citool.html\n      /git-column.html\n      /git-cvsexportcommit.html\n      /git-for-each-repo.html\n      /git-get-tar-commit-id.html\n      /git-http-fetch.html\n      /git-http-push.html\n      /git-merge-file.html\n      /git-merge-index.html\n      /git-merge-one-file.html\n      /git-merge-tree.html\n      /git-mktree.html\n      /git-p4.html\n      /git-pack-redundant.html\n      /git-quiltimport.html\n      /git-replay.html\n      /git-sh-i18n.html\n      /git-sh-i18n--envsubst.html\n      /git-sh-setup.html\n      /git-show-index.html\n      /git-unpack-file.html\n      /git-verify-commit.html\n      /gitformat-index.html\n      /scalar.html\n    )\n    self.links = {\n      home: 'https://git-scm.com/',\n      code: 'https://github.com/git/git'\n    }\n\n    html_filters.push 'git/entries', 'git/clean_html'\n\n    options[:container] = '#content'\n    options[:only_patterns] = [/\\A\\/[^\\/]+\\z/]\n    options[:skip] = %w(/api-index /howto-index)\n\n    # https://github.com/git/git?tab=License-1-ov-file#readme\n    # NOT https://github.com/git/git-scm.com/blob/gh-pages/MIT-LICENSE.txt\n    options[:attribution] = <<-HTML\n      &copy; 2005&ndash;2026 Linus Torvalds and others<br>\n      Licensed under the GNU General Public License version 2.\n    HTML\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://git-scm.com/', opts)\n      doc.at_css('.version').content.strip\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/github.rb",
    "content": "module Docs\n  class Github < UrlScraper\n    self.abstract = true\n    self.type = 'github'\n\n    html_filters.push 'github/clean_html'\n\n    def process_response?(response)\n      if super(response)\n        return true\n      end\n      JSON.parse(response.body)\n      true\n    rescue JSON::ParserError, TypeError => e\n      false\n    end\n\n    def parse(response)\n      embedded_json = response\n        .response_body\n        .match(/react-app\\.embeddedData\">(.+?)<\\/script>/)\n        &.captures\n        &.first\n      parsed = JSON.parse(embedded_json)\n\n      [parsed['payload']['blob']['richText'], parsed['title']]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/gnu/gcc.rb",
    "content": "module Docs\n  class Gcc < Gnu\n    self.name = 'GCC'\n    self.slug = 'gcc'\n    self.links = {\n      home: 'https://gcc.gnu.org/'\n    }\n\n    html_filters.push 'gcc/clean_html', 'title'\n\n    options[:root_title] = false\n    options[:title] = false\n\n    options[:replace_paths] = {\n      'aarch64_002dfeature_002dmodifiers.html' => 'AArch64-Options.html',\n      'AssemblerTemplate.html' => 'Extended-Asm.html',\n      'AVR-Named-Address-Spaces.html' => 'Named-Address-Spaces.html',\n      'AVR-Variable-Attributes.html' => 'Variable-Attributes.html',\n      'Clobbers.html' => 'Extended-Asm.html',\n      'dashMF.html' => 'Preprocessor-Options.html',\n      'GotoLabels.html' => 'Extended-Asm.html',\n      'InputOperands.html' => 'Extended-Asm.html',\n      'OutputOperands.html' => 'Extended-Asm.html',\n      'PowerPC-Type-Attributes.html' => 'Type-Attributes.html',\n      'SPU-Type-Attributes.html' => 'Type-Attributes.html',\n      'Type_002dpunning.html' => 'Optimize-Options.html',\n      'Volatile.html' => 'Extended-Asm.html',\n      'Wtrigraphs.html' => 'Preprocessor-Options.html',\n      'x86-Type-Attributes.html' => 'Type-Attributes.html',\n      'x86-Variable-Attributes.html' => 'Variable-Attributes.html',\n      'x86floatingpointasmoperands.html' => 'Extended-Asm.html',\n      'x86Operandmodifiers.html' => 'Extended-Asm.html',\n\n      'Example-of-asm-with-clobbered-asm-reg.html' => 'Extended-Asm.html',\n      'Extended-asm-with-goto.html' => 'Extended-Asm.html',\n      'fdollars_002din_002didentifiers.html' => 'Preprocessor-Options.html',\n      'i386-Type-Attributes.html' => 'Variable-Attributes.html',\n      'i386-Variable-Attributes.html' => 'Variable-Attributes.html'\n    }\n\n    CPP_PATHS = {\n      'dashMF.html' => 'Invocation.html',\n      'fdollars_002din_002didentifiers.html' => 'Invocation.html',\n      'Identifier-characters.html' => 'Implementation_002ddefined-behavior.html',\n      'trigraphs.html' => 'Initial-processing.html',\n      'Wtrigraphs.html' => 'Invocation.html'\n    }\n\n    version '14' do\n      self.release = '14.2.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/\"\n    end\n\n    version '14 CPP' do\n      self.release = '14.2.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/\"\n    end\n\n    version '13' do\n      self.release = '13.3.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/\"\n    end\n\n    version '13 CPP' do\n      self.release = '13.3.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/\"\n    end\n\n    version '12' do\n      self.release = '12.2.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/\"\n    end\n\n    version '12 CPP' do\n      self.release = '12.1.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/\"\n    end\n\n    version '11' do\n      self.release = '11.4.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/\"\n    end\n\n    version '11 CPP' do\n      self.release = '11.4.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/\"\n    end\n\n    version '10' do\n      self.release = '10.5.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/\"\n    end\n\n    version '10 CPP' do\n      self.release = '10.5.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/\"\n    end\n\n    version '9' do\n      self.release = '9.5.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/\"\n    end\n\n    version '9 CPP' do\n      self.release = '9.5.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/\"\n\n      options[:replace_paths] = CPP_PATHS\n    end\n\n    version '8' do\n      self.release = '8.5.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/\"\n    end\n\n    version '8 CPP' do\n      self.release = '8.5.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/\"\n\n      options[:replace_paths] = CPP_PATHS\n    end\n\n    version '7' do\n      self.release = '7.5.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/\"\n    end\n\n    version '7 CPP' do\n      self.release = '7.5.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/\"\n\n      options[:replace_paths] = CPP_PATHS\n    end\n\n    version '6' do\n      self.release = '6.5.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/\"\n\n      options[:root_title] = 'Using the GNU Compiler Collection (GCC)'\n    end\n\n    version '6 CPP' do\n      self.release = '6.5.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/\"\n\n      options[:replace_paths] = CPP_PATHS\n    end\n\n    version '5' do\n      self.release = '5.5.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/\"\n\n      options[:root_title] = 'Using the GNU Compiler Collection (GCC)'\n    end\n\n    version '5 CPP' do\n      self.release = '5.5.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/\"\n\n      options[:replace_paths] = CPP_PATHS\n    end\n\n    version '4' do\n      self.release = '4.9.4'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/gcc/\"\n\n      options[:root_title] = 'Using the GNU Compiler Collection (GCC)'\n    end\n\n    version '4 CPP' do\n      self.release = '4.9.4'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/cpp/\"\n\n      options[:replace_paths] = CPP_PATHS\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://gcc.gnu.org/onlinedocs/', opts)\n      label = doc.at_css('details > ul > li > a')['href'].strip\n      label.scan(/([0-9.]+)/)[2..-1][0][0]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/gnu/gnu_fortran.rb",
    "content": "module Docs\n  class GnuFortran < Gnu\n    self.name = 'GNU Fortran'\n    self.slug = 'gnu_fortran'\n    self.links = {\n      home: 'https://gcc.gnu.org/fortran/'\n    }\n\n    version '14' do\n      self.release = '14.2.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/\"\n    end\n\n    version '13' do\n      self.release = '13.3.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/\"\n    end\n\n    version '12' do\n      self.release = '12.1.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/\"\n    end\n\n    version '11' do\n      self.release = '11.1.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/\"\n    end\n\n    version '10' do\n      self.release = '10.2.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/\"\n    end\n\n    version '9' do\n      self.release = '9.3.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/\"\n    end\n\n    version '8' do\n      self.release = '8.4.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/\"\n    end\n\n    version '7' do\n      self.release = '7.3.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/\"\n    end\n\n    version '6' do\n      self.release = '6.4.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/\"\n    end\n\n    version '5' do\n      self.release = '5.4.0'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/\"\n    end\n\n    version '4' do\n      self.release = '4.9.3'\n      self.base_url = \"https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/\"\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://gcc.gnu.org/onlinedocs/', opts)\n      label = doc.at_css('details > ul > li > a')['href'].strip\n      label.scan(/([0-9.]+)/)[2..-1][0][0]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/gnu.rb",
    "content": "module Docs\n  class Gnu < FileScraper\n    self.type = 'simple'\n    self.root_path = 'index.html'\n    self.abstract = 'true'\n\n    html_filters.push 'gnu/clean_html', 'gnu/entries'\n\n    options[:skip] = %w(\n      GNU-Project.html\n      Service.html\n    )\n\n    options[:skip_patterns] = [\n      /Funding/,\n      /Projects/,\n      /Copying/,\n      /License/,\n      /Proposed/,\n      /Contribut/,\n      /Index/,\n      /\\ABug/\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; Free Software Foundation<br>\n      Licensed under the GNU Free Documentation License, Version 1.3.\n    HTML\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/gnu_cobol.rb",
    "content": "module Docs\n  class GnuCobol < UrlScraper\n    self.name = 'GnuCOBOL'\n    self.slug = 'gnu_cobol'\n    self.type = 'simple'\n    self.release = '3.1'\n    self.base_url = 'https://gnucobol.sourceforge.io/HTML/gnucobpg.html'\n    self.links = {\n      home: 'https://gnucobol.sourceforge.io/',\n      code: 'https://sourceforge.net/p/gnucobol/code/HEAD/tree/trunk/'\n    }\n\n    html_filters.push 'gnu_cobol/entries', 'gnu_cobol/clean_html'\n\n    options[:skip_links] = true\n\n    options[:attribution] = <<-HTML\n      Copyright &copy; 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc.<br>\n      Licensed under the GNU Free Documentation License.\n    HTML\n\n    def get_latest_version(opts)\n      fetch_doc('https://sourceforge.net/projects/gnucobol/files/gnucobol/', opts)\n        .css('#files_list > tbody > tr')\n        .map { |file| file['title'] }\n        .sort_by { |version| version.to_f }\n        .last\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/gnu_make.rb",
    "content": "# coding: utf-8\nmodule Docs\n  class GnuMake < FileScraper\n    self.name = 'GNU Make'\n    self.type = 'gnu_make'\n    self.slug = 'gnu_make'\n    self.release = '4.4'\n    self.base_url= 'https://www.gnu.org/software/make/manual/html_node/'\n    self.root_path = 'index.html'\n    self.links = {\n      home:'https://www.gnu.org/software/make/manual/html_node/',\n      code: 'http://git.savannah.gnu.org/cgit/make.git/'\n    }\n\n    html_filters.push 'gnu_make/entries', 'gnu_make/clean_html'\n\n    options[:skip] = [\n      'Concept-Index.html',\n      'Name-Index.html',\n      'GNU-Free-Documentation-License.html'\n    ]\n\n    options[:attribution]= <<-HTML\n      Copyright © 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Free Software Foundation, Inc. <br>\n      Licensed under the GNU Free Documentation License.\n    HTML\n\n    def get_latest_version(opts)\n      body = fetch(\"https://www.gnu.org/software/make/manual/html_node/\", opts)\n      body.scan(/version \\d*\\.?\\d*/)[0].sub('version', '')\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/gnuplot.rb",
    "content": "module Docs\n  class Gnuplot < FileScraper\n    self.type = 'gnuplot'\n    self.release = '5.4.0'\n    self.links = {\n      home: 'http://www.gnuplot.info/',\n      code: 'https://sourceforge.net/projects/gnuplot/'\n    }\n\n    self.root_path = 'nofigures.html'\n\n    html_filters.push 'gnuplot/entries', 'gnuplot/clean_html'\n\n    options[:skip_links] = false\n\n    options[:skip] = %w(\n      Copyright.html\n      External_libraries.html\n      Known_limitations.html\n      Introduction.html\n      About_this_document.html\n      New_features.html\n      Differences_from_version_4.html\n      Seeking_assistance.html\n      Gnuplot.html\n      Deprecated_syntax.html\n      Demos_Online_Examples.html\n      Terminal_types.html\n      Plotting_styles.html\n      Commands.html\n      Contents.html\n      Bugs.html\n      Index.html\n    )\n\n    options[:attribution] = <<-HTML\n      Copyright 1986 - 1993, 1998, 2004   Thomas Williams, Colin Kelley<br>\n      Distributed under the <a href=\"https://sourceforge.net/p/gnuplot/gnuplot-main/ci/master/tree/Copyright\">gnuplot license</a> (rights to distribute modified versions are withheld).\n    HTML\n\n    def get_latest_version(opts)\n      doc = fetch_doc('http://www.gnuplot.info/download.html', opts)\n      label = doc.at_css('h2').content.strip\n      label.sub(/[^0-9.]*/, '')\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/go.rb",
    "content": "module Docs\n  class Go < UrlScraper\n    self.type = 'go'\n    self.release = '1.26.0'\n    self.base_url = 'https://golang.org/pkg/'\n    self.links = {\n      home: 'https://golang.org/',\n      code: 'https://go.googlesource.com/go'\n    }\n\n    # Run godoc locally, since https://golang.org/pkg/ redirects to https://pkg.go.dev/std with rate limiting / scraping protection.\n\n    # podman run --net host --rm -it docker.io/golang:1.26.0\n    #podman# go install golang.org/x/tools/cmd/godoc@latest\n    #podman# rm -r /usr/local/go/test/\n    #podman# godoc -http 0.0.0.0:6060 -v\n\n    # or using alpine\n    # podman run --net host --rm -it alpine:latest\n    #podman# apk add curl\n    #podman# curl -LO https://go.dev/dl/go1.26.0.linux-amd64.tar.gz\n    #podman# tar xf go1.26.0.linux-amd64.tar.gz\n    #podman# go/bin/go install golang.org/x/tools/cmd/godoc@latest\n    #podman# /root/go/bin/godoc -http 0.0.0.0:6060 -v\n\n    self.base_url = 'http://localhost:6060/pkg/'\n\n    html_filters.push 'clean_local_urls'\n    html_filters.push 'go/clean_html', 'go/entries'\n    text_filters.replace 'attribution', 'go/attribution'\n\n    options[:trailing_slash] = true\n    options[:container] = '#page .container'\n    options[:skip] = %w(runtime/msan/)\n    options[:skip_patterns] = [/\\/\\//]\n\n    options[:fix_urls] = ->(url) do\n      url.sub '/pkg//', '/pkg/'\n    end\n\n    options[:attribution] = <<-HTML\n      &copy; Google, Inc.<br>\n      Licensed under the Creative Commons Attribution License 3.0.\n    HTML\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://go.dev/dl/', opts)\n      doc.at_css('.download[href]')['href'][/go1[0-9.]+[0-9]/][2..]\n    end\n\n    private\n\n    def parse(response) # Hook here because Nokogori removes whitespace from textareas\n      response.body.gsub! %r{<textarea\\ class=\"code\"[^>]*>([\\W\\w]+?)</textarea>}, '<pre class=\"code\">\\1</pre>'\n      super\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/godot.rb",
    "content": "module Docs\n  class Godot < UrlScraper\n    self.type = 'sphinx_simple'\n    self.links = {\n      home: 'https://godotengine.org/',\n      code: 'https://github.com/godotengine/godot'\n    }\n    # godot docs since 3.5 don't link everything from the index.\n    self.initial_paths = %w[\n      getting_started/introduction/index.html\n      getting_started/step_by_step/index.html\n      classes/index.html\n    ]\n\n    options[:container] = '.document > [itemprop=\"articleBody\"]'\n    options[:download_images] = false\n    options[:only_patterns] = [%r{\\Agetting_started/}, %r{\\Aclasses/}]\n\n    options[:attribution] = <<-HTML\n      &copy; 2014&ndash;present Juan Linietsky, Ariel Manzur and the Godot community<br>\n      Licensed under the Creative Commons Attribution Unported License v3.0.\n    HTML\n\n    version '4.2' do\n      self.release = '4.2.2'\n      self.base_url = \"https://docs.godotengine.org/en/#{self.version}/\"\n      html_filters.push 'godot/entries', 'godot/clean_html', 'sphinx/clean_html'\n    end\n\n    version '3.5' do\n      self.release = '3.5.3'\n      self.base_url = \"https://docs.godotengine.org/en/#{self.version}/\"\n\n      # godot 3.5 upstream docs are formatted like godot4\n      html_filters.push 'godot/entries', 'godot/clean_html', 'sphinx/clean_html'\n    end\n\n    version '3.4' do\n      self.release = '3.4.5'\n      self.base_url = \"https://docs.godotengine.org/en/#{self.version}/\"\n\n      options[:container] = '.document > [itemprop=\"articleBody\"] > section[id]'\n      html_filters.push 'godot/entries_v3', 'godot/clean_html_v3', 'sphinx/clean_html'\n    end\n\n    version '3.3' do\n      self.release = '3.3.0'\n      self.base_url = \"https://docs.godotengine.org/en/#{self.version}/\"\n      self.initial_paths = %w[/index.html]\n\n      options[:only_patterns] = [%r{\\Aclasses/}]\n      options[:container] = '.document .section'\n      html_filters.push 'godot/entries_v3', 'godot/clean_html_v3', 'sphinx/clean_html'\n    end\n\n    version '3.2' do\n      self.release = '3.2.3'\n      self.base_url = \"https://docs.godotengine.org/en/#{self.version}/\"\n      self.initial_paths = %w[/index.html]\n\n      options[:only_patterns] = [%r{\\Aclasses/}]\n      options[:container] = '.document .section'\n      html_filters.push 'godot/entries_v3', 'godot/clean_html_v3', 'sphinx/clean_html'\n    end\n\n    version '2.1' do\n      self.release = '2.1.6'\n      self.base_url = \"https://docs.godotengine.org/en/#{self.version}/\"\n      self.initial_paths = %w[/index.html]\n\n      options[:skip] = %w(classes/class_@global\\ scope.html)\n      options[:only_patterns] = [%r{\\Alearning/}, %r{\\Aclasses/}]\n      options[:container] = '.document .section'\n      html_filters.push 'godot/entries_v2', 'godot/clean_html_v2', 'sphinx/clean_html'\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('godotengine', 'godot', opts).split('-')[0]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/graphite.rb",
    "content": "module Docs\n  class Graphite < UrlScraper\n    self.type = 'graphite'\n    self.release = '1.1.4'\n    self.base_url = 'https://graphite.readthedocs.io/en/latest/'\n    self.links = {\n      home: 'https://graphiteapp.org/',\n      code: 'https://github.com/graphite-project/graphite-web'\n    }\n\n    html_filters.push 'graphite/entries', 'graphite/clean_html'\n\n    options[:container] = '.document > div'\n    options[:skip] = %w(releases.html who-is-using.html composer.html search.html py-modindex.html genindex.html)\n\n    options[:attribution] = <<-HTML\n      &copy; 2008&ndash;2012 Chris Davis<br>\n      &copy; 2011&ndash;2016 The Graphite Project<br>\n      Licensed under the Apache License, Version 2.0.\n    HTML\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://graphite.readthedocs.io/en/latest/releases.html', opts)\n      doc.at_css('#release-notes li > a').content\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/graphviz.rb",
    "content": "module Docs\n  class Graphviz < UrlScraper\n    self.name = 'Graphviz'\n    self.slug = 'graphviz'\n    self.type = 'simple'\n\n    self.links = {\n      home: 'https://www.graphviz.org/',\n      code: 'https://gitlab.com/graphviz/graphviz'\n    }\n\n    options[:container] = 'main'\n\n    # These images are too large:\n    # 980KB https://www.graphviz.org/doc/info/plugins.png\n    # 650KB https://www.graphviz.org/Gallery/twopi/twopi2.svg\n    # All other files are under 100KB\n    options[:max_image_size] = 100_000\n\n    # TODO: the UrlScraper is very unreliable on this website.\n    # I often get several errors:\n    # - SSL connect error\n    # - Failure when receiving data from the peer\n    # - was slow to process (30s)\n    # Setting a :rate_limit doesn't help.\n    # We have to figure out a more reliable solution.\n    #options[:rate_limit] = 100\n\n    options[:attribution] = <<-HTML\n      &copy; 2025 The Graphviz Authors<br>\n      Licensed under the Eclipse Public License 1.0.\n    HTML\n\n    html_filters.push 'graphviz/entries', 'graphviz/clean_html'\n\n    self.release = '14.01'\n    self.base_url = 'https://www.graphviz.org/'\n    self.root_path = 'documentation/'\n    options[:only_patterns] = [\n      /^documentation\\//,\n      /^doc\\//,\n      /^docs\\//,\n    ]\n    options[:replace_paths] = {\n      # Redirections:\n      'docs/outputs/cmap/' => 'docs/outputs/imap/',\n      'doc/info/output.html' => 'docs/outputs/',\n    }\n\n    def get_latest_version(opts)\n      tags = get_gitlab_tags('gitlab.com', 'graphviz', 'graphviz', opts)\n      tags[0]['name']\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/groovy.rb",
    "content": "module Docs\n  class Groovy < UrlScraper\n    self.type = 'groovy'\n    self.root_path = 'overview-summary.html'\n    self.links = {\n      home: 'https://groovy-lang.org/',\n      code: 'https://github.com/apache/groovy'\n    }\n\n    html_filters.push 'groovy/clean_html', 'groovy/entries'\n\n    options[:skip] = %w(\n      index-all.html\n      deprecated-list.html\n      help-doc.html\n    )\n    options[:skip_patterns] = [\n      /\\Aindex.html/\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; 2003-2022 The Apache Software Foundation<br>\n      Licensed under the Apache license.\n    HTML\n\n    version '4.0' do\n      self.release = '4.0.0'\n      self.base_url = \"https://docs.groovy-lang.org/#{self.release}/html/gapi/\"\n    end\n\n    version '3.0' do\n      self.release = '3.0.9'\n      self.base_url = \"https://docs.groovy-lang.org/#{self.release}/html/gapi/\"\n    end\n\n    version '2.5' do\n      self.release = '2.5.14'\n      self.base_url = \"https://docs.groovy-lang.org/#{self.release}/html/gapi/\"\n    end\n\n    version '2.4' do\n      self.release = '2.4.21'\n      self.base_url = \"https://docs.groovy-lang.org/#{self.release}/html/gapi/\"\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://groovy.apache.org/download.html', opts)\n      doc.at_css('#big-download-button').content.split(' ').last\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/grunt.rb",
    "content": "module Docs\n  class Grunt < UrlScraper\n    self.name = 'Grunt'\n    self.type = 'simple'\n    self.release = '1.5.0'\n    self.base_url = 'https://gruntjs.com/'\n    self.root_path = 'getting-started'\n    self.initial_paths = %w(api/grunt)\n    self.links = {\n      home: 'https://gruntjs.com/',\n      code: 'https://github.com/gruntjs/grunt'\n    }\n\n    html_filters.push 'grunt/clean_html', 'grunt/entries'\n\n    options[:only] = %w(\n      configuring-tasks\n      sample-gruntfile\n      creating-tasks\n      creating-plugins\n      using-the-cli\n      installing-grunt\n      project-scaffolding\n    )\n    options[:only_patterns] = [/\\Aapi\\//, /\\Aupgrading-/]\n\n    options[:container] = '.container > .row-fluid'\n\n    options[:attribution] = <<-HTML\n      &copy; GruntJS Team<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version('grunt-cli', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/gtk.rb",
    "content": "module Docs\n  class Gtk < UrlScraper\n    self.name = 'GTK'\n    self.slug = 'gtk'\n    self.type = 'gtk'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://www.gtk.org/',\n      code: 'https://gitlab.gnome.org/GNOME/gtk/'\n    }\n\n    html_filters.push 'gtk/entries', 'gtk/clean_html', 'title'\n\n    options[:container] = '.content'\n\n    # These are all \"index\"-ish pages with no valuable content\n    GTK3_SKIP = %w(\n      gtk.html\n        gtk-resources.html gtk-question-index.html\n      gtkobjects.html\n        Application.html Builder.html WindowWidgets.html LayoutContainers.html\n        DisplayWidgets.html ButtonWidgets.html NumericEntry.html\n        TextWidgetObjects.html TreeWidgetObjects.html MenusAndCombos.html\n        SelectorWidgets.html Ornaments.html ScrollingWidgets.html Printing.html\n        ShortcutsOverview.html MiscObjects.html AbstractObjects.html\n        PlugSocket.html RecentDocuments.html ApplicationChoosing.html\n        Gestures.html DeprecatedObjects.html\n      gtkbase.html\n      theming.html\n      migrating.html\n        ch26s02.html ch28s02.html\n      pt06.html\n      platform-support.html\n      glossary.html\n      annotation-glossary.html\n    )\n\n    GTK3_SKIP_PATTERNS = [\n      /migrating/, /checklist/, /ch30/, /ch32/, /api-index-/\n    ]\n\n    # These are all \"index\"-ish pages with no valuable content\n    GTK4_SKIP = %w(\n      gtk.html\n        gtk-resources.html gtk-question-index.html ch02s02.html\n      concepts.html\n      gtkobjects.html\n        Lists.html Trees.html Application.html Builder.html WindowWidgets.html\n        LayoutContainers.html LayoutManagers.html DisplayWidgets.html\n        MediaSupport.html ButtonWidgets.html NumericEntry.html\n        MenusAndCombos.html SelectorWidgets.html DrawingWidgets.html\n        Ornaments.html ScrollingWidgets.html Printing.html\n        ShortcutsOverview.html MiscObjects.html AbstractObjects.html\n        RecentDocuments.html ApplicationChoosing.html Gestures.html ch36.html\n        ch37.html\n      gtkbase.html\n      theming.html\n      migrating.html\n        ch41s02.html ch41s03.html\n      pt07.html\n      platform-support.html\n      api-index-full.html\n      annotation-glossary.html\n    )\n\n    GTK4_SKIP_PATTERNS = [\n      /migrating/, /ch03/, /ch09/, /ch10/\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; 2005&ndash;2020 The GNOME Project<br>\n      Licensed under the GNU Lesser General Public License version 2.1 or later.\n    HTML\n\n    version '4.0' do\n      self.release = '4.0.0'\n      self.base_url = \"https://developer-old.gnome.org/gtk4/#{self.version}/\"\n\n      options[:root_title] = 'GTK 4 Reference Manual'\n      options[:skip] = GTK4_SKIP\n      options[:skip_patterns] = GTK4_SKIP_PATTERNS\n    end\n\n    version '3.24' do\n      self.release = '3.24.24'\n      self.base_url = \"https://developer.gnome.org/gtk3/#{self.version}/\"\n\n      options[:root_title] = 'GTK+ 3 Reference Manual'\n      options[:skip] = GTK3_SKIP\n      options[:skip_patterns] = GTK3_SKIP_PATTERNS\n    end\n\n    version '3.22' do\n      self.release = '3.22.3'\n      self.base_url = \"https://developer.gnome.org/gtk3/#{self.version}/\"\n\n      options[:root_title] = 'GTK+ 3 Reference Manual'\n      options[:skip] = GTK3_SKIP\n      options[:skip_patterns] = GTK3_SKIP_PATTERNS\n    end\n\n    version '3.20' do\n      self.release = '3.20.4'\n      self.base_url = \"https://developer.gnome.org/gtk3/#{self.version}/\"\n\n      options[:root_title] = 'GTK+ 3 Reference Manual'\n      options[:skip] = GTK3_SKIP\n      options[:skip_patterns] = GTK3_SKIP_PATTERNS\n    end\n\n    def get_latest_version(opts)\n      tags = get_gitlab_tags('gitlab.gnome.org', 'GNOME', 'gtk', opts)\n      tags[0]['name']\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/hammerspoon.rb",
    "content": "module Docs\n  class Hammerspoon < UrlScraper\n    self.type = 'hammerspoon'\n    self.root_path = ''\n    self.links = {\n      home: 'https://www.hammerspoon.org',\n      code: 'https://github.com/Hammerspoon/hammerspoon'\n    }\n    self.base_url = 'https://www.hammerspoon.org/docs/'\n    self.release = '0.9.100'\n\n    html_filters.push 'hammerspoon/clean_html', 'hammerspoon/entries'\n\n    # links with no content will still render a page, this is an error in the docs\n    # (see: https://github.com/Hammerspoon/hammerspoon/pull/3579)\n    options[:skip] = ['module.lp/matrix.md']\n    options[:skip_patterns] = [\n      /LuaSkin/,\n    ]\n\n    # Hammerspoon docs don't have a license (MIT specified in the hammerspoon repo)\n    # https://github.com/Hammerspoon/hammerspoon/blob/master/LICENSE\n    options[:attribution] = <<-HTML\n      &copy; 2014–2017 Hammerspoon contributors<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('Hammerspoon', 'hammerspoon', opts)\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/handlebars.rb",
    "content": "module Docs\n  class Handlebars < UrlScraper\n    self.name = 'Handlebars.js'\n    self.slug = 'handlebars'\n    self.type = 'simple'\n    self.release = '4.0.12'\n    self.base_url = 'https://handlebarsjs.com/'\n    self.links = {\n      home: 'https://handlebarsjs.com/',\n      code: 'https://github.com/wycats/handlebars.js/'\n    }\n\n    html_filters.push 'handlebars/entries', 'handlebars/clean_html', 'title'\n\n    options[:container] = '#contents'\n    options[:root_title] = 'Handlebars.js'\n\n    options[:attribution] = <<-HTML\n      &copy; 2011&ndash;2017 by Yehuda Katz<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version('handlebars', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/hapi.rb",
    "content": "module Docs\n\n  class Hapi < UrlScraper\n    self.name = \"Hapi\"\n    self.slug = \"hapi\"\n    self.type = \"hapi\"\n    self.release = \"21.3.2\"\n    self.base_url = \"https://hapi.dev/api/?v=#{self.release}\"\n    self.links = {\n      home: \"https://hapi.dev/\",\n      code: \"https://github.com/hapijs/hapi\",\n    }\n\n    html_filters.push \"hapi/entries\", \"hapi/clean_html\"\n\n    options[:container] = '.markdown-wrapper'\n    options[:title] = \"Hapi\"\n    options[:attribution] = <<-HTML\n      Copyright &copy; 2011-2022, Project contributors Copyright &copy; 2011-2020, Sideway Inc Copyright &copy; 2011-2014, Walmart<br>\n      Copyright &copy; 2011, Yahoo Inc.<br>\n      Licensed under the BSD 3-clause License.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version(\"@hapi/hapi\", opts)\n    end\n\n    private\n\n  end\n\nend\n"
  },
  {
    "path": "lib/docs/scrapers/haproxy.rb",
    "content": "module Docs\n  class Haproxy < UrlScraper\n    self.name = 'HAProxy'\n    self.type = 'haproxy'\n    self.root_path = 'intro.html'\n    self.initial_paths = %w(intro.html configuration.html management.html)\n    self.links = {\n      home: 'https://www.haproxy.org/',\n      code: 'https://github.com/haproxy/haproxy'\n    }\n\n    html_filters.push 'haproxy/clean_html', 'haproxy/entries'\n\n    options[:container] = '#page-wrapper > .row > .col-lg-12'\n\n    options[:follow_links] = false\n\n    options[:attribution] = <<-HTML\n      &copy; 2025 Willy Tarreau, HAProxy contributors<br>\n      Licensed under the GNU General Public License version 2.\n    HTML\n\n    version '3.3' do\n      self.release = '3.3.0'\n      self.base_url = \"https://docs.haproxy.org/#{self.version}/\"\n    end\n\n    version '3.2' do\n      self.release = '3.2.9'\n      self.base_url = \"https://docs.haproxy.org/#{self.version}/\"\n    end\n\n    version '3.1' do\n      self.release = '3.1.10'\n      self.base_url = \"https://docs.haproxy.org/#{self.version}/\"\n    end\n\n    version '3.0' do\n      self.release = '3.0.12'\n      self.base_url = \"https://docs.haproxy.org/#{self.version}/\"\n    end\n\n    version '2.9' do\n      self.release = '2.9.0'\n      self.base_url = \"https://docs.haproxy.org/#{self.version}/\"\n    end\n\n    version '2.8' do\n      self.release = '2.8.0'\n      self.base_url = \"https://docs.haproxy.org/#{self.version}/\"\n    end\n\n    version '2.7' do\n      self.release = '2.7.0'\n      self.base_url = \"https://docs.haproxy.org/#{self.version}/\"\n    end\n\n    version '2.6' do\n      self.release = '2.6.5'\n      self.base_url = \"https://docs.haproxy.org/#{self.version}/\"\n    end\n\n    version '2.5' do\n      self.release = '2.5.8'\n      self.base_url = \"https://docs.haproxy.org/#{self.version}/\"\n    end\n\n    version '2.4' do\n      self.release = '2.4.18'\n      self.base_url = \"https://docs.haproxy.org/#{self.version}/\"\n    end\n\n    version '2.3' do\n      self.release = '2.3.0'\n      self.base_url = \"http://cbonte.github.io/haproxy-dconv/#{self.version}/\"\n    end\n\n    version '2.2' do\n      self.release = '2.2.5'\n      self.base_url = \"http://cbonte.github.io/haproxy-dconv/#{self.version}/\"\n    end\n\n    version '2.1' do\n      self.release = '2.1.10'\n      self.base_url = \"http://cbonte.github.io/haproxy-dconv/#{self.version}/\"\n    end\n\n    version '2.0' do\n      self.release = '2.0.19'\n      self.base_url = \"http://cbonte.github.io/haproxy-dconv/#{self.version}/\"\n    end\n\n    version '1.9' do\n      self.release = '1.9.16'\n      self.base_url = \"http://cbonte.github.io/haproxy-dconv/#{self.version}/\"\n    end\n\n    version '1.8' do\n      self.release = '1.8.27'\n      self.base_url = \"http://cbonte.github.io/haproxy-dconv/#{self.version}/\"\n    end\n\n    version '1.7' do\n      self.release = '1.7.12'\n      self.base_url = \"http://cbonte.github.io/haproxy-dconv/#{self.version}/\"\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://www.haproxy.org', opts)\n      doc.at_css('table[cols=6]').at_css('tr:not(:first-child) > td:first-child:not(:contains(\"dev\"))').content\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/haskell.rb",
    "content": "module Docs\n  class Haskell < UrlScraper\n    self.name = 'Haskell'\n    self.type = 'haskell'\n    self.root_path = 'users_guide/index.html'\n    self.initial_paths = %w(libraries/index.html)\n    self.links = {\n      home: 'https://www.haskell.org/'\n    }\n\n    html_filters.push 'haskell/entries', 'haskell/clean_html'\n\n    options[:container] = ->(filter) {filter.subpath.start_with?('users_guide') ? '.body' : '#content'}\n\n    options[:only_patterns] = [/\\Alibraries\\//, /\\Ausers_guide\\//]\n\n    options[:skip_patterns] = [\n      /-notes/,\n      /editing-guide/,\n      /src\\//,\n      /doc-index/,\n      /haskell2010/,\n      /ghc-/,\n      /Cabal-/,\n      /Compiler-Hoopl-Internals\\.html\\z/i,\n      /Control-Exception-Base\\.html\\z/i,\n      /Data-Binary-Get-Internal\\.html\\z/i,\n      /Language-Haskell-TH-Lib\\.html\\z/i,\n      /Text-PrettyPrint\\.html\\z/i,\n      /Data-OldTypeable-Internal\\.html\\z/i,\n      /Data-Typeable-Internal\\.html\\z/i,\n      /GHC-IO-Encoding-Types\\.html\\z/i,\n      /System-Posix-Process-Internals\\.html\\z/i,\n      /Data-Map-Strict-Internal\\.html\\z/i,\n      /Data-IntMap-Internal\\.html\\z/i,\n      /Data-Set-Internal\\.html\\z/i,\n      /Data-Map-Internal\\.html\\z/i,\n      /Data-Sequence-Internal\\.html\\z/i\n    ]\n\n    options[:skip] = %w(\n      users_guide/license.html\n      users_guide/genindex.html\n      users_guide/search.html\n    )\n\n    options[:attribution] = ->(filter) do\n      if filter.subpath.start_with?('users_guide')\n        <<-HTML\n          &copy; 2002&ndash;2007 The University Court of the University of Glasgow. All rights reserved.<br>\n          Licensed under the Glasgow Haskell Compiler License.\n        HTML\n      else\n        <<-HTML\n          &copy; The University of Glasgow and others<br>\n          Licensed under a BSD-style license (see top of the page).\n        HTML\n      end\n    end\n\n    version '9' do\n      self.release = '9.12.1'\n      self.base_url = \"https://downloads.haskell.org/~ghc/#{release}/docs/\"\n      options[:container] = ->(filter) {filter.subpath.start_with?('users_guide') ? '.document' : '#content'}\n    end\n\n    version '8' do\n      self.release = '8.10.2'\n      self.base_url = \"https://downloads.haskell.org/~ghc/#{release}/docs/html/\"\n    end\n\n    version '7' do\n      self.release = '7.10.3'\n      self.base_url = \"https://downloads.haskell.org/~ghc/#{release}/docs/html/\"\n      self.root_path = 'libraries/index.html'\n\n      options[:only_patterns] = [/\\Alibraries\\//]\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://www.haskell.org/ghc/download.html', opts)\n      links = doc.css('a').to_a\n      versions = links.map {|link| link.content.scan(/\\A([0-9.]+)\\Z/)}\n      versions.find {|version| !version.empty?}[0][0]\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/haxe.rb",
    "content": "module Docs\n  class Haxe < UrlScraper\n    self.name = 'Haxe'\n    self.type = 'simple'\n    self.release = '4.1.3'\n    self.base_url = 'https://api.haxe.org/'\n\n    html_filters.push 'haxe/clean_html', 'haxe/entries'\n\n    options[:container] = '.span9'\n\n    options[:attribution] = <<-HTML\n      &copy; 2005&ndash;2020 Haxe Foundation<br>\n      Licensed under a MIT license.\n    HTML\n\n    version do\n      self.links = {\n        home: 'https://haxe.org',\n        code: 'https://github.com/HaxeFoundation/haxe'\n      }\n\n      options[:skip_patterns] = [/\\A(?:cpp|cs|flash|java|js|neko|php|python|lua|hl|sys|eval)/i]\n    end\n\n    version 'C++' do\n      self.base_url = 'https://api.haxe.org/cpp/'\n    end\n\n    version 'C#' do\n      self.base_url = 'https://api.haxe.org/cs/'\n    end\n\n    version 'Flash' do\n      self.base_url = 'https://api.haxe.org/flash/'\n    end\n\n    version 'Java' do\n      self.base_url = 'https://api.haxe.org/java/'\n    end\n\n    version 'JavaScript' do\n      self.base_url = 'https://api.haxe.org/js/'\n    end\n\n    version 'Neko' do\n      self.base_url = 'https://api.haxe.org/neko/'\n    end\n\n    version 'PHP' do\n      self.base_url = 'https://api.haxe.org/php/'\n    end\n\n    version 'Lua' do\n      self.base_url = 'https://api.haxe.org/lua/'\n    end\n\n    version 'HashLink' do\n      self.base_url = 'https://api.haxe.org/hl/'\n    end\n\n    version 'Sys' do\n      self.base_url = 'https://api.haxe.org/sys/'\n    end\n\n    version 'Python' do\n      self.base_url = 'https://api.haxe.org/python/'\n    end\n\n    version 'Eval' do\n      self.base_url = 'https://api.haxe.org/eval/'\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://api.haxe.org/', opts)\n      label = doc.at_css('.container.main-content h1 > small').content\n      label.sub(/version /, '')\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/homebrew.rb",
    "content": "module Docs\n  class Homebrew < UrlScraper\n    self.name = 'Homebrew'\n    self.type = 'simple'\n    self.release = '4.6.15'\n    self.base_url = 'https://docs.brew.sh/'\n    self.links = {\n      home: 'https://brew.sh',\n      code: 'https://github.com/Homebrew/brew'\n    }\n\n    html_filters.push 'homebrew/entries', 'homebrew/clean_html'\n\n    options[:container] = ->(filter) { filter.root_page? ? '#home' : '#page' }\n\n    options[:skip_patterns] = [/maintainer/i, /core\\-contributor/i, /kickstarter/i, /governance/i]\n\n    options[:attribution] = <<-HTML\n      &copy; 2009&ndash;present Homebrew contributors<br>\n      Licensed under the BSD 2-Clause License.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('Homebrew', 'brew', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/htmx.rb",
    "content": "module Docs\n  class Htmx < UrlScraper\n    self.name = 'htmx'\n    self.type = 'simple'\n    self.slug = 'htmx'\n    self.links = {\n      home: 'https://htmx.org/',\n      code: 'https://github.com/bigskysoftware/htmx'\n    }\n    self.initial_paths = %w(reference/)\n\n    html_filters.push 'htmx/entries', 'htmx/clean_html'\n\n    options[:trailing_slash] = true\n    options[:container] = '.content'\n    options[:download_images] = false\n    options[:skip_patterns] = [\n      /\\Aessays/,\n      /\\Aexamples/,\n      /\\Amigration-guide/,\n      /\\Aposts/,\n    ]\n\n    # https://github.com/bigskysoftware/htmx/blob/master/LICENSE\n    options[:attribution] = <<-HTML\n    Licensed under the Zero-Clause BSD License.\n    HTML\n\n    version do\n      self.release = '2.0.7'\n      self.base_url = \"https://htmx.org/\"\n    end\n\n    version '1' do\n      self.release = '1.9.12'\n      self.base_url = \"https://v1.htmx.org/\"\n    end\n\n    def get_latest_version(opts)\n      get_npm_version('htmx.org', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/http.rb",
    "content": "module Docs\n  class Http < Mdn\n    include MultipleBaseUrls\n\n    # release = '2022-11-17'\n    self.name = 'HTTP'\n    self.base_urls = [\n      'https://developer.mozilla.org/en-US/docs/Web/HTTP',\n      'https://datatracker.ietf.org/doc/html/',\n    ]\n    self.links = {\n      home: 'https://developer.mozilla.org/en-US/docs/Web/HTTP',\n      code: 'https://github.com/mdn/content/tree/main/files/en-us/web/http'\n    }\n\n    options[:attribution] = <<-HTML\n      &copy; 2005&ndash;2023 MDN contributors.<br>\n      Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.\n    HTML\n\n    html_filters.push 'http/clean_html', 'http/entries', 'title'\n\n    options[:root_title] = 'HTTP'\n    options[:title] = ->(filter) do\n      filter.current_url.host == 'datatracker.ietf.org' ? false : filter.default_title\n    end\n    options[:container] = ->(filter) do\n      filter.current_url.host == 'datatracker.ietf.org' ? '.content' : Docs::Mdn.options[:container]\n    end\n    options[:skip_links] = ->(filter) do\n      filter.current_url.host == 'datatracker.ietf.org'\n    end\n    options[:replace_paths] = { '/Access_control_CORS' => '/CORS' }\n    options[:fix_urls] = ->(url) do\n      url.sub! %r{(Status/\\d\\d\\d)_[A-Z].+}, '\\1'\n      url\n    end\n\n    options[:attribution] = ->(filter) do\n      if filter.current_url.host == 'datatracker.ietf.org'\n        \"&copy; document authors. All rights reserved.\"\n      else\n        Docs::Mdn.options[:attribution]\n      end\n    end\n\n    def initial_urls\n      %w(https://developer.mozilla.org/en-US/docs/Web/HTTP\n         https://datatracker.ietf.org/doc/html/rfc4918\n         https://datatracker.ietf.org/doc/html/rfc9110\n         https://datatracker.ietf.org/doc/html/rfc9111\n         https://datatracker.ietf.org/doc/html/rfc9112\n         https://datatracker.ietf.org/doc/html/rfc9113\n         https://datatracker.ietf.org/doc/html/rfc9114\n         https://datatracker.ietf.org/doc/html/rfc5023)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/i3.rb",
    "content": "module Docs\n  class I3 < UrlScraper\n    self.name = 'i3'\n    self.type = 'simple'\n    self.slug = 'i3'\n    self.release = '4.25.1'\n    self.base_url = 'https://i3wm.org/docs/userguide.html'\n    self.links = {\n      home: 'https://i3wm.org/',\n      code: 'https://github.com/i3/i3'\n    }\n\n    html_filters.push 'i3/entries', 'title'\n\n    options[:container] = 'main'\n    options[:skip_links] = true\n\n    options[:attribution] = <<-HTML\n      &copy; 2009, Michael Stapelberg and contributors\n    HTML\n\n    def get_latest_version(opts)\n        tags = get_github_tags('i3', 'i3', opts)\n        tag = tags.find {|tag| tag['name'].start_with?('4.')}\n        tag['name']\n      end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/immutable.rb",
    "content": "module Docs\n  class Immutable < UrlScraper\n    self.name = 'Immutable.js'\n    self.slug = 'immutable'\n    self.type = 'simple'\n    self.release = '4.2.1'\n    self.base_url = \"https://immutable-js.com/docs/v#{release}/\"\n    self.links = {\n      home: 'https://immutable-js.com/',\n      code: 'https://github.com/facebook/immutable-js'\n    }\n\n    html_filters.push 'immutable/clean_html', 'immutable/entries'\n\n    options[:container] = '.docContents'\n    options[:root_title] = 'Immutable.js'\n\n    options[:trailing_slash] = true\n    options[:fix_urls] = ->(url) do\n      url.sub! '/index.html', ''\n      url.sub! '/index', ''\n      url\n    end\n\n\n    options[:attribution] = <<-HTML\n      &copy; 2014–present, Lee Byron and other contributors<br>\n      Licensed under the 3-clause BSD License.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version('immutable', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/influxdata.rb",
    "content": "module Docs\n  class Influxdata < UrlScraper\n    self.name = 'InfluxData'\n    self.type = 'simple'\n    self.release = '1.3'\n    self.base_url = 'https://docs.influxdata.com/'\n    self.links = {\n      home: 'https://www.influxdata.com/',\n      code: 'https://github.com/influxdata/influxdb'\n    }\n\n    html_filters.push 'influxdata/entries', 'influxdata/clean_html', 'title'\n\n    options[:trailing_slash] = true\n\n    options[:root_title] = 'InfluxData Documentation'\n    options[:title] = false\n\n    options[:only_patterns] = [/(telegraf|influxdb|chronograf|kapacitor)\\/v#{release}/]\n    options[:skip_patterns] = [/enterprise/]\n\n    options[:skip] = [\n      \"influxdb/v#{release}/sample_data/data_download/\",\n      \"influxdb/v#{release}/tools/grafana/\",\n      \"influxdb/v#{release}/about/\",\n      \"influxdb/v#{release}/external_resources/\",\n      \"influxdb/v#{release}/administration/security_best_practices/\"\n    ]\n\n    options[:replace_paths] = {\n      \"telegraf/v#{release}/introduction/getting-started-telegraf/\"  => \"telegraf/v#{release}/introduction/getting_started/\",\n      \"influxdb/v#{release}/write_protocols/line/\"                   => \"influxdb/v#{release}/write_protocols/line_protocol_tutorial/\",\n      \"influxdb/v#{release}/write_protocols/graphite/\"               => \"influxdb/v#{release}/tools/graphite/\",\n      \"influxdb/v#{release}/clients/api/\"                            => \"influxdb/v#{release}/tools/api_client_libraries/\",\n      \"influxdb/v#{release}/concepts/010_vs_011/\"                    => \"influxdb/v#{release}/administration/differences/\",\n      \"influxdb/v#{release}/write_protocols/write_syntax/\"           => \"influxdb/v#{release}/write_protocols/line_protocol_reference/\",\n      \"influxdb/v#{release}/write_protocols/udp/\"                    => \"influxdb/v#{release}/tools/udp/\"\n    }\n\n    options[:fix_urls] = ->(url) do\n      url.sub! %r{/influxdb/v([\\d\\.]+)/.+/influxdb/v[\\d\\.]+/}, '/influxdb/v\\1/'\n      url\n    end\n\n    options[:attribution] = <<-HTML\n      &copy; 2015 InfluxData, Inc.<br>\n      Licensed under the MIT license.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('influxdata', 'influxdb', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/jasmine.rb",
    "content": "module Docs\n  class Jasmine < UrlScraper\n    self.type = 'jasmine'\n    self.release = '4.0.0'\n    self.base_url = 'https://jasmine.github.io/api/4.0/'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://jasmine.github.io/',\n      code: 'https://github.com/jasmine/jasmine'\n    }\n\n    html_filters.push 'jasmine/clean_html', 'jasmine/entries'\n\n    options[:container] = '.main-content'\n\n    options[:attribution] = <<-HTML\n      &copy; 2008&ndash;2019 Pivotal Labs<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('jasmine', 'jasmine', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/jekyll.rb",
    "content": "module Docs\n  class Jekyll < UrlScraper\n    self.type = 'jekyll'\n    self.base_url = 'https://jekyllrb.com/docs/'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://jekyllrb.com/',\n      code: 'https://github.com/jekyll/jekyll'\n    }\n\n    html_filters.push 'jekyll/clean_html', 'jekyll/entries'\n\n    options[:trailing_slash] = true\n    options[:skip] = %w(sites/ upgrading/)\n    options[:skip_patterns] = [\n      /conduct/,\n      /history/,\n      /maintaining/,\n      /contributing/,\n    ]\n    options[:replace_paths] = {\n      'templates/' => 'liquid/'\n    }\n\n    options[:attribution] = <<-HTML\n      &copy; 2025 Jekyll Core Team and contributors<br>\n      Licensed under the MIT license.\n    HTML\n\n    version '4' do\n      self.release = '4.4.1'\n    end\n\n    version '3' do\n      self.release = '3.7.2'\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://jekyllrb.com/docs/', opts)\n      doc.at_css('.meta a').content[1..-1]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/jest.rb",
    "content": "module Docs\n  class Jest < UrlScraper\n    include MultipleBaseUrls\n    self.type = 'simple'\n    self.release = '29.0.1'\n\n    self.base_urls = [\n      'https://jestjs.io/docs/',\n      'https://jestjs.io/docs/expect'\n    ]\n\n    self.root_path = 'getting-started'\n    self.links = {\n      home: 'https://jestjs.io/',\n      code: 'https://github.com/facebook/jest'\n    }\n\n    html_filters.push 'jest/entries', 'jest/clean_html'\n\n    options[:skip_patterns] = [\n      /^next/,\n      /upgrading-to-/,\n      /\\d+\\.[x\\d]/ # avoid deprecated versions\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; 2022 Facebook, Inc.<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      doc = get_latest_github_release('facebook', 'jest', opts)\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/jinja.rb",
    "content": "module Docs\n  class Jinja < UrlScraper\n    self.type = 'sphinx'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://palletsprojects.com/p/jinja/',\n      code: 'https://github.com/pallets/jinja'\n    }\n\n    html_filters.push 'jinja/entries', 'sphinx/clean_html'\n\n    options[:container] = '.body > section'\n    options[:skip] = %w(integration/ switching/ faq/ changelog/ search/ genindex/)\n\n    options[:attribution] = <<-HTML\n      &copy; 2007&ndash;2021 Pallets<br>\n      Licensed under the BSD 3-clause License.\n    HTML\n\n    version '3.1' do\n      self.release = '3.1.x'\n      self.base_url = \"https://jinja.palletsprojects.com/en/#{self.release}/\"\n    end\n\n    version '3.0' do\n      self.release = '3.0.x'\n      self.base_url = \"https://jinja.palletsprojects.com/en/#{self.release}/\"\n    end\n\n    version '2.11' do\n      self.release = '2.11.x'\n      self.base_url = \"https://jinja.palletsprojects.com/en/#{self.release}/\"\n    end\n\n    version '2.10' do\n      self.release = '2.10.x'\n      self.base_url = \"https://jinja.palletsprojects.com/en/#{self.release}/\"\n    end\n\n    version '2.9' do\n      self.release = '2.9.x'\n      self.base_url = \"https://jinja.palletsprojects.com/en/#{self.release}/\"\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('pallets', 'jinja', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/joi.rb",
    "content": "module Docs\n\n  class Joi < UrlScraper\n    self.name = \"Joi\"\n    self.slug = \"joi\"\n    self.type = \"joi\"\n    self.release = \"17.11.0\"\n    self.base_url = \"https://joi.dev/api/?v=#{self.release}\"\n    self.links = {\n      home: \"https://joi.dev/\",\n      code: \"https://github.com/hapijs/joi\",\n    }\n\n    html_filters.push \"joi/entries\", \"joi/clean_html\"\n\n    options[:container] = '.markdown-wrapper'\n    options[:title] = \"Joi\"\n    options[:attribution] = <<-HTML\n      Copyright &copy; 2012-2022, Project contributors Copyright &copy; 2012-2022, Sideway Inc Copyright &copy; 2012-2014, Walmart<br>\n      Licensed under the BSD 3-clause License.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version(\"joi\", opts)\n    end\n\n    private\n\n  end\n\nend\n"
  },
  {
    "path": "lib/docs/scrapers/jq.rb",
    "content": "module Docs\n  class Jq < UrlScraper\n    self.name = 'jq'\n    self.slug = 'jq'\n    self.type = 'jq'\n    self.release = '1.7'\n    self.links = {\n      home: 'https://jqlang.github.io/jq/'\n    }\n\n    self.base_url = \"https://jqlang.github.io/jq/manual/v#{self.release}/index.html\"\n\n    html_filters.push 'jq/entries', 'jq/clean_html'\n\n    options[:container] = 'main'\n    options[:skip_links] = true\n\n    options[:attribution] = <<-HTML\n      &copy; 2012 Stephen Dolan<br>\n      Licensed under the <a href=\"https://creativecommons.org/licenses/by/3.0/\">Creative Commons Attribution 3.0 license</a>\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('stedolan', 'jq', opts).split('-')[1]\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/jquery/jquery.rb",
    "content": "module Docs\n  class Jquery < UrlScraper\n    self.abstract = true\n    self.type = 'jquery'\n\n    html_filters.push 'jquery/clean_html', 'title'\n\n    options[:title] = false\n    options[:container] = '#content'\n    options[:trailing_slash] = false\n    options[:skip_patterns] = [/deprecated/, /category\\/version/]\n\n    options[:attribution] = <<-HTML\n      &copy; The jQuery Foundation and other contributors<br>\n      Licensed under the MIT License.\n    HTML\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/jquery/jquery_core.rb",
    "content": "module Docs\n  class JqueryCore < Jquery\n    self.name = 'jQuery'\n    self.release = '4.0.0'\n    self.base_url = 'https://api.jquery.com/'\n    self.initial_paths = %w(/index/index)\n    self.links = {\n      home: 'https://jquery.com/',\n      code: 'https://github.com/jquery/jquery'\n    }\n\n    html_filters.insert_before 'jquery/clean_html', 'jquery_core/entries'\n\n    options[:root_title] = 'jQuery'\n\n    options[:fix_urls] = ->(url) do\n      url.sub! 'http://api.jquery.com/', 'https://api.jquery.com/'\n    end\n\n    options[:skip_patterns] += [\n      /h\\/deferred\\.reject/i,\n      /Selectors\\/odd/i,\n      /index/i\n    ]\n\n    def get_latest_version(opts)\n      get_npm_version('jquery', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/jquery/jquery_mobile.rb",
    "content": "module Docs\n  class JqueryMobile < Jquery\n    self.name = 'jQuery Mobile'\n    self.slug = 'jquerymobile'\n    self.release = '1.4.5'\n    self.base_url = 'https://api.jquerymobile.com'\n    self.root_path = '/category/all'\n\n    html_filters.insert_before 'jquery/clean_html', 'jquery_mobile/entries'\n\n    options[:root_title] = 'jQuery Mobile'\n    options[:skip] = %w(/tabs /theme)\n    options[:skip_patterns] += [/\\A\\/icons/, /cdn-cgi/i]\n    options[:replace_paths] = { '/select/' => '/selectmenu' }\n\n    options[:fix_urls] = ->(url) do\n      url.sub! 'http://api.jquerymobile.com/', 'https://api.jquerymobile.com/'\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://jquerymobile.com/', opts)\n      doc.at_css('.download-box > .download-option:last-child > span').content.sub(/Version /, '')\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/jquery/jquery_ui.rb",
    "content": "module Docs\n  class JqueryUi < Jquery\n    self.name = 'jQuery UI'\n    self.slug = 'jqueryui'\n    self.release = '1.14.1'\n    self.base_url = 'https://api.jqueryui.com'\n    self.root_path = '/category/all'\n\n    html_filters.insert_before 'jquery/clean_html', 'jquery_ui/entries'\n\n    options[:root_title] = 'jQuery UI'\n    options[:skip] = %w(/theming)\n    options[:skip_patterns] += [/\\A\\/1\\./]\n\n    options[:fix_urls] = ->(url) do\n      url.sub! 'http://api.jqueryui.com/', 'https://api.jqueryui.com/'\n    end\n\n    def get_latest_version(opts)\n      get_npm_version('jquery-ui', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/jsdoc.rb",
    "content": "module Docs\n  class Jsdoc < UrlScraper\n    self.name = 'JSDoc'\n    self.type = 'simple'\n    self.release = '3.6.7'\n    self.base_url = 'https://jsdoc.app/'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://jsdoc.app/',\n      code: 'https://github.com/jsdoc/jsdoc'\n    }\n\n    html_filters.push 'jsdoc/clean_html', 'jsdoc/entries'\n\n    options[:trailing_slash] = false\n    options[:container] = 'article'\n    options[:skip] = [\n      'about-license-jsdoc3.html'\n    ]\n    options[:attribution] = <<-HTML\n      &copy; 2011&ndash;2017 the contributors to the JSDoc 3 documentation project<br>\n      Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version('jsdoc', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/julia.rb",
    "content": "module Docs\n  class Julia < UrlScraper\n    self.links = {\n      home: 'https://julialang.org/',\n      code: 'https://github.com/JuliaLang/julia'\n    }\n\n\n    options[:attribution] = <<-HTML\n      &copy; 2009&ndash;2024 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors<br>\n      Licensed under the MIT License.\n    HTML\n\n    version '1.11' do\n      self.release = '1.11.2'\n      self.base_url = \"https://docs.julialang.org/en/v#{version}/\"\n      self.type = 'julia'\n\n      html_filters.push 'julia/entries', 'julia/clean_html'\n\n      options[:container] = '.docs-main'\n      options[:only_patterns] = [/\\Amanual\\//, /\\Abase\\//, /\\Astdlib\\//]\n    end\n\n    version '1.10' do\n      self.release = '1.10.7'\n      self.base_url = \"https://docs.julialang.org/en/v#{version}/\"\n      self.type = 'julia'\n\n      html_filters.push 'julia/entries', 'julia/clean_html'\n\n      options[:container] = '.docs-main'\n      options[:only_patterns] = [/\\Amanual\\//, /\\Abase\\//, /\\Astdlib\\//]\n    end\n\n    version '1.9' do\n      self.release = '1.9.2'\n      self.base_url = \"https://docs.julialang.org/en/v#{version}/\"\n      self.type = 'julia'\n\n      html_filters.push 'julia/entries', 'julia/clean_html'\n\n      options[:container] = '.docs-main'\n      options[:only_patterns] = [/\\Amanual\\//, /\\Abase\\//, /\\Astdlib\\//]\n    end\n\n    version '1.8' do\n      self.release = '1.8.5'\n      self.base_url = \"https://docs.julialang.org/en/v#{version}/\"\n      self.type = 'julia'\n\n      html_filters.push 'julia/entries', 'julia/clean_html'\n\n      options[:container] = '.docs-main'\n      options[:only_patterns] = [/\\Amanual\\//, /\\Abase\\//, /\\Astdlib\\//]\n    end\n\n    version '1.7' do\n      self.release = '1.7.0'\n      self.base_url = \"https://docs.julialang.org/en/v#{release}/\"\n      self.type = 'julia'\n\n      html_filters.push 'julia/entries', 'julia/clean_html'\n\n      options[:container] = '.docs-main'\n      options[:only_patterns] = [/\\Amanual\\//, /\\Abase\\//, /\\Astdlib\\//]\n    end\n\n    version '1.6' do\n      self.release = '1.6.0'\n      self.base_url = \"https://docs.julialang.org/en/v#{release}/\"\n      self.type = 'julia'\n\n      html_filters.push 'julia/entries', 'julia/clean_html'\n\n      options[:container] = '.docs-main'\n      options[:only_patterns] = [/\\Amanual\\//, /\\Abase\\//, /\\Astdlib\\//]\n    end\n\n    version '1.5' do\n      self.release = '1.5.3'\n      self.base_url = \"https://docs.julialang.org/en/v#{release}/\"\n      self.type = 'julia'\n\n      html_filters.push 'julia/entries', 'julia/clean_html'\n\n      options[:container] = '.docs-main'\n      options[:only_patterns] = [/\\Amanual\\//, /\\Abase\\//, /\\Astdlib\\//]\n    end\n\n    version '1.4' do\n      self.release = '1.4.2'\n      self.base_url = \"https://docs.julialang.org/en/v#{release}/\"\n      self.type = 'julia'\n\n      html_filters.push 'julia/entries', 'julia/clean_html'\n\n      options[:container] = '.docs-main'\n      options[:only_patterns] = [/\\Amanual\\//, /\\Abase\\//, /\\Astdlib\\//]\n    end\n\n    version '1.3' do\n      self.release = '1.3.1'\n      self.base_url = \"https://docs.julialang.org/en/v#{release}/\"\n      self.type = 'julia'\n\n      html_filters.push 'julia/entries', 'julia/clean_html'\n\n      options[:container] = '#docs'\n      options[:only_patterns] = [/\\Amanual\\//, /\\Abase\\//, /\\Astdlib\\//]\n    end\n\n    version '1.2' do\n      self.release = '1.2.0'\n      self.base_url = \"https://docs.julialang.org/en/v#{release}/\"\n      self.type = 'julia'\n\n      html_filters.push 'julia/entries', 'julia/clean_html'\n\n      options[:container] = '#docs'\n      options[:only_patterns] = [/\\Amanual\\//, /\\Abase\\//, /\\Astdlib\\//]\n    end\n\n    version '1.1' do\n      self.release = '1.1.1'\n      self.base_url = \"https://docs.julialang.org/en/v#{release}/\"\n      self.type = 'julia'\n\n      html_filters.push 'julia/entries', 'julia/clean_html'\n\n      options[:container] = '#docs'\n      options[:only_patterns] = [/\\Amanual\\//, /\\Abase\\//, /\\Astdlib\\//]\n    end\n\n    version '1.0' do\n      self.release = '1.0.4'\n      self.base_url = \"https://docs.julialang.org/en/v#{release}/\"\n      self.type = 'julia'\n\n      html_filters.push 'julia/entries', 'julia/clean_html'\n\n      options[:container] = '#docs'\n      options[:only_patterns] = [/\\Amanual\\//, /\\Abase\\//, /\\Astdlib\\//]\n    end\n\n    version '0.7' do\n      self.release = '0.7.0'\n      self.base_url = \"https://docs.julialang.org/en/v#{release}/\"\n      self.type = 'julia'\n\n      html_filters.push 'julia/entries', 'julia/clean_html'\n\n      options[:container] = '#docs'\n      options[:only_patterns] = [/\\Amanual\\//, /\\Abase\\//, /\\Astdlib\\//]\n    end\n\n    version '0.6' do\n      self.release = '0.6.2'\n      self.base_url = \"https://docs.julialang.org/en/v#{release}/\"\n      self.type = 'julia'\n\n      html_filters.push 'julia/entries', 'julia/clean_html'\n\n      options[:container] = '#docs'\n      options[:only_patterns] = [/\\Amanual\\//, /\\Astdlib\\//]\n    end\n\n    version '0.5' do\n      self.release = '0.5.2'\n      self.base_url = \"https://docs.julialang.org/en/v#{release}/\"\n      self.type = 'sphinx_simple'\n\n      html_filters.push 'julia/entries_sphinx', 'julia/clean_html_sphinx', 'sphinx/clean_html'\n\n      options[:only_patterns] = [/\\Amanual\\//, /\\Astdlib\\//]\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('JuliaLang', 'julia', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/knockout.rb",
    "content": "module Docs\n  class Knockout < UrlScraper\n    self.name = 'Knockout.js'\n    self.slug = 'knockout'\n    self.type = 'knockout'\n    self.release = '3.5.1'\n    self.base_url = 'https://knockoutjs.com/documentation/'\n    self.root_path = 'introduction.html'\n    self.links = {\n      home: 'https://knockoutjs.com/',\n      code: 'https://github.com/knockout/knockout'\n    }\n\n    html_filters.push 'knockout/clean_html', 'knockout/entries'\n\n    options[:follow_links] = ->(filter) { filter.root_page? }\n    options[:container] = ->(filter) { filter.root_page? ? '#wrapper' : '.content' }\n\n    options[:only] = %w(\n      json-data.html\n      extenders.html\n      deferred-updates.html\n      unobtrusive-event-handling.html\n      fn.html\n      microtasks.html\n      asynchronous-error-handling.html\n      amd-loading.html)\n\n    options[:only_patterns] = [\n      /observable/i,\n      /computed/i,\n      /component/i,\n      /binding/,\n      /plugin/]\n\n    options[:attribution] = <<-HTML\n      &copy; Steven Sanderson, the Knockout.js team, and other contributors<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('knockout', 'knockout', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/koa.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class Koa < Github\n    self.initial_paths = %w[\n      error-handling\n      faq\n      guide\n      koa-vs-express\n      migration\n      troubleshooting\n      api/index\n      api/context\n      api/request\n      api/response\n    ].map { |name| name + '.md' }\n\n    self.links = {\n      home: 'https://koajs.com/',\n      code: 'https://github.com/koajs/koa'\n    }\n\n    html_filters.push 'koa/clean_html', 'koa/entries'\n\n    options[:skip_patterns] = [/\\.gif/]\n    options[:trailing_slash] = false\n    options[:container] = '.markdown-body'\n\n\n\n    options[:attribution] = <<-HTML\n      &copy; 2020 Koa contributors<br>\n      Licensed under the MIT License.\n    HTML\n\n    version do\n      self.base_url = 'https://github.com/koajs/koa/blob/v3.0.0/docs'\n      self.root_path = 'api/index.md'\n      self.release = '3.0.0'\n      options[:fix_urls] = ->(url) do\n        url.sub! 'https://koajs.com/#error-handling', self.base_url + '/error-handling.md'\n        url\n      end\n    end\n    \n    version '2' do\n      self.base_url = 'https://github.com/koajs/koa/blob/v2.16.1/docs'\n      self.root_path = 'api/index.md'\n      self.release = '2.16.1'\n      options[:fix_urls] = ->(url) do\n        url.sub! 'https://koajs.com/#error-handling', self.base_url + '/error-handling.md'\n        url\n      end\n    end\n    \n    version '1' do\n      self.base_url = 'https://github.com/koajs/koa/blob/1.7.1/docs'\n      self.root_path = 'api/index.md'\n      self.release = '1.7.1'\n      options[:fix_urls] = ->(url) do\n        url.sub! 'https://koajs.com/#error-handling', self.base_url + '/error-handling.md'\n        url\n      end\n    end\n    \n    def get_latest_version(opts)\n      get_npm_version('koa', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/kotlin.rb",
    "content": "module Docs\n  class Kotlin < UrlScraper\n    self.type = 'kotlin'\n    self.base_url = 'https://kotlinlang.org/'\n    self.root_path = 'api/latest/jvm/stdlib/index.html'\n    self.links = {\n      home: 'https://kotlinlang.org/',\n      code: 'https://github.com/JetBrains/kotlin'\n    }\n\n    html_filters.push 'kotlin/entries', 'kotlin/clean_html'\n\n    options[:container] = 'article'\n    options[:only_patterns] = [/\\Adocs\\//, /\\Aapi\\/latest\\/jvm\\/stdlib\\//]\n    options[:skip_patterns] = [/stdlib\\/org\\./]\n    options[:skip] = %w(\n      api/latest/jvm/stdlib/alltypes/index.html\n      docs/\n      docs/videos.html\n      docs/events.html\n      docs/resources.html\n      docs/reference/grammar.html)\n\n    options[:fix_urls] = ->(url) do\n      url.sub! %r{/docs/reference/}, '/docs/'\n      url\n    end\n\n    options[:attribution] = <<-HTML\n      &copy; 2010&ndash;2023 JetBrains s.r.o. and Kotlin Programming Language contributors<br>\n      Licensed under the Apache License, Version 2.0.\n    HTML\n\n    version '1.9' do\n      self.release = '1.9.0'\n      self.headers = { 'User-Agent' => 'devdocs.io' , 'Cookie' => 'x-ab-test-spring-boot-learning-path=0; userToken=r33dgpe8x3q5vswekg16a'  }\n    end\n\n    version '1.8' do\n      self.release = '1.8.0'\n      self.headers = { 'User-Agent' => 'devdocs.io' , 'Cookie' => 'x-ab-test-spring-boot-learning-path=0; userToken=r33dgpe8x3q5vswekg16a'  }\n    end\n\n    version '1.7' do\n      self.release = '1.7.20'\n    end\n\n    version '1.6' do\n      self.release = '1.6.20'\n    end\n\n    version '1.4' do\n      self.release = '1.4.10'\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('JetBrains', 'kotlin', opts)\n    end\n\n    private\n\n    def process_response?(response)\n      return false unless super\n      response.body !~ /http-equiv=\"refresh\"/i\n    end\n\n    def parse(response)\n      response.body.gsub! %r{<div\\ class=\"code-block\" data-lang=\"([^\"]+)\"[^>]*>([\\W\\w]+?)</div>}, '<pre class=\"code\" data-language=\"\\1\">\\2</pre>'\n      super\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/kubectl.rb",
    "content": "module Docs\n  class Kubectl < UrlScraper\n    self.name = 'Kubectl'\n    self.type = 'kubectl'\n    self.root_path = ''\n    self.links = {\n      home: 'https://kubernetes.io/docs/reference/kubectl/',\n      code: 'https://github.com/kubernetes/kubernetes'\n    }\n    self.base_url = \"https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands\"\n\n    html_filters.push 'kubectl/entries', 'kubectl/clean_html'\n\n    options[:container] = '#page-content-wrapper'\n\n    options[:attribution] = <<-HTML\n      &copy; 2025 The Kubernetes Authors | Documentation Distributed under CC BY 4.0 <br>\n      Copyright &copy; 2025 The Linux Foundation ®. All rights reserved.\n    HTML\n\n    # latest version has a special URL that does not include the version identifier \n    version do\n      self.release = \"1.33.1\"\n      self.base_url = \"https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands\"\n    end\n\n    version '1.20' do\n      self.release = \"#{version}\"\n      self.base_url = \"https://v#{version.sub('.', '-')}.docs.kubernetes.io/docs/reference/generated/kubectl/kubectl-commands\"\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('kubernetes', 'kubernetes', opts)\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/kubernetes.rb",
    "content": "module Docs\n  class Kubernetes < UrlScraper\n    self.name = 'Kubernetes'\n    self.type = 'kubernetes'\n    self.root_path = '/'\n    self.links = {\n      home: 'https://kubernetes.io/',\n      code: 'https://github.com/kubernetes/kubernetes'\n    }\n\n    # https://kubernetes.io/docs/reference/kubernetes-api/\n    html_filters.push 'kubernetes/entries', 'kubernetes/clean_html'\n\n    options[:container] = '.td-content'\n\n    options[:attribution] = <<-HTML\n      &copy; 2025 The Kubernetes Authors | Documentation Distributed under CC BY 4.0 <br>\n      Copyright &copy; 2025 The Linux Foundation ®. All rights reserved.\n    HTML\n\n    # latest version has a special URL that does not include the version identifier \n    version do\n      self.release = \"1.33.1\"\n      self.base_url = \"https://kubernetes.io/docs/reference/kubernetes-api/\"\n    end\n\n    version '1.28' do\n      self.release = \"#{version}\"\n      self.base_url = \"https://v#{version.sub('.', '-')}.docs.kubernetes.io/docs/reference/kubernetes-api/\"\n    end\n\n    version '1.27' do\n      self.release = \"#{version}\"\n      self.base_url = \"https://v#{version.sub('.', '-')}.docs.kubernetes.io/docs/reference/kubernetes-api/\"\n    end\n\n   version '1.26' do\n      self.release = \"#{version}\"\n      self.base_url = \"https://v#{version.sub('.', '-')}.docs.kubernetes.io/docs/reference/kubernetes-api/\"\n   end\n\n   version '1.25' do\n      self.release = \"#{version}\"\n      self.base_url = \"https://v#{version.sub('.', '-')}.docs.kubernetes.io/docs/reference/kubernetes-api/\"\n   end\n\n   version '1.24' do\n      self.release = \"#{version}\"\n      self.base_url = \"https://v#{version.sub('.', '-')}.docs.kubernetes.io/docs/reference/kubernetes-api/\"\n   end\n\n    def get_latest_version(opts)\n      get_latest_github_release('kubernetes', 'kubernetes', opts)\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/laravel.rb",
    "content": "module Docs\n  class Laravel < UrlScraper\n    self.type = 'laravel'\n    self.base_url = 'https://laravel.com'\n    self.links = {\n      home: 'https://laravel.com/',\n      code: 'https://github.com/laravel/laravel'\n    }\n\n    html_filters.push 'laravel/entries', 'laravel/clean_html'\n\n    options[:container] = ->(filter) {\n      filter.subpath.start_with?('/api') ? '#content' : '#docsScreen'\n    }\n\n    options[:skip_patterns] = [\n      %r{\\A/api/[1-9]?\\d\\.[0-9x]/\\.html},\n      %r{\\A/api/[1-9]?\\d\\.[0-9x]/panel\\.html},\n      %r{\\A/api/[1-9]?\\d\\.[0-9x]/namespaces\\.html},\n      %r{\\A/api/[1-9]?\\d\\.[0-9x]/interfaces\\.html},\n      %r{\\A/api/[1-9]?\\d\\.[0-9x]/traits\\.html},\n      %r{\\A/api/[1-9]?\\d\\.[0-9x]/doc-index\\.html},\n      %r{\\A/api/[1-9]?\\d\\.[0-9x]/Illuminate\\.html},\n      %r{\\A/api/[1-9]?\\d\\.[0-9x]/search\\.html} ]\n\n    options[:attribution] = <<-HTML\n      &copy; Taylor Otwell<br>\n      Licensed under the MIT License.<br>\n      Laravel is a trademark of Taylor Otwell.\n    HTML\n\n    version '11' do\n      self.release = '11.11.1'\n      self.root_path = '/api/11.x/index.html'\n      self.initial_paths = %w(/docs/11.x/installation /api/11.x/classes.html)\n\n      options[:only_patterns] = [%r{\\A/api/11\\.x/}, %r{\\A/docs/11\\.x/}]\n\n      options[:fix_urls] = ->(url) do\n        url.sub! %r{11.x/+}, \"11.x/\"\n        url.sub! %r{#{Regexp.escape(Laravel.base_url)}/docs\\/(?![1-9]?\\d)}, \"#{Laravel.base_url}/docs/11.x/\"\n        url\n      end\n    end\n\n    version '10' do\n      self.release = '10.48.14'\n      self.root_path = '/api/10.x/index.html'\n      self.initial_paths = %w(/docs/10.x/installation /api/10.x/classes.html)\n\n      options[:only_patterns] = [%r{\\A/api/10\\.x/}, %r{\\A/docs/10\\.x/}]\n\n      options[:fix_urls] = ->(url) do\n        url.sub! %r{10.x/+}, \"10.x/\"\n        url.sub! %r{#{Regexp.escape(Laravel.base_url)}/docs\\/(?![1-9]?\\d)}, \"#{Laravel.base_url}/docs/10.x/\"\n        url\n      end\n    end\n\n    version '9' do\n      self.release = '9.52.16'\n      self.root_path = '/api/9.x/index.html'\n      self.initial_paths = %w(/docs/9.x/installation /api/9.x/classes.html)\n\n      options[:only_patterns] = [%r{\\A/api/9\\.x/}, %r{\\A/docs/9\\.x/}]\n\n      options[:fix_urls] = ->(url) do\n        url.sub! %r{9.x/+}, \"9.x/\"\n        url.sub! %r{#{Regexp.escape(Laravel.base_url)}/docs\\/(?!\\d)}, \"#{Laravel.base_url}/docs/9.x/\"\n        url\n      end\n    end\n\n    version '8' do\n      self.release = '8.83.27'\n      self.root_path = '/api/8.x/index.html'\n      self.initial_paths = %w(/docs/8.x/installation /api/8.x/classes.html)\n\n      options[:only_patterns] = [%r{\\A/api/8\\.x/}, %r{\\A/docs/8\\.x/}]\n\n      options[:fix_urls] = ->(url) do\n        url.sub! %r{8.x/+}, \"8.x/\"\n        url.sub! %r{#{Regexp.escape(Laravel.base_url)}/docs\\/(?!\\d)}, \"#{Laravel.base_url}/docs/8.x/\"\n        url\n      end\n    end\n\n    version '7' do\n      self.release = '7.30.6'\n      self.root_path = '/api/7.x/index.html'\n      self.initial_paths = %w(/docs/7.x/installation /api/7.x/classes.html)\n\n      options[:only_patterns] = [%r{\\A/api/7\\.x/}, %r{\\A/docs/7\\.x/}]\n\n      options[:fix_urls] = ->(url) do\n        url.sub! %r{7.x/+}, \"7.x/\"\n        url.sub! %r{#{Regexp.escape(Laravel.base_url)}/docs\\/(?!\\d)}, \"#{Laravel.base_url}/docs/7.x/\"\n        url\n      end\n    end\n\n    version '6' do\n      self.release = '6.20.44'\n      self.root_path = '/api/6.x/index.html'\n      self.initial_paths = %w(/docs/6.x/installation /api/6.x/classes.html)\n\n      options[:only_patterns] = [%r{\\A/api/6\\.x/}, %r{\\A/docs/6\\.x/}]\n\n      options[:fix_urls] = ->(url) do\n        url.sub! %r{6.x/+}, \"6.x/\"\n        url.sub! %r{#{Regexp.escape(Laravel.base_url)}/docs\\/(?!\\d)}, \"#{Laravel.base_url}/docs/6.x/\"\n        url\n      end\n    end\n\n    version '5.8' do\n      self.release = '5.8.38'\n      self.root_path = '/api/5.8/index.html'\n      self.initial_paths = %w(/docs/5.8/installation /api/5.8/classes.html)\n\n      options[:only_patterns] = [%r{\\A/api/5\\.8/}, %r{\\A/docs/5\\.8/}]\n\n      options[:fix_urls] = ->(url) do\n        url.sub! %r{#{Regexp.escape(Laravel.base_url)}/docs\\/(?!\\d)}, \"#{Laravel.base_url}/docs/5.8/\"\n        url\n      end\n    end\n\n    version '5.7' do\n      self.release = '5.7.7'\n      self.root_path = '/api/5.7/index.html'\n      self.initial_paths = %w(/docs/5.7/installation /api/5.7/classes.html)\n\n      options[:only_patterns] = [%r{\\A/api/5\\.7/}, %r{\\A/docs/5\\.7/}]\n\n      options[:fix_urls] = ->(url) do\n        url.sub! %r{#{Regexp.escape(Laravel.base_url)}/docs\\/(?!\\d)}, \"#{Laravel.base_url}/docs/5.7/\"\n        url\n      end\n    end\n\n    version '5.6' do\n      self.release = '5.6.33'\n      self.root_path = '/api/5.6/index.html'\n      self.initial_paths = %w(/docs/5.6/installation /api/5.6/classes.html)\n\n      options[:only_patterns] = [%r{\\A/api/5\\.6/}, %r{\\A/docs/5\\.6/}]\n\n      options[:fix_urls] = ->(url) do\n        url.sub! %r{#{Regexp.escape(Laravel.base_url)}/docs\\/(?!\\d)}, \"#{Laravel.base_url}/docs/5.6/\"\n        url\n      end\n    end\n\n    version '5.5' do\n      self.release = '5.5.28'\n      self.root_path = '/api/5.5/index.html'\n      self.initial_paths = %w(/docs/5.5/installation /api/5.5/classes.html)\n\n      options[:only_patterns] = [%r{\\A/api/5\\.5/}, %r{\\A/docs/5\\.5/}]\n\n      options[:fix_urls] = ->(url) do\n        url.sub! %r{#{Regexp.escape(Laravel.base_url)}/docs\\/(?!\\d)}, \"#{Laravel.base_url}/docs/5.5/\"\n        url\n      end\n    end\n\n    version '5.4' do\n      self.release = '5.4.30'\n      self.root_path = '/api/5.4/index.html'\n      self.initial_paths = %w(/docs/5.4/installation /api/5.4/classes.html)\n\n      options[:only_patterns] = [%r{\\A/api/5\\.4/}, %r{\\A/docs/5\\.4/}]\n\n      options[:fix_urls] = ->(url) do\n        url.sub! %r{#{Regexp.escape(Laravel.base_url)}/docs\\/(?!\\d)}, \"#{Laravel.base_url}/docs/5.4/\"\n        url\n      end\n    end\n\n    version '5.3' do\n      self.release = '5.3.30'\n      self.root_path = '/api/5.3/index.html'\n      self.initial_paths = %w(/docs/5.3/installation /api/5.3/classes.html)\n\n      options[:only_patterns] = [%r{\\A/api/5\\.3/}, %r{\\A/docs/5\\.3/}]\n\n      options[:fix_urls] = ->(url) do\n        url.sub! %r{#{Regexp.escape(Laravel.base_url)}/docs\\/(?!\\d)}, \"#{Laravel.base_url}/docs/5.3/\"\n        url\n      end\n    end\n\n    version '5.2' do\n      self.release = '5.2.31'\n      self.root_path = '/api/5.2/index.html'\n      self.initial_paths = %w(/docs/5.2/installation /api/5.2/classes.html)\n\n      options[:only_patterns] = [%r{\\A/api/5\\.2/}, %r{\\A/docs/5\\.2/}]\n\n      options[:fix_urls] = ->(url) do\n        url.sub! %r{#{Regexp.escape(Laravel.base_url)}/docs\\/(?!\\d)}, \"#{Laravel.base_url}/docs/5.2/\"\n        url\n      end\n    end\n\n    version '5.1' do\n      self.release = '5.1.33'\n      self.root_path = '/api/5.1/index.html'\n      self.initial_paths = %w(/docs/5.1/installation /api/5.1/classes.html)\n\n      options[:only_patterns] = [%r{\\A/api/5\\.1/}, %r{\\A/docs/5\\.1/}]\n\n      options[:fix_urls] = ->(url) do\n        url.sub! %r{#{Regexp.escape(Laravel.base_url)}/docs\\/(?!\\d)}, \"#{Laravel.base_url}/docs/5.1/\"\n        url\n      end\n    end\n\n\n    version '4.2' do\n      self.release = '4.2.11'\n      self.root_path = '/api/4.2/index.html'\n      self.initial_paths = %w(/docs/4.2/installation /api/4.2/classes.html)\n\n      options[:only_patterns] = [%r{\\A/api/4\\.2/}, %r{\\A/docs/4\\.2/}]\n\n      options[:fix_urls] = ->(url) do\n        url.sub! %r{#{Regexp.escape(Laravel.base_url)}/docs\\/(?!\\d)}, \"#{Laravel.base_url}/docs/4.2/\"\n        url\n      end\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('laravel', 'laravel', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/latex.rb",
    "content": "# coding: utf-8\nmodule Docs\n  class Latex < UrlScraper\n    self.name = 'LaTeX'\n    self.slug = 'latex'\n    self.type = 'simple'\n    self.release = 'May 2022'\n    self.links = {\n        home: 'https://ctan.org/pkg/latex2e-help-texinfo/'\n    }\n\n    self.base_url = 'http://latexref.xyz'\n\n    html_filters.push 'latex/entries', 'latex/clean_html'\n\n    options[:skip_patterns] = [/^\\/dev\\//, /\\.(dvi|pdf)$/]\n\n    options[:attribution] = <<-HTML\n      &copy; 2007–2018 Karl Berry<br>\n      Public Domain Software\n    HTML\n\n    def get_latest_version(opts)\n      body = fetch('https://latexref.xyz/', opts)\n      body = body.scan(/\\(\\w+\\s\\d+\\)/)[0]\n      body.sub!('(', '')\n      body.sub!(')', '')\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/leaflet.rb",
    "content": "module Docs\n  class Leaflet < UrlScraper\n    self.name = 'Leaflet'\n    self.type = 'simple'\n    self.slug = 'leaflet'\n    self.links = {\n      home: 'https://leafletjs.com/',\n      code: 'https://github.com/Leaflet/Leaflet'\n    }\n\n    html_filters.push 'leaflet/entries', 'leaflet/clean_html', 'title'\n\n    options[:container] = '.container'\n    options[:skip_links] = true\n\n    options[:attribution] = <<-HTML\n      &copy; 2010&ndash;2025 Vladimir Agafonkin<br>\n      &copy; 2010&ndash;2011, CloudMade<br>\n      Maps &copy; OpenStreetMap contributors.\n    HTML\n\n    version '2.0' do\n      self.release = '2.0.0-alpha1'\n      self.base_url = \"https://leafletjs.com/reference-2.0.0.html\"\n    end\n\n    version '1.9' do\n      self.release = '1.9.4'\n      self.base_url = \"https://leafletjs.com/reference.html\"\n    end\n\n    version '1.8' do\n      self.release = '1.8.0'\n      self.base_url = \"https://leafletjs.com/reference-#{release}.html\"\n    end\n\n    version '1.7' do\n      self.release = '1.7.1'\n      self.base_url = \"https://leafletjs.com/reference-#{release}.html\"\n    end\n\n    version '1.6' do\n      self.release = '1.6.0'\n      self.base_url = \"https://leafletjs.com/reference-#{release}.html\"\n    end\n\n    version '1.5' do\n      self.release = '1.5.1'\n      self.base_url = \"https://leafletjs.com/reference-#{release}.html\"\n    end\n\n    version '1.4' do\n      self.release = '1.4.0'\n      self.base_url = \"https://leafletjs.com/reference-#{release}.html\"\n    end\n\n    version '1.3' do\n      self.release = '1.3.4'\n      self.base_url = \"https://leafletjs.com/reference-#{release}.html\"\n    end\n\n    version '1.2' do\n      self.release = '1.2.0'\n      self.base_url = \"https://leafletjs.com/reference-#{release}.html\"\n    end\n\n    version '1.1' do\n      self.release = '1.1.0'\n      self.base_url = \"https://leafletjs.com/reference-#{release}.html\"\n    end\n\n    version '1.0' do\n      self.release = '1.0.3'\n      self.base_url = \"https://leafletjs.com/reference-#{release}.html\"\n    end\n\n    def get_latest_version(opts)\n      get_npm_version('leaflet', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/less.rb",
    "content": "module Docs\n  class Less < UrlScraper\n    self.type = 'simple'\n    self.base_url = 'http://lesscss.org'\n    self.root_path = '/features'\n    self.initial_paths = %w(/functions)\n    self.links = {\n      home: 'http://lesscss.org/',\n      code: 'https://github.com/less/less.js'\n    }\n\n    html_filters.push 'less/clean_html', 'less/entries', 'title'\n\n    options[:title] = 'Less'\n    options[:container] = 'div[role=main]'\n    options[:follow_links] = false\n    options[:trailing_slash] = false\n\n    options[:attribution] = <<-HTML\n      &copy; 2009&ndash;2020 The Core Less Team<br>\n      Licensed under the Creative Commons Attribution License 3.0.\n    HTML\n\n    version '4' do\n      self.release = '4.0.0'\n    end\n\n    version '3' do\n      self.release = '3.12.0'\n    end\n\n    version '2' do\n      self.release = '2.7.2'\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('http://lesscss.org/features/', opts)\n      label = doc.at_css('.footer-links > li').content\n      label.scan(/([0-9.]+)/)[0][0]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/liquid.rb",
    "content": "module Docs\n  class Liquid < UrlScraper\n    self.name = 'Liquid'\n    self.type = 'liquid'\n    self.base_url = 'https://shopify.github.io/liquid/'\n    self.release = '5.0.0'\n    self.links = {\n      home: 'https://shopify.github.io/liquid/',\n      code: 'https://github.com/Shopify/liquid'\n    }\n\n    html_filters.push 'liquid/entries', 'liquid/clean_html', 'title'\n\n    options[:title] = false\n    options[:root_title] = 'Liquid'\n    options[:trailing_slash] = true\n\n    options[:attribution] = <<-HTML\n      &copy; 2005, 2006 Tobias Luetke<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      tags = get_github_tags('Shopify', 'liquid', opts)\n      tags[0]['name'][1..-1]\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/lit.rb",
    "content": "module Docs\n  class Lit < UrlScraper\n    self.name = 'Lit'\n    self.slug = 'lit'\n    self.type = 'lit'\n\n    self.links = {\n      home: 'https://lit.dev/',\n      code: 'https://github.com/lit/lit/'\n    }\n\n    options[:container] = 'main'\n\n    options[:max_image_size] = 250_000\n\n    # Note: the copyright will change soon due to https://lit.dev/blog/2025-10-14-openjs/\n    options[:attribution] = <<-HTML\n      &copy; Google LLC<br>\n      Licensed under the Creative Commons Attribution 3.0 Unported License.\n    HTML\n\n    options[:fix_urls] = ->(url) do\n      # A name without any extension is assumed to be a directory.\n      # example.com/foobar -> example.com/foobar/\n      url.sub! /(\\/[-a-z0-9]+)([#?]|$)/i, '\\1/\\2'\n\n      url\n    end\n\n    # The order of the filters is important.\n    # The entries filter is applied to the raw (mostly) unmodified HTML.\n    # The clean_html filter reformats the HTML to the a more appropriate markup for devdocs.\n    html_filters.push 'lit/entries', 'lit/clean_html'\n\n    version '3' do\n      self.release = '3.3.1'\n      self.base_url = 'https://lit.dev/docs/'\n      options[:skip_patterns] = [/v\\d+\\//]\n    end\n\n    version '2' do\n      self.release = '2.8.0'\n      self.base_url = 'https://lit.dev/docs/v2/'\n    end\n\n    version '1' do\n      self.release = '1.0.1'\n      self.base_url = 'https://lit.dev/docs/v1/'\n    end\n\n    def get_latest_version(opts)\n      get_npm_version('lit', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/lodash.rb",
    "content": "module Docs\n  class Lodash < UrlScraper\n    self.name = 'lodash'\n    self.slug = 'lodash'\n    self.type = 'simple'\n    self.links = {\n      home: 'https://lodash.com/',\n      code: 'https://github.com/lodash/lodash/'\n    }\n\n    html_filters.push 'lodash/entries', 'lodash/clean_html', 'title'\n\n    options[:title] = 'lodash'\n    options[:skip_links] = true\n\n    options[:attribution] = <<-HTML\n      &copy; JS Foundation and other contributors<br>\n      Licensed under the MIT License.\n    HTML\n\n    version '4' do\n      self.release = '4.17.10'\n      self.base_url = \"https://lodash.com/docs/#{release}\"\n    end\n\n    version '3' do\n      self.release = '3.10.1'\n      self.base_url = \"https://lodash.com/docs/#{release}\"\n    end\n\n    version '2' do\n      self.release = '2.4.2'\n      self.base_url = \"https://lodash.com/docs/#{release}\"\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://lodash.com/docs/', opts)\n      doc.at_css('#version > option[selected]').content\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/love.rb",
    "content": "module Docs\n  class Love < UrlScraper\n    self.name = 'LÖVE'\n    self.slug = 'love'\n    self.type = 'love'\n    self.release = '11.3'\n    self.base_url = 'https://love2d.org/wiki/'\n    self.root_path = 'Main_Page'\n    self.initial_paths = %w(love love.audio love.data love.event love.filesystem love.font\n      love.graphics love.image love.joystick love.keyboard love.math love.mouse love.physics\n      love.sound love.system love.thread love.timer love.touch love.video love.window lua-enet\n      socket utf8)\n    self.links = {\n      home: 'https://love2d.org/',\n      code: 'https://github.com/love2d/love'\n    }\n\n    html_filters.push 'love/entries', 'love/clean_html', 'title'\n\n    options[:root_title] = 'LÖVE'\n    options[:decode_and_clean_paths] = true\n    options[:container] = '#bodyContent'\n\n    options[:skip] = %w(Getting_Started Building_LÖVE Tutorial Tutorials Game_Distribution License\n      Games Libraries Software Snippets Version_History Lovers PO2_Syndrome HSL_color Guidelines)\n\n    options[:skip_patterns] = [\n      /_\\([^\\)]+\\)\\z/, # anything_(language)\n      /\\A(Special|Category|File|Help|Template|User|Tutorial):/,\n      /\\A\\d/\n    ]\n\n    options[:replace_paths] = {\n      'Config_Files' => 'love.conf',\n      'conf.lua' => 'love.conf',\n      'enet' => 'lua-enet',\n      'ImageFormat' => 'ImageEncodeFormat'\n    }\n\n    options[:attribution] = <<-HTML\n      &copy; 2006&ndash;2020 L&Ouml;VE Development Team<br>\n      Licensed under the GNU Free Documentation License, Version 1.3.\n    HTML\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://love2d.org/wiki/Version_History', opts)\n      doc.at_css('#mw-content-text table a').content\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/lua.rb",
    "content": "module Docs\n  class Lua < UrlScraper\n    self.type = 'lua'\n    self.root_path = 'manual.html'\n    self.links = {\n      home: 'https://www.lua.org/'\n    }\n\n    html_filters.push 'lua/clean_html', 'lua/entries'\n\n    options[:skip_links] = true\n\n    options[:attribution] = <<-HTML\n      &copy; 1994&ndash;2025 Lua.org, PUC-Rio.<br>\n      Licensed under the MIT License.\n    HTML\n\n    version '5.5' do\n      self.release = '5.5.0'\n      self.base_url = 'https://www.lua.org/manual/5.5/'\n    end\n\n    version '5.4' do\n      self.release = '5.4.1'\n      self.base_url = 'https://www.lua.org/manual/5.4/'\n    end\n\n    version '5.3' do\n      self.release = '5.3.6'\n      self.base_url = 'https://www.lua.org/manual/5.3/'\n    end\n\n    version '5.2' do\n      self.release = '5.2.4'\n      self.base_url = 'https://www.lua.org/manual/5.2/'\n    end\n\n    version '5.1' do\n      self.release = '5.1.5'\n      self.base_url = 'https://www.lua.org/manual/5.1/'\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://www.lua.org/manual/', opts)\n      doc.at_css('p.menubar > a').content\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/man.rb",
    "content": "module Docs\n  class Man < FileScraper\n    self.name = 'Linux man pages'\n    self.type = 'simple'\n    self.slug = 'man'\n\t  self.base_url = \"https://man7.org/linux/man-pages/\"\n\t  self.initial_paths = %w(dir_by_project.html)\n    self.links = {\n      home: 'https://man7.org/linux/man-pages/',\n    }\n    html_filters.push 'man/entries', 'man/clean_html'\n    options[:attribution] = <<-HTML\n\t  ...\n    HTML\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/mariadb.rb",
    "content": "module Docs\n  class Mariadb < UrlScraper\n    self.name = 'MariaDB'\n    self.type = 'mariadb'\n    self.release = '11.0.2'\n    self.base_url = 'https://mariadb.com/kb/en/'\n    self.root_path = 'documentation/'\n    self.links = {\n      home: 'https://mariadb.com/',\n      code: 'https://github.com/MariaDB/server'\n    }\n\n    html_filters.insert_before 'internal_urls', 'mariadb/erase_invalid_pages'\n    html_filters.push 'mariadb/entries', 'mariadb/clean_html'\n\n    options[:rate_limit] = 200\n    options[:skip_patterns] = [\n      /\\+/,\n      /\\/ask\\//,\n      /-release-notes\\//,\n      /-changelog\\//,\n      /^documentation\\//,\n      /^mariadb-server-documentation\\//,\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; 2023 MariaDB<br>\n      Licensed under the Creative Commons Attribution 3.0 Unported License and the GNU Free Documentation License.\n    HTML\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://mariadb.com/downloads/', opts)\n      doc.at_css('#version-select-community_server > option').content.split('-')[0]\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/marionette.rb",
    "content": "module Docs\n  class Marionette < UrlScraper\n    self.name = 'Marionette.js'\n    self.slug = 'marionette'\n    self.type = 'simple'\n    self.root_path = 'index'\n    self.links = {\n      home: 'https://marionettejs.com/',\n      code: 'https://github.com/marionettejs/backbone.marionette'\n    }\n\n    html_filters.push 'marionette/clean_html'\n\n    options[:container] = '.docs__content'\n\n    options[:attribution] = <<-HTML\n      &copy; 2017 Muted Solutions, LLC<br>\n      Licensed under the MIT License.\n    HTML\n\n    version '4' do\n      self.release = '4.0.0'\n      self.base_url = \"https://marionettejs.com/docs/v#{release}/\"\n\n      html_filters.push 'marionette/entries_v3'\n    end\n\n    version '3' do\n      self.release = '3.5.1'\n      self.base_url = \"https://marionettejs.com/docs/v#{release}/\"\n\n      html_filters.push 'marionette/entries_v3'\n    end\n\n    version '2' do\n      self.release = '2.4.7'\n      self.base_url = \"https://marionettejs.com/docs/v#{release}/\"\n\n      html_filters.push 'marionette/entries_v2'\n    end\n\n    def get_latest_version(opts)\n      get_npm_version('backbone.marionette', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/markdown.rb",
    "content": "module Docs\n  class Markdown < UrlScraper\n    self.name = 'Markdown'\n    self.type = 'simple'\n    self.base_url = 'https://daringfireball.net/projects/markdown/syntax'\n\n    html_filters.push 'markdown/clean_html', 'markdown/entries'\n\n    options[:container] = '.article'\n    options[:skip_links] = true\n\n    options[:attribution] = <<-HTML\n      &copy; 2004 John Gruber<br>\n      Licensed under the BSD License.\n    HTML\n\n    def get_latest_version(opts)\n      '1.0.0'\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/matplotlib.rb",
    "content": "module Docs\n  class Matplotlib < UrlScraper\n    include MultipleBaseUrls\n\n    self.name = 'Matplotlib'\n    self.type = 'sphinx'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://matplotlib.org/',\n      code: 'https://github.com/matplotlib/matplotlib'\n    }\n\n    html_filters.push 'matplotlib/entries', 'sphinx/clean_html'\n\n    options[:container] = '.body, section'\n    options[:skip] = %w(api_changes.html tutorial.html faq.html)\n\n    options[:attribution] = <<-HTML\n      &copy; 2012&ndash;2023 Matplotlib Development Team. All rights reserved.<br>\n      Licensed under the Matplotlib License Agreement.\n    HTML\n\n    version do\n      self.release = '3.9.2'\n      self.base_urls = [\n        \"https://matplotlib.org/stable/api/\",\n        \"https://matplotlib.org/stable/mpl_toolkits/mplot3d/\",\n        \"https://matplotlib.org/stable/mpl_toolkits/axes_grid/api/\"\n      ]\n    end\n\n    version '3.8' do\n      self.release = '3.8.4'\n      self.base_urls = [\n        \"https://matplotlib.org/#{release}/api/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/\"\n      ]\n    end\n\n    version '3.7' do\n      self.release = '3.7.5'\n      self.base_urls = [\n        \"https://matplotlib.org/#{release}/api/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/\"\n      ]\n    end\n\n    version '3.6' do\n      self.release = '3.6.0'\n      self.base_urls = [\n        \"https://matplotlib.org/#{release}/api/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/\"\n      ]\n    end\n\n    version '3.5' do\n      self.release = '3.5.1'\n      self.base_urls = [\n        \"https://matplotlib.org/#{release}/api/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/\"\n      ]\n    end\n\n    version '3.4' do\n      self.release = '3.4.3'\n      self.base_urls = [\n        \"https://matplotlib.org/#{release}/api/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/\"\n      ]\n    end\n\n    version '3.3' do\n      self.release = '3.3.3'\n      self.base_urls = [\n        \"https://matplotlib.org/#{release}/api/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/\"\n      ]\n    end\n\n    version '3.2' do\n      self.release = '3.2.2'\n      self.base_urls = [\n        \"https://matplotlib.org/#{release}/api/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/\"\n      ]\n    end\n\n    version '3.1' do\n      self.release = '3.1.1'\n      self.base_urls = [\n        \"https://matplotlib.org/#{release}/api/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/\"\n      ]\n    end\n\n    version '3.0' do\n      self.release = '3.0.0'\n      self.base_urls = [\n        \"https://matplotlib.org/#{release}/api/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/\"\n      ]\n    end\n\n    version '2.2' do\n      self.release = '2.2.3'\n      self.base_urls = [\n        \"https://matplotlib.org/#{release}/api/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/\"\n      ]\n    end\n\n    version '2.1' do\n      self.release = '2.1.0'\n      self.base_urls = [\n        \"https://matplotlib.org/#{release}/api/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/\"\n      ]\n    end\n\n    version '2.0' do\n      self.release = '2.0.2'\n      self.base_urls = [\n        \"https://matplotlib.org/#{release}/api/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/\"\n      ]\n    end\n\n    version '1.5' do\n      self.release = '1.5.3'\n      self.base_urls = [\n        \"https://matplotlib.org/#{release}/api/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/mplot3d/\",\n        \"https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/\"\n      ]\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('matplotlib', 'matplotlib', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/mdn/css.rb",
    "content": "module Docs\n  class Css < Mdn\n    # release = '2025-09-15'\n    self.name = 'CSS'\n    self.base_url = 'https://developer.mozilla.org/en-US/docs/Web/CSS'\n    self.root_path = '/Reference'\n    self.links = {\n      home: 'https://developer.mozilla.org/en-US/docs/Web/CSS',\n      code: 'https://github.com/mdn/content/tree/main/files/en-us/web/css'\n    }\n\n    html_filters.push 'css/clean_html', 'css/entries'\n\n    options[:root_title] = 'CSS'\n\n    options[:skip] = %w(/CSS3 /Media/Visual /paged_media /Media/TV /Media/Tactile)\n    options[:skip] += %w(/mq-boolean /single-transition-timing-function) # bug\n    options[:skip_patterns] = [/Extensions/, /Tools/, /@media\\/-webkit/, /webkit-mask/, /-moz-system-metric/]\n\n    options[:replace_paths] = {\n      '/%3Cbasic-shape%3E' => '/basic-shape',\n      '/fallback' => '/@counter-style/fallback',\n      '/range' => '/@counter-style/range',\n      '/symbols' => '/@counter-style/symbols',\n      '/system' => '/@counter-style/system',\n      '/var' => '/var()',\n      '/element' => '/element()',\n      '/Flexbox' => '/CSS_Flexible_Box_Layout/Using_CSS_flexible_boxes',\n      '/flexbox' => '/CSS_Flexible_Box_Layout/Using_CSS_flexible_boxes',\n      '/currentColor' => '/color_value'\n    }\n\n    options[:fix_urls] = ->(url) do\n      url.sub! %r{https://developer\\.mozilla\\.org/en\\-US/docs/CSS/([\\w\\-@:])}, \"#{Css.base_url}/\\\\1\"\n      url.sub! '%3A', ':'\n      url.sub! '%40', '@'\n      url\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/mdn/dom.rb",
    "content": "module Docs\n  class Dom < Mdn\n    # release = '2025-09-15'\n    self.name = 'Web APIs'\n    self.slug = 'dom'\n    self.base_url = 'https://developer.mozilla.org/en-US/docs/Web/API'\n    self.links = {\n      home: 'https://developer.mozilla.org/en-US/docs/Web/API',\n      code: 'https://github.com/mdn/content/tree/main/files/en-us/web/api'\n    }\n\n    html_filters.push 'dom/clean_html', 'dom/entries'\n\n    options[:root_title] = 'Web APIs'\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/mdn/html.rb",
    "content": "module Docs\n  class Html < Mdn\n    prepend FixInternalUrlsBehavior\n\n    # release = '2025-09-15'\n    self.name = 'HTML'\n    self.base_url = 'https://developer.mozilla.org/en-US/docs/Web/HTML'\n    self.links = {\n      home: 'https://developer.mozilla.org/en-US/docs/Web/HTML',\n      code: 'https://github.com/mdn/content/tree/main/files/en-us/web/html'\n    }\n\n    html_filters.push 'html/clean_html', 'html/entries'\n\n    options[:root_title] = 'HTML'\n\n    options[:replace_paths] = {\n      '/Element/h1' => '/Element/Heading_Elements',\n      '/Element/h2' => '/Element/Heading_Elements',\n      '/Element/h3' => '/Element/Heading_Elements',\n      '/Element/h4' => '/Element/Heading_Elements',\n      '/Element/h5' => '/Element/Heading_Elements',\n      '/Element/h6' => '/Element/Heading_Elements',\n      '/Global_attributes/data-%2A' => '/Global_attributes/data-*' }\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/mdn/javascript.rb",
    "content": "module Docs\n  class Javascript < Mdn\n    prepend FixInternalUrlsBehavior\n    prepend FixRedirectionsBehavior\n\n    # release = '2026-03-16'\n    self.name = 'JavaScript'\n    self.base_url = 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference'\n    self.links = {\n      home: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript',\n      code: 'https://github.com/mdn/content/tree/main/files/en-us/web/javascript'\n    }\n\n    html_filters.push 'javascript/clean_html', 'javascript/entries'\n\n    options[:root_title] = 'JavaScript'\n\n    # Duplicates\n    options[:skip] = %w(\n      /Global_Objects\n      /Operators\n      /Statements)\n\n    options[:skip_patterns] = [\n      /contributors.txt/,\n      /Deprecated_and_obsolete_features/\n    ]\n\n    options[:fix_urls] = ->(url) do\n      url.sub! '%2A', '*'\n      url.sub! '%40', '@'\n      url\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/mdn/mdn.rb",
    "content": "module Docs\n  class Mdn < UrlScraper\n    self.abstract = true\n    self.type = 'mdn'\n    self.links = {\n      home: 'https://developer.mozilla.org',\n      code: 'https://github.com/mdn/content'\n    }\n\n    html_filters.insert_before 'container', 'mdn/compat_tables' # needs access to <script type=\"application/json\" id=\"hydration\">\n    html_filters.push 'mdn/clean_html'\n\n    options[:container] = '#content'\n    options[:trailing_slash] = false\n\n    options[:skip_link] = ->(link) {\n      link['title'].try(:include?, 'not yet been written'.freeze) && !link['href'].try(:include?, 'transform-function'.freeze)\n    }\n\n    options[:attribution] = <<-HTML\n      &copy; 2005&ndash;2025 MDN contributors.<br>\n      Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_commit_date('mdn', 'content', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/mdn/svg.rb",
    "content": "module Docs\n  class Svg < Mdn\n    prepend FixInternalUrlsBehavior\n    prepend FixRedirectionsBehavior\n\n    # release = '2025-09-15'\n    self.name = 'SVG'\n    self.base_url = 'https://developer.mozilla.org/en-US/docs/Web/SVG'\n    self.links = {\n      home: 'https://developer.mozilla.org/en-US/docs/Web/SVG',\n      code: 'https://github.com/mdn/content/tree/main/files/en-us/web/svg'\n    }\n\n    html_filters.push 'svg/clean_html', 'svg/entries'\n\n    options[:root_title] = 'SVG'\n\n    options[:title] = ->(filter) do\n      if filter.slug.starts_with?('Reference/Element/')\n        \"<#{filter.default_title}>\"\n      elsif filter.slug != 'Reference/Attribute' && filter.slug != 'Reference/Element'\n        filter.default_title\n      else\n        false\n      end\n    end\n\n    options[:fix_urls] = ->(url) do\n      url.sub! 'https://developer.mozilla.org/en-US/Web/SVG', Svg.base_url\n      url.sub! 'https://developer.mozilla.org/en-US/docs/SVG', Svg.base_url\n      url.sub! 'https://developer.mozilla.org/en/SVG', Svg.base_url\n      url\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/mdn/web_extensions.rb",
    "content": "module Docs\n  class WebExtensions < Mdn\n    # release = '2023-04-24'\n    self.name = 'Web Extensions'\n    self.slug = 'web_extensions'\n    self.links = {\n      home: 'https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions',\n      code: 'https://github.com/mdn/content/tree/main/files/en-us/mozilla/add-ons/webextensions'\n    }\n    self.base_url = 'https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions'\n\n    html_filters.push 'web_extensions/entries', 'web_extensions/clean_html'\n\n    options[:skip_patterns] = [\n      /\\/contributors\\.txt$/\n    ]\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/mdn/xslt_xpath.rb",
    "content": "module Docs\n  class XsltXpath < Mdn\n    # release = '2022-09-06'\n    self.name = 'XSLT & XPath'\n    self.slug = 'xslt_xpath'\n    self.base_url = 'https://developer.mozilla.org/en-US/docs/Web'\n    self.root_path = '/XSLT'\n    self.initial_paths = %w(/XPath)\n\n    html_filters.push 'xslt_xpath/clean_html', 'xslt_xpath/entries'\n\n    options[:root_title] = 'XSLT'\n\n    options[:only_patterns] = [/\\A\\/XSLT/, /\\A\\/XPath/]\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/meteor.rb",
    "content": "module Docs\n  class Meteor < UrlScraper\n    include MultipleBaseUrls\n\n    self.type = 'meteor'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://www.meteor.com/',\n      code: 'https://github.com/meteor/meteor/'\n    }\n\n    html_filters.push 'meteor/entries', 'meteor/clean_html'\n\n    options[:skip_patterns] = [/\\Av\\d/]\n    options[:skip] = %w(\n      CONTRIBUTING.html\n      CHANGELOG.html\n      using-packages.html\n      writing-packages.html\n    )\n\n    options[:fix_urls] = ->(url) {\n      url.sub! %r{\\Ahttps://docs\\.meteor\\.com/(v[\\d\\.]*\\/)?api/blaze\\.html}, 'http://blazejs.org/api/blaze.html'\n      url.sub! %r{\\Ahttps://docs\\.meteor\\.com/(v[\\d\\.]*\\/)?api/templates\\.html}, 'http://blazejs.org/api/templates.html'\n      url\n    }\n\n    options[:attribution] = <<-HTML\n      &copy; 2011&ndash;2017 Meteor Development Group, Inc.<br>\n      Licensed under the MIT License.\n    HTML\n\n    version '1.5' do\n      self.release = '1.5.2'\n      self.base_urls = ['https://docs.meteor.com/', 'https://guide.meteor.com/', 'http://blazejs.org/']\n    end\n\n    version '1.4' do\n      self.release = '1.4.4'\n      self.base_urls = ['https://guide.meteor.com/', \"https://docs.meteor.com/v#{self.release}/\", 'http://blazejs.org/']\n    end\n\n    version '1.3' do\n      self.release = '1.3.5'\n      self.base_urls = ['https://guide.meteor.com/v1.3/', \"https://docs.meteor.com/v#{self.release}/\"]\n      options[:fix_urls] = nil\n    end\n\n    def get_latest_version(opts)\n      get_npm_version('meteor', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/mkdocs/django_rest_framework.rb",
    "content": "module Docs\n  class DjangoRestFramework < Mkdocs\n    self.name = 'Django REST Framework'\n    self.release = '3.15.2'\n    self.slug = 'django_rest_framework'\n    self.base_url = 'https://www.django-rest-framework.org/'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://www.django-rest-framework.org/',\n      code: 'https://github.com/encode/django-rest-framework'\n    }\n\n    html_filters.push 'django_rest_framework/clean_html', 'django_rest_framework/entries'\n\n    options[:skip_patterns] = [\n      /\\Atopics\\//,\n      /\\Acommunity\\//,\n    ]\n\n    options[:attribution] = <<-HTML\n      Copyright &copy; 2011&ndash;present Encode OSS Ltd.<br>\n      Licensed under the BSD License.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('encode', 'django-rest-framework', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/mkdocs/mkdocs.rb",
    "content": "module Docs\n  class Mkdocs < UrlScraper\n    self.abstract = true\n    self.type = 'mkdocs'\n\n    html_filters.push 'mkdocs/clean_html'\n\n    private\n\n    def handle_response(response)\n      # Some scrapped urls don't have ending slash\n      # which leads to page duplication\n      if !response.url.path.ends_with?('/') && !response.url.path.ends_with?('index.html')\n        response.url.path << '/'\n      end\n      super\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/mocha.rb",
    "content": "module Docs\n  class Mocha < UrlScraper\n    self.type = 'simple'\n    self.release = '9.0.2'\n    self.base_url = 'https://mochajs.org/'\n    self.links = {\n      home: 'https://mochajs.org/',\n      code: 'https://github.com/mochajs/mocha'\n    }\n\n    html_filters.push 'mocha/clean_html', 'mocha/entries', 'title'\n\n    options[:container] = '#content'\n    options[:title] = 'mocha'\n    options[:skip_links] = true\n\n    options[:attribution] = <<-HTML\n      &copy; 2011&ndash;2021 JS Foundation and contributors<br>\n      Licensed under the Creative Commons Attribution 4.0 International License.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version('mocha', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/modernizr.rb",
    "content": "module Docs\n  class Modernizr < UrlScraper\n    self.name = 'Modernizr'\n    self.type = 'modernizr'\n    self.release = '3.11.3'\n    self.base_url = 'https://modernizr.com/docs/'\n    self.links = {\n      home: 'https://modernizr.com/',\n      code: 'https://github.com/Modernizr/Modernizr'\n    }\n\n    html_filters.push 'modernizr/entries', 'modernizr/clean_html', 'title'\n\n    options[:title] = 'Modernizr'\n    options[:container] = '#main'\n    options[:skip_links] = true\n\n    options[:attribution] = <<-HTML\n      &copy; 2009&ndash;2020 The Modernizr team<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version('modernizr', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/moment.rb",
    "content": "module Docs\n  class Moment < UrlScraper\n    self.name = 'Moment.js'\n    self.slug = 'moment'\n    self.type = 'moment'\n    self.release = '2.30.1'\n    self.base_url = 'https://momentjs.com'\n    self.root_path = '/docs/'\n    self.initial_paths = %w(/guides/)\n    self.links = {\n      home: 'http://momentjs.com/',\n      code: 'https://github.com/moment/moment/'\n    }\n\n    html_filters.push 'moment/clean_html', 'moment/entries', 'title'\n\n    options[:title] = 'Moment.js'\n    options[:container] = '.docs-content'\n    options[:skip_links] = true\n\n    options[:attribution] = <<-HTML\n      &copy; JS Foundation and other contributors<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_github_tags('moment', 'moment', opts)[0]['name']\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/moment_timezone.rb",
    "content": "module Docs\n  class MomentTimezone < UrlScraper\n    self.name = 'Moment.js Timezone'\n    self.slug = 'moment_timezone'\n    self.type = 'moment'\n    self.release = '0.6.0'\n    self.base_url = 'https://momentjs.com/timezone'\n    self.root_path = '/docs/'\n    self.initial_paths = %w(/docs/)\n    self.links = {\n      home: 'https://momentjs.com/timezone/',\n      code: 'https://github.com/moment/moment-timezone/'\n    }\n\n    html_filters.push 'moment/clean_html', 'moment_timezone/entries', 'title'\n\n    options[:title] = 'Moment.js Timezone'\n    options[:container] = '.docs-content'\n    options[:skip_links] = true\n\n    options[:attribution] = <<-HTML\n      &copy; JS Foundation and other contributors<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_github_tags('moment', 'moment-timezone', opts)[0]['name']\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/mongoose.rb",
    "content": "module Docs\n  class Mongoose < UrlScraper\n    self.name = 'Mongoose'\n    self.type = 'simple'\n    self.release = '7.4.1'\n    self.base_url = 'https://mongoosejs.com/docs/'\n    self.root_path = 'index.html'\n    self.initial_paths = %w(guide.html)\n    self.force_gzip = true\n    self.links = {\n      home: 'http://mongoosejs.com/',\n      code: 'https://github.com/Automattic/mongoose'\n    }\n\n    html_filters.push 'mongoose/clean_html', 'mongoose/entries'\n\n    options[:container] = '#content'\n\n    options[:skip] = %w(\n      api.html\n      faq.html\n      prior.html\n      migration.html\n      plugins)\n\n    options[:attribution] = <<-HTML\n      &copy; 2010 LearnBoost<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://mongoosejs.com/docs/', opts)\n      label = doc.at_css('.pure-menu-link').content.strip\n      label.sub(/Version /, '')\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/nextjs.rb",
    "content": "module Docs\n    class Nextjs < UrlScraper\n        self.name = 'Next.js'\n        self.slug = 'nextjs'\n        self.type = 'simple'\n        self.release = '14.2.4'\n        self.base_url = 'https://nextjs.org/docs'\n        self.initial_paths = %w(reference/)\n        self.links = {\n          home: 'https://www.nextjs.org/',\n          code: 'https://github.com/vercel/next.js'\n        }\n\n        html_filters.push 'nextjs/entries', 'nextjs/clean_html'\n        options[:download_images] = false\n\n        options[:attribution] = <<-HTML\n          &copy; 2024 Vercel, Inc.<br>\n          Licensed under the MIT License.\n        HTML\n\n      def get_latest_version(opts)\n          get_npm_version('next', opts)\n      end\n    end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/nginx.rb",
    "content": "module Docs\n  class Nginx < UrlScraper\n    self.name = 'nginx'\n    self.type = 'nginx'\n    self.release = '1.29.0'\n    self.base_url = 'https://nginx.org/en/docs/'\n    self.links = {\n      home: 'https://nginx.org/',\n      code: 'https://hg.nginx.org/nginx'\n    }\n\n    html_filters.push 'nginx/clean_html', 'nginx/entries'\n\n    options[:container] = '#content'\n\n    options[:skip] = %w(\n      contributing_changes.html\n      dirindex.html\n      varindex.html)\n\n    options[:skip_patterns] = [/\\/faq\\//]\n\n    # http://nginx.org/LICENSE\n    options[:attribution] = <<-HTML\n      &copy; 2002-2021 Igor Sysoev<br>\n      &copy; 2011-2025 Nginx, Inc.<br>\n      Licensed under the BSD License.\n    HTML\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://nginx.org/en/download.html', opts)\n      table = doc.at_css('#content > table').inner_html\n      table.scan(/nginx-([0-9.]+)</)[0][0]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/nginx_lua_module.rb",
    "content": "module Docs\n  class NginxLuaModule < Github\n    self.name = 'nginx / Lua Module'\n    self.slug = 'nginx_lua_module'\n    self.release = '0.10.28'\n    self.base_url = \"https://github.com/openresty/lua-nginx-module/blob/v#{self.release}/\"\n    self.root_path = 'README.markdown'\n    self.links = {\n      code: 'https://github.com/openresty/lua-nginx-module'\n    }\n\n    html_filters.push 'nginx_lua_module/clean_html', 'nginx_lua_module/entries', 'title'\n\n    options[:root_title] = 'ngx_http_lua_module'\n    options[:container] = '.markdown-body'\n    options[:max_image_size] = 256_000\n    options[:attribution] = <<-HTML\n      &copy; 2009&ndash;2017 Xiaozhe Wang (chaoslawful)<br>\n      &copy; 2009&ndash;2019 Yichun \"agentzh\" Zhang (章亦春), OpenResty Inc.<br>\n      Licensed under the BSD License.\n    HTML\n    options[:skip_patterns] = [/\\.png/]\n\n    def get_latest_version(opts)\n      tags = get_github_tags('openresty', 'lua-nginx-module', opts)\n      tag = tags.find {|tag| !tag['name'].include?('rc')}\n      tag['name'][1..-1]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/nim.rb",
    "content": "module Docs\n  class Nim < UrlScraper\n    self.type = 'simple'\n    self.base_url = 'https://nim-lang.org/docs/'\n    self.root_path = 'overview.html'\n    self.links = {\n      home: 'https://nim-lang.org/',\n      code: 'https://github.com/nim-lang/Nim'\n    }\n\n    html_filters.push 'nim/entries', 'nim/clean_html'\n\n    options[:skip] = %w(theindex.html docgen.html tut1.html tut2.html tut3.html tools.html)\n\n    options[:attribution] = <<-HTML\n      &copy; 2006&ndash;2024 Andreas Rumpf<br>\n      Licensed under the MIT License.\n    HTML\n\n    version do\n      self.release = '2.2.0'\n    end\n\n    version '1' do\n      self.release = '1.4.8'\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://nim-lang.org/docs/overview.html', opts)\n      doc.at_css('.container > .docinfo > tbody > tr:last-child > td').content.strip\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/nix.rb",
    "content": "module Docs\n  class Nix < UrlScraper\n    self.type = 'simple'\n    self.release = '2.11.0'\n    self.base_url = 'https://nixos.org/manual/'\n    self.root_path = 'nix/stable/expressions/builtins.html'\n    self.initial_paths = %w(\n      nix/stable/expressions/builtins.html\n      nixpkgs/stable/index.html)\n    self.links = {\n      home: 'https://nixos.org/',\n      code: 'https://github.com/NixOS/nix'\n    }\n\n    html_filters.push 'nix/clean_html', 'nix/entries'\n\n    options[:skip_links] = true\n\n    options[:attribution] = <<-HTML\n      &copy; 2022 NixOS Contributors<br>\n      Licensed under the LGPL License.\n    HTML\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://nixos.org/manual/nix/stable/', opts)\n      doc.at_css('h1.menu-title').content.scan(/([0-9.]+)/).first.first\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/node.rb",
    "content": "module Docs\n  class Node < UrlScraper\n    self.name = 'Node.js'\n    self.slug = 'node'\n    self.type = 'node'\n    self.links = {\n      home: 'https://nodejs.org/',\n      code: 'https://github.com/nodejs/node'\n    }\n\n    html_filters.push 'node/clean_html', 'node/entries', 'title'\n\n    options[:title] = false\n    options[:root_title] = 'Node.js'\n    options[:container] = '#apicontent'\n    options[:skip] = %w(index.html all.html documentation.html synopsis.html)\n    # options[:only] = ['addons.html']\n\n    options[:attribution] = <<-HTML\n      &copy; Joyent, Inc. and other Node contributors<br>\n      Licensed under the MIT License.<br>\n      Node.js is a trademark of Joyent, Inc. and is used with its permission.<br>\n      We are not endorsed by or affiliated with Joyent.\n    HTML\n\n    version do\n      self.release = '25.8.0'\n      self.base_url = 'https://nodejs.org/api/'\n    end\n\n    version '24 LTS' do\n      self.release = '24.14.0'\n      self.base_url = 'https://nodejs.org/dist/latest-v24.x/docs/api/'\n    end\n\n    version '22 LTS' do\n      self.release = '22.20.0'\n      self.base_url = 'https://nodejs.org/dist/latest-v22.x/docs/api/'\n    end\n\n    version '20 LTS' do\n      self.release = '20.19.5'\n      self.base_url = 'https://nodejs.org/dist/latest-v20.x/docs/api/'\n    end\n\n    version '18 LTS' do\n      self.release = '18.18.0'\n      self.base_url = 'https://nodejs.org/dist/latest-v18.x/docs/api/'\n    end\n\n    version '16 LTS' do\n      self.release = '16.13.2'\n      self.base_url = 'https://nodejs.org/dist/latest-v16.x/docs/api/'\n    end\n\n    version '14 LTS' do\n      self.release = '14.17.0'\n      self.base_url = 'https://nodejs.org/dist/latest-v14.x/docs/api/'\n    end\n\n    version '12 LTS' do\n      self.release = '12.22.1'\n      self.base_url = 'https://nodejs.org/dist/latest-v12.x/docs/api/'\n      html_filters.replace('node/entries', 'node/old_entries')\n    end\n\n    version '10 LTS' do\n      self.release = '10.24.1'\n      self.base_url = 'https://nodejs.org/dist/latest-v10.x/docs/api/'\n      html_filters.replace('node/entries', 'node/old_entries')\n    end\n\n    version '8 LTS' do\n      self.release = '8.17.0'\n      self.base_url = 'https://nodejs.org/dist/latest-v8.x/docs/api/'\n      html_filters.replace('node/entries', 'node/old_entries')\n    end\n\n    version '6 LTS' do\n      self.release = '6.17.1'\n      self.base_url = 'https://nodejs.org/dist/latest-v6.x/docs/api/'\n      html_filters.replace('node/entries', 'node/old_entries')\n    end\n\n    version '4 LTS' do\n      self.release = '4.9.1'\n      self.base_url = 'https://nodejs.org/dist/latest-v4.x/docs/api/'\n      html_filters.replace('node/entries', 'node/old_entries')\n    end\n\n    def get_latest_version(opts)\n      tags = get_github_tags('nodejs', 'node', opts)\n      tags[0]['name'][1..-1]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/nokogiri2.rb",
    "content": "module Docs\n  class Nokogiri2 < Rdoc\n    # Instructions:\n    #   1. Download the latest release at https://github.com/sparklemotion/nokogiri/releases\n    #   2. Run \"bundle install && bundle exec rake docs\" (in the Nokogiri directory)\n    #   4. Copy the \"doc\" directory to \"docs/nokogiri\"\n\n    self.name = 'Nokogiri'\n    self.slug = 'nokogiri'\n    self.release = '1.14.2'\n    self.base_url = \"https://nokogiri.org/rdoc/\"\n\n    html_filters.replace 'rdoc/entries', 'nokogiri2/entries'\n\n    options[:root_title] = 'Nokogiri'\n    options[:only_patterns] = [/\\ANokogiri/, /\\AXSD/]\n\n    options[:attribution] = <<-HTML\n      &copy; 2008&ndash;2023 by Mike Dalessio, Aaron Patterson, Yoko Harada, Akinori MUSHA, John Shahid,<br>\n      Karol Bucek, Sam Ruby, Craig Barnes, Stephen Checkoway, Lars Kanis, Sergio Arbeo,<br>\n      Timothy Elliott, Nobuyoshi Nakada, Charles Nutter, Patrick Mahoney\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('sparklemotion', 'nokogiri', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/npm.rb",
    "content": "module Docs\n  class Npm < UrlScraper\n    self.name = 'npm'\n    self.type = 'npm'\n    self.release = '10.2.5'\n    self.base_url = 'https://docs.npmjs.com/'\n    self.force_gzip = true\n    self.links = {\n      home: 'https://www.npmjs.com/',\n      code: 'https://github.com/npm/npm'\n    }\n\n    html_filters.push 'npm/entries', 'npm/clean_html'\n\n    options[:download_images] = false\n    # options[:max_image_size] = 130_000\n\n    options[:skip] = [\n      'all',\n      'misc/index',\n      'cli',\n      'organizations/',\n      'orgs',\n      'removing-members-from-your-org',\n      'adding-members-to-your-org',\n      'downloading-and-installing-packages',\n    ]\n\n    options[:skip_patterns] = [\n      /\\Aenterprise/,\n      /\\Acompany/,\n      /\\Apolicies/,\n      /cli\\/v6/,\n      /cli\\/v7/,\n      /cli\\/v8/,\n      /cli\\/v9/,\n      /\\/\\Z/ # avoid pages with a trailing slash, those pages mess up the entries\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; npm, Inc. and Contributors<br>\n      Licensed under the npm License.<br>\n      npm is a trademark of npm, Inc.\n    HTML\n\n    #  fix duplicates\n    options[:fix_urls] = -> (url) do\n      url.sub!('private-modules/intro', 'creating-and-publishing-private-packages')\n      url.sub!('managing-team-access-to-packages', 'managing-team-access-to-organization-packages')\n      url.sub!('accepting-or-rejecting-an-org-invitation', 'accepting-or-rejecting-an-organization-invitation')\n      url.sub!('org-roles-and-permissions', 'organization-roles-and-permissions')\n      url.sub!('upgrading-to-a-paid-org-plan', 'upgrading-to-a-paid-organization-plan')\n      url.sub!('managing-team-access-to-org-packages', 'managing-team-access-to-organization-packages')\n      url.sub!('about-package-json-and-package-lock-json-files', 'creating-a-package-json-file')\n      url.sub!('cli/npm', 'cli/v8/commands/npm/')\n      url.sub!('cli/config', 'cli/v8/commands/npm-config/')\n      url.sub!('misc/registry', 'cli/v8/using-npm/registry/')\n      url.sub!('cli-documentation', 'cli/v8')\n      url.sub!('cli-documentation/files/npmrc', 'cli/v8/configuring-npm/npmrc/')\n      url.sub!('configuring-your-registry-settings-as-an-npm-enterprise-user', 'enterprise')\n      url.sub!('cli/publish', 'cli/v8/commands/npm-publish/')\n      url.sub!('cli/deprecate', 'cli/v8/commands/npm-deprecate/')\n      url.sub!('cli/access', 'cli/v8/commands/npm-access/')\n      url.sub!('cli/adduser', 'cli/v8/commands/npm-adduser/')\n      url.sub!('misc/config', 'cli/v8/using-npm/config/')\n      url.sub!('cli/token', 'cli/v8/commands/npm-token/')\n      url.sub!('cli/unpublish', 'cli/v8/commands/npm-unpublish/')\n      url.sub!('files/package.json', 'cli/v8/configuring-npm/package-json/')\n      url.sub!('cli/profile', 'cli/v8/commands/npm-profile/')\n      url.sub!('creating-a-packge-json-file', 'cli/v8/configuring-npm/package-json/')\n      url.sub!('cli/dist-tag', 'cli/v8/commands/npm-dist-tag/')\n      url.sub!('cli/team', 'cli/v8/commands/npm-team/')\n      url.sub!('cli/version', 'cli/v8/commands/npm-version/')\n      url.sub!('cli/owner', 'cli/v8/commands/npm-owner/')\n      url.sub!('cli/install', '/cli/v8/commands/npm-install/')\n      url.sub!('cli/audit', 'cli/v8/commands/npm-audit/')\n      url.sub!('cli/update', 'cli/v8/commands/npm-update/')\n      url.sub!('cli/outdated', 'cli/v8/commands/npm-outdated/')\n      url.sub!('cli/uninstall', 'cli/v8/commands/npm-uninstall/')\n      url.sub!('misc/developers', 'cli/v8/using-npm/developers//')\n      url\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('npm', 'cli', opts)\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/numpy.rb",
    "content": "module Docs\n  # Requires downloading the documents to local disk first.\n  # Go to https://numpy.org/doc/, click \"HTML+zip\" to download\n  # (example url: https://numpy.org/doc/2.4/numpy-html.zip),\n  # then extract into \"docs/numpy~#{version}/\"\n  class Numpy < FileScraper\n    self.name = 'NumPy'\n    self.type = 'sphinx'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://www.numpy.org/',\n      code: 'https://github.com/numpy/numpy'\n    }\n\n    html_filters.push 'numpy/entries', 'numpy/clean_html', 'sphinx/clean_html'\n\n    # .main contains more than the page's content alone, but we need something\n    # that includes the navigation bar as well in order to guess the type of\n    # most pages.\n    options[:container] = '.main'\n\n    options[:skip_patterns] = [\n      /.*(?<!\\.html)\\z/,\n      /\\Arelease\\/.*-notes.html\\Z/,\n      /\\Agenerated\\/numpy\\.chararray\\.[\\w\\-]+.html\\z/ # duplicate\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; 2005&ndash;2024 NumPy Developers<br>\n      Licensed under the 3-clause BSD License.\n    HTML\n\n    version '2.4' do\n      self.release = '2.4'\n      self.base_url = \"https://numpy.org/doc/#{self.version}/\"\n      options[:container] = nil\n    end\n\n    version '2.2' do\n      self.release = '2.2'\n      self.base_url = \"https://numpy.org/doc/#{self.version}/\"\n      options[:container] = nil\n    end\n\n    version '2.1' do\n      self.release = '2.1'\n      self.base_url = \"https://numpy.org/doc/#{self.version}/\"\n      options[:container] = nil\n    end\n\n    version '2.0' do\n      self.release = '2.0.1'\n      self.base_url = \"https://numpy.org/doc/#{self.version}/\"\n      options[:container] = nil\n    end\n\n    version '1.23' do\n      self.release = '1.23.0'\n      self.base_url = \"https://numpy.org/doc/#{self.version}/\"\n      options[:container] = nil\n    end\n\n    version '1.22' do\n      self.release = '1.22.4'\n      self.base_url = \"https://numpy.org/doc/#{self.version}/\"\n      options[:container] = nil\n    end\n\n    version '1.21' do\n      self.release = '1.21.6'\n      self.base_url = \"https://numpy.org/doc/#{self.version}/\"\n      options[:container] = nil\n    end\n\n    version '1.20' do\n      self.release = '1.20.3'\n      self.base_url = \"https://numpy.org/doc/#{self.version}/\"\n      options[:container] = nil\n    end\n\n    version '1.19' do\n      self.release = '1.19.0'\n      self.base_url = \"https://numpy.org/doc/#{self.version}/\"\n    end\n\n    version '1.18' do\n      self.release = '1.18.5'\n      self.base_url = \"https://numpy.org/doc/#{self.version}/\"\n    end\n\n    version '1.17' do\n      self.release = '1.17.0'\n      self.base_url = \"https://docs.scipy.org/doc/numpy-#{self.release}/reference/\"\n    end\n\n    version '1.16' do\n      self.release = '1.16.1'\n      self.base_url = \"https://docs.scipy.org/doc/numpy-#{self.release}/reference/\"\n    end\n\n    version '1.15' do\n      self.release = '1.15.4'\n      self.base_url = \"https://docs.scipy.org/doc/numpy-#{self.release}/reference/\"\n    end\n\n    version '1.14' do\n      self.release = '1.14.5'\n      self.base_url = \"https://docs.scipy.org/doc/numpy-#{self.release}/reference/\"\n    end\n\n    version '1.13' do\n      self.release = '1.13.0'\n      self.base_url = \"https://docs.scipy.org/doc/numpy-#{self.release}/reference/\"\n    end\n\n    version '1.12' do\n      self.release = '1.12.0'\n      self.base_url = \"https://docs.scipy.org/doc/numpy-#{self.release}/reference/\"\n    end\n\n    version '1.11' do\n      self.release = '1.11.0'\n      self.base_url = \"https://docs.scipy.org/doc/numpy-#{self.release}/reference/\"\n    end\n\n    version '1.10' do\n      self.release = '1.10.4'\n      self.base_url = \"https://docs.scipy.org/doc/numpy-#{self.release}/reference/\"\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('numpy', 'numpy', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/nushell.rb",
    "content": "module Docs\n\n  class Nushell < UrlScraper\n    include MultipleBaseUrls\n\n    self.name = \"Nushell\"\n    self.slug = \"nushell\"\n    self.type = \"nushell\"\n    self.release = \"0.85.0\"\n    self.links = {\n      home: \"https://www.nushell.sh/\",\n      code: \"https://github.com/nushell/nushell\",\n    }\n\n    html_filters.push \"nushell/clean_html\", \"nushell/entries\", \"nushell/fix_links\"\n\n    options[:container] = '.theme-container'\n    options[:follow_links] = true\n    options[:title] = \"Nushell\"\n    options[:attribution] = <<-HTML\n      Copyright &copy; 2019–2023 The Nushell Project Developers\n      Licensed under the MIT License.\n    HTML\n\n    # latest version has a special URL that does not include the version identifier\n    version do\n      self.base_urls = [\n        \"https://www.nushell.sh/book/\",\n        \"https://www.nushell.sh/commands/\"\n      ]\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('nushell', 'nushell', opts)\n    end\n\n  end\n\nend\n"
  },
  {
    "path": "lib/docs/scrapers/ocaml.rb",
    "content": "module Docs\n  class Ocaml < UrlScraper\n    self.name = 'OCaml'\n    self.type = 'ocaml'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://ocaml.org/',\n      code: 'https://github.com/ocaml/ocaml'\n    }\n\n    html_filters.push 'ocaml/entries', 'ocaml/clean_html'\n\n    options[:skip] = %w(\n      libref/index.html\n    )\n\n    options[:skip_patterns] = [\n      /\\Acompilerlibref\\//,\n      /\\Aapi\\/type_/,\n      /\\Aapi\\/Stdlib\\.\\w+\\.html/,\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; 1995-2025 INRIA.\n    HTML\n\n    version '' do\n      self.release = '5.4'\n      self.base_url = \"https://ocaml.org/manual/#{self.release}/\"\n    end\n\n    version '5.0' do\n      self.release = '5.0'\n      self.base_url = \"https://v2.ocaml.org/releases/#{self.release}/htmlman/\"\n    end\n\n    version '4.14' do\n      self.release = '4.14'\n      self.base_url = \"https://v2.ocaml.org/releases/#{self.release}/htmlman/\"\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('ocaml', 'ocaml', opts)\n    end\n\n    private\n\n    def parse(response) # Hook here because Nokogori removes whitespace from code fragments\n      response.body.gsub! %r{<div\\ class=\"pre([^\"]*)\"[^>]*>([\\W\\w]+?)</div>}, '<pre class=\"\\1\">\\2</pre>'\n      super\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/octave.rb",
    "content": "module Docs\n  class Octave < UrlScraper\n    self.name = 'Octave'\n    self.type = 'octave'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://www.octave.org/',\n      code: 'https://www.octave.org/hg/octave'\n    }\n\n    html_filters.push 'octave/clean_html', 'octave/entries', 'title'\n\n    options[:skip] = %w(\n      Copying.html\n      Preface.html\n      Acknowledgements.html\n      Citing-Octave-in-Publications.html\n      How-You-Can-Contribute-to-Octave.html\n      Distribution.html)\n\n    options[:title] = false\n\n    options[:root_title] = 'GNU Octave'\n\n    options[:attribution] = <<-HTML\n      &copy; 1996–2025 The Octave Project Developers<br>\n      Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice are preserved on all copies.<br/>\n      Permission is granted to copy and distribute modified versions of this manual under the conditions for verbatim copying, provided that the entire resulting derived work is distributed under the terms of a permission notice identical to this one.</br>\n      Permission is granted to copy and distribute translations of this manual into another language, under the above conditions for modified versions.\n    HTML\n\n    version '10' do\n      self.release = '10.1.0'\n      self.base_url = \"https://docs.octave.org/v#{self.release}/\"\n    end\n\n    version '9' do\n      self.release = '9.2.0'\n      self.base_url = \"https://docs.octave.org/v#{self.release}/\"\n    end\n\n    version '8' do\n      self.release = '8.1.0'\n      self.base_url = \"https://docs.octave.org/v#{self.release}/\"\n    end\n\n    version '7' do\n      self.release = '7.2.0'\n      self.base_url = \"https://docs.octave.org/v#{self.release}/\"\n    end\n\n    version '6' do\n      self.release = '6.4.0'\n      self.base_url = \"https://docs.octave.org/v#{self.release}/\"\n    end\n\n    version '5' do\n      self.release = '5.2.0'\n      self.base_url = \"https://docs.octave.org/v#{self.release}/\"\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://octave.org/doc/interpreter/', opts)\n      doc.at_css('#SEC_Top + p').content.scan(/([0-9.]+)/)[1][0][0..-2]\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/opengl.rb",
    "content": "module Docs\n  class Opengl < FileScraper\n    self.type = 'simple'\n    self.name = 'OpenGL'\n    self.root_path = 'index.php'\n    self.links = {\n      home: 'https://registry.khronos.org/OpenGL-Refpages/'\n    }\n    html_filters.push 'opengl/entries', 'opengl/clean_html'\n\n    # indexflat.php is a copy of index.php\n    options[:skip] = %w(indexflat.php)\n\n    options[:attribution] = ->(filter) {\n      # copyright is the last section in these pages\n      return filter.css('h2:contains(\"Copyright\") ~ p').inner_text\n    }\n\n    version '4' do\n      self.root_path = 'index.php'\n      self.release = '4'\n      self.base_url = \"https://registry.khronos.org/OpenGL-Refpages/gl#{self.version}/\"\n    end\n\n    version '2.1' do\n      self.root_path = 'index.html'\n      self.release = '2.1'\n      self.base_url = \"https://registry.khronos.org/OpenGL-Refpages/gl#{self.version}/\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/openjdk.rb",
    "content": "module Docs\n  class Openjdk < FileScraper\n\n    self.name = 'OpenJDK'\n    self.type = 'openjdk'\n    self.root_path = 'overview-summary.html'\n    self.links = {\n      home: 'https://openjdk.java.net/',\n      code: 'https://github.com/openjdk/jdk'\n    }\n\n    html_filters.insert_after 'internal_urls', 'openjdk/clean_urls'\n\n    options[:skip_patterns] = [\n      /compact[123]-/,\n      /package-frame\\.html/,\n      /package-tree\\.html/,\n      /package-use\\.html/,\n      /class-use\\//,\n      /doc-files\\//,\n      /\\.svg/,\n      /\\.png/\n    ]\n\n    options[:only_patterns] = [\n      /\\Ajava\\./,\n      /\\Ajdk\\./\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; 1993, 2025, Oracle and/or its affiliates. All rights reserved.<br>\n      Documentation extracted from Debian's OpenJDK Development Kit package.<br>\n      Licensed under the GNU General Public License, version 2, with the Classpath Exception.<br>\n      Various third party code in OpenJDK is licensed under different licenses (see Debian package).<br>\n      Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.\n    HTML\n\n    NEWFILTERS = ['openjdk/entries_new', 'openjdk/clean_html_new']\n\n    version '25' do\n      self.release = '25'\n      self.root_path = 'index.html'\n      self.base_url = 'https://docs.oracle.com/en/java/javase/25/docs/api/'\n\n      html_filters.push NEWFILTERS\n\n      options[:container] = 'main'\n    end\n\n    version '21' do\n      self.release = '21'\n      self.root_path = 'index.html'\n      self.base_url = 'https://docs.oracle.com/en/java/javase/21/docs/api/'\n\n      html_filters.push NEWFILTERS\n\n      options[:container] = 'main'\n    end\n\n    version '17' do\n      self.release = '17'\n      self.root_path = 'index.html'\n      self.base_url = 'https://docs.oracle.com/en/java/javase/17/docs/api/'\n\n      html_filters.push NEWFILTERS\n\n      options[:container] = 'main'\n    end\n\n    OLDFILTERS = ['openjdk/entries', 'openjdk/clean_html']\n\n    version '11' do\n      self.release = '11.0.11'\n      self.root_path = 'index.html'\n      self.base_url = 'https://docs.oracle.com/en/java/javase/11/docs/api/'\n\n      html_filters.push OLDFILTERS\n    end\n\n    version '8' do\n      self.release = '8'\n      self.base_url = 'https://docs.oracle.com/javase/8/docs/api/'\n\n      html_filters.push OLDFILTERS\n\n      options[:only_patterns] = [\n        /\\Ajava\\/beans\\//,\n        /\\Ajava\\/io\\//,\n        /\\Ajava\\/lang\\//,\n        /\\Ajava\\/math\\//,\n        /\\Ajava\\/net\\//,\n        /\\Ajava\\/nio\\//,\n        /\\Ajava\\/security\\//,\n        /\\Ajava\\/text\\//,\n        /\\Ajava\\/time\\//,\n        /\\Ajava\\/util\\//,\n        /\\Ajavax\\/annotation\\//,\n        /\\Ajavax\\/crypto\\//,\n        /\\Ajavax\\/imageio\\//,\n        /\\Ajavax\\/lang\\//,\n        /\\Ajavax\\/management\\//,\n        /\\Ajavax\\/naming\\//,\n        /\\Ajavax\\/net\\//,\n        /\\Ajavax\\/print\\//,\n        /\\Ajavax\\/script\\//,\n        /\\Ajavax\\/security\\//,\n        /\\Ajavax\\/sound\\//,\n        /\\Ajavax\\/tools\\//\n      ]\n    end\n\n    version '8 GUI' do\n      self.release = '8'\n      self.base_url = 'https://docs.oracle.com/javase/8/docs/api/'\n\n      html_filters.push OLDFILTERS\n\n      options[:only_patterns] = [\n        /\\Ajava\\/awt\\//,\n        /\\Ajavax\\/swing\\//\n      ]\n\n    end\n\n    version '8 Web' do\n      self.release = '8'\n      self.base_url = 'https://docs.oracle.com/javase/8/docs/api/'\n\n      html_filters.push OLDFILTERS\n\n      options[:only_patterns] = [\n        /\\Ajava\\/applet\\//,\n        /\\Ajava\\/rmi\\//,\n        /\\Ajava\\/sql\\//,\n        /\\Ajavax\\/accessibility\\//,\n        /\\Ajavax\\/activation\\//,\n        /\\Ajavax\\/activity\\//,\n        /\\Ajavax\\/jws\\//,\n        /\\Ajavax\\/rmi\\//,\n        /\\Ajavax\\/sql\\//,\n        /\\Ajavax\\/transaction\\//,\n        /\\Ajavax\\/xml\\//,\n        /\\Aorg\\/ietf\\//,\n        /\\Aorg\\/omg\\//,\n        /\\Aorg\\/w3c\\//,\n        /\\Aorg\\/xml\\//]\n    end\n\n    # Monkey patch to properly read HTML files encoded in ISO-8859-1\n    def read_file(path)\n      File.read(path).force_encoding('iso-8859-1').encode('utf-8') rescue nil\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc(\"https://jdk.java.net/archive/\", opts)\n      version = doc.at_css('#downloads > table > tr > th').content\n      version.gsub!(/\\(.*\\)/, '')\n      version.gsub!(/[a-zA-z]/, '')\n      version\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/openlayers.rb",
    "content": "module Docs\n  class Openlayers < UrlScraper\n    self.name = 'OpenLayers'\n    self.type = 'openlayers'\n    self.slug = 'openlayers'\n    self.release = '10.6.1'\n    self.base_url = \"https://openlayers.org/en/latest/apidoc/\"\n    self.links = {\n      home: 'https://openlayers.org/',\n      code: 'https://github.com/openlayers/openlayers'\n    }\n\n    html_filters.push 'openlayers/entries', 'openlayers/clean_html'\n\n    options[:attribution] = <<-HTML\n      &copy; 2005-present, OpenLayers Contributors All rights reserved.\n      Licensed under the BSD 2-Clause License.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version('ol', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/opentsdb.rb",
    "content": "module Docs\n  class Opentsdb < UrlScraper\n    self.name = 'OpenTSDB'\n    self.type = 'sphinx_simple'\n    self.release = '2.3.0'\n    self.base_url = 'http://opentsdb.net/docs/build/html/'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'http://opentsdb.net/',\n      code: 'https://github.com/OpenTSDB/opentsdb'\n    }\n\n    html_filters.push 'opentsdb/entries', 'opentsdb/clean_html'\n\n    options[:skip] = %w(genindex.html search.html)\n\n    options[:attribution] = <<-HTML\n      &copy; 2010&ndash;2016 The OpenTSDB Authors<br>\n      Licensed under the GNU LGPLv2.1+ and GPLv3+ licenses.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('OpenTSDB', 'opentsdb', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/padrino.rb",
    "content": "module Docs\n  class Padrino < UrlScraper\n    self.slug = 'padrino'\n    self.type = 'rubydoc'\n    self.release = '0.15'\n    self.base_url = 'https://www.rubydoc.info/github/padrino/padrino-framework/'\n    self.root_path = 'file/README.rdoc'\n    self.initial_paths = %w(index2)\n    self.links = {\n      home: 'http://padrinorb.com/',\n      code: 'https://github.com/padrino/padrino-framework'\n    }\n\n    html_filters.push 'padrino/clean_html', 'padrino/entries'\n\n    options[:container] = ->(filter) { filter.root_page? ? '#filecontents' : '#content' }\n\n    options[:attribution] = <<-HTML\n      &copy; 2010&ndash;2020 Padrino<br>\n      Licensed under the MIT License.\n    HTML\n\n    stub 'index2' do\n      request_one(url_for('index')).body\n    end\n\n    def get_latest_version(opts)\n      get_github_tags('padrino', 'padrino-framework', opts)[0]['name']\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/pandas.rb",
    "content": "module Docs\n  class Pandas < FileScraper\n    self.name = 'pandas'\n    self.type = 'sphinx'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://pandas.pydata.org/',\n      code: 'https://github.com/pydata/pandas'\n    }\n\n    options[:skip] = %w(internals.html release.html contributing.html whatsnew.html)\n    options[:skip_patterns] = [/whatsnew\\//]\n\n    # https://github.com/pandas-dev/pandas/blob/main/LICENSE\n    options[:attribution] = <<-HTML\n      &copy; 2008&ndash;2011, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team<br>\n      &copy; 2011&ndash;2025, Open source contributors<br>\n      Licensed under the 3-clause BSD License.\n    HTML\n\n    version '2' do\n      self.release = '2.3.0'\n      self.base_url = \"https://pandas.pydata.org/pandas-docs/version/#{self.release}/\"\n\n      html_filters.push 'pandas/clean_html', 'pandas/entries'\n\n      options[:container] = 'main section'\n\n      options[:skip_patterns] = [\n        /development/,\n        /getting_started/,\n        /whatsnew/\n      ]\n\n      options[:skip] = [\n        'panel.html',\n        'pandas.pdf',\n        'pandas.zip',\n        'ecosystem.html'\n      ]\n\n    end\n\n    version '1' do\n      self.release = '1.5.0'\n      self.base_url = \"https://pandas.pydata.org/pandas-docs/version/#{self.release}/\"\n\n      html_filters.push 'pandas/clean_html', 'pandas/entries'\n\n      options[:container] = 'main section'\n\n      options[:skip_patterns] = [\n        /development/,\n        /getting_started/,\n        /whatsnew/\n      ]\n\n      options[:skip] = [\n        'panel.html',\n        'pandas.pdf',\n        'pandas.zip',\n        'ecosystem.html'\n      ]\n\n    end\n\n    version '0.25' do\n      self.release = '0.25.0'\n      self.base_url = \"https://pandas.pydata.org/pandas-docs/version/#{self.release}/\"\n\n      html_filters.push 'pandas/entries_old', 'pandas/clean_html_old', 'sphinx/clean_html'\n\n      options[:container] = '.document'\n\n    end\n\n    version '0.24' do\n      self.release = '0.24.2'\n      self.base_url = \"https://pandas.pydata.org/pandas-docs/version/#{self.release}/\"\n\n      html_filters.push 'pandas/entries_old', 'pandas/clean_html_old', 'sphinx/clean_html'\n      options[:container] = '.document'\n\n    end\n\n    version '0.23' do\n      self.release = '0.23.4'\n      self.base_url = \"https://pandas.pydata.org/pandas-docs/version/#{self.release}/\"\n\n      html_filters.push 'pandas/entries_old', 'pandas/clean_html_old', 'sphinx/clean_html'\n      options[:container] = '.document'\n\n    end\n\n    version '0.22' do\n      self.release = '0.22.0'\n      self.base_url = \"https://pandas.pydata.org/pandas-docs/version/#{self.release}/\"\n\n      html_filters.push 'pandas/entries_old', 'pandas/clean_html_old', 'sphinx/clean_html'\n\n      options[:container] = '.document'\n\n    end\n\n    version '0.21' do\n      self.release = '0.21.1'\n      self.base_url = \"https://pandas.pydata.org/pandas-docs/version/#{self.release}/\"\n\n      html_filters.push 'pandas/entries_old', 'pandas/clean_html_old', 'sphinx/clean_html'\n\n      options[:container] = '.document'\n\n    end\n\n    version '0.20' do\n      self.release = '0.20.3'\n      self.base_url = \"https://pandas.pydata.org/pandas-docs/version/#{self.release}/\"\n\n      html_filters.push 'pandas/entries_old', 'pandas/clean_html_old', 'sphinx/clean_html'\n\n      options[:container] = '.document'\n\n    end\n\n    version '0.19' do\n      self.release = '0.19.2'\n      self.base_url = \"https://pandas.pydata.org/pandas-docs/version/#{self.release}/\"\n\n      html_filters.push 'pandas/entries_old', 'pandas/clean_html_old', 'sphinx/clean_html'\n\n      options[:container] = '.document'\n\n    end\n\n    version '0.18' do\n      self.release = '0.18.1'\n      self.base_url = \"https://pandas.pydata.org/pandas-docs/version/#{self.release}/\"\n\n      html_filters.push 'pandas/entries_old', 'pandas/clean_html_old', 'sphinx/clean_html'\n\n      options[:container] = '.document'\n\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('pandas-dev', 'pandas', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/perl.rb",
    "content": "module Docs\n  class Perl < UrlScraper\n    self.name = 'Perl'\n    self.type = 'perl'\n    self.initial_paths = ['modules.html', 'perlutil.html', 'perl.html']\n    self.links = {\n      home: 'https://www.perl.org/'\n    }\n\n    html_filters.push 'perl/pre_clean_html', 'perl/entries', 'perl/clean_html', 'title'\n\n    options[:container] = '#perldocdiv'\n\n    options[:skip] = %w(\n      perlbook perlcommunity perlexperiment perlartistic perlgpl perlhist\n      perlcn perljp perlko perltw\n      perlboot perlbot perlrepository perltodo perltooc perltoot )\n\n    options[:skip_patterns] = [/\\Afunctions/, /\\Avariables/, /\\.pdf/, /delta/]\n\n    options[:attribution] = <<-HTML\n      &copy; 1993&ndash;2025 Larry Wall and others<br>\n      Licensed under the GNU General Public License version 1 or later, or the Artistic License.<br>\n      The Perl logo is a trademark of the Perl Foundation.\n    HTML\n\n    version '5.42' do\n      self.release = '5.42.0'\n      self.base_url = \"https://perldoc.perl.org/#{release}/\"\n    end\n\n    version '5.40' do\n      self.release = '5.40.2'\n      self.base_url = \"https://perldoc.perl.org/#{release}/\"\n    end\n\n    version '5.38' do\n      self.release = '5.38.0'\n      self.base_url = \"https://perldoc.perl.org/#{release}/\"\n    end\n\n    version '5.36' do\n      self.release = '5.36.0'\n      self.base_url = \"https://perldoc.perl.org/#{release}/\"\n    end\n\n    version '5.34' do\n      self.release = '5.34.0'\n      self.base_url = \"https://perldoc.perl.org/#{release}/\"\n    end\n\n    version '5.32' do\n      self.release = '5.32.0'\n      self.base_url = \"https://perldoc.perl.org/#{release}/\"\n    end\n\n    version '5.30' do\n      self.release = '5.30.3'\n      self.base_url = \"https://perldoc.perl.org/#{release}/\"\n    end\n\n    version '5.28' do\n      self.release = '5.28.3'\n      self.base_url = \"https://perldoc.perl.org/#{release}/\"\n    end\n\n    version '5.26' do\n      self.release = '5.26.3'\n      self.base_url = \"https://perldoc.perl.org/#{release}/\"\n    end\n\n    version '5.24' do\n      self.release = '5.24.4'\n      self.base_url = \"https://perldoc.perl.org/#{release}/\"\n    end\n\n    version '5.22' do\n      self.release = '5.22.4'\n      self.base_url = \"https://perldoc.perl.org/#{release}/\"\n    end\n\n    version '5.20' do\n      self.release = '5.20.3'\n      self.base_url = \"https://perldoc.perl.org/#{release}/\"\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://perldoc.perl.org/', opts)\n      doc.at_css('#dropdownlink-stable').content\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/phalcon.rb",
    "content": "module Docs\n  class Phalcon < UrlScraper\n    self.type = 'phalcon'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://phalconphp.com/',\n      code: 'https://github.com/phalcon/cphalcon/'\n    }\n\n    html_filters.push 'phalcon/clean_html', 'phalcon/entries'\n\n    options[:root_title] = 'Phalcon'\n    options[:only_patterns] = [/reference\\//, /api\\//]\n    options[:skip] = %w(\n      api/index.html\n      reference/license.html)\n\n    options[:attribution] = <<-HTML\n      &copy; 2011&ndash;2017 Phalcon Framework Team<br>\n      Licensed under the Creative Commons Attribution License 3.0.\n    HTML\n\n    version '3' do\n      self.release = '3.1.1'\n      self.base_url = 'https://docs.phalconphp.com/en/latest/'\n    end\n\n    version '2' do\n      self.release = '2.0.13'\n      self.base_url = 'https://docs.phalconphp.com/en/2.0.0/'\n    end\n\n    def get_latest_version(opts)\n      tags = get_github_tags('phalcon', 'cphalcon', opts)\n      tags[0]['name'][1..-1]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/phaser.rb",
    "content": "module Docs\n  class Phaser < UrlScraper\n    self.type = 'phaser'\n    self.release = '2.6.2'\n    self.base_url = \"http://phaser.io/docs/#{release}\"\n    self.root_path = '/index'\n    self.links = {\n      home: 'http://phaser.io/',\n      code: 'https://github.com/photonstorm/phaser'\n    }\n\n    html_filters.push 'phaser/entries', 'phaser/clean_html'\n\n    options[:skip] = %w(\n      /docs_pixi-jsdoc.js.html\n      /p2.Body.html\n      /Phaser.html\n      /PIXI.html\n      /PIXI.WebGLMaskManager.html\n      /PIXI.WebGLShaderManager.html\n      /PIXI.WebGLSpriteBatch.html\n      /PIXI.WebGLStencilManager.html)\n\n    options[:attribution] = <<-HTML\n      &copy; 2016 Richard Davey, Photon Storm Ltd.<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('photonstorm', 'phaser', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/phoenix.rb",
    "content": "module Docs\n  class Phoenix < UrlScraper\n    self.type = 'elixir'\n    self.release = '1.6.11'\n    self.base_url = 'https://hexdocs.pm/'\n    self.root_path = 'phoenix/Phoenix.html'\n    self.initial_paths = %w(\n      phoenix/api-reference.html\n      ecto/api-reference.html\n      phoenix_html/api-reference.html\n      phoenix_live_view/api-reference.html\n      phoenix_pubsub/api-reference.html\n      plug/api-reference.html)\n    self.links = {\n      home: 'http://www.phoenixframework.org',\n      code: 'https://github.com/phoenixframework/phoenix'\n    }\n\n    html_filters.push 'elixir/clean_html', 'elixir/entries'\n\n    options[:container] = '#content'\n\n    options[:skip_patterns] = [/extra-api-reference/, /js/, /\\d+\\.\\d+\\.\\d+/]\n    options[:only_patterns] = [\n      /\\Aphoenix\\//,\n      /\\Aecto\\//,\n      /\\Aphoenix_pubsub\\//,\n      /\\Aphoenix_html\\//,\n      /\\Aphoenix_live_view\\//,\n      /\\Aplug\\//\n    ]\n\n    options[:attribution] = -> (filter) {\n      if filter.slug.start_with?('ecto')\n        <<-HTML\n          &copy; 2013 Plataformatec<br>\n          &copy; 2020 Dashbit<br>\n          Licensed under the Apache License, Version 2.0.\n        HTML\n      elsif filter.slug.start_with?('plug')\n        <<-HTML\n          &copy; 2013 Plataformatec<br>\n          Licensed under the Apache License, Version 2.0.\n        HTML\n      elsif filter.slug.start_with?('phoenix_live_view')\n        <<-HTML\n          &copy; 2018 Chris McCord<br>\n          Licensed under the MIT License.\n        HTML\n      else\n        <<-HTML\n          &copy; 2014 Chris McCord<br>\n          Licensed under the MIT License.\n        HTML\n      end\n    }\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://hexdocs.pm/phoenix/Phoenix.html', opts)\n      doc.at_css('.sidebar-projectVersion').content.strip[1..-1]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/php.rb",
    "content": "module Docs\n  class Php < FileScraper\n    # Downloaded from php.net/download-docs.php\n    include FixInternalUrlsBehavior\n\n    self.name = 'PHP'\n    self.type = 'php'\n    self.release = '8.5'\n    self.base_url = 'https://www.php.net/manual/en/'\n    self.root_path = 'index.html'\n    self.initial_paths = %w(\n      funcref.html\n      langref.html\n      refs.database.html\n      set.mysqlinfo.html\n      language.control-structures.html\n      reference.pcre.pattern.syntax.html\n      reserved.exceptions.html\n      reserved.interfaces.html\n      reserved.variables.html)\n\n    self.links = {\n      home: 'https://www.php.net/',\n      code: 'https://git.php.net/?p=php-src.git;a=summary'\n    }\n\n    html_filters.push 'php/internal_urls', 'php/entries', 'php/clean_html', 'title'\n    text_filters.push 'php/fix_urls'\n\n    options[:title] = false\n    options[:root_title] = 'PHP: Hypertext Preprocessor'\n    options[:skip_links] = ->(filter) { !filter.initial_page? }\n\n    options[:only_patterns] = [\n      /\\Alanguage\\./,\n      /\\Aclass\\./,\n      /\\Afunctions?\\./,\n      /\\Acontrol-structures/,\n      /\\Aregexp\\./,\n      /\\Areserved\\.exceptions/,\n      /\\Areserved\\.interfaces/,\n      /\\Areserved\\.variables/]\n\n    BOOKS = %w(apache apc apcu array bc blenc bzip2 calendar csprng  componere classobj ctype curl\n      datetime dba dbx dir dio dom ds eio errorfunc enchant ev event exec exif fileinfo filesystem filter\n      fdf ftp funchand fpm gearman geoip gettext gmagick gmp gnupg hash ibase iconv iisfunc image\n      imagick imap info inotify intl iisfunc json judy ldap libevent libxml lua lzf mail mailparse\n      math mhash mbstring mcrypt memcached misc mysqli ncurses network nsapi oauth openssl openal opcache\n      outcontrol password parle pcntl phpdbg pcre pdo pgsql phar posix proctitle pspell pthreads quickhash recode regex runkit runkit7 radius random rar\n      reflection readline sca session sem session-pgsql shmop simplexml ssdeep sdo sdodasrel sdo-das-xml sodium soap sockets solr snmp sphinx spl stomp\n      spl-types sqlite3 sqlsrv ssh2 stats stream strings sync svm svn taint tidy tokenizer uodbc url uopz\n      v8js var varnish wddx weakref wincache xattr xdiff xhprof xml xmlreader xmlrpc xmlwriter xsl yaf yar yaml yac zip zookeeper zlib)\n\n    options[:only] = BOOKS.map { |s| \"book.#{s}.html\" }\n\n    options[:skip] = %w(\n      control-structures.intro.html\n      control-structures.alternative-syntax.html\n      function.mssql-select-db.html\n      pthreads.modifiers.html)\n\n    options[:skip_patterns] = [/mysqlnd/, /xdevapi/i]\n\n    options[:attribution] = <<-HTML\n      &copy; 1997&ndash;2025 The PHP Documentation Group<br>\n      Licensed under the Creative Commons Attribution License v3.0 or later.\n    HTML\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://www.php.net/supported-versions.php', opts)\n      doc.at_css('table > tbody > .stable:last-of-type > td > a').content.strip\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/phpunit.rb",
    "content": "module Docs\n  class Phpunit < UrlScraper\n    self.name = 'PHPUnit'\n    self.type = 'phpunit'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://phpunit.de/',\n      code: 'https://github.com/sebastianbergmann/phpunit'\n    }\n\n    options[:skip] = [\n      'bibliography.html',\n      'copyright.html'\n    ]\n\n    options[:root_title] = 'PHPUnit'\n    options[:title] = false\n\n    options[:attribution] = <<-HTML\n      &copy; 2005&ndash;2025 Sebastian Bergmann<br>\n      Licensed under the Creative Commons Attribution 3.0 Unported License.\n    HTML\n\n    FILTERS = %w(phpunit/clean_html phpunit/entries title)\n\n    version do\n      self.release = '12.5'\n      self.base_url = \"https://docs.phpunit.de/en/#{release}/\"\n\n      html_filters.push FILTERS\n\n      options[:container] = '.document'\n    end\n\n    version '9' do\n      self.release = '9.5'\n      self.base_url = \"https://phpunit.readthedocs.io/en/#{release}/\"\n\n      html_filters.push FILTERS\n\n      options[:container] = '.document'\n    end\n\n    version '8' do\n      self.release = '8.5'\n      self.base_url = \"https://phpunit.readthedocs.io/en/#{release}/\"\n\n      html_filters.push FILTERS\n\n      options[:container] = '.document'\n    end\n\n    OLDFILTERS = %w(phpunit/clean_html_old phpunit/entries_old title)\n\n    OLDSKIP = %w(\n      appendixes.index.html\n      appendixes.bibliography.html\n      appendixes.copyright.html)\n\n    version '6' do\n      self.release = '6.5'\n      self.base_url = \"https://phpunit.de/manual/#{release}/en/\"\n\n      html_filters.push OLDFILTERS\n\n      options[:skip] = OLDSKIP\n    end\n\n    version '5' do\n      self.release = '5.7'\n      self.base_url = \"https://phpunit.de/manual/#{release}/en/\"\n\n      html_filters.push OLDFILTERS\n\n      options[:skip] = OLDSKIP\n    end\n\n    version '4' do\n      self.release = '4.8'\n      self.base_url = \"https://phpunit.de/manual/#{release}/en/\"\n\n      options[:skip] = OLDSKIP\n\n      html_filters.push OLDFILTERS\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://phpunit.readthedocs.io/', opts)\n      label = doc.at_css('meta[name=\"readthedocs-version-slug\"]')[\"content\"]\n      label\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/playwright.rb",
    "content": "module Docs\n  class Playwright < UrlScraper\n    self.name = 'Playwright'\n    self.type = 'simple'\n    self.release = '1.58.2'\n    self.base_url = 'https://playwright.dev/docs/'\n    self.root_path = 'intro'\n    self.links = {\n      home: 'https://playwright.dev/',\n      code: 'https://github.com/microsoft/playwright'\n    }\n\n    # Docusaurus like react_native\n    html_filters.push 'playwright/entries', 'playwright/clean_html'\n    options[:download_images] = false\n\n\t# https://github.com/microsoft/playwright/blob/main/LICENSE\n    options[:attribution] = <<-HTML\n      &copy; 2025 Microsoft<br>\n\t  Licensed under the Apache License, Version 2.0.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version('@playwright/test', opts)\n      end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/point_cloud_library.rb",
    "content": "module Docs\n  class PointCloudLibrary < UrlScraper\n    self.name = 'PointCloudLibrary'\n    self.type = 'point_cloud_library'\n    self.slug = 'point_cloud_library'\n    self.base_url = 'https://pointclouds.org/documentation/'\n    self.root_path = 'modules.html'\n    # Add hierarchy.html to crawl all classes*.html that's not reachable from modules.html\n    self.initial_paths = [\n      \"https://pointclouds.org/documentation/hierarchy.html\"\n    ]\n    self.release = '1.15.0'\n\n    self.links = {\n      home: 'https://pointclouds.org/',\n      code: 'https://github.com/PointCloudLibrary/pcl'\n    }\n\n    html_filters.push 'point_cloud_library/entries', 'point_cloud_library/clean_html'\n\n    # Remove the `clean_text` because Doxygen are actually creating empty\n    # anchor such as <a id=\"asd\"></a> to do anchor link.. and that anchor\n    # will be removed by clean_text\n    self.text_filters = FilterStack.new\n    text_filters.push 'images', 'inner_html', 'attribution'\n\n    def get_latest_version(opts)\n      get_latest_github_release('PointCloudLibrary', 'pcl', opts)[4..]\n    end\n\n    options[:attribution] = <<-HTML\n      &copy; 2009–2012, Willow Garage, Inc.<br>\n      &copy; 2012–, Open Perception, Inc.<br>\n      Licensed under the BSD License.\n    HTML\n\n    # Skip source code since it doesn't provide any useful docs\n    options[:skip_patterns] = [/_source/, /namespace/, /h\\.html/, /structsvm/, /struct_/, /classopenni/, /class_/]\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/pony.rb",
    "content": "module Docs\n  class Pony < UrlScraper\n    self.type = 'simple'\n    self.release = '0.38.1'\n    self.base_url = 'https://stdlib.ponylang.io/'\n    self.links = {\n      home: 'https://www.ponylang.io/',\n      code: 'https://github.com/ponylang/ponyc'\n    }\n\n    html_filters.push 'pony/clean_html', 'pony/entries'\n\n    options[:attribution] = <<-HTML\n      &copy; 2016-2020, The Pony Developers<br>\n      &copy; 2014-2015, Causality Ltd.<br>\n      Licensed under the BSD 2-Clause License.\n    HTML\n\n    options[:container] = 'article'\n    options[:trailing_slash] = false\n    options[:skip_patterns] = [/src/, /stdlib--index/]\n\n    def get_latest_version(opts)\n      get_latest_github_release('ponylang', 'ponyc', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/postgresql.rb",
    "content": "module Docs\n  class Postgresql < UrlScraper\n    include FixInternalUrlsBehavior\n\n    self.name = 'PostgreSQL'\n    self.type = 'postgres'\n    self.root_path = 'reference.html'\n    self.initial_paths = %w(sql.html admin.html internals.html appendixes.html tutorial.html)\n    self.links = {\n      home: 'https://www.postgresql.org/',\n      code: 'https://git.postgresql.org/gitweb/?p=postgresql.git'\n    }\n\n    html_filters.insert_before 'normalize_urls', 'postgresql/extract_metadata'\n    html_filters.push 'postgresql/clean_html', 'postgresql/entries', 'title'\n\n    options[:title] = false\n    options[:root_title] = 'PostgreSQL'\n    options[:follow_links] = ->(filter) { filter.initial_page? }\n    options[:rate_limit] = 200\n\n    options[:skip] = %w(\n      index.html\n      ddl-others.html\n      functions-event-triggers.html\n      functions-trigger.html\n      textsearch-migration.html\n      supported-platforms.html\n      error-message-reporting.html\n      error-style-guide.html\n      plhandler.html\n      sourcerepo.html\n      git.html\n      bug-reporting.html\n      client-interfaces.html)\n\n    options[:skip_patterns] = [\n      /\\Ainstall/,\n      /\\Aregress/,\n      /\\Aprotocol/,\n      /\\Asource/,\n      /\\Anls/,\n      /\\Afdw/,\n      /\\Atablesample/,\n      /\\Acustom-scan/,\n      /\\Abki/,\n      /\\Arelease/,\n      /\\Acontrib-prog/,\n      /\\Aexternal/,\n      /\\Adocguide/,\n      /\\Afeatures/,\n      /\\Aunsupported-features/ ]\n\n    options[:attribution] = <<-HTML\n      &copy; 1996&ndash;2026 The PostgreSQL Global Development Group<br>\n      Licensed under the PostgreSQL License.\n    HTML\n\n    version '18' do\n      self.release = '18.3'\n      self.base_url = \"https://www.postgresql.org/docs/#{version}/\"\n    end\n\n    version '17' do\n      self.release = '17.5'\n      self.base_url = \"https://www.postgresql.org/docs/#{version}/\"\n    end\n\n    version '16' do\n      self.release = '16.1'\n      self.base_url = \"https://www.postgresql.org/docs/#{version}/\"\n    end\n\n    version '15' do\n      self.release = '15.4'\n      self.base_url = \"https://www.postgresql.org/docs/#{version}/\"\n    end\n\n    version '14' do\n      self.release = '14.5'\n      self.base_url = \"https://www.postgresql.org/docs/#{version}/\"\n    end\n\n    version '13' do\n      self.release = '13.4'\n      self.base_url = \"https://www.postgresql.org/docs/#{version}/\"\n    end\n\n    version '12' do\n      self.release = '12.1'\n      self.base_url = \"https://www.postgresql.org/docs/#{version}/\"\n    end\n\n    version '11' do\n      self.release = '11.6'\n      self.base_url = \"https://www.postgresql.org/docs/#{version}/\"\n    end\n\n    version '10' do\n      self.release = '10.11'\n      self.base_url = \"https://www.postgresql.org/docs/#{version}/\"\n    end\n\n    version '9.6' do\n      self.release = '9.6.16'\n      self.base_url = \"https://www.postgresql.org/docs/#{version}/\"\n\n      html_filters.insert_before 'postgresql/extract_metadata', 'postgresql/normalize_class_names'\n    end\n\n    version '9.5' do\n      self.release = '9.5.20'\n      self.base_url = \"https://www.postgresql.org/docs/#{version}/\"\n\n      html_filters.insert_before 'postgresql/extract_metadata', 'postgresql/normalize_class_names'\n    end\n\n    version '9.4' do\n      self.release = '9.4.25'\n      self.base_url = \"https://www.postgresql.org/docs/#{version}/\"\n\n      html_filters.insert_before 'postgresql/extract_metadata', 'postgresql/normalize_class_names'\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://www.postgresql.org/docs/current/index.html', opts)\n      label = doc.at_css('#pgContentWrap h1.title').content\n      label.scan(/([0-9.]+)/)[0][0]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/prettier.rb",
    "content": "module Docs\n  class Prettier < UrlScraper\n    self.name = 'Prettier'\n    self.type = 'simple'\n    self.release = '3.7.4'\n    self.base_url = 'https://prettier.io/docs/'\n    self.links = {\n      home: 'https://prettier.io/',\n      code: 'https://github.com/prettier/prettier'\n    }\n\n    # Docusaurus like react_native\n    html_filters.push 'prettier/entries', 'prettier/clean_html'\n\n    options[:attribution] = <<-HTML\n      &copy; James Long and contributors\n    HTML\n    options[:skip_patterns] = [ /^next/ ]\n\n    def get_latest_version(opts)\n      get_npm_version('prettier', opts)\n      end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/pug.rb",
    "content": "module Docs\n  class Pug < UrlScraper\n    self.type = 'pug'\n    self.base_url = 'https://pugjs.org/'\n    self.root_path = 'api/getting-started.html'\n    self.release = '3.0.0'\n    self.links = {\n      home: 'https://pugjs.org/',\n      code: 'https://github.com/pugjs/pug'\n    }\n\n    html_filters.push 'pug/clean_html', 'pug/entries'\n\n    options[:container] = 'body > .container'\n\n    options[:attribution] = <<-HTML\n      &copy; Pug authors<br>\n      Licensed under the MIT license.\n    HTML\n\n    options[:skip_patterns] = [\n      /support/\n    ]\n\n    def get_latest_version(opts)\n      get_npm_version('pug', opts)\n    end\n\n    private\n\n    def parse(response) # Hook here because Nokogori removes whitespace from textareas\n      response.body.gsub! %r{<textarea\\ [^>]*>([\\W\\w]+?)</textarea>}, '<pre>\\1</pre>'\n      super\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/puppeteer.rb",
    "content": "module Docs\n  class Puppeteer < Github\n    self.release = '7.1.0'\n    self.base_url = \"https://github.com/puppeteer/puppeteer/blob/v#{self.release}/docs/api.md\"\n    self.links = {\n      code: 'https://github.com/puppeteer/puppeteer/'\n    }\n\n    html_filters.push 'puppeteer/entries', 'puppeteer/clean_html'\n\n    options[:container] = '.markdown-body'\n\n    options[:attribution] = <<-HTML\n      &copy; 2021 Google Inc<br>\n      Licensed under the Apache License 2.0.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version('puppeteer', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/pygame.rb",
    "content": "module Docs\n  class Pygame < UrlScraper\n    self.type = 'pygame'\n    self.release = '2.4.0'\n    self.base_url = 'https://www.pygame.org/docs/'\n    self.root_path = 'py-modindex.html'\n    self.links = {\n      home: 'https://www.pygame.org/',\n      code: 'https://github.com/pygame/pygame'\n    }\n\n    html_filters.push 'pygame/pre_clean_html', 'pygame/entries', 'pygame/clean_html'\n\n    options[:only_patterns] = [/ref\\//]\n\n    options[:attribution] = <<-HTML\n      &copy; Pygame Developers.<br>\n      Licensed under the GNU LGPL License version 2.1.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('pygame', 'pygame', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/python.rb",
    "content": "module Docs\n  class Python < UrlScraper\n    self.type = 'python'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://www.python.org/',\n      code: 'https://github.com/python/cpython'\n    }\n\n    # bypass the clean_text filter as it removes empty span with ids\n    options[:clean_text] = false\n\n    # bypass sphinx modifying empty ids\n    options[:sphinx_keep_empty_ids] = true\n\n    options[:skip_patterns] = [/whatsnew/]\n    options[:skip] = %w(\n      library/2to3.html\n      library/formatter.html\n      library/intro.html\n      library/undoc.html\n      library/unittest.mock-examples.html\n      library/sunau.html)\n\n    options[:attribution] = <<-HTML\n      &copy; 2001&ndash;2025 Python Software Foundation<br>\n      Licensed under the PSF License.\n    HTML\n\n    version '3.14' do\n      self.release = '3.14.3'\n      self.base_url = \"https://docs.python.org/#{self.version}/\"\n\n      html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'\n    end\n\n    version '3.13' do\n      self.release = '3.13.8'\n      self.base_url = \"https://docs.python.org/#{self.version}/\"\n\n      html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'\n    end\n\n    version '3.12' do\n      self.release = '3.12.9'\n      self.base_url = \"https://docs.python.org/#{self.version}/\"\n\n      html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'\n    end\n\n    version '3.11' do\n      self.release = '3.11.11'\n      self.base_url = \"https://docs.python.org/#{self.version}/\"\n\n      html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'\n    end\n\n    version '3.10' do\n      self.release = '3.10.13'\n      self.base_url = \"https://docs.python.org/#{self.version}/\"\n\n      html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'\n    end\n\n    version '3.9' do\n      self.release = '3.9.14'\n      self.base_url = 'https://docs.python.org/3.9/'\n\n      html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'\n    end\n\n    version '3.8' do\n      self.release = '3.8.14'\n      self.base_url = 'https://docs.python.org/3.8/'\n\n      html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'\n    end\n\n    version '3.7' do\n      self.release = '3.7.14'\n      self.base_url = 'https://docs.python.org/3.7/'\n\n      html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'\n    end\n\n    version '3.6' do\n      self.release = '3.6.12'\n      self.base_url = 'https://docs.python.org/3.6/'\n\n      html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'\n    end\n\n    version '3.5' do\n      self.release = '3.5.9'\n      self.base_url = 'https://docs.python.org/3.5/'\n\n      html_filters.push 'python/entries_v3', 'sphinx/clean_html', 'python/clean_html'\n    end\n\n    version '2.7' do\n      self.release = '2.7.17'\n      self.base_url = 'https://docs.python.org/2.7/'\n\n      html_filters.push 'python/entries_v2', 'sphinx/clean_html', 'python/clean_html'\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://docs.python.org/', opts)\n      doc.at_css('title').content.split(' ')[0]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/pytorch.rb",
    "content": "module Docs\n  class Pytorch < UrlScraper\n    self.name = 'PyTorch'\n    self.slug = 'pytorch'\n    self.type = 'sphinx'\n    self.links = {\n      home: 'https://pytorch.org/',\n      code: 'https://github.com/pytorch/pytorch'\n    }\n\n    html_filters.push 'pytorch/entries', 'pytorch/clean_html', 'sphinx/clean_html'\n\n    options[:skip] = ['cpp_index.html', 'deploy.html', 'packages.html', 'py-modindex.html', 'genindex.html']\n    options[:skip_patterns] = [/\\Acommunity/, /\\A_modules/, /\\Anotes/, /\\Aorg\\/pytorch\\//]\n    options[:max_image_size] = 1_000_000\n\n    options[:attribution] = <<-HTML\n    &copy; 2025, PyTorch Contributors<br>\n    PyTorch has a BSD-style license, as found in the <a href=\"https://github.com/pytorch/pytorch/blob/main/LICENSE\">LICENSE</a> file.\n    HTML\n\n    version '2.9' do\n      self.release = '2.9'\n      self.base_url = \"https://docs.pytorch.org/docs/#{release}/\"\n    end\n\n    version '2.8' do\n      self.release = '2.8'\n      self.base_url = \"https://docs.pytorch.org/docs/#{release}/\"\n    end\n\n    version '2.7' do\n      self.release = '2.7'\n      self.base_url = \"https://docs.pytorch.org/docs/#{release}/\"\n    end\n\n    version '2.6' do\n      self.release = '2.6'\n      self.base_url = \"https://docs.pytorch.org/docs/#{release}/\"\n    end\n\n    version '2.5' do\n      self.release = '2.5'\n      self.base_url = \"https://docs.pytorch.org/docs/#{release}/\"\n    end\n\n    version '2.4' do\n      self.release = '2.4'\n      self.base_url = \"https://docs.pytorch.org/docs/#{release}/\"\n    end\n\n    version '2.3' do\n      self.release = '2.3'\n      self.base_url = \"https://docs.pytorch.org/docs/#{release}/\"\n    end\n\n    version '2.2' do\n      self.release = '2.2'\n      self.base_url = \"https://docs.pytorch.org/docs/#{release}/\"\n    end\n\n    version '2.1' do\n      self.release = '2.1'\n      self.base_url = \"https://docs.pytorch.org/docs/#{release}/\"\n    end\n\n    version '2.0' do\n      self.release = '2.0'\n      self.base_url = \"https://docs.pytorch.org/docs/#{release}/\"\n    end\n\n    version '1.13' do\n      self.release = '1.13'\n      self.base_url = \"https://docs.pytorch.org/docs/#{release}/\"\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('pytorch', 'pytorch', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/q.rb",
    "content": "module Docs\n  class Q < UrlScraper\n    self.name = 'Q'\n    self.release = '1.5.1'\n    self.base_url = 'https://github.com/kriskowal/q/wiki/'\n    self.root_path = 'API-Reference'\n    self.links = {\n      home: 'http://documentup.com/kriskowal/q/',\n      code: 'https://github.com/kriskowal/q'\n    }\n\n    html_filters.push 'q/entries', 'title'\n\n    options[:container] = '.markdown-body'\n    options[:title] = 'Q'\n    options[:skip_links] = true\n\n    options[:attribution] = <<-HTML\n      &copy; 2009&ndash;2018 Kristopher Michael Kowal<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version('q', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/qt.rb",
    "content": "module Docs\n  class Qt < UrlScraper\n    self.name = 'Qt'\n    self.type = 'qt'\n    self.initial_paths = %w(classes.html qmltypes.html)\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://www.qt.io',\n      code: 'https://code.qt.io/cgit/'\n    }\n\n    html_filters.push 'qt/entries', 'qt/clean_html'\n\n    options[:container] = '.b-sidebar__content'\n    options[:max_image_size] = 256_000\n    options[:skip_patterns] = [\n      # License, copyright attributions\n      /3rdparty/,\n      /attribution/,\n      /license/,\n      /licensing/,\n\n      # Examples, guides, tutorials\n      /example/,\n      /guide$/,\n      /tutorial/,\n      /porting/,\n      /usecase/,\n      /topic/,\n      /^modelview/,\n      /deploy(ing|ment)/,\n      /building/,\n\n      # Old versions, changelog\n      /obsolete/,\n      /compatibility/,\n      /^whatsnew/,\n      /^newclasses/,\n\n      # Deprecated modules\n      /(qtopengl|qgl)/,\n      /qt?script/,\n\n      # Indexes\n      /members/,\n      /module/,\n      /overview/,\n      /^qopenglfunctions/,\n\n      # Tooling\n      /^(qt)?(linguist|assistant|qdbusviewer)/,\n    ]\n\n    options[:skip] = [\n      \"qt5-intro.html\",\n      \"compatmap.html\",\n\n      # Indexes\n      \"qdoc-index.html\",\n      \"qmake-manual.html\",\n      \"classes.html\",\n      \"qtmodules.html\",\n      \"modules-qml.html\",\n      \"modules-cpp.html\",\n      \"functions.html\",\n      \"namespaces.html\",\n      \"qmltypes.html\",\n      \"qt3d-qml.html\",\n      \"qmlbasictypes.html\",\n      \"guibooks.html\",\n      \"annotated.html\",\n      \"overviews-main.html\",\n      \"reference-overview.html\",\n\n      # Tutorials\n      \"qtvirtualkeyboard-build.html\",\n\n      # Copyright\n      \"trademarks.html\",\n      \"lgpl.html\",\n      \"bughowto.html\",\n\n      # Changelogs\n      \"changes.html\",\n      \"qtlocation-changes.html\",\n      \"sourcebreaks.html\",\n\n      # Best practice guides\n      \"accessible.html\",\n      \"accessible-qtquick.html\",\n      \"sharedlibrary.html\",\n      \"exceptionsafety.html\",\n      \"scalability.html\",\n      \"session.html\",\n      \"appicon.html\",\n      \"accelerators.html\",\n\n      # Other\n      \"ecmascript.html\",\n      \"qtremoteobjects-interaction.html\",\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; The Qt Company Ltd<br>\n      Licensed under the GNU Free Documentation License, Version 1.3.\n    HTML\n\n    version '6.9' do\n      self.release = '6.9'\n      self.base_url = \"https://doc.qt.io/qt-#{self.release}/\"\n    end\n\n    version '6.8' do\n      self.release = '6.8'\n      self.base_url = \"https://doc.qt.io/qt-#{self.release}/\"\n    end\n\n    version '6.2' do\n      self.release = '6.2'\n      self.base_url = \"https://doc.qt.io/qt-#{self.release}/\"\n    end\n\n    version '6.1' do\n      self.release = '6.1'\n      self.base_url = \"https://doc.qt.io/qt-#{self.release}/\"\n    end\n\n    version '6.0' do\n      self.release = '6.0'\n      self.base_url = \"https://doc.qt.io/qt-#{self.release}/\"\n    end\n\n    version '5.15' do\n      self.release = '5.15'\n      self.base_url = \"https://doc.qt.io/qt-#{self.release}/\"\n    end\n\n    version '5.14' do\n      self.release = '5.14'\n      self.base_url = \"https://doc.qt.io/qt-#{self.release}/\"\n    end\n\n    version '5.13' do\n      self.release = '5.13'\n      self.base_url = \"https://doc.qt.io/archives/qt-#{self.release}/\"\n    end\n\n    version '5.12' do\n      self.release = '5.12'\n      self.base_url = \"https://doc.qt.io/archives/qt-#{self.release}/\"\n    end\n\n    version '5.11' do\n      self.release = '5.11'\n      self.base_url = \"https://doc.qt.io/archives/qt-#{self.release}/\"\n    end\n\n    version '5.9' do\n      self.release = '5.9'\n      self.base_url = \"https://doc.qt.io/archives/qt-#{self.release}/\"\n    end\n\n    version '5.6' do\n      self.release = '5.6'\n      self.base_url = \"https://doc.qt.io/archives/qt-#{self.release}/\"\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://doc.qt.io/qt-6/index.html', opts)\n      doc.at_css('.mainContent h1.title').content.sub(/Qt /, '')\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/qunit.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class Qunit < UrlScraper\n    self.name = 'QUnit'\n    self.type = 'qunit'\n    self.release = '2.24.1'\n    self.base_url = 'https://qunitjs.com/api/'\n    self.root_path = '/'\n    self.links = {\n      home: 'https://qunitjs.com/',\n      code: 'https://github.com/qunitjs/qunit'\n    }\n\n    html_filters.push 'qunit/entries', 'qunit/clean_html'\n\n    options[:trailing_slash] = false\n\n    options[:container] = '.main'\n    options[:skip_patterns] = [\n      /^QUnit$/,\n      /^assert$/,\n      /^callbacks$/,\n      /^async$/,\n      /^config$/,\n      /^extension$/,\n      /^deprecated$/,\n      /^removed$/,\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; OpenJS Foundation and contributors.<br>\n      Licensed under the MIT license.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version('qunit', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/r.rb",
    "content": "module Docs\n  class R < FileScraper\n    self.name = 'R'\n    self.slug = 'r'\n    self.type = 'simple'\n    self.release = '4.4.2'\n    self.links = {\n      home: 'https://www.r-project.org/',\n      code: 'https://svn.r-project.org/R/'\n    }\n\n    self.root_path = 'doc/html/packages.html'\n\n    html_filters.push 'r/entries', 'r/clean_html'\n\n    options[:skip_links] = false\n\n    options[:attribution] = <<-HTML\n      Copyright (&copy;) 1999–2012 R Foundation for Statistical Computing.<br>\n      Licensed under the <a href=\"https://www.gnu.org/copyleft/gpl.html\">GNU General Public License</a>.\n    HTML\n\n    # Never want those\n    options[:skip_patterns] = [\n      /\\/DESCRIPTION$/,\n      /\\/NEWS(\\.[^\\/]*)?$/,\n      /\\/doc\\/index\\.html$/,\n      /\\/demo$/,\n      /\\.pdf$/\n    ]\n\n    options[:replace_paths] = {\n    ## We want to fix links like so − but only if the targets don’t exist:\n    #  'library/MASS/html/cov.mve.html' => 'library/MASS/html/cov.rob.html'\n    ## Paths for target packages or keywords that do not have their own file\n    ## are generated in the entries filter from 00Index.html files\n    }\n\n    options[:skip] = %w(\n      doc/html/packages-head-utf8.html\n      doc/html/SearchOn.html\n      doc/html/Search.html\n      doc/html/UserManuals.html\n      doc/html/faq.html\n      doc/manual/R-FAQ.html\n      doc/manual/R-admin.html\n      doc/manual/R-exts.html\n      doc/manual/R-ints.html\n      doc/manual/R-lang.html\n    )\n\n    def get_latest_version(opts)\n      body = fetch('https://cran.r-project.org/src/base/NEWS', opts)\n      body.match(/CHANGES IN R ([\\d.]+):/)[1]\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/ramda.rb",
    "content": "module Docs\n  class Ramda < UrlScraper\n    self.type = 'ramda'\n    self.links = {\n      home: 'http://ramdajs.com/',\n      code: 'https://github.com/ramda/ramda/'\n    }\n\n    html_filters.push 'ramda/entries', 'ramda/clean_html', 'title'\n\n    options[:title] = 'Ramda'\n    options[:attribution] = <<-HTML\n      &copy; 2013&ndash;2025 Scott Sauyet and Michael Hurley<br>\n      Licensed under the MIT License.\n    HTML\n\n    version do\n      self.release = '0.31.3'\n      self.base_url = \"https://ramdajs.com/#{release}/docs/\"\n    end\n\n    def get_latest_version(opts)\n      get_npm_version('ramda', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/rdoc/minitest.rb",
    "content": "module Docs\n  class Minitest < Rdoc\n\n    self.name = 'Ruby / Minitest'\n    self.slug = 'minitest'\n    self.release = '6.0.1'\n    self.links = {\n      home: 'https://minite.st',\n      code: 'https://github.com/minitest/minitest'\n    }\n\n    html_filters.replace 'rdoc/entries', 'minitest/entries'\n\n    options[:root_title] = 'Minitest'\n\n    options[:attribution] = <<-HTML\n      &copy; Ryan Davis, seattle.rb<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      contents = get_github_file_contents('seattlerb', 'minitest', 'History.rdoc', opts)\n      contents.scan(/([0-9.]+)/)[0][0]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/rdoc/rails.rb",
    "content": "module Docs\n  class Rails < Rdoc\n    include FixInternalUrlsBehavior\n\n    self.name = 'Ruby on Rails'\n    self.slug = 'rails'\n    self.initial_paths = %w(guides/index.html)\n    self.links = {\n      home: 'https://rubyonrails.org/',\n      code: 'https://github.com/rails/rails'\n    }\n\n    html_filters.replace 'rdoc/entries', 'rails/entries'\n    html_filters.push 'rails/clean_html_guides'\n\n    options[:skip_rdoc_filters?] = ->(filter) { filter.slug.start_with?('guides/') }\n\n    options[:root_title] = 'Ruby on Rails'\n\n    options[:skip] += %w(\n      guides/credits.html\n      guides/ruby_on_rails_guides_guidelines.html\n      guides/contributing_to_ruby_on_rails.html\n      guides/development_dependencies_install.html\n      guides/api_documentation_guidelines.html\n      ActionController/Instrumentation.html\n      ActionController/Rendering.html\n      ActionDispatch/DebugExceptions.html\n      ActionDispatch/Journey/Parser.html\n      ActionDispatch/Reloader.html\n      ActionDispatch/Routing/HtmlTableFormatter.html\n      ActionDispatch/ShowExceptions.html\n      ActionView/FixtureResolver.html\n      ActionView/LogSubscriber.html\n      ActionView/TestCase/Behavior/RenderedViewsCollection.html\n      ActiveRecord/Tasks/DatabaseTasks.html\n      ActiveSupport/Dependencies/WatchStack.html\n      ActiveSupport/Notifications/Fanout.html)\n\n    # False positives found by docs:generate\n    options[:skip].concat %w(\n      ActionDispatch/www.example.com\n      ActionDispatch/Http/www.rubyonrails.org\n      ActionDispatch/Http/www.rubyonrails.co.uk\n      'TZ'\n      active_record_migrations.html\n      association_basics.html)\n\n    options[:skip_patterns] += [\n      /release_notes/,\n      /\\AActionController\\/Testing/,\n      /\\AActionView\\/LookupContext/,\n      /\\AActionView\\/Resolver/,\n      /\\AActiveSupport\\/Multibyte\\/Unicode\\//,\n      /\\AActiveSupport\\/XML/i,\n      /\\ASourceAnnotationExtractor/,\n      /\\AI18n\\/Railtie/,\n      /\\AMinitest/,\n      /\\ARails\\/API/,\n      /\\ARails\\/AppBuilder/,\n      /\\ARails\\/PluginBuilder/,\n      /\\ARails\\/Generators\\/Testing/]\n\n    options[:attribution] = ->(filter) do\n      if filter.slug.start_with?('guides')\n        <<-HTML\n          &copy; 2004&ndash;2021 David Heinemeier Hansson<br>\n          Licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.\n        HTML\n      else\n        <<-HTML\n          &copy; 2004&ndash;2021 David Heinemeier Hansson<br>\n          Licensed under the MIT License.\n        HTML\n      end\n    end\n\n    version '8.1' do\n      self.release = '8.1.2'\n    end\n\n    version '8.0' do\n      self.release = '8.0.1'\n    end\n\n    version '7.2' do\n      self.release = '7.2.1'\n    end\n\n    version '7.1' do\n      self.release = '7.1.2'\n    end\n\n    version '7.0' do\n      self.release = '7.0.8'\n    end\n\n    version '6.1' do\n      self.release = '6.1.7'\n    end\n\n    version '6.0' do\n      self.release = '6.0.6'\n    end\n\n    version '5.2' do\n      self.release = '5.2.6'\n    end\n\n    version '5.1' do\n      self.release = '5.1.7'\n    end\n\n    version '5.0' do\n      self.release = '5.0.7'\n    end\n\n    version '4.2' do\n      self.release = '4.2.11'\n    end\n\n    version '4.1' do\n      self.release = '4.1.16'\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://rubyonrails.org/', opts)\n      doc.at_css('.heading__button span').content.scan(/\\d\\.\\d*\\.*\\d*\\.*\\d*/)[0]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/rdoc/rdoc.rb",
    "content": "module Docs\n  class Rdoc < FileScraper\n    self.abstract = true\n    self.type = 'rdoc'\n    self.root_path = 'table_of_contents.html'\n\n    html_filters.replace 'container', 'rdoc/container'\n    html_filters.push 'rdoc/entries', 'rdoc/clean_html', 'title'\n\n    options[:title] = false\n    options[:skip] = %w(index.html)\n    options[:skip_patterns] = [\n      /history/i,\n      /rakefile/i,\n      /changelog/i,\n      /readme/i,\n      /news/i,\n      /license/i]\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/rdoc/ruby.rb",
    "content": "module Docs\n  class Ruby < Rdoc\n    # Instructions:\n    #   1. Download Ruby's source code\n    #   2. Run \"./configure && make html\" (in the Ruby directory)\n    #   3. Copy the \".ext/html\" directory to \"docs/ruby~[version]\"\n\n    include FixInternalUrlsBehavior\n\n    self.links = {\n      home: 'https://www.ruby-lang.org/',\n      code: 'https://github.com/ruby/ruby'\n    }\n\n    html_filters.replace 'rdoc/entries', 'ruby/entries'\n\n    options[:root_title] = 'Ruby Programming Language'\n    options[:title] = ->(filter) { filter.slug == 'globals_rdoc' ? 'Globals' : false }\n\n    options[:skip] += %w(\n      contributing_rdoc.html\n      contributors_rdoc.html\n      dtrace_probes_rdoc.html\n      maintainers_rdoc.html\n      regexp_rdoc.html\n      standard_library_rdoc.html\n      syntax_rdoc.html\n      extension_rdoc.html\n      extension_ja_rdoc.html\n      English.html\n      Fcntl.html\n      Kconv.html\n      NKF.html\n      OLEProperty.html\n      OptParse.html\n      UnicodeNormalize.html)\n\n    options[:skip_patterns] += [\n      /\\Alib\\//,\n      /\\ADEBUGGER__/,\n      /\\AException2MessageMapper/,\n      /\\AJSON\\/Ext/,\n      /\\AGem/,\n      /\\AHTTP/i,\n      /\\AIRB/,\n      /\\AMakeMakefile/i,\n      /\\ANQXML/,\n      /\\APride/,\n      /\\AProfiler__/,\n      /\\APsych\\//,\n      /\\ARacc/,\n      /\\ARake/,\n      /\\ARbConfig/,\n      /\\ARDoc/,\n      /\\AREXML/,\n      /\\ARSS/,\n      /\\AShell\\//,\n      /\\ATest/,\n      /\\AWEBrick/,\n      /win32/i,\n      /\\AXML/,\n      /\\AXMP/]\n\n    options[:attribution] = <<-HTML\n      Ruby Core &copy; 1993&ndash;2025 Yukihiro Matsumoto<br>\n      Licensed under the Ruby License.<br>\n      Ruby Standard Library &copy; contributors<br>\n      Licensed under their own licenses.\n    HTML\n\n    version '4.0' do\n      self.release = '4.0.1'\n      self.root_path = 'index.html'\n    end\n\n    version '3.4' do\n      self.release = '3.4.7'\n    end\n\n    version '3.3' do\n      self.release = '3.3.0'\n    end\n\n    version '3.2' do\n      self.release = '3.2.2'\n    end\n\n    version '3.1' do\n      self.release = '3.1.4'\n    end\n\n    version '3' do\n      self.release = '3.0.6'\n    end\n\n    version '2.7' do\n      self.release = '2.7.2'\n    end\n\n    version '2.6' do\n      self.release = '2.6.3'\n    end\n\n    version '2.5' do\n      self.release = '2.5.3'\n    end\n\n    version '2.4' do\n      self.release = '2.4.5'\n    end\n\n    version '2.3' do\n      self.release = '2.3.8'\n    end\n\n    version '2.2' do\n      self.release = '2.2.10'\n    end\n\n    def get_latest_version(opts)\n      tags = get_github_tags('ruby', 'ruby', opts)\n      tags.each do |tag|\n        version = tag['name'].gsub(/_/, '.')[1..-1]\n\n        if !/^([0-9.]+)$/.match(version).nil? && version.count('.') == 2\n          return version\n        end\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/react.rb",
    "content": "module Docs\n  class React < UrlScraper\n\n    self.name = 'React'\n    self.type = 'react'\n    self.links = {\n      home: 'https://react.dev/',\n      code: 'https://github.com/facebook/react'\n    }\n\n    options[:attribution] = <<-HTML\n      &copy; 2013&ndash;present Facebook Inc.<br>\n      Licensed under the Creative Commons Attribution 4.0 International Public License.\n    HTML\n\n    version do\n      self.release = '19'\n      self.release = '19.2'\n      self.base_url = 'https://react.dev'\n      self.initial_paths = %w(/reference/react /learn)\n      html_filters.push 'react/entries_react_dev', 'react/clean_html_react_dev'\n\n      options[:only_patterns] = [/\\A\\/learn/, /\\A\\/reference/]\n    end\n\n    version '18' do\n      self.release = '18.3.1'\n      self.base_url = 'https://18.react.dev'\n      self.initial_paths = %w(/reference/react /learn)\n      html_filters.push 'react/entries_react_dev', 'react/clean_html_react_dev'\n\n      options[:only_patterns] = [/\\A\\/learn/, /\\A\\/reference/]\n    end\n\n    version '17' do\n      self.release = '17.0.2'\n      self.base_url = 'https://17.reactjs.org/docs/'\n      self.root_path = 'hello-world.html'\n      html_filters.push 'react/entries', 'react/clean_html'\n\n      options[:skip] = %w(\n        codebase-overview.html\n        design-principles.html\n        how-to-contribute.html\n        implementation-notes.html\n      )\n\n      options[:replace_paths] = {\n        'more-about-refs.html' => 'refs-and-the-dom.html',\n        'interactivity-and-dynamic-uis.html' => 'state-and-lifecycle.html',\n        'working-with-the-browser.html' => 'refs-and-the-dom.html',\n        'top-level-api.html' => 'react-api.html',\n      }\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://react.dev/', opts)\n      doc.at_css('a[href=\"/versions\"]').content.strip[1..-1]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/react_bootstrap.rb",
    "content": "module Docs\n  class ReactBootstrap < UrlScraper\n    self.name = 'React Bootstrap'\n    self.slug = 'react_bootstrap'\n    self.type = 'simple'\n    self.release = '1.5.0'\n    self.base_url = 'https://react-bootstrap.github.io/'\n\n    self.links = {\n      home: 'https://react-bootstrap.github.io',\n      code: 'https://github.com/react-bootstrap/react-bootstrap'\n    }\n\n    html_filters.push 'react_bootstrap/entries', 'react_bootstrap/clean_html'\n\n    options[:skip] = %w(\n      react-overlays/\n    )\n\n    options[:replace_paths] = {\n    }\n\n    options[:trailing_slash] = true\n\n    options[:attribution] = <<-HTML\n      &copy; 2014&ndash;present Stephen J. Collings, Matthew Honnibal, Pieter Vanderwerff<br>\n      Licensed under the MIT License (MIT).\n    HTML\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://react-bootstrap.github.io/', opts)\n      doc.at_css('.my-2').content.split()[-1].strip[0..-1]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/react_native.rb",
    "content": "module Docs\n  class ReactNative < UrlScraper\n    self.name = 'React Native'\n    self.slug = 'react_native'\n    self.type = 'react_native'\n    self.release = '0.75'\n    self.base_url = 'https://reactnative.dev/docs/'\n    self.root_path = 'getting-started.html'\n    self.links = {\n      home: 'https://reactnative.dev/',\n      code: 'https://github.com/facebook/react-native'\n    }\n\n    html_filters.push 'react_native/entries', 'react_native/clean_html'\n\n    options[:skip_patterns] = [/\\Asample\\-/, /\\A0\\./, /\\Anext\\b/]\n    options[:skip] = %w(\n      videos.html\n      transforms.html\n      troubleshooting.html\n      more-resources.html\n    )\n\n    options[:fix_urls] = ->(url) {\n      url.sub! 'docs/docs', 'docs'\n      url\n    }\n\n    # https://github.com/facebook/react-native-website/blob/main/LICENSE-docs\n    options[:attribution] = <<-HTML\n      &copy; 2022 Facebook Inc.<br>\n      Licensed under the Creative Commons Attribution 4.0 International Public License.\n    HTML\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://reactnative.dev/docs/getting-started', opts)\n      doc.at_css('meta[name=\"docsearch:version\"]')['content']\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/react_router.rb",
    "content": "module Docs\n  class ReactRouter < UrlScraper\n    self.name = 'React Router'\n    self.slug = 'react_router'\n    self.type = 'simple'\n    self.release = '6.4.1'\n    self.base_url = 'https://reactrouterdotcom.fly.dev/docs/en/v6'\n\n    self.links = {\n      home: 'https://reactrouterdotcom.fly.dev/',\n      code: 'https://github.com/remix-run/react-router'\n    }\n\n    html_filters.push 'react_router/entries', 'react_router/clean_html'\n\n    options[:skip_patterns] = [/upgrading/]\n\n    options[:attribution] = <<-HTML\n      &copy; React Training 2015-2019<br>\n      &copy; Remix Software 2020-2022<br>\n      Licensed under the MIT License (MIT).\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version('react-router', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/reactivex.rb",
    "content": "module Docs\n  class Reactivex < UrlScraper\n    self.name = 'ReactiveX'\n    self.type = 'reactivex'\n    self.base_url = 'http://reactivex.io/'\n    self.root_path = 'intro.html'\n    self.links = {\n      home: 'http://reactivex.io/'\n    }\n\n    html_filters.push 'reactivex/entries', 'reactivex/clean_html'\n\n    options[:download_images] = false\n\n    options[:only_patterns] = [/documentation\\//]\n    options[:skip_patterns] = [/ko\\//]\n\n    options[:attribution] = <<-HTML\n      &copy; ReactiveX contributors<br>\n      Licensed under the Apache License 2.0.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_commit_date('ReactiveX', 'reactivex.github.io', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/redis.rb",
    "content": "module Docs\n  class Redis < UrlScraper\n    self.type = 'redis'\n    self.release = '7.0.10'\n    self.base_url = 'https://redis.io/commands'\n    self.links = {\n      home: 'https://redis.io/',\n      code: 'https://github.com/redis/redis'\n    }\n\n    html_filters.push 'redis/entries', 'redis/clean_html', 'title'\n\n    options[:container] = ->(filter) { filter.root_page? ? '#commands-grid' : 'section' }\n    options[:title] = false\n    options[:root_title] = 'Redis'\n    options[:follow_links] = ->(filter) { filter.root_page? }\n\n    options[:attribution] = <<-HTML\n      &copy; 2006&ndash;2022 Salvatore Sanfilippo<br>\n      Licensed under the Creative Commons Attribution-ShareAlike License 4.0.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('redis', 'redis', opts)\n    end\n\n    private\n\n    def parse(response)\n      response.body.gsub! '<form', '<pre'\n      response.body.gsub! '</form', '</pre'\n      super\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/redux.rb",
    "content": "module Docs\n  class Redux < UrlScraper\n\n    self.type = 'simple'\n    self.base_url = 'https://redux.js.org/api'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'http://redux.js.org/',\n      code: 'https://github.com/reduxjs/redux/'\n    }\n\n    html_filters.push 'redux/clean_html', 'redux/entries'\n\n    options[:container] = '.markdown'\n\n    options[:attribution] = <<-HTML\n      &copy; 2015&ndash;2022 Dan Abramov<br>\n      Licensed under the MIT License.\n    HTML\n\n    version do\n      self.release = '4.2.0'\n    end\n\n    version '3' do\n      self.release = '3.7.2'\n    end\n\n    def get_latest_version(opts)\n      get_npm_version('redux', opts)\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/relay.rb",
    "content": "module Docs\n  class Relay < UrlScraper\n    self.type = 'simple'\n    self.root_path = 'introduction-to-relay'\n    self.links = {\n      home: 'https://relay.dev/',\n      code: 'https://github.com/facebook/relay'\n    }\n\n    html_filters.push 'relay/entries', 'relay/clean_html'\n\n    options[:skip] = %w(videos)\n\n    options[:attribution] = <<-HTML\n      &copy; 2020&ndash;present Facebook Inc.<br>\n      Licensed under the BSD License.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('facebook', 'relay', opts)\n    end\n\n    version '10' do\n      self.release = '10.1.0'\n      self.base_url = \"https://relay.dev/docs/en/\"\n      # For some reason, the most-recent version isn't available at a versioned URL\n    end\n\n    version '9' do\n      self.release = '9.1.0'\n      self.base_url = \"https://relay.dev/docs/en/v#{self.release}/\"\n    end\n\n    version '8' do\n      self.release = '8.0.0'\n      self.base_url = \"https://relay.dev/docs/en/v#{self.release}/\"\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/requests.rb",
    "content": "module Docs\n  class Requests < UrlScraper\n    self.type = 'sphinx'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://requests.readthedocs.io/',\n      code: 'https://github.com/psf/requests'\n    }\n    self.release = '2.32.5'\n    self.base_url = \"https://requests.readthedocs.io/en/latest/api/\"\n\n    html_filters.push 'requests/entries', 'sphinx/clean_html'\n\n    options[:container] = '.body > section'\n\n    options[:attribution] = <<-HTML\n      &copy; 2011-2025 Kenneth Reitz and other contributors<br>\n      Licensed under the Apache license.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('psf', 'requests', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/requirejs.rb",
    "content": "module Docs\n  class Requirejs < UrlScraper\n    self.name = 'RequireJS'\n    self.type = 'simple'\n    self.release = '2.3.5'\n    self.base_url = 'http://requirejs.org/docs/'\n    self.links = {\n      home: 'http://requirejs.org/',\n      code: 'https://github.com/requirejs/requirejs'\n    }\n    self.root_path = 'api.html'\n    self.initial_paths = %w(\n      optimization.html\n      jquery.html\n      node.html\n      dojo.html\n      commonjs.html\n      errors.html\n      plugins.html\n      why.html\n      whyamd.html)\n\n    html_filters.push 'requirejs/clean_html', 'requirejs/entries'\n\n    options[:container] = '#content'\n    options[:follow_links] = false\n    options[:only] = self.initial_paths\n\n    options[:attribution] = <<-HTML\n      &copy; jQuery Foundation and other contributors<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version('requirejs', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/rethinkdb.rb",
    "content": "module Docs\n  class Rethinkdb < UrlScraper\n    self.name = 'RethinkDB'\n    self.type = 'rethinkdb'\n    self.base_url = 'https://rethinkdb.com/'\n    self.release = '2.4.1'\n    self.root_path = 'docs/'\n    self.links = {\n      home: 'https://rethinkdb.com/',\n      code: 'https://github.com/rethinkdb/rethinkdb'\n    }\n\n    html_filters.push 'rethinkdb/entries', 'rethinkdb/clean_html'\n\n    options[:trailing_slash] = true\n\n    options[:container] = '.documentation'\n\n    options[:only_patterns] = [/\\Adocs/]\n\n    options[:skip_patterns] = [/docs\\/install(\\-drivers)?\\/./]\n\n    options[:skip] = %w(\n      docs/build/\n      docs/tutorials/elections/\n      docs/tutorials/superheroes/\n      )\n\n    MULTILANG_DOCS = %w(\n      changefeeds\n      cookbook\n      dates-and-times\n      geo-support\n      guide\n      nested-fields\n      publish-subscribe\n      rabbitmq\n      secondary-indexes\n      sql-to-reql\n      storing-binary\n      )\n\n    options[:attribution] = <<-HTML\n      &copy; RethinkDB contributors<br>\n      Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License.\n    HTML\n\n    version 'javascript' do\n      self.initial_paths = %w(api/javascript/)\n\n      options[:only_patterns] += [/\\Aapi\\/javascript\\//]\n\n      options[:fix_urls] = ->(url) do\n        url.sub! %r{rethinkdb.com/docs/(#{MULTILANG_DOCS.join('|')})\\\\z}, 'rethinkdb.com/docs/\\\\1/javascript/'\n        url.sub! %r{rethinkdb.com/docs/(#{MULTILANG_DOCS.join('|')})/(?!javascript/).*}, 'rethinkdb.com/docs/\\\\1/javascript/'\n        url.sub! %r{rethinkdb.com/api/(?!javascript|ruby|python|java)}, 'rethinkdb.com/api/javascript/'\n        url\n      end\n    end\n\n    version 'ruby' do\n      self.initial_paths = %w(api/ruby/)\n\n      options[:only_patterns] += [/\\Aapi\\/ruby\\//]\n\n      options[:fix_urls] = ->(url) do\n        url.sub! %r{rethinkdb.com/docs/(#{MULTILANG_DOCS.join('|')})\\\\z}, 'rethinkdb.com/docs/\\\\1/ruby/'\n        url.sub! %r{rethinkdb.com/docs/(#{MULTILANG_DOCS.join('|')})/(?!ruby/).*}, 'rethinkdb.com/docs/\\\\1/ruby/'\n        url.sub! %r{rethinkdb.com/api/(?!javascript|ruby|python|java)}, 'rethinkdb.com/api/ruby/'\n        url\n      end\n    end\n\n      version 'python' do\n        self.initial_paths = %w(api/python/)\n\n        options[:only_patterns] += [/\\Aapi\\/python\\//]\n\n        options[:fix_urls] = ->(url) do\n          url.sub! %r{rethinkdb.com/docs/(#{MULTILANG_DOCS.join('|')})\\\\z}, 'rethinkdb.com/docs/\\\\1/python/'\n          url.sub! %r{rethinkdb.com/docs/(#{MULTILANG_DOCS.join('|')})/(?!python/).*}, 'rethinkdb.com/docs/\\\\1/python/'\n          url.sub! %r{rethinkdb.com/api/(?!javascript|ruby|python|java)}, 'rethinkdb.com/api/python/'\n          url\n        end\n      end\n\n      version 'java' do\n        self.initial_paths = %w(api/java/)\n\n        options[:only_patterns] += [/\\Aapi\\/java\\//]\n\n        options[:fix_urls] = ->(url) do\n          url.sub! %r{rethinkdb.com/docs/(#{MULTILANG_DOCS.join('|')})\\\\z}, 'rethinkdb.com/docs/\\\\1/java/'\n          url.sub! %r{rethinkdb.com/docs/(#{MULTILANG_DOCS.join('|')})/(?!java/).*}, 'rethinkdb.com/docs/\\\\1/java/'\n          url.sub! %r{rethinkdb.com/api/(?!javascript|ruby|python|java)}, 'rethinkdb.com/api/java/'\n          url\n        end\n      end\n\n    def get_latest_version(opts)\n      get_latest_github_release('rethinkdb', 'rethinkdb', opts)\n    end\n\n    private\n\n    def process_response?(response)\n      return false unless super\n      response.body !~ /http-equiv=\"refresh\"/i\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/rust.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class Rust < UrlScraper\n    self.type = 'rust'\n    self.release = '1.93.1'\n    self.base_url = 'https://doc.rust-lang.org/'\n    self.root_path = 'book/index.html'\n    self.initial_paths = %w(\n      reference/introduction.html\n      std/index.html\n      error_codes/error-index.html)\n    self.links = {\n      home: 'https://www.rust-lang.org/',\n      code: 'https://github.com/rust-lang/rust'\n    }\n\n    html_filters.push 'rust/entries', 'rust/clean_html'\n\n    options[:only_patterns] = [\n      /\\Abook\\//,\n      /\\Areference\\//,\n      /\\Acollections\\//,\n      /\\Astd\\//,\n      /\\Aerror_codes\\//, ]\n\n    options[:skip] = %w(book/README.html book/ffi.html)\n    options[:skip_patterns] = [/(?<!\\.html)\\z/, /\\/print\\.html/, /\\Abook\\/second-edition\\//]\n\n    options[:fix_urls] = ->(url) do\n      url.sub! %r{(#{Rust.base_url}.+/)\\z}, '\\1index.html'\n      url.sub! \"#{Rust.base_url}nightly/\", Rust.base_url\n      url.sub! '/unicode/u_str', '/unicode/str/'\n      url.sub! '/std/std/', '/std/'\n      url\n    end\n\n    options[:attribution] = <<-HTML\n      &copy; 2010 The Rust Project Developers<br>\n      Licensed under the Apache License, Version 2.0 or the MIT license, at your option.\n    HTML\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://www.rust-lang.org/', opts)\n      label = doc.at_css('.button-download + p > a').content\n      label.sub(/Version /, '')\n    end\n\n    private\n\n    REDIRECT_RGX = /http-equiv=\"refresh\"/i\n    NOT_FOUND_RGX = /<title>Not Found<\\/title>/\n\n    def process_response?(response)\n      !(response.body =~ REDIRECT_RGX || response.body =~ NOT_FOUND_RGX || response.body.blank?)\n    end\n\n    def parse(response) # Hook here because Nokogori removes whitespace from headings\n      response.body.gsub! %r{<h[1-6] class=\"code-header\">}, '<pre class=\"code-header\">'\n      # And the reference uses whitespace for indentation in grammar definitions\n      response.body.gsub! %r{<div class=\"grammar-container\">([\\W\\w]+?)</div>}, '<pre class=\"grammar-container\">\\1</pre>'\n      super\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/rxjs.rb",
    "content": "require 'yajl/json_gem'\n\nmodule Docs\n  class Rxjs < UrlScraper\n    self.name = 'RxJS'\n    self.type = 'rxjs'\n    self.release = '7.8.1'\n    self.base_url = 'https://rxjs.dev/'\n    self.root_path = 'guide/overview'\n    self.links = {\n      home: 'https://rxjs.dev/',\n      code: 'https://github.com/ReactiveX/rxjs'\n    }\n\n    html_filters.push 'rxjs/clean_html', 'rxjs/entries'\n\n    options[:follow_links] = false\n    options[:only_patterns] = [/guide\\//, /api\\//]\n    options[:skip_patterns] = [/api\\/([^\\/]+)\\.json/, /api\\/index/]\n    options[:fix_urls_before_parse] = ->(url) do\n      url.sub! %r{\\A(\\.\\/)?guide/}, '/guide/'\n      url.sub! %r{\\Aapi/}, '/api/'\n      url.sub! %r{\\Agenerated/}, '/generated/'\n      url\n    end\n\n    options[:max_image_size] = 256_000\n\n    options[:attribution] = <<-HTML\n      &copy; 2015&ndash;2022 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors.<br>\n      Code licensed under an Apache-2.0 License. Documentation licensed under CC BY 4.0.\n    HTML\n\n    def get_latest_version(opts)\n      json = fetch_json('https://rxjs.dev/generated/navigation.json', opts)\n      json['__versionInfo']['raw']\n    end\n\n    private\n\n    def initial_urls\n      initial_urls = []\n\n      Request.run \"#{self.class.base_url}generated/navigation.json\" do |response|\n        data = JSON.parse(response.body)\n        dig = ->(entry) do\n          initial_urls << url_for(\"generated/docs/#{entry['url']}.json\") if entry['url'] && entry['url'] != 'api'\n          entry['children'].each(&dig) if entry['children']\n        end\n        data['SideNav'].each(&dig)\n      end\n\n      Request.run \"#{self.class.base_url}generated/docs/api/api-list.json\" do |response|\n        data = JSON.parse(response.body)\n        dig = ->(entry) do\n          initial_urls << url_for(\"generated/docs/#{entry['path']}.json\") if entry['path']\n          initial_urls << url_for(\"generated/docs/api/#{entry['name']}.json\") if entry['name'] && !entry['path']\n          entry['items'].each(&dig) if entry['items']\n        end\n        data.each(&dig)\n      end\n\n      initial_urls.select do |url|\n        options[:only_patterns].any? { |pattern| url =~ pattern } &&\n          options[:skip_patterns].none? { |pattern| url =~ pattern }\n      end\n    end\n\n    def handle_response(response)\n      if response.mime_type.include?('json')\n        begin\n          response.options[:response_body] = JSON.parse(response.body)['contents']\n        rescue JSON::ParserError\n          response.options[:response_body] = ''\n        end\n        response.headers['Content-Type'] = 'text/html'\n        response.url.path = response.url.path.sub('/generated/docs/', '/').remove('.json')\n        response.effective_url.path = response.effective_url.path.sub('/generated/docs/', '/').remove('.json')\n      end\n      super\n    end\n\n    def parse(response)\n      response.body.gsub! '<code-example', '<pre'\n      response.body.gsub! '</code-example', '</pre'\n      response.body.gsub! '<code-pane', '<pre'\n      response.body.gsub! '</code-pane', '</pre'\n      response.body.gsub! '<live-example></live-example>', 'live example'\n      response.body.gsub! '<live-example', '<span'\n      response.body.gsub! '</live-example', '</span'\n      super\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/salt_stack.rb",
    "content": "module Docs\n  class SaltStack < UrlScraper\n    self.type = 'simple'\n    self.slug = 'saltstack'\n    self.release = '3003'\n    self.base_url = 'https://docs.saltproject.io/en/latest/'\n    self.root_path = 'ref/index.html'\n    self.links = {\n      home: 'https://www.saltproject.io/',\n      code: 'https://github.com/saltstack/salt'\n    }\n\n    html_filters.push 'salt_stack/clean_html', 'salt_stack/entries'\n\n    options[:only_patterns] = [/all\\//]\n    options[:container] = '.body-content > .section'\n\n    options[:attribution] = <<-HTML\n      &copy; 2021 SaltStack.<br>\n      Licensed under the Apache License, Version 2.0.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('saltstack', 'salt', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/sanctuary.rb",
    "content": "module Docs\n\n  class Sanctuary < UrlScraper\n    self.name = \"Sanctuary\"\n    self.slug = \"sanctuary\"\n    self.type = \"sanctuary\"\n    self.release = \"3.1.0\"\n    self.base_url = \"https://sanctuary.js.org/\"\n    self.links = {\n      home: \"https://sanctuary.js.org/\",\n      code: \"https://github.com/sanctuary-js/sanctuary\",\n    }\n\n    html_filters.push \"sanctuary/entries\", \"sanctuary/clean_html\"\n\n    options[:container] = '#css-main'\n    options[:title] = \"Sanctuary\"\n    options[:attribution] = <<-HTML\n      &copy; 2020 Sanctuary<br>\n      &copy; 2016 Plaid Technologies, Inc.<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version(\"sanctuary\", opts)\n    end\n\n    private\n\n    def parse(response) # Hook here because Nokogori removes whitespace from textareas\n      response.body.gsub! %r{<div class=\"output\"[^>]*>([\\W\\w]+?)</div>}, '<pre class=\"output\">\\1</pre>'\n      super\n    end\n\n  end\n\nend\n"
  },
  {
    "path": "lib/docs/scrapers/sanctuary_def.rb",
    "content": "module Docs\n\n  class SanctuaryDef < Github\n    self.name = \"Sanctuary Def\"\n    self.slug = \"sanctuary_def\"\n    self.type = \"sanctuary_def\"\n    self.release = \"0.22.0\"\n    self.base_url = \"https://github.com/sanctuary-js/sanctuary-def/blob/v#{self.release}/README.md\"\n    self.links = {\n      home: \"https://github.com/sanctuary-js/sanctuary-def\",\n      code: \"https://github.com/sanctuary-js/sanctuary-def\",\n    }\n\n    html_filters.push \"sanctuary_def/entries\", \"sanctuary_def/clean_html\"\n\n    options[:container] = '.markdown-body'\n    options[:title] = \"Sanctuary Def\"\n    options[:trailing_slash] = false\n    options[:attribution] = <<-HTML\n      &copy; 2020 Sanctuary<br>\n      &copy; 2016 Plaid Technologies, Inc.<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version(\"sanctuary-def\", opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/sanctuary_type_classes.rb",
    "content": "module Docs\n\n  class SanctuaryTypeClasses < Github\n    self.name = \"Sanctuary Type Classes\"\n    self.slug = \"sanctuary_type_classes\"\n    self.type = \"sanctuary_type_classes\"\n    self.release = \"13.0.0\"\n    self.base_url = \"https://github.com/sanctuary-js/sanctuary-type-classes/blob/v#{self.release}/README.md\"\n    self.links = {\n      home: \"https://github.com/sanctuary-js/sanctuary-type-classes\",\n      code: \"https://github.com/sanctuary-js/sanctuary-type-classes\",\n    }\n\n    html_filters.push \"sanctuary_type_classes/entries\", \"sanctuary_type_classes/clean_html\"\n\n    options[:container] = '.markdown-body'\n    options[:title] = \"Sanctuary Type Classes\"\n    options[:trailing_slash] = false\n    options[:attribution] = <<-HTML\n      &copy; 2020 Sanctuary<br>\n      &copy; 2016 Plaid Technologies, Inc.<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version(\"sanctuary-type-classes\", opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/sass.rb",
    "content": "module Docs\n  class Sass < UrlScraper\n    self.type = 'yard'\n    self.release = '1.89.1'\n    self.base_url = 'https://sass-lang.com/documentation'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'http://sass-lang.com/',\n      code: 'https://github.com/sass/sass'\n    }\n\n    html_filters.push 'sass/entries', 'sass/clean_html'\n\n    options[:root_title] = false\n\n    options[:skip_patterns] = [/breaking-changes/]\n    options[:trailing_slash] = false\n\n    options[:attribution] = <<-HTML\n      &copy; 2006&ndash;2025 the Sass team, and numerous contributors<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version('sass', opts)\n    end\n\n    private\n\n    def parse(response)\n      response.body.gsub! '<span class=\"widont\">&nbsp;</span>', '&nbsp;'\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/scala.rb",
    "content": "module Docs\n  class Scala < FileScraper\n    self.name = 'Scala'\n    self.type = 'scala'\n    self.links = {\n      home: 'https://www.scala-lang.org/',\n      code: 'https://github.com/scala/scala'\n    }\n\n    options[:attribution] = <<-HTML\n        &copy; 2002-2022 EPFL, with contributions from Lightbend.<br>\n        Licensed under the Apache License, Version 2.0.\n    HTML\n\n    # For Scala 3, there is no official download link for the documentation\n    # (see https://contributors.scala-lang.org/t/5537).\n    #\n    # We currently need to build the docs ourselves. To do so:\n    # 1. Make sure that Scala 3 and sbt are installed\n    #    (https://www.scala-lang.org/download/scala3.html)\n    # 2. Clone the Scala 3 (Dotty) repository (https://github.com/lampepfl/dotty)\n    # 3. From the Dotty folder, run this command in the terminal:\n    #    $ sbt scaladoc/generateScalaDocumentation\n    # 4. Extract scaladoc/output/scala3/api/ into docs/scala~3.1\n    version '3.2' do\n      self.release = '3.2.0'\n      self.base_url = 'https://scala-lang.org/api/3.2.0/'\n      self.root_path = 'index.html'\n\n      options[:skip_patterns] = [\n        # Ignore class names with include “#”, which cause issues with the scraper\n        /%23/,\n\n        # Ignore local links to the Java documentation created by a Scaladoc bug\n        /java\\/lang/,\n      ]\n\n      html_filters.push 'scala/entries_v3', 'scala/clean_html_v3'\n    end\n\n    version '3.1' do\n      self.release = '3.1.1'\n      self.base_url = 'https://scala-lang.org/api/3.1.1/'\n      self.root_path = 'index.html'\n\n      options[:skip_patterns] = [\n        # Ignore class names with include “#”, which cause issues with the scraper\n        /%23/,\n\n        # Ignore local links to the Java documentation created by a Scaladoc bug\n        /java\\/lang/,\n      ]\n\n      html_filters.push 'scala/entries_v3', 'scala/clean_html_v3'\n    end\n\n    # https://downloads.lightbend.com/scala/2.13.0/scala-docs-2.13.0.zip\n    # Extract api/scala-library into docs/scala~2.13_library\n    version '2.13 Library' do\n      self.release = '2.13.0'\n      self.base_url = 'https://www.scala-lang.org/api/2.13.0/'\n      self.root_path = 'index.html'\n      options[:container] = '#content-container'\n\n      html_filters.push 'scala/entries_v2', 'scala/clean_html_v2'\n    end\n\n    # https://downloads.lightbend.com/scala/2.13.0/scala-docs-2.13.0.zip\n    # Extract api/scala-reflect into docs/scala~2.13_reflection\n    version '2.13 Reflection' do\n      self.release = '2.13.0'\n      self.base_url = 'https://www.scala-lang.org/api/2.13.0/scala-reflect/'\n      self.root_path = 'index.html'\n      options[:container] = '#content-container'\n\n      html_filters.push 'scala/entries_v2', 'scala/clean_html_v2'\n    end\n\n    # https://downloads.lightbend.com/scala/2.12.9/scala-docs-2.12.9.zip\n    # Extract api/scala-library into docs/scala~2.12_library\n    version '2.12 Library' do\n      self.release = '2.12.9'\n      self.base_url = 'https://www.scala-lang.org/api/2.12.9/'\n      self.root_path = 'index.html'\n      options[:container] = '#content-container'\n\n      html_filters.push 'scala/entries_v2', 'scala/clean_html_v2'\n    end\n\n    # https://downloads.lightbend.com/scala/2.12.9/scala-docs-2.12.9.zip\n    # Extract api/scala-reflect into docs/scala~2.12_reflection\n    version '2.12 Reflection' do\n      self.release = '2.12.9'\n      self.base_url = 'https://www.scala-lang.org/api/2.12.9/scala-reflect/'\n      self.root_path = 'index.html'\n      options[:container] = '#content-container'\n\n      html_filters.push 'scala/entries_v2', 'scala/clean_html_v2'\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://www.scala-lang.org/api/3.x/', opts)\n      doc.at_css('.projectVersion').content\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/scikit_image.rb",
    "content": "module Docs\n  class ScikitImage < UrlScraper\n    self.name = 'scikit-image'\n    self.slug = 'scikit_image'\n    self.type = 'sphinx'\n    self.release = '0.25.0'\n    v = self.release[/\\d+\\.\\d+/]\n    self.base_url = \"https://scikit-image.org/docs/#{v}.x/\"\n    self.initial_paths = %w(/ /api/ /user_guide/)\n    self.links = {\n      home: 'https://scikit-image.org/',\n      code: 'https://github.com/scikit-image/scikit-image'\n    }\n\n    html_filters.push 'scikit_image/entries', 'sphinx/clean_html'\n\n    options[:container] = 'main article'\n    options[:skip] = %w(api_changes.html)\n    options[:only_patterns] = [/\\Aapi/, /\\Auser_guide/]\n\n    options[:attribution] = <<-HTML\n      &copy; 2019 the scikit-image team<br>\n      Licensed under the BSD 3-clause License.\n    HTML\n\n    def get_latest_version(opts)\n      tags = get_github_tags('scikit-image', 'scikit-image', opts)\n      tags[0]['name'][1..-1]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/scikit_learn.rb",
    "content": "module Docs\n  class ScikitLearn < UrlScraper\n    self.name = 'scikit-learn'\n    self.slug = 'scikit_learn'\n    self.type = 'sphinx'\n    self.release = '1.6.1'\n    v = self.release[/\\d+\\.\\d+/]\n    self.base_url = \"https://scikit-learn.org/#{v}/\"\n    self.root_path = 'index.html'\n    self.force_gzip = true\n    self.links = {\n      home: 'https://scikit-learn.org/',\n      code: 'https://github.com/scikit-learn/scikit-learn'\n    }\n\n    html_filters.push 'scikit_learn/entries', 'scikit_learn/clean_html', 'sphinx/clean_html', 'title'\n\n    options[:skip] = %w(modules/generated/sklearn.experimental.enable_iterative_imputer.html\n                        modules/generated/sklearn.experimental.enable_hist_gradient_boosting.html)\n    options[:only_patterns] = [/\\Amodules/, /\\Adatasets/, /\\Atutorial/, /\\Aauto_examples/]\n    options[:skip_patterns] = [/\\Adatasets\\/(?!index)/]\n    options[:title] = false\n    options[:root_title] = 'scikit-learn'\n    options[:max_image_size] = 256_000\n\n    options[:attribution] = <<-HTML\n      &copy; 2007&ndash;2025 The scikit-learn developers<br>\n      Licensed under the 3-clause BSD License.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('scikit-learn', 'scikit-learn', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/sequelize.rb",
    "content": "module Docs\n  class Sequelize < UrlScraper\n    include MultipleBaseUrls\n\n    self.name = 'Sequelize'\n    self.slug = 'sequelize'\n    self.type = 'simple'\n    self.links = {\n      home: 'https://sequelize.org/',\n      code: 'https://github.com/sequelize/sequelize'\n    }\n\n    # List of content filters (to be applied sequentially)\n    html_filters.push 'sequelize/entries', 'sequelize/clean_html'\n\n    # Skip the source files, the license page and the \"Who's using Sequelize\" page\n    options[:skip_patterns] = [/\\.js\\.html/, /manual\\/legal\\.html/, /manual\\/whos-using\\.html/]\n\n    # License information that appears appears at the bottom of the entry page\n    options[:attribution] = <<-HTML\n      Copyright &copy; 2014&ndash;present Sequelize contributors<br>\n      Licensed under the MIT License.\n    HTML\n\n    def initial_urls\n      [\n        \"https://sequelize.org/docs/v6/\",\n        \"https://sequelize.org/api/v6/identifiers.html\",\n      ]\n    end\n\n    version '6' do\n      self.release = '6.37.5'\n      self.base_url = \"https://sequelize.org/docs/v6/\"\n      self.base_urls = [\n        \"https://sequelize.org/docs/v6/\",\n        \"https://sequelize.org/api/v6/\",\n      ]\n    end\n\n    version '5' do\n      self.release = '5.22.0'\n      self.base_url = \"https://sequelize.org/v#{version}/\"\n    end\n\n    version '4' do\n      self.release = '4.44.4'\n      self.base_url = \"https://sequelize.org/v#{version}/\"\n    end\n\n    # Method to fetch the most recent version of the project\n    def get_latest_version(opts)\n     get_npm_version('sequelize', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/sinon.rb",
    "content": "module Docs\n  class Sinon < UrlScraper\n    self.name = 'Sinon.JS'\n    self.slug = 'sinon'\n    self.type = 'sinon'\n    self.links = {\n      home: 'https://sinonjs.org/',\n      code: 'https://github.com/sinonjs/sinon'\n    }\n\n    html_filters.push 'sinon/clean_html', 'sinon/entries'\n\n    options[:title] = 'Sinon.JS'\n    options[:container] = '.content .container'\n\n    options[:attribution] = <<-HTML\n      &copy; 2010&ndash;2022 Christian Johansen<br>\n      Licensed under the BSD License.\n    HTML\n\n    # Links in page point to '../page' what makes devdocs points to non-existent links\n    options[:fix_urls] = -> (url) do\n      if !(url =~ /releases\\/v\\d*/)\n        url.gsub!(/.*releases\\//, \"\")\n      end\n\n      url\n    end\n\n    RELEASE_MAPPINGS = {\n      '15' => '15.0.1',\n      '14' => '14.0.2',\n      '13' => '13.0.1',\n      '12' => '12.0.1',\n      '11' => '11.1.2',\n      '10' => '10.0.1',\n      '9'  => '9.2.2.',\n      '8'  => '8.1.1',\n      '7'  => '7.5.0',\n      '6'  => '6.3.5',\n      '5'  => '5.1.0',\n      '4'  => '4.5.0',\n      '3'  => '3.3.0',\n      '2'  => '2.4.1',\n      '1'  => '1.17.7'\n    }\n\n    RELEASE_MAPPINGS.each do |ver, release|\n      version ver do\n        self.release = release\n        self.base_url = \"https://sinonjs.org/releases/v#{ver}/\"\n      end\n    end\n\n    def get_latest_version(opts)\n      tags = get_github_tags('sinonjs', 'sinon', opts)\n      tags[0]['name'][1..-1]\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/socketio.rb",
    "content": "module Docs\n  class Socketio < UrlScraper\n    self.name = 'Socket.IO'\n    self.slug = 'socketio'\n    self.type = 'sphinx'\n    self.links = {\n      home: 'https://socket.io/',\n      code: 'https://github.com/socketio/socket.io'\n    }\n\n    html_filters.push 'socketio/clean_html', 'socketio/entries', 'sphinx/clean_html'\n\n    options[:trailing_slash] = false\n    options[:skip] = %w(/faq /glossary)\n\n    options[:attribution] = <<-HTML\n      &copy; 2014&ndash;2021 Automattic<br>\n      Licensed under the MIT License.\n    HTML\n\n    version '4' do\n      self.release = '4.5.2'\n      self.base_url = \"https://socket.io/docs/v#{version}\"\n    end\n\n    version '3' do\n      self.release = '3.1.2'\n      self.base_url = \"https://socket.io/docs/v#{version}\"\n    end\n\n    version '2' do\n      self.release = '2.4.0'\n      self.base_url = \"https://socket.io/docs/v#{version}\"\n    end\n\n    def get_latest_version(opts)\n      get_npm_version('socket.io', opts)\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/sphinx.rb",
    "content": "module Docs\n  class Sphinx < Scraper\n    self.abstract = true\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/spring_boot.rb",
    "content": "module Docs\n  class SpringBoot < UrlScraper\n    self.name = 'Spring Boot'\n    self.slug = 'spring_boot'\n    self.type = 'simple'\n    self.root_path = \"index.html\"\n    self.links = {\n      home: 'https://spring.io/',\n      code: 'https://github.com/spring-projects/spring-boot'\n    }\n\n    html_filters.push 'spring_boot/entries', 'spring_boot/clean_html'\n\n    options[:skip_patterns] = [/legal/]\n\n    # https://github.com/spring-projects/spring-boot/blob/main/buildSrc/src/main/resources/NOTICE.txt\n    options[:attribution] = <<-HTML\n    Copyright &copy; 2012-2023 VMware, Inc.<br>\n    Licensed under the Apache License, Version 2.0.\n    HTML\n\n    self.release = '3.1.3'\n    self.base_url = \"https://docs.spring.io/spring-boot/docs/#{release}/reference/html/\"\n\n    def get_latest_version(opts)\n      get_latest_github_release('spring-projects', 'spring-boot', opts)\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/sqlite.rb",
    "content": "module Docs\n  class Sqlite < UrlScraper\n    self.name = 'SQLite'\n    self.type = 'sqlite'\n    self.release = '3.51.1'\n    self.base_url = 'https://sqlite.org/'\n    self.root_path = 'docs.html'\n    self.initial_paths = %w(keyword_index.html)\n    self.links = {\n      home: 'https://sqlite.org/',\n      code: 'https://www.sqlite.org/src/'\n    }\n\n    html_filters.insert_before 'clean_html', 'sqlite/clean_js_tables'\n    html_filters.push 'sqlite/entries', 'sqlite/clean_html'\n\n    options[:clean_text] = false  # keep SVG elements\n    options[:only_patterns] = [/\\.html\\z/]\n    options[:skip_patterns] = [/releaselog/, /consortium/]\n    options[:skip] = %w(\n      index.html\n      about.html\n      download.html\n      copyright.html\n      support.html\n      prosupport.html\n      hp1.html\n      news.html\n      oldnews.html\n      doclist.html\n      dev.html\n      chronology.html\n      not-found.html\n      famous.html\n      books.html\n      crew.html\n      mostdeployed.html\n      requirements.html\n      session/intro.html\n      syntax.html\n      src/doc/trunk/doc/lemon.html\n    )\n\n    options[:attribution] = 'SQLite is in the Public Domain.'\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://sqlite.org/chronology.html', opts)\n      doc.at_css('#chrontab > tbody > tr > td:last-child > a').content\n    end\n\n    private\n\n    def parse(response)\n      response.body.gsub! %r{(<h2[^>]*>[^<]+)</h1>}, '\\1</h2>'\n      super\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/statsmodels.rb",
    "content": "module Docs\n  class Statsmodels < UrlScraper\n    self.type = 'sphinx'\n    self.release = '0.9.0'\n    self.base_url = 'http://www.statsmodels.org/stable/'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'http://www.statsmodels.org/',\n      code: 'https://github.com/statsmodels/statsmodels/'\n    }\n\n    html_filters.push 'statsmodels/entries', 'statsmodels/clean_html', 'sphinx/clean_html'\n\n    options[:skip] = %w(about.html search.html genindex.html)\n    options[:skip_patterns] = [/\\Arelease/, /\\Adev/, /\\A_modules/, /\\Adatasets/]\n\n    options[:attribution] = <<-HTML\n      &copy; 2009&ndash;2012 Statsmodels Developers<br>\n      &copy; 2006&ndash;2008 Scipy Developers<br>\n      &copy; 2006 Jonathan E. Taylor<br>\n      Licensed under the 3-clause BSD License.\n    HTML\n\n    def get_latest_version(opts)\n      get_latest_github_release('statsmodels', 'statsmodels', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/support_tables.rb",
    "content": "require 'yajl/json_gem'\n\nmodule Docs\n  class SupportTables < Scraper\n    include Instrumentable\n\n    self.name = 'Support Tables'\n    self.slug = 'browser_support_tables'\n    self.type = 'support_tables'\n    self.release = '1.0.30001751'\n    self.base_url = 'https://github.com/Fyrd/caniuse/raw/main/'\n\n    # https://github.com/Fyrd/caniuse/blob/main/LICENSE\n    options[:attribution] = <<-HTML\n      &copy; 2020 Alexis Deveria<br>\n      Licensed under the Creative Commons Attribution 4.0 International License.\n    HTML\n\n    def build_pages\n      url = 'https://github.com/Fyrd/caniuse/raw/main/data.json'\n      instrument 'running.scraper', urls: [url]\n\n      response = Request.run(url)\n      instrument 'process_response.scraper', response: response\n\n      data = JSON.parse(response.body)\n      instrument 'queued.scraper', urls: data['data'].keys\n\n      data['agents']['and_chr']['browser'] = 'Android Chrome'\n      data['agents']['and_ff']['browser'] = 'Android Firefox'\n      data['agents']['and_uc']['browser'] = 'Android UC Browser'\n      data['desktop_agents'] = data['agents'].select { |_, agent| agent['type'] == 'desktop' }\n      data['mobile_agents']  = data['agents'].select { |_, agent| agent['type'] == 'mobile' }\n      data['total_versions'] = data['agents']['firefox']['versions'].length\n\n      index_page = {\n        path: 'index',\n        store_path: 'index.html',\n        output: ERB.new(INDEX_PAGE_ERB, trim_mode:\">\").result(binding),\n        entries: [Entry.new(nil, 'index', nil)]\n      }\n\n      yield index_page\n\n      data['data'].each do |feature_id, feature|\n        url = \"https://github.com/Fyrd/caniuse/raw/main/features-json/#{feature_id}.json\"\n\n        response = Request.run(url)\n        instrument 'process_response.scraper', response: response\n\n        feature = JSON.parse(response.body)\n\n        name = feature['title']\n        type = feature['categories'].find { |category| name.include?(category) } || feature['categories'].first\n\n        page = {\n          path: feature_id,\n          store_path: \"#{feature_id}.html\",\n          output: ERB.new(PAGE_ERB, trim_mode:\">\").result(binding).split(\"\\n\").map(&:strip).join(\"\\n\"),\n          entries: [Entry.new(name, feature_id, type)]\n        }\n\n        yield page\n      end\n    end\n\n    def md_to_html(str)\n      str = CGI::escape_html(str.strip)\n      str.gsub! %r{`(.*?)`}, '<code>\\1</code>'\n      str.gsub! %r{\\n\\s*\\n}, '</p><p>'\n      str.gsub! \"\\n\", '<br>'\n      str.gsub! %r{\\[(.+?)\\]\\((.+?)\\)}, '<a href=\"\\2\">\\1</a>'\n      str\n    end\n\n    def support_to_css_class(support)\n      support.select { |s| s.length == 1 }.join(' ')\n    end\n\n    def support_to_note_indicators(support)\n      notes = support.select { |s| s.start_with?('#') }.map { |s| s[1..-1] }\n      notes << '*' if support.include?('x')\n      \"<sup>(#{notes.join(',')})</sup>\" if notes.present?\n    end\n\n    INDEX_PAGE_ERB = <<-HTML.strip_heredoc\n      <h1>Browser support tables</h1>\n    HTML\n\n    PAGE_ERB = <<-HTML.strip_heredoc\n      <h1><%= feature['title'] %></h1>\n\n      <p><%= md_to_html feature['description'] %></p>\n\n      <table>\n        <% if feature['spec'].present? %>\n          <tr>\n            <th>Spec</th>\n            <td><a href=\"<%= feature['spec'] %>\"><%= feature['spec'] %></a></td>\n          </tr>\n        <% end %>\n\n        <% if feature['status'].present? %>\n          <tr>\n            <th>Status</th>\n            <td><%= data['statuses'][feature['status']] %></td>\n          </tr>\n        <% end %>\n      </table>\n\n      <% ['desktop', 'mobile'].each do |type| %>\n        <table class=\"stats\">\n          <tr>\n            <% data[\"\\#{type}_agents\"].each do |agent_id, agent| %>\n              <th><%= agent['browser'] %></th>\n            <% end %>\n          </tr>\n          <% (0...(data['total_versions'])).reverse_each do |i| %>\n            <% next if data[\"\\#{type}_agents\"].none? { |_, agent| agent['versions'][i] } %>\n            <% if i == (data['total_versions'] - 8) %>\n              <tr class=\"show-all\">\n                <th class=\"show-all\" colspan=\"<%= data[\"\\#{type}_agents\"].length %>\">\n                  <a href=\"#\" class=\"show-all\">Show all</a>\n                </th>\n              </tr>\n            <% end %>\n            <tr<%= ' class=\"current\"' if i == (data['total_versions'] - 4) %>>\n              <% data[\"\\#{type}_agents\"].each do |agent_id, agent| %>\n                <% version = agent['versions'][i] %>\n                <% if version %>\n                  <% support = feature['stats'][agent_id][version].split(' ') %>\n                  <% feature['prefix'] = true if support.include?('x') %>\n                  <td class=\"<%= support_to_css_class(support)  %>\"><%= version %> <%= support_to_note_indicators(support) %></td>\n                <% else %>\n                  <td>&nbsp;</td>\n                <% end %>\n              <% end %>\n            </tr>\n          <% end %>\n        </table>\n      <% end %>\n\n      <h2>Notes</h2>\n\n      <% if feature['notes'].present? %>\n        <p><%= md_to_html feature['notes'] %></p>\n      <% end %>\n\n      <% if feature['notes_by_num'].present? %>\n        <ol>\n          <% feature['notes_by_num'].each do |num, note| %>\n            <li><p><%= md_to_html note %></p></li>\n          <% end %>\n        </ol>\n      <% end %>\n\n      <% if feature['prefix'] %>\n        <dl>\n          <dd><sup>*</sup> Partial support with prefix.</dd>\n        </dl>\n      <% end %>\n\n      <% if feature['bugs'].present? %>\n        <h2>Bugs</h2>\n        <ul>\n          <% feature['bugs'].each do |bug| %>\n            <li><p><%= md_to_html bug['description'] %></p></li>\n          <% end %>\n        </ul>\n      <% end %>\n\n      <% if feature['links'].present? %>\n        <h2>Resources</h2>\n        <ul>\n          <% feature['links'].each do |link| %>\n            <li><a href=\"<%= link['url'] %>\"><%= link['title'] %></a></li>\n          <% end %>\n        </ul>\n      <% end %>\n\n      <div class=\"_attribution\">\n        <p class=\"_attribution-p\">\n          Data by caniuse.com<br>\n          Licensed under the Creative Commons Attribution License v4.0.<br>\n          <a href=\"https://caniuse.com/<%= feature_id %>\" class=\"_attribution-link\">https://caniuse.com/<%= feature_id %></a>\n        </p>\n      </div>\n    HTML\n\n    def get_latest_version(opts)\n      get_npm_version('caniuse-db', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/svelte.rb",
    "content": "module Docs\n  class Svelte < UrlScraper\n    self.name = 'Svelte'\n    self.slug = 'svelte'\n    self.type = 'simple'\n    self.root_path = '/'\n    self.links = {\n      home: 'https://svelte.dev/',\n      code: 'https://github.com/sveltejs/svelte'\n    }\n\n    options[:root_title] = 'Svelte'\n\n    # https://github.com/sveltejs/svelte/blob/master/LICENSE.md\n    options[:attribution] = <<-HTML\n      &copy; 2016–2025 Rich Harris and contributors<br>\n      Licensed under the MIT License.\n    HTML\n\n    self.base_url = 'https://svelte.dev/docs/svelte/'\n    html_filters.push 'svelte/entries', 'svelte/clean_html'\n\n    version do\n      self.release = '5.38.10'\n    end\n\n    version '4' do\n      self.release = '4.2.1'\n    end\n\n    version '3' do\n      self.release = '3.55.0'\n    end\n\n    def get_latest_version(opts)\n      get_npm_version('svelte', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/symfony.rb",
    "content": "module Docs\n  class Symfony < UrlScraper\n    self.name = 'Symfony'\n    self.slug = 'symfony'\n    self.type = 'laravel'\n    self.root_path = 'namespaces.html'\n    self.initial_paths = %w(classes.html)\n    self.links = {\n      home: 'https://symfony.com/',\n      code: 'https://github.com/symfony/symfony'\n    }\n\n    html_filters.push 'symfony/entries', 'symfony/clean_html'\n\n    options[:skip] = %w(\n      panel.html\n      namespaces.html\n      interfaces.html\n      traits.html\n      doc-index.html\n      search.html\n      Symfony.html)\n\n    options[:attribution] = <<-HTML\n      &copy; 2004&ndash;2017 Fabien Potencier<br>\n      Licensed under the MIT License.\n    HTML\n\n    version '4.1' do\n      self.release = '4.1.7'\n      self.base_url = \"https://api.symfony.com/#{version}/\"\n    end\n\n    version '4.0' do\n      self.release = '4.0.3'\n      self.base_url = \"https://api.symfony.com/#{version}/\"\n    end\n\n    version '3.4' do\n      self.release = '3.4.3'\n      self.base_url = \"https://api.symfony.com/#{version}/\"\n    end\n\n    version '3.3' do\n      self.release = '3.3.15'\n      self.base_url = \"https://api.symfony.com/#{version}/\"\n    end\n\n    version '3.2' do\n      self.release = '3.2.13'\n      self.base_url = \"https://api.symfony.com/#{version}/\"\n    end\n\n    version '3.1' do\n      self.release = '3.1.8'\n      self.base_url = \"https://api.symfony.com/#{version}/\"\n    end\n\n    version '3.0' do\n      self.release = '3.0.1'\n      self.base_url = \"https://api.symfony.com/#{version}/\"\n    end\n\n    version '2.8' do\n      self.release = '2.8.28'\n      self.base_url = \"https://api.symfony.com/#{version}/\"\n    end\n\n    version '2.7' do\n      self.release = '2.7.35'\n      self.base_url = \"https://api.symfony.com/#{version}/\"\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('symfony', 'symfony', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/tailwindcss.rb",
    "content": "module Docs\n  class Tailwindcss < UrlScraper\n    self.name = 'Tailwind CSS'\n    self.type = 'tailwindcss'\n    self.slug = 'tailwindcss'\n    self.root_path = '/'\n    self.links = {\n      home: 'https://tailwindcss.com/',\n      code: 'https://github.com/tailwindlabs/tailwindcss'\n    }\n\n    html_filters.push 'tailwindcss/entries', 'tailwindcss/clean_html'\n\n    # Disable the clean text filter which removes empty nodes - we'll do it ourselves more selectively\n    text_filters.replace(\"clean_text\", \"tailwindcss/noop\")\n\n    options[:skip_patterns] = [\n      # Skip setup instructions\n      /\\/browser-support$/,\n      /\\/editor-setup$/,\n      /\\/installation$/,\n      /\\/optimizing-for-production$/,\n      /\\/upgrade-guide/,\n      /\\/using-with-preprocessors/\n    ]\n\n    #Obtainable from https://github.com/tailwindlabs/tailwindcss/blob/master/LICENSE\n    options[:attribution] = <<-HTML\n      &copy; Tailwind Labs Inc.\n    HTML\n\n    version do\n      self.release = '4.1.11'\n      self.base_url = 'https://tailwindcss.com/docs'\n\n      # Fix redirects\n      options[:fix_urls] = lambda do |url|\n        if url.include? \"installation/\"\n          break \"/docs/installation\"\n        end\n\n        if url.end_with? \"text-color\"\n          break \"/docs/color\"\n        end\n      end\n    end\n\n    version '3' do\n      self.release = '3.4.17'\n      self.base_url = 'https://v3.tailwindcss.com/docs'\n\n      # Fix redirects from older tailwind 2 docs\n      options[:fix_urls] = lambda do |url|\n        if url.include? \"installation/\"\n          break \"/docs/installation\"\n        end\n\n        if url.end_with? \"/breakpoints\"\n          break \"/docs/screens#{/#.*$/.match(url)}\"\n        end\n        if url.end_with? \"/adding-base-styles\"\n          break \"/docs/adding-custom-styles#adding-base-styles\"\n        end\n        if url.end_with? \"/ring-opacity\"\n          break \"/docs/ring-color#changing-the-opacity\"\n        end\n\n        if url.match(/\\/colors#?/)\n          break \"/docs/customizing-colors#{/#.*$/.match(url)}\"\n        end\n      end\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('tailwindlabs', 'tailwindcss', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/tcl_tk.rb",
    "content": "module Docs\n  class TclTk < UrlScraper\n    self.name = 'Tcl/Tk'\n    self.type = 'tcl_tk'\n    self.slug = 'tcl_tk'\n    self.links = {\n      home: 'https://www.tcl-lang.org/',\n      code: 'https://sourceforge.net/projects/tcl/files/Tcl/'\n    }\n\n    html_filters.push 'tcl_tk/entries', 'tcl_tk/clean_html', 'title'\n\n    options[:root_title] = 'Tcl/Tk Documentation'\n    options[:trailing_slash] = false\n    options[:skip] = %w(siteinfo.htm)\n    options[:skip_patterns] = [\n      # ignore keyword list pages\n      /\\AKeywords\\//,\n      # ignore C-API, only required for extension developers\n      /\\ATclLib\\//,\n      /\\ATkLib\\//,\n      /\\AItclLib\\//,\n      /\\ATdbcLib\\//\n    ]\n\n    options[:attribution] = <<-HTML\n      Licensed under <a href=\"http://www.tcl-lang.org/software/tcltk/license.html\">Tcl/Tk terms</a>\n    HTML\n\n    version '9.0' do\n      self.base_url = \"https://www.tcl-lang.org/man/tcl#{self.version}/\"\n      self.release = '9.0.2'\n    end\n\n    version '8.6' do\n      self.base_url = \"https://www.tcl-lang.org/man/tcl#{self.version}/\"\n      self.root_path = 'contents.htm'\n      self.release = '8.6.16'\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://www.tcl-lang.org/man/tcl/', opts)\n      doc.at_css('h2').content.scan(/Tk([0-9.]+)/)[0][0]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/tcllib.rb",
    "content": "module Docs\n  class Tcllib < UrlScraper\n    self.name = 'Tcllib'\n    self.type = 'simple'\n    self.slug = 'tcllib'\n    self.release = '2.0'\n    self.base_url = 'https://core.tcl-lang.org/tcllib/doc/trunk/embedded/md/'\n    self.root_path = 'toc0.md'\n    self.links = {\n      home: 'https://core.tcl-lang.org/tcllib/doc/trunk/embedded/index.md',\n      code: 'https://sourceforge.net/projects/tcllib/files/tcllib/'\n    }\n\n    html_filters.push 'tcllib/entries', 'tcllib/clean_html', 'title'\n    # The docs have incorrect <base> elements, so we should just skip that\n    html_filters.replace('apply_base_url', 'tcllib/nop')\n\n    options[:root_title] = 'Tcllib Documentation'\n    options[:container] = '.content'\n    options[:skip] = [\n      # Full of broken links, path improperly duplicates \"tcllib\" segment\n      'tcllib/toc.md',\n      # The other ones aren't terribly useful\n      'toc.md', 'toc1.md', 'toc2.md',\n      # Keyword index\n      'index.md'\n    ]\n\n    options[:attribution] = <<-HTML\n      Licensed under the <a href=\"https://core.tcl-lang.org/tcllib/doc/trunk/embedded/md/tcllib/files/devdoc/tcllib_license.md\">BSD license</a>\n    HTML\n\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://core.tcl-lang.org/tcllib/doc/trunk/embedded/index.md', opts)\n      doc.at_css('strong').content.scan(/([0-9.]+)/)[0][0]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/tensorflow/tensorflow.rb",
    "content": "module Docs\n  class Tensorflow < UrlScraper\n    self.name = 'TensorFlow'\n    self.type = 'tensorflow'\n    self.root_path = '/'\n    self.links = {\n      home: 'https://www.tensorflow.org/',\n      code: 'https://github.com/tensorflow/tensorflow'\n    }\n\n    html_filters.push 'tensorflow/entries', 'tensorflow/clean_html'\n\n    options[:max_image_size] = 300_000\n    options[:container] = '.devsite-main-content'\n\n    options[:attribution] = <<-HTML\n      &copy; 2022 The TensorFlow Authors. All rights reserved.<br>\n      Licensed under the Creative Commons Attribution License 4.0.<br>\n      Code samples licensed under the Apache 2.0 License.\n    HTML\n\n    version do\n      self.release = \"2.16.1\"\n      self.base_url = \"https://www.tensorflow.org/api_docs/python/tf\"\n    end\n\n    version '2.9' do\n      self.release = \"2.9.1\"\n      self.base_url = \"https://www.tensorflow.org/versions/r#{version}/api_docs/python/tf\"\n    end\n\n    version '2.4' do\n      self.release = \"#{version}.0\"\n      self.base_url = \"https://www.tensorflow.org/versions/r#{version}/api_docs/python/tf\"\n    end\n\n    version '2.3' do\n      self.release = \"#{version}.0\"\n      self.base_url = \"https://www.tensorflow.org/versions/r#{version}/api_docs/python/tf\"\n    end\n\n    version '1.15' do\n      self.release = \"#{version}.0\"\n      self.base_url = \"https://www.tensorflow.org/versions/r#{version}/api_docs/python/tf\"\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc(self.base_url, opts)\n      doc.title[/TensorFlow v([.\\d]+)/, 1]\n    end\n\n    private\n\n    def parse(response)\n      unless response.url == root_url\n        response.body.sub!(/<nav class=\"devsite-nav-responsive-sidebar.+?<\\/nav>/m, '')\n        response.body.gsub!(/<li class=\"devsite-nav-item\">.+?<\\/li>/m, '')\n      end\n\n      super\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/tensorflow/tensorflow_cpp.rb",
    "content": "module Docs\n  class TensorflowCpp < Tensorflow\n    self.name = 'TensorFlow C++'\n    self.slug = 'tensorflow_cpp'\n\n    version do\n      self.release = \"2.16.1\"\n      self.base_url = \"https://www.tensorflow.org/api_docs/cc\"\n    end\n\n    version '2.9' do\n      self.release = \"2.9.1\"\n      self.base_url = \"https://www.tensorflow.org/versions/r#{version}/api_docs/cc\"\n    end\n\n    version '2.4' do\n      self.release = \"#{version}.0\"\n      self.base_url = \"https://www.tensorflow.org/versions/r#{version}/api_docs/cc\"\n    end\n\n    version '2.3' do\n      self.release = \"#{version}.0\"\n      self.base_url = \"https://www.tensorflow.org/versions/r#{version}/api_docs/cc\"\n    end\n\n    version '1.15' do\n      self.release = \"#{version}.0\"\n      self.base_url = \"https://www.tensorflow.org/versions/r#{version}/api_docs/cc\"\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/terraform.rb",
    "content": "module Docs\n  class Terraform < UrlScraper\n    self.name = 'Terraform'\n    self.type = 'terraform'\n    self.release = '0.11.7'\n    self.base_url = 'https://www.terraform.io/docs/'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://www.terraform.io/',\n      code: 'https://github.com/hashicorp/terraform'\n    }\n\n    html_filters.push 'terraform/entries', 'terraform/clean_html'\n\n    options[:skip_patterns] = [/enterprise/, /enterprise-legacy/]\n\n    options[:attribution] = <<-HTML\n      &copy; 2018 HashiCorp</br>\n      Licensed under the MPL 2.0 License.\n    HTML\n\n    def get_latest_version(opts)\n      contents = get_latest_github_release('hashicorp', 'terraform', opts)\n      contents.sub(\"v\", \"\")\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/threejs.rb",
    "content": "module Docs\n  class Threejs < UrlScraper\n    self.name = 'Three.js'\n    self.type = 'simple'\n    self.slug = 'threejs'\n    self.links = {\n      home: 'https://threejs.org/',\n      code: 'https://github.com/mrdoob/three.js'\n    }\n\n    html_filters.push 'threejs/entries', 'threejs/clean_html'\n\n    # The content is directly in the body\n    options[:container] = 'body'\n\n    options[:skip] = %w(\n      prettify.js\n      lesson.js\n      lang.css\n      lesson.css\n      editor.html\n      list.js\n      page.js\n    )\n\n    options[:only_patterns] = [\n      /\\Aapi\\/en\\/.+\\.html/,   # API documentation\n      /\\Amanual\\/en\\/.+\\.html/ # Manual pages\n    ]\n\n    options[:skip_patterns] = [\n      /examples/,\n      /\\A_/,\n      /\\Aresources\\//,\n      /\\Ascenes\\//\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; 2010&ndash;#{Time.current.year} Three.js Authors<br>\n      Licensed under the MIT License.\n    HTML\n\n    self.release = '173'\n    self.base_url = \"https://threejs.org/docs\"\n\n    def get_latest_version(opts)\n      get_latest_github_release('mrdoob', 'three.js', opts)[1..]\n    end\n\n    def initial_paths\n      paths = []\n      url = 'https://threejs.org/docs/list.json'\n      response = Request.run(url)\n      json_data = JSON.parse(response.body)\n\n      # Process both API and manual sections\n      process_documentation(json_data['en'], paths)\n      paths\n    end\n\n    private\n\n    def process_documentation(data, paths, prefix = '')\n      data.each do |category, items|\n        if items.is_a?(Hash)\n          if items.values.first.is_a?(String)\n            # This is a leaf node with actual pages\n            items.each do |name, path|\n              paths << \"#{path}.html\"\n            end\n          else\n            # This is a category with subcategories\n            items.each do |subcategory, subitems|\n              process_documentation(items, paths, \"#{prefix}#{category}/\")\n            end\n          end\n        end\n      end\n    end\n  end\nend "
  },
  {
    "path": "lib/docs/scrapers/trio.rb",
    "content": "module Docs\n  class Trio < UrlScraper\n    self.type = 'simple'\n    self.release = '0.29.0'\n    self.base_url = \"https://trio.readthedocs.io/en/v#{self.release}/\"\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://trio.readthedocs.io/',\n      code: 'https://github.com/python-trio/trio'\n    }\n\n    html_filters.push 'trio/entries', 'trio/clean_html'\n\n    options[:only_patterns] = [\n      /reference-core/,\n      /reference-io/,\n      /reference-testing/,\n      /reference-lowlevel/,\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; 2017 Nathaniel J. Smith<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://trio.readthedocs.io/en/stable/', opts)\n      doc.at_css('div.trio-version').content[0..-1]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/twig.rb",
    "content": "module Docs\n  class Twig < UrlScraper\n    self.type = 'sphinx'\n    self.root_path = 'index.html'\n    self.initial_paths = %w(extensions/index.html)\n    self.links = {\n      home: 'https://twig.symfony.com/',\n      code: 'https://github.com/twigphp/Twig'\n    }\n\n    options[:attribution] = <<-HTML\n      &copy; 2009&ndash;2018 by the Twig Team<br>\n      Licensed under the three clause BSD license.<br>\n      The Twig logo is &copy; 2010&ndash;2020 Symfony\n    HTML\n\n    html_filters.push 'twig/clean_html', 'twig/entries'\n\n    options[:container] = 'div.bd > div.content'\n    options[:skip] = %w(deprecated.html advanced_legacy.html)\n\n    version '3' do\n      self.release = '3.1.1'\n      self.base_url = 'https://twig.symfony.com/doc/3.x/'\n    end\n\n    version '2' do\n      self.release = '2.14.1'\n      self.base_url = 'https://twig.symfony.com/doc/2.x/'\n    end\n\n    version '1' do\n      self.release = '1.44.1'\n      self.base_url = 'https://twig.symfony.com/doc/1.x/'\n    end\n\n    def get_latest_version(opts)\n      tags = get_github_tags('twigphp', 'Twig', opts)\n      tags[0]['name'][1..-1]\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/typescript.rb",
    "content": "module Docs\n  class Typescript < UrlScraper\n    self.name = 'TypeScript'\n    self.type = 'typescript'\n\n    self.root_path = 'docs/'\n\n    self.links = {\n      home: 'https://www.typescriptlang.org',\n      code: 'https://github.com/Microsoft/TypeScript'\n    }\n\n    html_filters.push 'typescript/entries', 'typescript/clean_html', 'title'\n\n    options[:only_patterns] = [\n      /\\Adocs\\Z/,\n      /\\Adocs\\/handbook/,\n      /\\Atsconfig/,\n    ]\n    options[:skip_patterns] = [\n      /\\Abranding/,\n      /\\Acommunity/,\n      /\\Adocs\\Z/,\n      /\\Atools/,\n      /react.*webpack/,\n      /release-notes/,\n      /dt\\/search/,\n      /play/\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; 2012-2025 Microsoft<br>\n      Licensed under the Apache License, Version 2.0.\n    HTML\n\n    version do\n      self.release = '5.9.2'\n      self.base_url = 'https://www.typescriptlang.org/'\n    end\n\n    version '5.1' do\n      self.release = '5.1.3'\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('Microsoft', 'TypeScript', opts)\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/underscore.rb",
    "content": "module Docs\n  class Underscore < UrlScraper\n    self.name = 'Underscore.js'\n    self.slug = 'underscore'\n    self.type = 'underscore'\n    self.release = '1.13.1'\n    self.base_url = 'https://underscorejs.org'\n    self.links = {\n      home: 'https://underscorejs.org',\n      code: 'https://github.com/jashkenas/underscore'\n    }\n\n    html_filters.push 'underscore/clean_html', 'underscore/entries', 'title'\n\n    options[:title] = 'Underscore.js'\n    options[:container] = '#documentation'\n    options[:skip_links] = true\n\n    options[:attribution] = <<-HTML\n      &copy; 2009&ndash;2021 Jeremy Ashkenas, DocumentCloud and Investigative Reporters &amp; Editors<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://underscorejs.org/', opts)\n      doc.at_css('.version').content[1...-1]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/vagrant.rb",
    "content": "module Docs\n  class Vagrant < UrlScraper\n    self.name = 'Vagrant'\n    self.type = 'simple'\n    self.release = '2.2.0'\n    self.base_url = 'https://www.vagrantup.com/docs/'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://www.vagrantup.com/',\n      code: 'https://github.com/mitchellh/vagrant'\n    }\n\n    html_filters.push 'vagrant/entries', 'vagrant/clean_html'\n\n    options[:skip_patterns] = [/vagrant-cloud/]\n\n    options[:attribution] = <<-HTML\n      &copy; 2010&ndash;2018 Mitchell Hashimoto<br>\n      Licensed under the MPL 2.0 License.\n    HTML\n\n    def get_latest_version(opts)\n      get_github_tags('hashicorp', 'vagrant', opts)[0]['name'][1..-1]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/varnish.rb",
    "content": "module Docs\n  class Varnish < UrlScraper\n    self.name = 'Varnish'\n    self.type = 'sphinx'\n\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://varnish-cache.org/',\n      code: 'https://github.com/varnishcache/varnish-cache'\n    }\n\n    html_filters.push 'varnish/entries', 'sphinx/clean_html'\n\n    options[:container] = '.body > section'\n    options[:skip] = %w(genindex.html search.html)\n    options[:skip_patterns] = [/phk/, /glossary/, /whats-new/]\n\n    options[:attribution] = <<-HTML\n      Copyright &copy; 2006 Verdens Gang AS<br>\n      Copyright &copy; 2006&ndash;2020 Varnish Software AS<br>\n      Licensed under the BSD-2-Clause License.\n    HTML\n\n    version do\n      self.release = '7.4'\n      self.base_url = \"https://varnish-cache.org/docs/#{release}/\"\n    end\n\n    def get_latest_version(opts)\n      contents = get_github_file_contents('varnishcache', 'varnish-cache', 'doc/changes.rst', opts)\n      contents.scan(/Varnish\\s+Cache\\s+([0-9.]+)/)[0][0]\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/vertx.rb",
    "content": "module Docs\n  class Vertx < UrlScraper\n    self.name = 'Vert.x'\n    self.slug = 'vertx'\n    self.type = 'vertx'\n    self.links = {\n      home: 'http://vertx.io',\n      code: 'https://github.com/eclipse-vertx/vert.x'\n    }\n\n    html_filters.push 'vertx/entries', 'vertx/clean_html'\n\n    options[:attribution] = <<-HTML\n      © 2025 Eclipse Vert.x™</br>\n      Eclipse Vert.x™ is open source and dual-licensed under the Eclipse Public License 2.0 and the Apache License 2.0.</br>\n      Website design by Michel Krämer.\n    HTML\n\n    options[:skip_patterns] = [\n      /api/,\n      /5.0.0/,\n      /apidocs/,\n      /blog/,\n    ]\n\n    version '5' do\n      self.release = '5.0.0'\n      self.base_url = \"https://vertx.io/docs/\"\n    end\n\n    version '4' do\n      self.release = '4.5.15'\n      self.base_url = \"https://vertx.io/docs/#{self.release}\"\n    end\n\n    version '3' do\n      self.release = '3.9.16'\n      self.base_url = \"https://vertx.io/docs/#{self.release}\"\n    end\n\n    def get_latest_version(opts)\n      doc = fetch_doc('https://repo1.maven.org/maven2/io/vertx/vertx-stack-manager/maven-metadata.xml', opts)\n      doc.css('version')[-1].text\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/vite.rb",
    "content": "module Docs\n  class Vite < UrlScraper\n    self.name = 'Vite'\n    self.slug = 'vite'\n    self.type = 'simple'\n    self.links = {\n      home: 'https://vite.dev/',\n      code: 'https://github.com/vitejs/vite'\n    }\n\n    options[:root_title] = 'Vite'\n\n    options[:attribution] = <<-HTML\n      &copy; 2019-present, VoidZero Inc. and Vite contributors<br>\n      Licensed under the MIT License.\n    HTML\n\n    options[:skip] = %w(team.html team)\n    options[:skip_patterns] = [/\\Ablog/, /\\Aplugins/]\n\n    self.initial_paths = %w(guide/)\n    html_filters.push 'vite/entries', 'vite/clean_html'\n\n    version do\n      self.release = '8.0.0'\n      self.base_url = 'https://vite.dev/'\n    end\n\n    version '7' do\n      self.release = '7.3.1'\n      self.base_url = 'https://v7.vite.dev/'\n    end\n\n    version '6' do\n      self.release = '6.3.5'\n      self.base_url = 'https://v6.vite.dev/'\n    end\n\n    version '5' do\n      self.release = '5.4.11'\n      self.base_url = 'https://v5.vite.dev/'\n    end\n\n    version '4' do\n      self.release = '4.5.5'\n      self.base_url = 'https://v4.vite.dev/'\n    end\n\n    version '3' do\n      self.release = '3.2.11'\n      self.base_url = 'https://v3.vite.dev/'\n    end\n\n    def get_latest_version(opts)\n      get_npm_version('vite', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/vitest.rb",
    "content": "module Docs\n  class Vitest < UrlScraper\n    self.name = 'Vitest'\n    self.slug = 'vitest'\n    self.type = 'simple'\n    self.links = {\n      home: 'https://vitest.dev/',\n      code: 'https://github.com/vitest-dev/vitest'\n    }\n\n    options[:root_title] = 'Vitest'\n    options[:download_images] = false\n    options[:skip] = %w(blog)\n\n    options[:attribution] = <<-HTML\n      &copy; 2021-Present VoidZero Inc. and Vitest contributors<br>\n      Licensed under the MIT License.\n    HTML\n\n    self.initial_paths = %w(guide/)\n    html_filters.push 'vitest/entries', 'vite/clean_html'\n\n    version do\n      self.release = '4.1.0'\n      self.base_url = 'https://vitest.dev/'\n    end\n\n    version '3' do\n      self.release = '3.2.4'\n      self.base_url = 'https://vitest.dev/'\n    end\n\n    version '2' do\n      self.release = '2.1.9'\n      self.base_url = 'https://v2.vitest.dev/'\n    end\n\n    def get_latest_version(opts)\n      get_npm_version('vitest', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/vue.rb",
    "content": "module Docs\n  class Vue < UrlScraper\n    self.name = 'Vue'\n    self.slug = 'vue'\n    self.type = 'vue'\n    self.links = {\n      home: 'https://vuejs.org/',\n      code: 'https://github.com/vuejs/core'\n    }\n\n    options[:only_patterns] = [/^$/ ,/guide\\//, /api\\//]\n    options[:skip] = %w(guide/team.html)\n    options[:skip_patterns] = [/guide\\/contributing/]\n    options[:replace_paths] = { 'guide/' => 'guide/index.html' }\n\n    options[:attribution] = <<-HTML\n      &copy; 2018-present, Yuxi (Evan) You and Vue contributors<br>\n      Licensed under the MIT License.\n    HTML\n\n    version '3' do\n      self.release = '3.5.28'\n      self.base_url = 'https://vuejs.org/'\n      self.initial_paths = %w(guide/introduction.html)\n      html_filters.push 'vue/entries_v3', 'vue/clean_html'\n    end\n\n    version '2' do\n      self.release = '2.7.14'\n      self.base_url = 'https://v2.vuejs.org/v2/'\n      self.initial_paths = %w(api/)\n      self.root_path = 'guide/'\n      html_filters.push 'vue/entries', 'vue/clean_html'\n    end\n\n    version '1' do\n      self.release = '1.0.28'\n      self.base_url = 'https://v1.vuejs.org'\n      self.initial_paths = %w(/api/index.html)\n      html_filters.push 'vue/entries', 'vue/clean_html'\n    end\n\n    def get_latest_version(opts)\n      get_npm_version('vue', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/vue_router.rb",
    "content": "module Docs\n  class VueRouter < UrlScraper\n    self.name = 'Vue Router'\n    self.slug = 'vue_router'\n    self.type = 'simple'\n    self.links = {\n      home: 'https://router.vuejs.org',\n      code: 'https://github.com/vuejs/vue-router'\n    }\n\n    html_filters.push 'vue_router/entries', 'vue_router/clean_html'\n\n    options[:skip_patterns] = [\n      # Other languages\n      /^(zh|ja|ru|kr|fr)\\//,\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; 2014-present Evan You, Eduardo San Martin Morote<br>\n      Licensed under the MIT License.\n    HTML\n\n    version '5' do\n      self.release = '5.0.2'\n      self.base_url = 'https://router.vuejs.org/'\n    end\n\n    version '4' do\n      self.release = '4.0.12'\n      self.base_url = 'https://next.router.vuejs.org/'\n    end\n\n    version '3' do\n      self.release = '3.5.1'\n      self.base_url = 'https://router.vuejs.org/'\n    end\n\n    def get_latest_version(opts)\n      get_npm_version('vue-router', opts, 'next')\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/vueuse.rb",
    "content": "module Docs\n  class Vueuse < UrlScraper\n    self.name = 'VueUse'\n    self.slug = 'vueuse'\n    self.type = 'vueuse'\n    self.links = {\n      home: 'https://vueuse.org/',\n      code: 'https://github.com/vueuse/vueuse'\n    }\n\n    options[:skip] = %w(add-ons contributing ecosystem)\n    options[:skip_patterns] = [/index$/]\n    options[:fix_urls] = ->(url) do\n      url.sub! %r{/index$}, ''\n      url.sub! 'vueuse.org/on', 'vueuse.org/core/on'\n      url.sub! 'vueuse.org/use', 'vueuse.org/core/use'\n      url\n    end\n\n    options[:attribution] = <<-HTML\n      &copy; 2019-present Anthony Fu<br>\n      Licensed under the MIT License.\n    HTML\n\n    self.release = '13.6.0'\n    self.base_url = 'https://vueuse.org/'\n    self.initial_paths = %w(functions.html)\n    html_filters.push 'vueuse/entries', 'vite/clean_html', 'vueuse/clean_html'\n\n    def get_latest_version(opts)\n      get_npm_version('@vueuse/core', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/vuex.rb",
    "content": "module Docs\n  class Vuex < UrlScraper\n    self.type = 'simple'\n    self.links = {\n      home: 'https://vuex.vuejs.org',\n      code: 'https://github.com/vuejs/vuex'\n    }\n\n    html_filters.push 'vuex/entries', 'vuex/clean_html'\n\n    options[:skip_patterns] = [\n      # Other languages\n      /^(zh|ja|ru|kr|fr|ptbr)\\//,\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; 2015&ndash;present Evan You<br>\n      Licensed under the MIT License.\n    HTML\n\n    version '4' do\n      self.release = '4.0.2'\n      self.base_url = 'https://next.vuex.vuejs.org/'\n    end\n\n    version '3' do\n      self.release = '3.6.2'\n      self.base_url = 'https://vuex.vuejs.org/'\n    end\n\n    def get_latest_version(opts)\n      get_npm_version('vuex', opts, 'next')\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/vulkan.rb",
    "content": "module Docs\n  class Vulkan < UrlScraper\n    self.name = 'Vulkan'\n    self.type = 'simple'\n    self.release = '1.0.59'\n    self.base_url = 'https://www.khronos.org/registry/vulkan/specs/1.0/'\n    self.root_path = 'apispec.html'\n    self.links = {\n      home: 'https://www.khronos.org/vulkan/'\n    }\n\n    html_filters.push 'vulkan/entries', 'vulkan/clean_html', 'title'\n\n    options[:skip_links] = true\n    options[:container] = '#content'\n    options[:root_title] = 'Vulkan API Reference'\n\n    options[:attribution] = <<-HTML\n      &copy; 2014&ndash;2017 Khronos Group Inc.<br>\n      Licensed under the Creative Commons Attribution 4.0 International License.<br>\n      Vulkan and the Vulkan logo are registered trademarks of the Khronos Group Inc.\n    HTML\n\n    def get_latest_version(opts)\n      tags = get_github_tags('KhronosGroup', 'Vulkan-Docs', opts)\n      tags[0]['name'][1..-1]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/wagtail.rb",
    "content": "module Docs\n  class Wagtail < UrlScraper\n    self.name = 'Wagtail'\n    self.type = 'sphinx'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://wagtail.org/',\n      code: 'https://github.com/wagtail/wagtail'\n    }\n\n    # adding filters from lib/docs/filters/wagtail\n    html_filters.push 'wagtail/entries', 'sphinx/clean_html', 'wagtail/clean_html'\n\n    # attributions are seen at the bottom of every page(copyright and license etc. details)\n    options[:attribution] = <<-HTML\n      &copy; 2014-present Torchbox Ltd and individual contributors.<br>\n      All rights are reserved.<br>\n      Licensed under the BSD License.\n    HTML\n\n    # no one wants to see docs about search or release notes\n    options[:skip] = %w[search.html]\n    options[:skip_patterns] = [\n      %r{\\Areleases/}\n    ]\n\n    # updating release and base_url for different versions\n    version do\n      self.release = '4.1.1'\n      self.base_url = 'https://docs.wagtail.org/en/stable/'\n    end\n\n    version '3' do\n      self.release = '3.0.3'\n      self.base_url = \"https://docs.wagtail.org/en/v#{release}/\"\n    end\n\n    version '2' do\n      self.release = '2.16.3'\n      self.base_url = \"https://docs.wagtail.org/en/v#{release}/\"\n    end\n\n    # this method will fetch the latest version of wagtail\n    def get_latest_version(opts)\n      get_latest_github_release('wagtail', 'wagtail', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/webpack.rb",
    "content": "module Docs\n  class Webpack < UrlScraper\n    self.name = 'webpack'\n    self.type = 'webpack'\n\n    self.root_path = 'guides/'\n    self.initial_paths = %w(\n      concepts/\n      guides/\n      api/\n      configuration/\n      loaders/\n      plugins/\n    )\n    self.links = {\n      home: 'https://webpack.js.org/',\n      code: 'https://github.com/webpack/webpack'\n    }\n\n    html_filters.push 'webpack/clean_html', 'webpack/entries'\n\n    options[:container] = '.page'\n    options[:trailing_slash] = false\n    options[:only_patterns] = [\n      /\\Aconcepts\\//,\n      /\\Aguides\\//,\n      /\\Aapi\\//,\n      /\\Aconfiguration\\//,\n      /\\Aloaders\\//,\n      /\\Aplugins\\//\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; JS Foundation and other contributors<br>\n      Licensed under the Creative Commons Attribution License 4.0.\n    HTML\n\n    version '5' do\n      self.release = '5.97.1'\n      self.base_url = 'https://webpack.js.org/'\n    end\n\n    version '4' do\n      self.release = '4.44.2'\n      self.base_url = 'https://v4.webpack.js.org/'\n    end\n\n    version '1' do\n      self.release = '1.15.0'\n      self.base_url = 'https://webpack.github.io/docs/'\n      self.links = {\n        home: 'https://webpack.github.io/',\n        code: 'https://github.com/webpack/webpack/tree/webpack-1'\n      }\n\n      html_filters.push 'webpack/entries_old', 'webpack/clean_html_old', 'title'\n\n      options[:title] = false\n      options[:root_title] = 'webpack'\n\n      options[:skip] = %w(list-of-tutorials.html examples.html changelog.html ideas.html roadmap.html)\n\n      options[:attribution] = <<-HTML\n        &copy; 2012&ndash;2015 Tobias Koppers<br>\n        Licensed under the MIT License.\n      HTML\n    end\n\n    def get_latest_version(opts)\n      get_npm_version('webpack', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/werkzeug.rb",
    "content": "module Docs\n  class Werkzeug < UrlScraper\n    self.type = 'sphinx'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://palletsprojects.com/p/werkzeug/',\n      code: 'https://github.com/pallets/werkzeug'\n    }\n\n    html_filters.push 'werkzeug/entries', 'sphinx/clean_html'\n\n    options[:container] = '.body > section'\n    options[:skip] = %w(changes/)\n\n    options[:attribution] = <<-HTML\n      &copy; 2007 Pallets<br>\n      Licensed under the BSD 3-clause License.\n    HTML\n\n    version do\n      self.release = '3.1.1'\n      self.base_url = \"https://werkzeug.palletsprojects.com/en/latest/\"\n    end\n\n    version '3.0' do\n      self.release = '3.0.x'\n      self.base_url = \"https://werkzeug.palletsprojects.com/en/#{self.release}/\"\n    end\n\n    version '2.3' do\n      self.release = '2.3.x'\n      self.base_url = \"https://werkzeug.palletsprojects.com/en/#{self.release}/\"\n    end\n\n    version '2.2' do\n      self.release = '2.2.x'\n      self.base_url = \"https://werkzeug.palletsprojects.com/en/#{self.release}/\"\n    end\n\n    version '2.1' do\n      self.release = '2.1.x'\n      self.base_url = \"https://werkzeug.palletsprojects.com/en/#{self.release}/\"\n    end\n\n    version '2.0' do\n      self.release = '2.0.x'\n      self.base_url = \"https://werkzeug.palletsprojects.com/en/#{self.release}/\"\n    end\n\n    version '1.0' do\n      self.release = '1.0.x'\n      self.base_url = \"https://werkzeug.palletsprojects.com/en/#{self.release}/\"\n    end\n\n    version '0.16' do\n      self.release = '0.16.x'\n      self.base_url = \"https://werkzeug.palletsprojects.com/en/#{self.release}/\"\n    end\n\n    version '0.15' do\n      self.release = '0.15.x'\n      self.base_url = \"https://werkzeug.palletsprojects.com/en/#{self.release}/\"\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('pallets', 'werkzeug', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/wordpress.rb",
    "content": "module Docs\n  class Wordpress < UrlScraper\n    self.name = 'WordPress'\n    self.type = 'wordpress'\n    self.release = '6.7'\n    self.base_url = 'https://developer.wordpress.org/reference/'\n    self.initial_paths = %w(\n      functions/\n      hooks/\n      classes/\n    )\n\n    self.links = {\n      home: 'https://wordpress.org/',\n      code: 'https://github.com/WordPress/WordPress'\n    }\n\n    html_filters.push 'wordpress/entries', 'wordpress/clean_html'\n\n    options[:container] = 'main'\n    options[:trailing_slash] = false\n    options[:only_patterns] = [\n      /\\Afunctions\\//,\n      /\\Ahooks\\//,\n      /\\Aclasses\\//\n    ]\n\n    options[:skip_patterns] = [\n      /\\Afunctions\\/page\\/\\d+/,\n      /\\Ahooks\\/page\\/\\d+/,\n      /\\Aclasses\\/page\\/\\d+/\n    ]\n\n    options[:attribution] = <<-HTML\n      &copy; 2003&ndash;2024 WordPress Foundation<br>\n      Licensed under the GNU GPLv2+ License.\n    HTML\n\n    def get_latest_version(opts)\n      tags = get_github_tags('WordPress', 'wordpress-develop', opts)\n      tags[0]['name']\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/yarn.rb",
    "content": "module Docs\n  class Yarn < UrlScraper\n    self.type = 'simple'\n\n    options[:root_title] = 'Yarn'\n    options[:trailing_slash] = false\n\n    options[:skip] = %w(nightly)\n\n    options[:attribution] = <<-HTML\n      &copy; 2016&ndash;present Yarn Contributors<br>\n      Licensed under the BSD License.\n    HTML\n\n    version do\n      self.release = '4.12.0'\n      self.base_url = 'https://yarnpkg.com/'\n      self.links = {\n        home: 'https://yarnpkg.com/',\n        code: 'https://github.com/yarnpkg/berry'\n      }\n      self.root_path = 'getting-started'\n      html_filters.push 'yarn/entries_berry', 'yarn/clean_html_berry'\n      options[:skip] = ['cli', 'cli/builder', 'cli/pnpify', 'cli/sdks', 'protocols']\n      options[:skip_patterns] = [/\\Aapi/, /\\Ablog/, /\\Apackage/, /\\Aassets/]\n    end\n\n    version '3' do\n      self.release = '3.1.1'\n      self.base_url = 'https://v3.yarnpkg.com/'\n      self.links = {\n        home: 'https://v3.yarnpkg.com/',\n        code: 'https://github.com/yarnpkg/berry'\n      }\n      self.root_path = 'getting-started'\n      html_filters.push 'yarn/entries_berry', 'yarn/clean_html_berry', 'title'\n      options[:skip] = ['features', 'cli', 'configuration', 'advanced']\n      options[:skip_patterns] = [/\\Aapi/, /\\Apackage/]    end\n\n    version 'Classic' do\n      self.release = '1.22.17'\n      self.base_url = 'https://classic.yarnpkg.com/en/docs/'\n      self.links = {\n        home: 'https://classic.yarnpkg.com/',\n        code: 'https://github.com/yarnpkg/yarn'\n      }\n      html_filters.push 'yarn/entries', 'yarn/clean_html', 'title'\n      options[:skip_patterns] = [/\\Aorg\\//]\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('yarnpkg', 'berry', opts)[/[\\d.]+/]\n    end\n\n    private\n\n    # Some pages contain null bytes and cause the parser to fail\n    def parse(response)\n      response.body.gsub!(/[\\x00\\u0000\\0]/, '')\n      super\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/yii.rb",
    "content": "module Docs\n  class Yii < UrlScraper\n    self.type = 'yii'\n\n    options[:attribution] = <<-HTML\n      &copy; 2008&ndash;2017 by Yii Software LLC<br>\n      Licensed under the three clause BSD license.\n    HTML\n\n    version '2.0' do\n      self.release = '2.0.12'\n      self.base_url = 'http://www.yiiframework.com/doc-2.0/'\n      self.root_path = 'index.html'\n      self.links = {\n        home: 'http://www.yiiframework.com/',\n        code: 'https://github.com/yiisoft/yii2'\n      }\n\n      html_filters.push 'yii/clean_html_v2', 'yii/entries_v2'\n\n      options[:container] = 'div[role=main]'\n      options[:skip_patterns] = [/\\Ayii-apidoc/]\n    end\n\n    version '1.1' do\n      self.release = '1.1.19'\n      self.base_url = 'http://www.yiiframework.com/doc/api/1.1/'\n      self.links = {\n        home: 'http://www.yiiframework.com/',\n        code: 'https://github.com/yiisoft/yii'\n      }\n\n      html_filters.push 'yii/clean_html_v1', 'yii/entries_v1'\n\n      options[:container] = '.grid_9'\n    end\n\n    def get_latest_version(opts)\n      get_latest_github_release('yiisoft', 'yii2', opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/zig.rb",
    "content": "module Docs\n  class Zig < UrlScraper\n    self.name = 'Zig'\n    self.type = 'simple'\n    self.release = '0.15.2'\n    self.base_url = \"https://ziglang.org/documentation/#{self.release}/\"\n    self.links = {\n      home: 'https://ziglang.org/',\n      code: 'https://github.com/ziglang/zig'\n    }\n\n    html_filters.push 'zig/entries', 'zig/clean_html'\n\n    options[:follow_links] = false\n    options[:attribution] = <<-HTML\n      &copy; 2015–2025, Zig contributors<br>\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      tags = get_github_tags('ziglang', 'zig', opts)\n      tags[0]['name']\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/scrapers/zsh.rb",
    "content": "module Docs\n  class Zsh < UrlScraper\n    self.type = 'zsh'\n    self.release = '5.9.0'\n    self.base_url = 'https://zsh.sourceforge.io/Doc/Release/'\n    self.root_path = 'index.html'\n    self.links = {\n      home: 'https://zsh.sourceforge.io/',\n      code: 'https://sourceforge.net/p/zsh/web/ci/master/tree/',\n    }\n\n    options[:skip] = %w(\n      zsh_toc.html\n      zsh_abt.html\n      The-Z-Shell-Manual.html\n      Introduction.html\n    )\n    options[:skip_patterns] = [/-Index.html/]\n\n    html_filters.push 'zsh/entries', 'zsh/clean_html'\n\n    options[:attribution] = <<-HTML\n      The Z Shell is copyright &copy; 1992&ndash;2017 Paul Falstad, Richard Coleman,\n Zoltán Hidvégi, Andrew Main, Peter Stephenson, Sven Wischnowsky, and others.<br />\n      Licensed under the MIT License.\n    HTML\n\n    def get_latest_version(opts)\n      body = fetch('https://zsh.sourceforge.io/Doc/Release', opts)\n      body.scan(/Zsh version ([0-9.]+)/)[0][0]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/storage/abstract_store.rb",
    "content": "require 'pathname'\n\nmodule Docs\n  class AbstractStore\n    class InvalidPathError < StandardError; end\n    class LockError < StandardError; end\n\n    include Instrumentable\n\n    def initialize(path)\n      path = Pathname.new(path).cleanpath\n      raise ArgumentError if path.relative?\n      @root_path = @working_path = path.freeze\n    end\n\n    def root_path\n      @root_path.to_s\n    end\n\n    def working_path\n      @working_path.to_s\n    end\n\n    def expand_path(path)\n      join_paths @working_path, path\n    end\n\n    def open(path, &block)\n      if block_given?\n        open_yield_close(path, &block)\n      else\n        set_working_path join_paths(@root_path, path)\n      end\n    end\n\n    def close\n      set_working_path @root_path\n    end\n\n    def read(path)\n      path = expand_path(path)\n      read_file(path) if file_exist?(path)\n    end\n\n    def write(path, value)\n      path = expand_path(path)\n      touch(path)\n\n      if file_exist?(path)\n        update(path, value)\n      else\n        create(path, value)\n      end\n    end\n\n    def delete(path)\n      path = expand_path(path)\n\n      if file_exist?(path)\n        destroy(path)\n        true\n      end\n    end\n\n    def exist?(path)\n      file_exist? expand_path(path)\n    end\n\n    def mtime(path)\n      path = expand_path(path)\n      file_mtime(path) if file_exist?(path)\n    end\n\n    def size(path)\n      path = expand_path(path)\n      file_size(path) if file_exist?(path)\n    end\n\n    def each(&block)\n      list_files(working_path, &block)\n    end\n\n    def replace(path = nil, &block)\n      if path\n        return open(path) { replace(&block) }\n      else\n        lock { track_touched { yield.tap { delete_untouched } } }\n      end\n    end\n\n    private\n\n    def read_file(path)\n      raise NotImplementedError\n    end\n\n    def create_file(path, value)\n      raise NotImplementedError\n    end\n\n    def update_file(path, value)\n      raise NotImplementedError\n    end\n\n    def delete_file(path)\n      raise NotImplementedError\n    end\n\n    def file_exist?(path)\n      raise NotImplementedError\n    end\n\n    def file_mtime(path)\n      raise NotImplementedError\n    end\n\n    def file_size(path)\n      raise NotImplementedError\n    end\n\n    def list_files(path, &block)\n      raise NotImplementedError\n    end\n\n    def set_working_path(path)\n      @working_path = Pathname.new(path).freeze if assert_unlocked\n    end\n\n    def join_paths(base, path)\n      base = Pathname.new(base).cleanpath\n      path = Pathname.new(path).cleanpath\n      path = base + path unless path.absolute?\n\n      unless File.join(path, '').start_with? File.join(base, '')\n        raise InvalidPathError, \"Tried accessing #{path} outside #{base}\"\n      end\n\n      path.to_s\n    end\n\n    def open_yield_close(path)\n      working_path_was = working_path\n      open(path)\n      yield\n    ensure\n      set_working_path working_path_was\n    end\n\n    def create(path, value)\n      instrument 'create.store', path: path do\n        create_file(path, value)\n      end\n    end\n\n    def update(path, value)\n      instrument 'update.store', path: path do\n        update_file(path, value)\n      end\n    end\n\n    def destroy(path)\n      instrument 'destroy.store', path: path do\n        delete_file(path)\n      end\n    end\n\n    def lock\n      assert_unlocked\n      @locked = true\n      yield\n    ensure\n      @locked = false\n    end\n\n    def assert_unlocked\n      raise LockError if @locked\n      true\n    end\n\n    def track_touched\n      @touched = []\n      yield\n    ensure\n      @touched = nil\n    end\n\n    def touch(path)\n      @touched << path if @touched\n    end\n\n    def touched?(path)\n      dir = File.join(path, '')\n\n      @touched.any? do |touched_path|\n        touched_path == path || touched_path.start_with?(dir)\n      end\n    end\n\n    def delete_untouched\n      return if @touched.empty?\n\n      each do |path|\n        destroy(path) unless touched?(path)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/storage/file_store.rb",
    "content": "require 'fileutils'\nrequire 'find'\n\nmodule Docs\n  class FileStore < AbstractStore\n    private\n\n    def read_file(path)\n      File.read(path)\n    end\n\n    def create_file(path, value)\n      FileUtils.mkpath File.dirname(path)\n\n      if value.is_a? Tempfile\n        FileUtils.move(value, path)\n      else\n        File.write(path, value)\n      end\n    end\n\n    alias_method :update_file, :create_file\n\n    def delete_file(path)\n      if File.directory?(path)\n        FileUtils.rmtree(path, secure: true)\n      else\n        FileUtils.rm(path)\n      end\n    end\n\n    def file_exist?(path)\n      File.exist?(path)\n    end\n\n    def file_mtime(path)\n      File.mtime(path)\n    end\n\n    def file_size(path)\n      File.size(path)\n    end\n\n    def list_files(path)\n      Find.find path do |file|\n        next if file == path\n        Find.prune if File.basename(file)[0] == '.'\n        yield file\n        Find.prune unless File.exist?(file)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/storage/null_store.rb",
    "content": "module Docs\n  class NullStore < AbstractStore\n    def initialize\n      super '/'\n    end\n\n    private\n\n    def nil(*args)\n      nil\n    end\n\n    alias_method :read_file, :nil\n    alias_method :create_file, :nil\n    alias_method :update_file, :nil\n    alias_method :delete_file, :nil\n    alias_method :file_exist?, :nil\n    alias_method :file_mtime, :nil\n    alias_method :file_size, :nil\n    alias_method :list_files, :nil\n  end\nend\n"
  },
  {
    "path": "lib/docs/subscribers/doc_subscriber.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class DocSubscriber < Subscriber\n    self.namespace = 'doc'\n\n    def index(event)\n      before, after = parse_payload(event)\n      size = event.payload[:after].bytesize\n      size_diff = size - event.payload[:before].bytesize\n      log \"Entries: (#{(size / 1_000.0).ceil} KB, #{'+' if size_diff >= 0}#{(size_diff / 1_000.0).ceil} KB)\"\n      log_diff before['entries'], after['entries'], 'name'\n      log \"Types:\"\n      log_diff before['types'],   after['types'],   'name'\n    end\n\n    def db(event)\n      before, after = parse_payload(event)\n      size = event.payload[:after].bytesize\n      size_diff = size - event.payload[:before].bytesize\n      log \"Files: (#{(size / 1_000_000.0).ceil(1)} MB, #{'+' if size_diff >= 0}#{(size_diff / 1_000_000.0).ceil(1)} MB)\"\n      log_diff before.keys, after.keys\n    end\n\n    def info(event)\n      log event.payload[:msg]\n    end\n\n    def warn(event)\n      log \"ERROR: #{event.payload[:msg]}\"\n    end\n\n    def error(event)\n      exception = event.payload[:exception]\n      log \"ERROR:\"\n      puts \"  #{event.payload[:url]}\"\n      puts \"  #{exception.class}: #{exception.message.gsub(\"\\n\", \"\\n    \")}\"\n      puts exception.backtrace.select { |line| line.start_with?(Docs.root_path) }.join(\"\\n  \").prepend(\"\\n  \")\n      puts \"\\n\"\n    end\n\n    private\n\n    def parse_payload(event)\n      [JSON.parse(event.payload[:before]), JSON.parse(event.payload[:after])]\n    end\n\n    def log_diff(before, after, prop = nil)\n      before ||= []\n      after  ||= []\n\n      if prop\n        before = before.map { |obj| obj[prop] }\n        after  = after.map  { |obj| obj[prop] }\n      end\n\n      created, updated, deleted = (after - before), (before & after), (before - after)\n\n      log \"  Updated: #{updated.length}\"\n      log \"  Created: #{created.length}\"\n      created.each { |str| log \"    + #{str}\" }\n      log \"  Deleted: #{deleted.length}\"\n      deleted.each { |str| log \"    - #{str}\" }\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/subscribers/filter_subscriber.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class FilterSubscriber < Subscriber\n    self.namespace = 'html_pipeline'\n\n    def call_filter(event)\n      log \"Filter: #{event.payload[:filter].remove('Docs::').remove('Filter')} [#{event.duration.round}ms]\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/subscribers/image_subscriber.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class ImageSubscriber < Subscriber\n    self.namespace = 'image'\n\n    def broken(event)\n      log \"Skipped broken image (#{event.payload[:code]}): #{event.payload[:url]}\"\n    end\n\n    def invalid(event)\n      log \"Skipped invalid image (#{event.payload[:content_type]}): #{event.payload[:url]}\"\n    end\n\n    def too_big(event)\n      log \"Skipped large image (#{(event.payload[:size] / 1_000.0).round} KB): #{event.payload[:url]}\"\n    end\n\n    def error(event)\n      exception = event.payload[:exception]\n      log \"ERROR: #{event.payload[:url]}\"\n      puts \"  #{exception.class}: #{exception.message.gsub(\"\\n\", \"\\n    \")}\"\n      puts exception.backtrace.select { |line| line.start_with?(Docs.root_path) }.join(\"\\n  \").prepend(\"\\n  \")\n      puts \"\\n\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/subscribers/progress_bar_subscriber.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'progress_bar'\n\nmodule Docs\n  class ProgressBarSubscriber < Subscriber\n    self.namespace = 'scraper'\n\n    def running(event)\n      @progress_bar = ::ProgressBar.new event.payload[:urls].length\n      @progress_bar.write\n    end\n\n    def queued(event)\n      @progress_bar.max += event.payload[:urls].length\n      @progress_bar.write\n    end\n\n    def process_response(event)\n      @progress_bar.increment!\n    end\n\n    def ignore_response(event)\n      @progress_bar.increment!\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/subscribers/request_subscriber.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class RequestSubscriber < Subscriber\n    self.namespace = 'request'\n\n    def response(event)\n      log \"Request: #{format_url event.payload[:url]} [#{event.payload[:response].code}] [#{event.duration.round}ms]\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/subscribers/requester_subscriber.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class RequesterSubscriber < Subscriber\n    self.namespace = 'requester'\n\n    def handle_response(event)\n      if event.duration > 10_000\n        log \"WARN: #{format_url event.payload[:url]} was slow to process (#{(event.duration / 1000).round}s)\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/subscribers/scraper_subscriber.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class ScraperSubscriber < Subscriber\n    self.namespace = 'scraper'\n\n    def queued(event)\n      event.payload[:urls].each do |url|\n        log \"Queue:   #{format_url url}\"\n      end\n    end\n\n    alias_method :running, :queued\n\n    def ignore_response(event)\n      msg = \"Ignore:  #{format_url event.payload[:response].url}\"\n      msg += \" [#{event.payload[:response].code}]\" if event.payload[:response].respond_to?(:code)\n      log(msg)\n    end\n\n    def process_response(event)\n      log \"Process: #{format_url event.payload[:response].url} [#{event.duration.round}ms]\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs/subscribers/store_subscriber.rb",
    "content": "# frozen_string_literal: true\n\nmodule Docs\n  class StoreSubscriber < Subscriber\n    self.namespace = 'store'\n\n    def create(event)\n      log \"Create: #{format_path event.payload[:path]}\"\n    end\n\n    def update(event)\n      log \"Update: #{format_path event.payload[:path]}\"\n    end\n\n    def destroy(event)\n      log \"Delete: #{format_path event.payload[:path]}\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/docs.rb",
    "content": "require 'bundler/setup'\nBundler.require :default, :docs\n\nrequire 'active_support'\nrequire 'active_support/core_ext'\nI18n.enforce_available_locales = true\n\nmodule Docs\n  require 'docs/core/autoload_helper'\n  extend AutoloadHelper\n\n  mattr_reader :root_path\n  @@root_path = File.expand_path '..', __FILE__\n\n  autoload :URL, 'docs/core/url'\n  autoload_all 'docs/core'\n  autoload_all 'docs/filters/core', 'filter'\n  autoload_all 'docs/scrapers'\n  autoload_all 'docs/storage'\n  autoload_all 'docs/subscribers'\n\n  mattr_accessor :store_class\n  self.store_class = FileStore\n\n  mattr_accessor :store_path\n  self.store_path = File.expand_path '../public/docs', @@root_path\n\n  mattr_accessor :rescue_errors\n  self.rescue_errors = false\n\n  class DocNotFound < NameError; end\n  class SetupError < StandardError; end\n\n  def self.all\n    Dir[\"#{root_path}/docs/scrapers/**/*.rb\"].\n      map { |file| File.basename(file, '.rb') }.\n      map { |name| const_get(name.camelize) }.\n      sort { |a, b| a.name.casecmp(b.name) }.\n      reject(&:abstract)\n  end\n\n  def self.all_versions\n    all.flat_map(&:versions)\n  end\n\n  def self.defaults\n    %w(css dom html http javascript).map(&method(:find))\n  end\n\n  def self.installed\n    Dir[\"#{store_path}/**/index.json\"].\n      map { |file| file[%r{/([^/]*)/index\\.json\\z}, 1] }.\n      sort!.\n      map { |path| all_versions.find { |doc| doc.path == path } }.\n      compact\n  end\n\n  def self.find(name, version = nil)\n    const = name.camelize\n    doc = const_get(const)\n\n    if version.present?\n      doc = doc.versions.find { |klass| klass.version == version || klass.version_slug == version }\n      raise DocNotFound.new(%(could not find version \"#{version}\" for doc \"#{name}\"), name) unless doc\n    elsif version != false\n      doc = doc.versions.first\n    end\n\n    doc\n  rescue NameError => error\n    if error.name.to_s == const\n      raise DocNotFound.new(%(could not find doc \"#{name}\"), name)\n    else\n      raise error\n    end\n  end\n\n  def self.find_by_slug(slug, version = nil)\n    doc = all.find { |klass| klass.slug == slug }\n\n    unless doc\n      raise DocNotFound.new(%(could not find doc with \"#{slug}\"), slug)\n    end\n\n    if version.present?\n      version = doc.versions.find { |klass| klass.version == version || klass.version_slug == version }\n      raise DocNotFound.new(%(could not find version \"#{version}\" for doc \"#{doc.name}\"), doc.name) unless version\n      doc = version\n    end\n\n    doc\n  end\n\n  def self.generate_page(name, version, page_id)\n    find(name, version).store_page(store, page_id)\n  end\n\n  def self.generate(doc, version = nil)\n    doc = find(doc, version) unless doc.respond_to?(:store_pages)\n    doc.store_pages(store)\n  end\n\n  def self.generate_manifest\n    Manifest.new(store, all_versions).store\n  end\n\n  def self.store\n    store_class.new(store_path)\n  end\n\n  def self.aliases\n    {\n      'angular' => 'ng',\n      'angular.js' => 'ng',\n      'backbone' => 'bb',\n      'cpp' => 'c++',\n      'coffeescript' => 'cs',\n      'crystal' => 'cr',\n      'elixir' => 'ex',\n      'javascript' => 'js',\n      'julia' => 'jl',\n      'jquery' => '$',\n      'knockout' => 'ko',\n      'kubernetes' => 'k8s',\n      'less' => 'ls',\n      'lodash' => '_',\n      'love' => 'löve',\n      'marionette' => 'mn',\n      'markdown' => 'md',\n      'matplotlib' => 'mpl',\n      'modernizr' => 'mdr',\n      'moment' => 'mt',\n      'openjdk' => 'java',\n      'nginx' => 'ngx',\n      'numpy' => 'np',\n      'pandas' => 'pd',\n      'postgresql' => 'pg',\n      'python' => 'py',\n      'rails' => 'ror',\n      'ruby' => 'rb',\n      'rust' => 'rs',\n      'sass' => 'scss',\n      'tensorflow' => 'tf',\n      'typescript' => 'ts',\n      'underscore.js' => '_',\n    }\n  end\n\n  extend Instrumentable\n\n  def self.install_report(*names)\n    names.each do |name|\n      const_get(\"#{name}_subscriber\".camelize).subscribe_to(self)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/tasks/assets.thor",
    "content": "class AssetsCLI < Thor\n  def self.to_s\n    'Assets'\n  end\n\n  def initialize(*args)\n    ENV['RACK_ENV'] = 'production'\n    require 'app'\n    super\n  end\n\n  desc 'compile [--clean] [--keep=<n>] [--verbose]', 'Compile all assets'\n  option :clean, type: :boolean, desc: 'Clean old assets after compilation'\n  option :keep, type: :numeric, default: 0, desc: 'Number of old assets to keep'\n  option :verbose, type: :boolean\n  def compile\n    load 'tasks/sprites.thor'\n    invoke 'sprites:generate', [], :remove_public_icons => true, :verbose => options[:verbose]\n\n    manifest.compile App.assets_compile\n    manifest.clean(options[:keep]) if options[:clean]\n  end\n\n  desc 'clean [--keep=<n>] [--verbose]', 'Clean old assets'\n  option :keep, type: :numeric, default: 0, desc: 'Number of old assets to keep'\n  option :verbose, type: :boolean\n  def clean\n    manifest.clean(options[:keep])\n  end\n\n  private\n\n  def sprockets\n    @sprockets ||= App.sprockets.tap do |sprockets|\n      sprockets.logger = logger\n      sprockets.cache = nil\n    end\n  end\n\n  def manifest\n    @manifest ||= Sprockets::Manifest.new sprockets.index, App.assets_manifest_path\n  end\n\n  def logger\n    @logger ||= Logger.new($stdout).tap do |logger|\n      logger.level = options[:verbose] ? Logger::DEBUG : Logger::INFO\n      logger.formatter = proc { |severity, datetime, progname, msg| \"#{msg}\\n\" }\n    end\n  end\nend\n"
  },
  {
    "path": "lib/tasks/console.thor",
    "content": "require 'pry'\n\nclass ConsoleCLI < Thor\n  def self.to_s\n    'Console'\n  end\n\n  def initialize(*args)\n    trap('INT') { puts; exit } # exit on ^C\n    super\n  end\n\n  default_command :default\n\n  desc '', 'Start a REPL'\n  def default\n    Pry.start\n  end\n\n  desc 'docs', 'Start a REPL in the \"Docs\" module'\n  def docs\n    require 'docs'\n    Docs.pry\n  end\nend\n\nPry::Commands.create_command 'test' do\n  description 'Run tests in the \"test\" directory'\n  group 'Testing'\n\n  banner <<-BANNER\n    Usage: test [<path>]\n\n    If <path> is a file, run it (\"_test.rb\" suffix is optional).\n    If <path> is a directory, run all test files inside it.\n    Default to all test files.\n  BANNER\n\n  def process\n    if pattern = args.first\n      pattern.prepend 'test/'\n\n      if File.directory?(pattern)\n        pattern << '/**/*_test.rb'\n      elsif File.extname(pattern).empty?\n        pattern << '*_test.rb'\n      end\n    else\n      pattern = 'test/**/*_test.rb'\n    end\n\n    paths = Dir.glob(pattern).map(&File.method(:expand_path))\n\n    if paths.empty?\n      output.puts 'No test files found.'\n      return\n    end\n\n    pid = fork do\n      begin\n        $LOAD_PATH.unshift 'test'\n        paths.each(&method(:require))\n      rescue Exception => e\n        _pry_.last_exception = e\n        run 'wtf?'\n        exit!\n      end\n    end\n\n    Process.wait(pid)\n  end\nend\n"
  },
  {
    "path": "lib/tasks/docs.thor",
    "content": "class DocsCLI < Thor\n  include Thor::Actions\n\n  def self.to_s\n    'Docs'\n  end\n\n  def initialize(*args)\n    require 'docs'\n    trap('INT') { puts; exit! } # hide backtrace on ^C\n    super\n  end\n\n  desc 'list', 'List available documentations'\n  option :packaged, type: :boolean\n  def list\n    if options[:packaged]\n      slugs = Dir[File.join(Docs.store_path, '*.tar.gz')].map { |f| File.basename(f, '.tar.gz') }\n      names = find_docs_by_slugs(slugs).map do |doc|\n        name = if doc.version?\n          \"#{doc.superclass.to_s.demodulize.underscore}@#{doc.version}\"\n        else\n          doc.to_s.demodulize.underscore\n        end\n      end\n    else\n      names = Docs.all.flat_map do |doc|\n        name = doc.to_s.demodulize.underscore\n        if doc.versioned?\n          doc.versions.map { |_doc| \"#{name}@#{_doc.version}\" }\n        else\n          name\n        end\n      end\n    end\n\n    output = names.join(\"\\n\")\n\n    require 'tty-pager'\n    TTY::Pager.new.page(output)\n  end\n\n  desc 'page (<doc> | <doc@version>) [path] [--verbose] [--debug]', 'Generate a page (no indexing)'\n  option :verbose, type: :boolean\n  option :debug, type: :boolean\n  def page(name, path = '')\n    unless path.empty? || path.start_with?('/')\n      return puts 'ERROR: [path] must be an absolute path.'\n    end\n\n    Docs.install_report :image\n    Docs.install_report :store if options[:verbose]\n    if options[:debug]\n      GC.disable\n      Docs.install_report :filter, :request, :doc\n    end\n\n    name, version = name.split(/@|~/)\n    if Docs.generate_page(name, version, path)\n      puts 'Done'\n    else\n      puts \"Failed!#{' (try running with --debug for more information)' unless options[:debug]}\"\n    end\n  rescue Docs::DocNotFound => error\n    handle_doc_not_found_error(error)\n  end\n\n  desc 'generate (<doc> | <doc@version>) [--verbose] [--debug] [--force] [--package]', 'Generate a documentation'\n  option :all, type: :boolean\n  option :verbose, type: :boolean\n  option :debug, type: :boolean\n  option :force, type: :boolean\n  option :package, type: :boolean\n  def generate(name)\n    Docs.rescue_errors = true\n    Docs.install_report :store if options[:verbose]\n    Docs.install_report :scraper if options[:debug]\n    Docs.install_report :progress_bar, :doc, :image, :requester if $stdout.tty?\n\n    require 'unix_utils' if options[:package]\n\n    doc = find_doc(name)\n\n    if doc < Docs::UrlScraper && !options[:force]\n      puts <<-TEXT.strip_heredoc\n        /!\\\\ WARNING /!\\\\\n\n        Some scrapers send thousands of HTTP requests in a short period of time,\n        which can slow down the source site and trouble its maintainers.\n\n        Please scrape responsibly. Don't do it unless you're modifying the code.\n\n        To download the latest tested version of this documentation, run:\n          thor docs:download #{name}\\n\n      TEXT\n      return unless yes? 'Proceed? (y/n)'\n    end\n\n    result = if doc.version && options[:all]\n      doc.superclass.versions.all? do |_doc|\n        puts \"==> #{_doc.version}\"\n        generate_doc(_doc, package: options[:package]).tap { puts \"\\n\" }\n      end\n    else\n      generate_doc(doc, package: options[:package])\n    end\n\n    generate_manifest if result\n  rescue Docs::DocNotFound => error\n    handle_doc_not_found_error(error)\n  ensure\n    Docs.rescue_errors = false\n  end\n\n  desc 'manifest', 'Create the manifest'\n  def manifest\n    generate_manifest\n    puts 'Done'\n  end\n\n  desc 'download (<doc> <doc@version>... | --default | --installed | --all)', 'Download documentation packages'\n  option :default, type: :boolean\n  option :installed, type: :boolean\n  option :all, type: :boolean\n  def download(*names)\n    require 'unix_utils'\n    docs = if options[:default]\n      Docs.defaults\n    elsif options[:installed]\n      Docs.installed\n    elsif options[:all]\n      Docs.all_versions\n    else\n      find_docs(names)\n    end\n    assert_docs(docs)\n    download_docs(docs)\n    generate_manifest\n    puts 'Done'\n  rescue Docs::DocNotFound => error\n    handle_doc_not_found_error(error)\n  end\n\n  desc 'package <doc> <doc@version>...', 'Create documentation packages'\n  def package(*names)\n    require 'unix_utils'\n    docs = find_docs(names)\n    assert_docs(docs)\n    docs.each(&method(:package_doc))\n    puts 'Done'\n  rescue Docs::DocNotFound => error\n    handle_doc_not_found_error(error)\n  end\n\n  desc 'clean', 'Delete documentation packages'\n  def clean\n    File.delete(*Dir[File.join Docs.store_path, '*.tar.gz'])\n    puts 'Done'\n  end\n\n  desc 'upload', '[private]'\n  option :dryrun, type: :boolean\n  option :packaged, type: :boolean\n  option :rclone, type: :boolean\n  def upload(*names)\n    if options[:packaged]\n      slugs = Dir[File.join(Docs.store_path, '*.tar.gz')].map { |f| File.basename(f, '.tar.gz') }\n      docs = find_docs_by_slugs(slugs)\n    else\n      docs = find_docs(names)\n    end\n\n    assert_docs(docs)\n\n    # Verify files are present\n    docs.each do |doc|\n      unless Dir.exist?(File.join(Docs.store_path, doc.path))\n        puts \"ERROR: directory #{File.join(Docs.store_path, doc.path)} not found.\"\n        return\n      end\n\n      unless File.exist?(File.join(Docs.store_path, \"#{doc.path}.tar.gz\"))\n        puts \"ERROR: package for '#{doc.slug}' documentation not found. Run 'thor docs:package #{doc.slug}' to create it.\"\n        return\n      end\n    end\n\n    # Sync files with S3 (used by the web app)\n    puts '[S3] Begin syncing.'\n    docs.each do |doc|\n      puts \"[S3] Syncing #{doc.path}...\"\n      cmd = \"aws s3 sync #{File.join(Docs.store_path, doc.path)} s3://devdocs-documents/#{doc.path} --delete --profile devdocs\"\n      cmd << ' --dryrun' if options[:dryrun]\n      if options[:rclone]\n        puts \"[S3] Syncing #{doc.path} using rclone...\"\n        cmd = \"rclone sync #{File.join(Docs.store_path, doc.path)} devdocs:devdocs-documents/#{doc.path} --delete-after --progress\"\n        cmd << ' --dry-run' if options[:dryrun]\n      end\n      system(cmd)\n    end\n    puts '[S3] Done syncing.'\n\n    # Upload packages to downloads.devdocs.io (used by the \"thor docs:download\" command)\n    puts '[S3 bundle] Begin uploading.'\n\n    docs.each do |doc|\n      filename = \"#{doc.path}.tar.gz\"\n      puts \"[S3 bundle] Uploading #{filename}...\"\n      cmd = \"aws s3 cp #{File.join(Docs.store_path, filename)} s3://devdocs-downloads/#{filename} --profile devdocs\"\n      cmd << ' --dryrun' if options[:dryrun]\n      if options[:rclone]\n        puts \"[S3 bundle] Uploading #{filename} using rclone...\"\n        cmd = \"rclone copy #{File.join(Docs.store_path, filename)} devdocs:devdocs-downloads/ --s3-no-check-bucket\"\n        cmd << ' --dry-run' if options[:dryrun]\n      end\n      system(cmd)\n    end\n    puts '[S3 bundle] Done uploading.'\n  end\n\n  desc 'commit', '[private]'\n  option :message, type: :string\n  option :amend, type: :boolean\n  def commit(name)\n    doc = Docs.find(name, false)\n    message = options[:message] || \"Update #{doc.name} documentation (#{doc.versions.first.release})\"\n    amend = \" --amend\" if options[:amend]\n    system(\"git add assets/ *#{name}*\") && system(\"git commit -m '#{message}'#{amend}\")\n  rescue Docs::DocNotFound => error\n    handle_doc_not_found_error(error)\n  end\n\n  desc 'prepare_deploy', 'Internal task executed before deployment'\n  def prepare_deploy\n    puts 'Docs -- BEGIN'\n\n    require 'open-uri'\n    require 'thread'\n\n    docs = Docs.all_versions\n    time = Time.now.to_i\n    mutex = Mutex.new\n\n    (1..6).map do\n      Thread.new do\n        while doc = docs.shift\n          dir = File.join(Docs.store_path, doc.path)\n          FileUtils.mkpath(dir)\n\n          ['index.json', 'meta.json'].each do |filename|\n            json = \"https://documents.devdocs.io/#{doc.path}/#{filename}?#{time}\"\n            begin\n              URI.open(json, \"Accept-Encoding\" => \"identity\") do |file|\n                mutex.synchronize do\n                  path = File.join(dir, filename)\n                  File.write(path, file.read)\n                end\n              end\n            rescue => e\n              puts \"Docs -- Failed to download #{json}!\"\n              throw e\n            end\n          end\n\n          puts \"Docs -- Downloaded #{doc.slug}\"\n        end\n      end\n    end.map(&:join)\n\n    puts 'Docs -- Generating manifest...'\n    generate_manifest\n\n    puts 'Docs -- DONE'\n  end\n\n  private\n\n  def find_doc(name)\n    name, version = name.split(/@|~/)\n    if version == 'all'\n      Docs.find(name, false).versions\n    else\n      Docs.find(name, version)\n    end\n  end\n\n  def find_docs(names)\n    names.flat_map {|name| find_doc(name)}\n  end\n\n  def find_docs_by_slugs(slugs)\n    slugs.flat_map do |slug|\n      slug, version = slug.split(/~/)\n      Docs.find_by_slug(slug, version)\n    end\n  end\n\n  def assert_docs(docs)\n    if docs.empty?\n      puts 'ERROR: called with no arguments.'\n      puts 'Run \"thor list\" for usage patterns.'\n      exit\n    end\n  end\n\n  def handle_doc_not_found_error(error)\n    puts %(ERROR: #{error}.)\n    puts 'Run \"thor docs:list\" to see the list of docs and versions.'\n  end\n\n  def generate_doc(doc, package: nil)\n    if Docs.generate(doc)\n      package_doc(doc) if package\n      puts 'Done'\n      true\n    else\n      puts \"Failed!#{' (try running with --debug for more information)' unless options[:debug]}\"\n      false\n    end\n  end\n\n  def download_docs(docs)\n    # Don't allow downloaded files to be created as StringIO\n    require 'open-uri'\n    OpenURI::Buffer.send :remove_const, 'StringMax' if OpenURI::Buffer.const_defined?('StringMax')\n    OpenURI::Buffer.const_set 'StringMax', 0\n\n    require 'thread'\n    length = docs.length\n    mutex = Mutex.new\n    i = 0\n\n    (1..4).map do\n      Thread.new do\n        while doc = docs.shift\n          status = begin\n            download_doc(doc)\n            'OK'\n          rescue => e\n            \"FAILED (#{e.class}: #{e.message})\"\n          end\n          mutex.synchronize { puts \"(#{i += 1}/#{length}) #{doc.name}#{ \" #{doc.version}\" if doc.version} #{status}\" }\n        end\n      end\n    end.map(&:join)\n  end\n\n  def download_doc(doc)\n    target_path = File.join(Docs.store_path, doc.path)\n    URI.open \"https://downloads.devdocs.io/#{doc.path}.tar.gz\" do |file|\n      FileUtils.mkpath(target_path)\n      file.close\n      tar = UnixUtils.gunzip(file.path)\n      dir = UnixUtils.untar(tar)\n      FileUtils.rm(tar)\n      FileUtils.rm_rf(target_path)\n      FileUtils.mv(dir, target_path)\n      FileUtils.rm(file.path)\n    end\n  end\n\n  def package_doc(doc)\n    path = File.join Docs.store_path, doc.path\n\n    if File.exist?(path)\n      tar = UnixUtils.tar(path)\n      gzip = UnixUtils.gzip(tar)\n      FileUtils.mv(gzip, \"#{path}.tar.gz\")\n      FileUtils.rm(tar)\n    else\n      puts %(ERROR: can't find \"#{doc.name}\" documentation files.)\n    end\n  end\n\n  def generate_manifest\n    Docs.generate_manifest\n  end\nend\n"
  },
  {
    "path": "lib/tasks/sprites.thor",
    "content": "class SpritesCLI < Thor\n  def self.to_s\n    'Sprites'\n  end\n\n  def initialize(*args)\n    require 'docs'\n    require 'chunky_png'\n    require 'fileutils'\n    require 'image_optim'\n    require 'terminal-table'\n    super\n  end\n\n  desc 'generate [--remove-public-icons] [--disable-optimization] [--verbose]', 'Generate the documentation icon spritesheets'\n  option :remove_public_icons, type: :boolean, desc: 'Remove public/icons after generating the spritesheets'\n  option :disable_optimization, type: :boolean, desc: 'Disable optimizing the spritesheets with OptiPNG'\n  option :verbose, type: :boolean\n  def generate\n    items = get_items\n    items_with_icons = items.select {|item| item[:has_icons]}\n    items_without_icons = items.select {|item| !item[:has_icons]}\n    icons_per_row = Math.sqrt(items_with_icons.length).ceil\n\n    bg_color = get_sidebar_background\n\n    items_with_icons.each_with_index do |item, index|\n      item[:row] = (index / icons_per_row).floor\n      item[:col] = index - item[:row] * icons_per_row\n\n      item[:icon_16] = get_icon(item[:path_16], 16)\n      item[:icon_32] = get_icon(item[:path_32], 32)\n\n      item[:dark_icon_fix] = needs_dark_icon_fix(item[:icon_32], bg_color)\n    end\n\n    return unless items_with_icons.length > 0\n\n    log_details(items_with_icons, icons_per_row) if options[:verbose]\n\n    generate_spritesheet(16, items_with_icons) {|item| item[:icon_16]}\n    generate_spritesheet(32, items_with_icons) {|item| item[:icon_32]}\n\n    unless options[:disable_optimization]\n      optimize_spritesheet(get_output_path(16))\n      optimize_spritesheet(get_output_path(32))\n    end\n\n    # Add Mongoose's icon details to docs without custom icons\n    default_item = items_with_icons.find {|item| item[:type] == 'mongoose'}\n    items_without_icons.each do |item|\n      item[:row] = default_item[:row]\n      item[:col] = default_item[:col]\n      item[:dark_icon_fix] = default_item[:dark_icon_fix]\n    end\n\n    save_manifest(items, icons_per_row, 'assets/images/sprites/docs.json')\n\n    # SCSS wrapped in ERB templates must be compiled manually.\n    compile_scss_erb\n\n    if options[:remove_public_icons]\n      logger.info('Removing public/icons')\n      FileUtils.rm_rf('public/icons')\n    end\n  end\n\n  private\n\n  def get_items\n    items = Docs.all.map do |doc|\n      base_path = \"public/icons/docs/#{doc.slug}\"\n      {\n        :type => doc.slug,\n        :path_16 => \"#{base_path}/16.png\",\n        :path_32 => \"#{base_path}/16@2x.png\"\n      }\n    end\n\n    # Checking paths against an array of possible paths is faster than 200+ File.exist? calls\n    files = Dir.glob('public/icons/docs/**/*.png')\n\n    items.each do |item|\n      item[:has_icons] = files.include?(item[:path_16]) && files.include?(item[:path_32])\n    end\n  end\n\n  def get_icon(path, max_size)\n    icon = ChunkyPNG::Image.from_file(path)\n\n    # Check if the icon is too big\n    # If it is, resize the image without changing the aspect ratio\n    if icon.width > max_size || icon.height > max_size\n      ratio = icon.width.to_f / icon.height\n      new_width = (icon.width >= icon.height ? max_size : max_size * ratio).floor\n      new_height = (icon.width >= icon.height ? max_size / ratio : max_size).floor\n\n      logger.warn(\"Icon #{path} is too big: max size is #{max_size} x #{max_size}, icon is #{icon.width} x #{icon.height}, resizing to #{new_width} x #{new_height}\")\n\n      icon.resample_nearest_neighbor!(new_width, new_height)\n    end\n\n    icon\n  end\n\n  def get_sidebar_background\n    # This is a hacky way to get the background color of the sidebar\n    # Unfortunately, it's not possible to get the value of a SCSS variable from a Thor task\n    # Because hard-coding the value is even worse, we extract it using some regex\n    path = 'assets/stylesheets/global/_variables-dark.scss'\n    regex = /--sidebarBackground:\\s+([^;]+);/\n    ChunkyPNG::Color.parse(File.read(path)[regex, 1])\n  end\n\n  def needs_dark_icon_fix(icon, bg_color)\n    # Determine whether the icon needs to be grayscaled if the user has enabled the dark theme\n    # The logic is roughly based on https://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast\n    contrast = icon.pixels.select {|pixel| ChunkyPNG::Color.a(pixel) > 0}.map do |pixel|\n      get_contrast(bg_color, pixel)\n    end\n\n    avg = contrast.reduce(:+) / contrast.size.to_f\n    avg < 2.5\n  end\n\n  def get_contrast(base, other)\n    # Calculating the contrast ratio as described in the WCAG 2.0:\n    # https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef\n    l1 = get_luminance(base) + 0.05\n    l2 = get_luminance(other) + 0.05\n    ratio = l1 / l2\n    l2 > l1 ? 1 / ratio : ratio\n  end\n\n  def get_luminance(color)\n    rgb = [\n      ChunkyPNG::Color.r(color).to_f,\n      ChunkyPNG::Color.g(color).to_f,\n      ChunkyPNG::Color.b(color).to_f\n    ]\n\n    # Calculating the relative luminance as described in the WCAG 2.0:\n    # https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef\n\n    rgb.map! do |value|\n      value /= 255\n      value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4\n    end\n\n    0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2]\n  end\n\n  def generate_spritesheet(size, items_with_icons, &item_to_icon)\n    output_path = get_output_path(size)\n\n    logger.info(\"Generating spritesheet to #{output_path} with icons of size #{size} x #{size}\")\n\n    icons_per_row = Math.sqrt(items_with_icons.length).ceil\n    spritesheet = ChunkyPNG::Image.new(size * icons_per_row, size * icons_per_row)\n\n    items_with_icons.each do |item|\n      icon = item_to_icon.call(item)\n\n      # Calculate the base coordinates\n      base_x = item[:col] * size\n      base_y = item[:row] * size\n\n      # Center the icon if it's not a perfect rectangle\n      x = base_x + ((size - icon.width) / 2).floor\n      y = base_y + ((size - icon.height) / 2).floor\n\n      spritesheet.compose!(icon, x, y)\n    end\n\n    FileUtils.mkdir_p(File.dirname(output_path))\n    spritesheet.save(output_path)\n  end\n\n  def optimize_spritesheet(path)\n    logger.info(\"Optimizing spritesheet at #{path}\")\n    image_optim.optimize_image!(path)\n  end\n\n  def save_manifest(items, icons_per_row, path)\n    logger.info(\"Saving spritesheet details to #{path}\")\n\n    FileUtils.mkdir_p(File.dirname(path))\n\n    # Only save the details that the scss file needs\n    manifest_items = items.map do |item|\n      {\n        :type => item[:type],\n        :row => item[:row],\n        :col => item[:col],\n        :dark_icon_fix => item[:dark_icon_fix]\n      }\n    end\n\n    manifest = {:icons_per_row => icons_per_row, :items => manifest_items}\n\n    File.open(path, 'w') do |f|\n      f.write(JSON.generate(manifest))\n    end\n  end\n\n  def log_details(items_with_icons, icons_per_row)\n    title = \"#{items_with_icons.length} items with icons (#{icons_per_row} per row)\"\n    headings = ['Type', 'Row', 'Column', \"Dark icon fix (#{items_with_icons.count {|item| item[:dark_icon_fix]}})\"]\n    rows = items_with_icons.map {|item| [item[:type], item[:row], item[:col], item[:dark_icon_fix] ? 'Yes' : 'No']}\n\n    table = Terminal::Table.new :title => title, :headings => headings, :rows => rows\n    puts table\n  end\n\n  def get_output_path(size)\n    \"assets/images/sprites/docs#{size == 32 ? '@2x' : ''}.png\"\n  end\n\n  def compile_scss_erb\n    scss_erb_files = Dir['assets/stylesheets/**/*.scss.erb']\n\n    scss_erb_files.each do |erb_path|\n      scss_path = erb_path.gsub('.erb', '')\n      File.open(scss_path, 'w') do |f|\n        f.write(ERB.new(File.open(erb_path).read).result)\n        logger.info(\"Compiling #{erb_path} to #{scss_path}\")\n      end\n    end\n  end\n\n  def image_optim\n    @image_optim ||= ImageOptim.new(\n      :config_paths => [],\n      :advpng => false,\n      :gifsicle => false,\n      :jhead => false,\n      :jpegoptim => false,\n      :jpegrecompress => false,\n      :jpegtran => false,\n      :pngcrush => false,\n      :pngout => false,\n      :pngquant => false,\n      :svgo => false,\n      :optipng => {\n        :level => 7,\n      },\n    )\n  end\n\n  def logger\n    @logger ||= Logger.new($stdout).tap do |logger|\n      logger.level = options[:verbose] ? Logger::DEBUG : Logger::INFO\n      logger.formatter = proc {|severity, datetime, progname, msg| \"#{msg}\\n\"}\n    end\n  end\nend\n"
  },
  {
    "path": "lib/tasks/test.thor",
    "content": "require 'pry'\n\nclass TestCLI < Thor\n  def self.to_s\n    'Test'\n  end\n\n  default_command :all\n\n  def initialize(*args)\n    $LOAD_PATH.unshift 'test'\n    super\n  end\n\n  desc 'all', 'Run all tests'\n  def all\n    Dir['test/**/*_test.rb'].map(&File.method(:expand_path)).each(&method(:require))\n  end\n\n  desc 'docs', 'Run \"Docs\" tests'\n  def docs\n    Dir['test/lib/docs/**/*_test.rb'].map(&File.method(:expand_path)).each(&method(:require))\n  end\n\n  desc 'app', 'Run \"App\" tests'\n  def app\n    require 'app_test'\n  end\nend\n"
  },
  {
    "path": "lib/tasks/updates.thor",
    "content": "class UpdatesCLI < Thor\n  # The GitHub user that is allowed to upload reports\n  UPLOAD_USER = 'devdocs-bot'\n\n  # The repository to create an issue in when uploading the results\n  UPLOAD_REPO = 'freeCodeCamp/devdocs'\n\n  def self.to_s\n    'Updates'\n  end\n\n  def initialize(*args)\n    require 'docs'\n    require 'progress_bar'\n    require 'terminal-table'\n    require 'date'\n    super\n  end\n\n  desc 'check [--markdown] [--github-token] [--upload] [--verbose] [doc]...', 'Check for outdated documentations'\n  option :markdown, :type => :boolean\n  option :github_token, :type => :string\n  option :upload, :type => :boolean\n  option :verbose, :type => :boolean\n  def check(*names)\n    # Convert names to a list of Scraper instances\n    # Versions are omitted, if v10 is outdated than v8 is aswell\n    docs = names.map {|name| Docs.find(name.split(/@|~/)[0], false)}.uniq\n\n    # Check all documentations for updates when no arguments are given\n    docs = Docs.all if docs.empty?\n\n    opts = {\n      logger: logger\n    }\n\n    if options.key?(:github_token)\n      opts[:github_token] = options[:github_token]\n    end\n\n    with_progress_bar do |bar|\n      bar.max = docs.length\n      bar.write\n    end\n\n    results = docs.map do |doc|\n      result = check_doc(doc, opts)\n      with_progress_bar(&:increment!)\n      result\n    end\n\n    process_results(results)\n  rescue Docs::DocNotFound => error\n    logger.error(error)\n    logger.info('Run \"thor docs:list\" to see the list of docs.')\n  end\n\n  private\n\n  def check_doc(doc, opts)\n    logger.debug(\"Checking #{doc.name}\")\n\n    instance = doc.versions.first.new\n    scraper_version = instance.get_scraper_version(opts)\n    latest_version = instance.get_latest_version(opts)\n\n    {\n      name: doc.name,\n      scraper_version: format_version(scraper_version),\n      latest_version: format_version(latest_version),\n      outdated_state: instance.outdated_state(scraper_version, latest_version)\n    }\n  rescue NotImplementedError\n    logger.warn(\"Couldn't check #{doc.name}, get_latest_version is not implemented\")\n    error_result(doc, '`get_latest_version` is not implemented')\n  rescue => error\n    logger.error(\"Error while checking #{doc.name}\\n#{error.full_message.strip}\")\n    error_result(doc, error.message.gsub(/'/, '`'))\n  end\n\n  def format_version(version)\n    str = version.to_s\n\n    # If the version is numeric and greater than or equal to 1e9 it's probably a timestamp\n    return str if str.match(/^(\\d)+$/).nil? or str.to_i < 1e9\n\n    DateTime.strptime(str, '%s').strftime('%F')\n  end\n\n  def error_result(doc, reason)\n    {\n      name: doc.name,\n      error: reason\n    }\n  end\n\n  def process_results(results)\n    successful_results = results.select {|result| result.key?(:outdated_state)}\n    grouped_results = successful_results.group_by {|result| result[:outdated_state]}\n    failed_results = results.select {|result| result.key?(:error)}\n\n    log_results(grouped_results, failed_results)\n    upload_results(grouped_results, failed_results) if options[:upload]\n  end\n\n  #\n  # Result logging methods\n  #\n\n  def log_results(grouped_results, failed_results)\n    if options[:markdown]\n      puts all_results_to_markdown(grouped_results, failed_results)\n      return\n    end\n    log_failed_results(failed_results) unless failed_results.empty?\n    grouped_results.each do |label, results|\n      log_successful_results(label, results)\n    end\n  end\n\n  def log_successful_results(label, results)\n    title = \"#{label} documentations (#{results.length})\"\n    headings = ['Documentation', 'Scraper version', 'Latest version']\n    rows = results.map {|result| [result[:name], result[:scraper_version], result[:latest_version]]}\n\n    table = ::Terminal::Table.new :title => title, :headings => headings, :rows => rows\n    puts table\n  end\n\n  def log_failed_results(results)\n    title = \"Documentations that could not be checked (#{results.length})\"\n    headings = %w(Documentation Reason)\n    rows = results.map {|result| [result[:name], result[:error]]}\n\n    table = ::Terminal::Table.new :title => title, :headings => headings, :rows => rows\n    puts table\n  end\n\n  #\n  # Upload methods\n  #\n\n  def upload_results(grouped_results, failed_results)\n    # We can't create issues without a GitHub token\n    unless options.key?(:github_token)\n      logger.error(\"Please specify a GitHub token with the public_repo permission for #{UPLOAD_USER} with the --github-token parameter\")\n      return\n    end\n\n    logger.info('Uploading the results to a new GitHub issue')\n\n    logger.info('Checking if the GitHub token belongs to the correct user')\n    user = github_get('/user')\n\n    # Only allow the DevDocs bot to upload reports\n    unless user['login'] == UPLOAD_USER\n      logger.error(\"Only #{UPLOAD_USER} is supposed to upload the results to a new issue. The specified github token is not for #{UPLOAD_USER}.\")\n      return\n    end\n\n    logger.info('Creating a new GitHub issue')\n\n    issue = {\n      title: \"Documentation versions report for #{Date.today.strftime('%B %Y')}\",\n      body: all_results_to_markdown(grouped_results, failed_results)\n    }\n    created_issue = github_post(\"/repos/#{UPLOAD_REPO}/issues\", issue)\n\n    logger.info('Checking if the previous issue is still open')\n\n    search_params = {\n      q: \"Documentation versions report in:title author:#{UPLOAD_USER} is:issue repo:#{UPLOAD_REPO}\",\n      sort: 'created',\n      order: 'desc'\n    }\n\n    matching_issues = github_get('/search/issues', **search_params)\n    previous_issue = matching_issues['items'].find {|item| item['number'] != created_issue['number']}\n\n    if previous_issue.nil?\n      logger.info('No previous issue found')\n      log_upload_success(created_issue)\n    else\n      logger.info('Commenting on the previous issue')\n\n      comment = \"This report was superseded by ##{created_issue['number']}.\"\n      github_post(\"/repos/#{UPLOAD_REPO}/issues/#{previous_issue['number']}/comments\", {body: comment})\n      if previous_issue['closed_at'].nil?\n        logger.info('Closing the previous issue')\n        github_patch(\"/repos/#{UPLOAD_REPO}/issues/#{previous_issue['number']}\", {state: 'closed'})\n        log_upload_success(created_issue)\n      else\n        logger.info('The previous issue has already been closed')\n        log_upload_success(created_issue)\n      end\n    end\n  end\n\n  def all_results_to_markdown(grouped_results, failed_results)\n    all_results = []\n    grouped_results.each do |label, results|\n      all_results.push(successful_results_to_markdown(label, results))\n    end\n    all_results.push(failed_results_to_markdown(failed_results))\n\n    results_str = all_results.select {|result| !result.nil?}.join(\"\\n\\n\")\n    travis_str = ENV['TRAVIS'].nil? ? '' : \"\\n\\nThis issue was created by Travis CI build [##{ENV['TRAVIS_BUILD_NUMBER']}](#{ENV['TRAVIS_BUILD_WEB_URL']}).\"\n\n    body = <<-MARKDOWN\n## What is this?\n\nThis is an automatically created issue which contains information about the version status of the documentations available on DevDocs. The results of this report can be used by maintainers when updating outdated documentations.\n\nMaintainers can close this issue when all documentations are up-to-date. The issue is also automatically closed when the next report is created.#{travis_str}\n\n## Results\n\n    MARKDOWN\n    body.strip + \"\\n\\n\" + results_str\n  end\n\n  def successful_results_to_markdown(label, results)\n    return nil if results.empty?\n\n    title = \"#{label} documentations (#{results.length})\"\n    headings = ['Documentation', 'Scraper version', 'Latest version']\n    rows = results.map {|result| [result[:name], result[:scraper_version], result[:latest_version]]}\n\n    results_to_markdown(title, headings, rows)\n  end\n\n  def failed_results_to_markdown(results)\n    return nil if results.empty?\n\n    title = \"Documentations that could not be checked (#{results.length})\"\n    headings = %w(Documentation Reason)\n    rows = results.map {|result| [result[:name], result[:error]]}\n\n    results_to_markdown(title, headings, rows)\n  end\n\n  def results_to_markdown(title, headings, rows)\n    \"<details>\\n<summary>#{title}</summary>\\n\\n#{create_markdown_table(headings, rows)}\\n</details>\"\n  end\n\n  def create_markdown_table(headings, rows)\n    header = headings.join(' | ')\n    separator = '-|' * headings.length\n    body = rows.map {|row| row.join(' | ')}\n\n    header + \"\\n\" + separator[0...-1] + \"\\n\" + body.join(\"\\n\")\n  end\n\n  def log_upload_success(created_issue)\n    logger.info(\"Successfully uploaded the results to #{created_issue['html_url']}\")\n  end\n\n  #\n  # HTTP utilities\n  #\n\n  def github_get(endpoint, **params)\n    github_request(endpoint, {method: :get, params: params})\n  end\n\n  def github_post(endpoint, params)\n    github_request(endpoint, {method: :post, body: params.to_json})\n  end\n\n  def github_patch(endpoint, params)\n    github_request(endpoint, {method: :patch, body: params.to_json})\n  end\n\n  def github_request(endpoint, opts)\n    url = \"https://api.github.com#{endpoint}\"\n\n    # GitHub token authentication\n    opts[:headers] = {\n      Authorization: \"token #{options[:github_token]}\"\n    }\n\n    # GitHub requires the Content-Type to be application/json when a body is passed\n    if opts.key?(:body)\n      opts[:headers]['Content-Type'] = 'application/json'\n    end\n\n    logger.debug(\"Making a #{opts[:method]} request to #{url}\")\n    response = Docs::Request.run(url, opts)\n\n    # response.success? is false if the response code is 201\n    # GitHub returns 201 Created after an issue is created\n    if response.success? || response.code == 201\n      JSON.parse(response.body)\n    else\n      logger.error(\"Couldn't make a #{opts[:method]} request to #{url} (response code #{response.code})\")\n      nil\n    end\n  end\n\n  # A utility method which ensures no progress bar is shown when stdout is not a tty\n  def with_progress_bar(&block)\n    return unless $stdout.tty?\n    @progress_bar ||= ::ProgressBar.new\n    block.call @progress_bar\n  end\n\n  def logger\n    @logger ||= Logger.new($stdout).tap do |logger|\n      logger.level = options[:verbose] ? Logger::DEBUG : Logger::INFO\n      logger.formatter = proc {|severity, datetime, progname, msg| \"[#{severity}] #{msg}\\n\"}\n    end\n  end\nend\n"
  },
  {
    "path": "newrelic.yml",
    "content": "# This file configures the New Relic Agent.  New Relic monitors\n# Ruby, Java, .NET, PHP, and Python applications with deep visibility and low overhead.\n# For more information, visit www.newrelic.com.\n\n# Here are the settings that are common to all environments\ncommon: &default_settings\n  # ============================== LICENSE KEY ===============================\n\n  # You must specify the license key associated with your New Relic\n  # account.  This key binds your Agent's data to your account in the\n  # New Relic service.\n  license_key: '<%= ENV[\"NEW_RELIC_LICENSE_KEY\"] %>'\n\n  # Agent Enabled (Ruby/Rails Only)\n  # Use this setting to force the agent to run or not run.\n  # Default is 'auto' which means the agent will install and run only\n  # if a valid dispatcher such as Mongrel is running.  This prevents\n  # it from running with Rake or the console.  Set to false to\n  # completely turn the agent off regardless of the other settings.\n  # Valid values are true, false and auto.\n  #\n  # agent_enabled: auto\n\n  # Application Name Set this to be the name of your application as\n  # you'd like it show up in New Relic. The service will then auto-map\n  # instances of your application into an \"application\" on your\n  # dashboard page. If you want to map this instance into multiple\n  # apps, like \"AJAX Requests\" and \"All UI\" then specify a semicolon\n  # separated list of up to three distinct names, or a yaml list.\n  # Defaults to the capitalized RAILS_ENV or RACK_ENV (i.e.,\n  # Production, Staging, etc)\n  #\n  # Example:\n  #\n  #   app_name:\n  #       - Ajax Service\n  #       - All Services\n  #\n  app_name: '<%= ENV[\"NEW_RELIC_APP_NAME\"] %>'\n\n  # When \"true\", the agent collects performance data about your\n  # application and reports this data to the New Relic service at\n  # newrelic.com. This global switch is normally overridden for each\n  # environment below. (formerly called 'enabled')\n  monitor_mode: true\n\n  # Developer mode should be off in every environment but\n  # development as it has very high overhead in memory.\n  developer_mode: false\n\n  # The newrelic agent generates its own log file to keep its logging\n  # information separate from that of your application. Specify its\n  # log level here.\n  log_level: info\n\n  # Optionally set the path to the log file This is expanded from the\n  # root directory (may be relative or absolute, e.g. 'log/' or\n  # '/var/log/') The agent will attempt to create this directory if it\n  # does not exist.\n  # log_file_path: 'log'\n\n  # Optionally set the name of the log file, defaults to 'newrelic_agent.log'\n  # log_file_name: 'newrelic_agent.log'\n\n  # The newrelic agent communicates with the service via https by default.  This\n  # prevents eavesdropping on the performance metrics transmitted by the agent.\n  # The encryption required by SSL introduces a nominal amount of CPU overhead,\n  # which is performed asynchronously in a background thread.  If you'd prefer\n  # to send your metrics over http uncomment the following line.\n  # ssl: false\n\n  #============================== Browser Monitoring ===============================\n  # New Relic Real User Monitoring gives you insight into the performance real users are\n  # experiencing with your website. This is accomplished by measuring the time it takes for\n  # your users' browsers to download and render your web pages by injecting a small amount\n  # of JavaScript code into the header and footer of each page.\n  browser_monitoring:\n      # By default the agent automatically injects the monitoring JavaScript\n      # into web pages. Set this attribute to false to turn off this behavior.\n      auto_instrument: false\n\n  # Proxy settings for connecting to the New Relic server.\n  #\n  # If a proxy is used, the host setting is required.  Other settings\n  # are optional. Default port is 8080.\n  #\n  # proxy_host: hostname\n  # proxy_port: 8080\n  # proxy_user:\n  # proxy_pass:\n\n  # The agent can optionally log all data it sends to New Relic servers to a\n  # separate log file for human inspection and auditing purposes. To enable this\n  # feature, change 'enabled' below to true.\n  # See: https://newrelic.com/docs/ruby/audit-log\n  audit_log:\n    enabled: false\n\n  # Tells transaction tracer and error collector (when enabled)\n  # whether or not to capture HTTP params.  When true, frameworks can\n  # exclude HTTP parameters from being captured.\n  # Rails: the RoR filter_parameter_logging excludes parameters\n  # Java: create a config setting called \"ignored_params\" and set it to\n  #     a comma separated list of HTTP parameter names.\n  #     ex: ignored_params: credit_card, ssn, password\n  capture_params: false\n\n  # Transaction tracer captures deep information about slow\n  # transactions and sends this to the New Relic service once a\n  # minute. Included in the transaction is the exact call sequence of\n  # the transactions including any SQL statements issued.\n  transaction_tracer:\n\n    # Transaction tracer is enabled by default. Set this to false to\n    # turn it off. This feature is only available at the Professional\n    # and above product levels.\n    enabled: false\n\n    # Threshold in seconds for when to collect a transaction\n    # trace. When the response time of a controller action exceeds\n    # this threshold, a transaction trace will be recorded and sent to\n    # New Relic. Valid values are any float value, or (default) \"apdex_f\",\n    # which will use the threshold for an dissatisfying Apdex\n    # controller action - four times the Apdex T value.\n    transaction_threshold: apdex_f\n\n    # When transaction tracer is on, SQL statements can optionally be\n    # recorded. The recorder has three modes, \"off\" which sends no\n    # SQL, \"raw\" which sends the SQL statement in its original form,\n    # and \"obfuscated\", which strips out numeric and string literals.\n    record_sql: obfuscated\n\n    # Threshold in seconds for when to collect stack trace for a SQL\n    # call. In other words, when SQL statements exceed this threshold,\n    # then capture and send to New Relic the current stack trace. This is\n    # helpful for pinpointing where long SQL calls originate from.\n    stack_trace_threshold: 0.500\n\n    # Determines whether the agent will capture query plans for slow\n    # SQL queries.  Only supported in mysql and postgres.  Should be\n    # set to false when using other adapters.\n    # explain_enabled: true\n\n    # Threshold for query execution time below which query plans will\n    # not be captured.  Relevant only when `explain_enabled` is true.\n    # explain_threshold: 0.5\n\n  # Error collector captures information about uncaught exceptions and\n  # sends them to New Relic for viewing\n  error_collector:\n\n    # Error collector is enabled by default. Set this to false to turn\n    # it off. This feature is only available at the Professional and above\n    # product levels.\n    enabled: true\n\n    # Rails Only - tells error collector whether or not to capture a\n    # source snippet around the place of the error when errors are View\n    # related.\n    capture_source: true\n\n    # To stop specific errors from reporting to New Relic, set this property\n    # to comma-separated values.  Default is to ignore routing errors,\n    # which are how 404's get triggered.\n    ignore_errors: \"ActionController::RoutingError,Sinatra::NotFound\"\n\n  # If you're interested in capturing memcache keys as though they\n  # were SQL uncomment this flag. Note that this does increase\n  # overhead slightly on every memcached call, and can have security\n  # implications if your memcached keys are sensitive\n  # capture_memcache_keys: true\n\n# Application Environments\n# ------------------------------------------\n# Environment-specific settings are in this section.\n# For Rails applications, RAILS_ENV is used to determine the environment.\n# For Java applications, pass -Dnewrelic.environment <environment> to set\n# the environment.\n\n# NOTE if your application has other named environments, you should\n# provide newrelic configuration settings for these environments here.\n\ndevelopment:\n  <<: *default_settings\n  # Turn off communication to New Relic service in development mode (also\n  # 'enabled').\n  # NOTE: for initial evaluation purposes, you may want to temporarily\n  # turn the agent on in development mode.\n  monitor_mode: false\n\n  # Rails Only - when running in Developer Mode, the New Relic Agent will\n  # present performance information on the last 100 transactions you have\n  # executed since starting the mongrel.\n  # NOTE: There is substantial overhead when running in developer mode.\n  # Do not use for production or load testing.\n  developer_mode: true\n\n  # Enable textmate links\n  # textmate: true\n\ntest:\n  <<: *default_settings\n  # It almost never makes sense to turn on the agent when running\n  # unit, functional or integration tests or the like.\n  monitor_mode: false\n\n# Turn on the agent in production for 24x7 monitoring. NewRelic\n# testing shows an average performance impact of < 5 ms per\n# transaction, you can leave this on all the time without\n# incurring any user-visible performance degradation.\nproduction:\n  <<: *default_settings\n  monitor_mode: true\n\n# Many applications have a staging environment which behaves\n# identically to production. Support for that environment is provided\n# here.  By default, the staging environment has the agent turned on.\nstaging:\n  <<: *default_settings\n  monitor_mode: true\n  # app_name: My Application (Staging)\n"
  },
  {
    "path": "public/404.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <title>Page not found</title>\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n  <style type=\"text/css\">\n    html {\n      font-size: 125%;\n      -webkit-text-size-adjust: 100%;\n      -ms-text-size-adjust: 100%;\n    }\n\n    @media (max-width: 800px) { html { font-size: 100%; } }\n    @media (max-width: 600px) { html { font-size: 80%; } }\n\n    body {\n      margin: 0;\n      height: 100vh;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      background: white;\n      font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;\n      color: #555;\n      text-align: center;\n    }\n\n    h1 {\n      margin: 0;\n      line-height: 1;\n      font-size: 4em;\n      font-weight: 200;\n      color: #444;\n      text-transform: uppercase;\n    }\n\n    p {\n      margin: 1.5em 0;\n      line-height: 1.8;\n      font-weight: 300;\n    }\n\n    a, a:link, a:visited {\n      color: #1e7ad3;\n      text-decoration: none;\n    }\n\n    a:hover {\n      color: #0d5cdd;\n      text-decoration: underline;\n    }\n  </style>\n</head>\n<body>\n  <div>\n    <h1>Oops!</h1>\n    <p>\n      <strong>The page you were looking for doesn't exist.</strong><br>\n      Go back to <a href=\"//devdocs.io\">devdocs.io</a>.\n    </p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "public/500.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <title>Error</title>\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n  <style type=\"text/css\">\n    html {\n      font-size: 125%;\n      -webkit-text-size-adjust: 100%;\n      -ms-text-size-adjust: 100%;\n    }\n\n    @media (max-width: 800px) { html { font-size: 100%; } }\n    @media (max-width: 600px) { html { font-size: 80%; } }\n\n    body {\n      margin: 0;\n      height: 100vh;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;\n      color: #555;\n      text-align: center;\n      background: white;\n    }\n\n    h1 {\n      margin: 0;\n      line-height: 1;\n      font-size: 4em;\n      font-weight: 200;\n      color: #444;\n      text-transform: uppercase;\n    }\n\n    p {\n      margin: 1.5em 0;\n      line-height: 1.8;\n      font-weight: 300;\n    }\n\n    a, a:link, a:visited {\n      color: #1e7ad3;\n      text-decoration: none;\n    }\n\n    a:hover {\n      color: #0d5cdd;\n      text-decoration: underline;\n    }\n  </style>\n</head>\n<body>\n  <div>\n    <h1>Oops!</h1>\n    <p>\n      <strong>Something is technically wrong.</strong><br>\n      Thanks for noticing&mdash;we're going to fix it up and have things back to normal soon.<br>\n      Go back to <a href=\"//devdocs.io\">devdocs.io</a>.\n    </p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "public/icons/docs/angular/SOURCE",
    "content": "https://angular.io/presskit.html\n"
  },
  {
    "path": "public/icons/docs/angularjs/SOURCE",
    "content": "https://github.com/angular/angular.js/tree/master/images/logo/AngularJS-Shield.exports\n"
  },
  {
    "path": "public/icons/docs/ansible/SOURCE",
    "content": "https://www.ansible.com/logos\n"
  },
  {
    "path": "public/icons/docs/apache_http_server/SOURCE",
    "content": "https://github.com/Kapeli/Dash-X-Platform-Resources/blob/master/docset_icons/apache%402x.png\n"
  },
  {
    "path": "public/icons/docs/astro/SOURCE",
    "content": "https://docs.astro.build/favicon.ico\nhttps://docs.astro.build/favicon.svg\n"
  },
  {
    "path": "public/icons/docs/async/SOURCE",
    "content": "https://raw.githubusercontent.com/caolan/async/master/logo/favicon.ico\n"
  },
  {
    "path": "public/icons/docs/axios/SOURCE",
    "content": "https://raw.githubusercontent.com/axios/axios-docs/master/assets/favicon.png\n"
  },
  {
    "path": "public/icons/docs/babel/SOURCE",
    "content": "https://github.com/babel/website/tree/master/website/static/img"
  },
  {
    "path": "public/icons/docs/backbone/SOURCE",
    "content": "https://github.com/jashkenas/backbone/blob/master/favicon.ico\n"
  },
  {
    "path": "public/icons/docs/bash/SOURCE",
    "content": "https://github.com/odb/official-bash-logo\n"
  },
  {
    "path": "public/icons/docs/bazel/SOURCE",
    "content": "https://upload.wikimedia.org/wikipedia/en/7/7d/Bazel_logo.svg\n"
  },
  {
    "path": "public/icons/docs/bluebird/SOURCE",
    "content": "https://github.com/petkaantonov/bluebird/blob/master/docs/img/libbblog_v3.png\n"
  },
  {
    "path": "public/icons/docs/bootstrap/SOURCE",
    "content": "https://raw.githubusercontent.com/twbs/bootstrap/gh-pages/favicon.ico\n"
  },
  {
    "path": "public/icons/docs/bottle/SOURCE",
    "content": "https://raw.githubusercontent.com/bottlepy/bottlepy.org/master/sphinx/static/logo_icon.png\nhttps://github.com/bottlepy/bottle/issues/886\n"
  },
  {
    "path": "public/icons/docs/bower/SOURCE",
    "content": "http://bower.io/docs/about/#logo\n"
  },
  {
    "path": "public/icons/docs/bun/SOURCE",
    "content": "https://bun.com/icons/favicon-16x16.png\nhttps://bun.com/icons/favicon-32x32.png\n"
  },
  {
    "path": "public/icons/docs/c/SOURCE",
    "content": "http://dribbble.com/shots/799814-Standard-C-Logo\nwith authorization from Jeremy Kratz\n"
  },
  {
    "path": "public/icons/docs/cakephp/SOURCE",
    "content": "https://github.com/cakephp/cakephp-api-docs/tree/2.x/static/assets/resources/favicons\n"
  },
  {
    "path": "public/icons/docs/chai/SOURCE",
    "content": "https://www.chaijs.com/\n"
  },
  {
    "path": "public/icons/docs/chef/SOURCE",
    "content": "https://www.chef.io/"
  },
  {
    "path": "public/icons/docs/click/SOURCE",
    "content": "https://github.com/pallets/click/blob/main/docs/_static/click-icon.png\n"
  },
  {
    "path": "public/icons/docs/clojure/SOURCE",
    "content": "https://en.wikipedia.org/wiki/File:Clojure_logo.svg\n"
  },
  {
    "path": "public/icons/docs/cmake/SOURCE",
    "content": "https://gitlab.kitware.com/cmake/cmake/-/blob/v3.31.0/Source/QtDialog/CMakeSetup32.png\n"
  },
  {
    "path": "public/icons/docs/codeception/SOURCE",
    "content": "https://www.openhub.net/p/codeception\n"
  },
  {
    "path": "public/icons/docs/codeceptjs/SOURCE",
    "content": "https://github.com/codeceptjs/website/blob/a4e7e9a8ebe56db7ed3783cb1f1d8d26a6c87b43/docs/.vuepress/public/logo.svg\n"
  },
  {
    "path": "public/icons/docs/codeigniter/SOURCE",
    "content": "https://www.codeigniter.com/data/ci-logo.zip\n"
  },
  {
    "path": "public/icons/docs/coffeescript/SOURCE",
    "content": "https://github.com/jashkenas/coffeescript/blob/master/docs/favicon-32x32.png\n"
  },
  {
    "path": "public/icons/docs/composer/SOURCE",
    "content": "https://github.com/composer/getcomposer.org/blob/master/web/img/logo-composer-transparent.png\n"
  },
  {
    "path": "public/icons/docs/cordova/SOURCE",
    "content": "https://cordova.apache.org/artwork/\n"
  },
  {
    "path": "public/icons/docs/couchdb/SOURCE",
    "content": "https://docs.couchdb.org/en/stable/_static/favicon.ico\n"
  },
  {
    "path": "public/icons/docs/cpp/SOURCE",
    "content": "http://dribbble.com/shots/799814-Standard-C-Logo\nwith authorization from Jeremy Kratz\n"
  },
  {
    "path": "public/icons/docs/crystal/SOURCE",
    "content": "https://crystal-lang.org/media/\n"
  },
  {
    "path": "public/icons/docs/css/SOURCE",
    "content": "https://github.com/CSS-Next/logo.css\nhttps://commons.wikimedia.org/wiki/File:Official_CSS_Logo.svg\n"
  },
  {
    "path": "public/icons/docs/cypress/SOURCE",
    "content": "https://github.com/cypress-io/cypress/tree/develop/assets\n"
  },
  {
    "path": "public/icons/docs/d/SOURCE",
    "content": "https://github.com/dlang/dlang.org/tree/master/images\n"
  },
  {
    "path": "public/icons/docs/d3/SOURCE",
    "content": "http://d3js.org/\n"
  },
  {
    "path": "public/icons/docs/dart/SOURCE",
    "content": "https://dart.dev/brand\n"
  },
  {
    "path": "public/icons/docs/date_fns/SOURCE",
    "content": "https://date-fns.org/static/favicon-16x16.png\nhttps://date-fns.org/static/favicon-32x32.png\n"
  },
  {
    "path": "public/icons/docs/deno/SOURCE",
    "content": "https://deno.land/logo.svg\n"
  },
  {
    "path": "public/icons/docs/django/SOURCE",
    "content": "https://github.com/django/djangoproject.com/blob/main/djangoproject/static/img/favicon.ico\n"
  },
  {
    "path": "public/icons/docs/django_rest_framework/SOURCE",
    "content": "https://github.com/encode/django-rest-framework/blob/master/docs_theme/img/favicon.ico\n"
  },
  {
    "path": "public/icons/docs/docker/SOURCE",
    "content": "https://www.docker.com/company/newsroom/media-resources/\nhttps://www.docker.com/legal/trademark-guidelines/\n"
  },
  {
    "path": "public/icons/docs/dom/DOM.sketch/fonts",
    "content": ""
  },
  {
    "path": "public/icons/docs/dom/DOM.sketch/version",
    "content": "10"
  },
  {
    "path": "public/icons/docs/dom_events/DOM_events.sketch/fonts",
    "content": ""
  },
  {
    "path": "public/icons/docs/dom_events/DOM_events.sketch/version",
    "content": "10"
  },
  {
    "path": "public/icons/docs/drupal/SOURCE",
    "content": "https://www.drupal.org/node/9068\n"
  },
  {
    "path": "public/icons/docs/duckdb/SOURCE",
    "content": "https://github.com/duckdb/duckdb/tree/main/logo"
  },
  {
    "path": "public/icons/docs/eigen3/SOURCE",
    "content": "https://gitlab.com/libeigen/eigen/-/blob/master/doc/Eigen_Silly_Professor_64x64.png\n"
  },
  {
    "path": "public/icons/docs/electron/SOURCE",
    "content": "https://github.com/electron/electron.atom.io/tree/gh-pages/images\n"
  },
  {
    "path": "public/icons/docs/elisp/SOURCE",
    "content": "https://www.gnu.org/software/emacs/\n"
  },
  {
    "path": "public/icons/docs/elixir/SOURCE",
    "content": "https://raw.githubusercontent.com/elixir-lang/elixir-lang.github.com/main/images/logo/logo-dark.png#gh-dark-mode-only\nhttps://elixir-lang.org/images/logo/logo.png\n"
  },
  {
    "path": "public/icons/docs/ember/SOURCE",
    "content": "https://emberjs.com/logos/\n"
  },
  {
    "path": "public/icons/docs/erlang/SOURCE",
    "content": "https://github.com/Kapeli/Dash-X-Platform-Resources"
  },
  {
    "path": "public/icons/docs/es_toolkit/SOURCE",
    "content": "https://es-toolkit.slash.page/favicon-100x100.png\n"
  },
  {
    "path": "public/icons/docs/esbuild/SOURCE",
    "content": "https://esbuild.github.io/favicon.svg\n"
  },
  {
    "path": "public/icons/docs/eslint/SOURCE",
    "content": "https://github.com/eslint/website/blob/master/assets/img/favicon.512x512.png\n"
  },
  {
    "path": "public/icons/docs/falcon/SOURCE",
    "content": "https://github.com/falconry/falcon/tree/master/logo\n"
  },
  {
    "path": "public/icons/docs/fastapi/SOURCE",
    "content": "https://fastapi.tiangolo.com/img/favicon.png\n"
  },
  {
    "path": "public/icons/docs/fish/SOURCE",
    "content": "https://fishshell.com/favicon.ico\n"
  },
  {
    "path": "public/icons/docs/flask/SOURCE",
    "content": "https://flask.palletsprojects.com/en/2.3.x/_static/shortcut-icon.png\n"
  },
  {
    "path": "public/icons/docs/flow/SOURCE",
    "content": "https://github.com/facebook/flow/blob/master/website/favicon.svg\nhttps://github.com/facebook/flow/blob/master/website/static/favicon.png\n"
  },
  {
    "path": "public/icons/docs/fluture/SOURCE",
    "content": "https://github.com/fluture-js/Fluture/\n"
  },
  {
    "path": "public/icons/docs/git/SOURCE",
    "content": "http://git-scm.com/downloads/logos\n"
  },
  {
    "path": "public/icons/docs/gnu_cobol/SOURCE",
    "content": "https://gnucobol.sourceforge.io/images/shire_200.png\n"
  },
  {
    "path": "public/icons/docs/gnu_fortran/SOURCE",
    "content": "https://commons.wikimedia.org/wiki/File:Fortran.png\n"
  },
  {
    "path": "public/icons/docs/gnu_make/SOURCE",
    "content": "https://www.gnu.org/graphics/heckert_gnu.png"
  },
  {
    "path": "public/icons/docs/gnuplot/SOURCE",
    "content": "https://sourceforge.net/p/gnuplot/gnuplot-main/ci/master/tree/demo/html/favicon.ico\n"
  },
  {
    "path": "public/icons/docs/go/SOURCE",
    "content": "http://golang.org/doc/gopher/\n"
  },
  {
    "path": "public/icons/docs/godot/SOURCE",
    "content": "https://godotengine.org/themes/godotengine/assets/favicon.png\n"
  },
  {
    "path": "public/icons/docs/graphviz/SOURCE",
    "content": "https://gitlab.com/graphviz/graphviz.gitlab.io/-/blob/main/static/Resources/favicon.png\n"
  },
  {
    "path": "public/icons/docs/groovy/SOURCE",
    "content": "https://docs.groovy-lang.org/latest/html/gapi/groovy.ico\n"
  },
  {
    "path": "public/icons/docs/grunt/SOURCE",
    "content": "https://github.com/gruntjs/gruntjs.com/tree/master/src/img\n"
  },
  {
    "path": "public/icons/docs/gtk/SOURCE",
    "content": "https://www.gtk.org/assets/img/logo-gtk-sm.png\n"
  },
  {
    "path": "public/icons/docs/hammerspoon/SOURCE",
    "content": "https://www.hammerspoon.org/images/hammerspoon.ico"
  },
  {
    "path": "public/icons/docs/handlebars/SOURCE",
    "content": "https://github.com/yahoo/formatjs-site/tree/master/public/img\n"
  },
  {
    "path": "public/icons/docs/hapi/SOURCE",
    "content": "https://hapi.dev/\n"
  },
  {
    "path": "public/icons/docs/haproxy/SOURCE",
    "content": "http://www.haproxy.org/img/HAProxyCommunityEdition_60px.png\n"
  },
  {
    "path": "public/icons/docs/haskell/SOURCE",
    "content": "https://www.haskell.org/img/favicon.ico\nhttp://www.haskell.org/haskellwiki/Thompson-Wheeler_logo\n"
  },
  {
    "path": "public/icons/docs/haxe/SOURCE",
    "content": "https://github.com/HaxeFoundation/haxe.org/tree/master/www/img\n"
  },
  {
    "path": "public/icons/docs/homebrew/SOURCE",
    "content": "https://docs.brew.sh/assets/img/apple-touch-icon.png\n"
  },
  {
    "path": "public/icons/docs/html/HTML5.sketch/fonts",
    "content": ""
  },
  {
    "path": "public/icons/docs/html/HTML5.sketch/version",
    "content": "10"
  },
  {
    "path": "public/icons/docs/html/SOURCE",
    "content": "http://www.w3.org/html/logo/\n"
  },
  {
    "path": "public/icons/docs/htmx/SOURCE",
    "content": "https://github.com/bigskysoftware/htmx/blob/v1.9.10/www/static/img/htmx_logo.2.png\n"
  },
  {
    "path": "public/icons/docs/http/SOURCE",
    "content": "http://www.entypo.com/\n"
  },
  {
    "path": "public/icons/docs/i3/SOURCE",
    "content": "https://github.com/i3/i3.github.io/blob/master/logo.png\n"
  },
  {
    "path": "public/icons/docs/immutable/SOURCE",
    "content": "https://github.com/immutable-js/immutable-js/blob/main/website/public/favicon.png"
  },
  {
    "path": "public/icons/docs/jasmine/SOURCE",
    "content": "https://github.com/jasmine/jasmine/blob/main/images/jasmine_favicon.png\n"
  },
  {
    "path": "public/icons/docs/javascript/SOURCE",
    "content": "https://github.com/voodootikigod/logo.js\n"
  },
  {
    "path": "public/icons/docs/jekyll/SOURCE",
    "content": "https://raw.githubusercontent.com/jekyll/jekyll/master/docs/favicon.ico\n"
  },
  {
    "path": "public/icons/docs/jest/SOURCE",
    "content": "https://github.com/facebook/jest/tree/main/website/static/img\n"
  },
  {
    "path": "public/icons/docs/jinja/SOURCE",
    "content": "https://jinja.palletsprojects.com/en/2.11.x/_static/jinja-logo-sidebar.png\n"
  },
  {
    "path": "public/icons/docs/joi/SOURCE",
    "content": "https://joi.dev/img/joiTransparent.png\n"
  },
  {
    "path": "public/icons/docs/jq/SOURCE",
    "content": "https://stedolan.github.io/jq/jq.png\n"
  },
  {
    "path": "public/icons/docs/jquery/SOURCE",
    "content": "http://brand.jquery.org/logos/\n"
  },
  {
    "path": "public/icons/docs/jquerymobile/SOURCE",
    "content": "http://brand.jquery.org/logos/\n"
  },
  {
    "path": "public/icons/docs/jqueryui/SOURCE",
    "content": "http://brand.jquery.org/logos/\n"
  },
  {
    "path": "public/icons/docs/julia/SOURCE",
    "content": "https://docs.julialang.org/en/v1/assets/logo-dark.svg"
  },
  {
    "path": "public/icons/docs/knockout/SOURCE",
    "content": "http://learn.knockoutjs.com/\nhttp://learn.knockoutjs.com/Content/App/icon.png"
  },
  {
    "path": "public/icons/docs/kotlin/SOURCE",
    "content": "https://github.com/JetBrains/kotlin-web-site/blob/master/assets/images/favicon.ico\n"
  },
  {
    "path": "public/icons/docs/kubectl/SOURCE",
    "content": "https://cncf-branding.netlify.app/projects/kubernetes/"
  },
  {
    "path": "public/icons/docs/kubernetes/SOURCE",
    "content": "https://cncf-branding.netlify.app/projects/kubernetes/"
  },
  {
    "path": "public/icons/docs/laravel/SOURCE",
    "content": "https://github.com/laravel/art\n"
  },
  {
    "path": "public/icons/docs/latex/SOURCE",
    "content": "Compiling \\LaTeX with pdflatex https://www.tug.org/applications/pdftex/\nhttps://www.latex-project.org/img/latex-project-logo.svg\nhttps://www.latex-project.org/get/"
  },
  {
    "path": "public/icons/docs/leaflet/SOURCE",
    "content": "https://leafletjs.com/docs/images/favicon.ico\n"
  },
  {
    "path": "public/icons/docs/lit/SOURCE",
    "content": "https://github.com/lit/lit.dev/blob/main/packages/lit-dev-content/site/images/flame.svg\nhttps://github.com/lit/lit.dev/blob/main/packages/lit-dev-content/site/images/icon.svg\nhttps://github.com/lit/lit/blob/main/packages/lit/logo.svg\n"
  },
  {
    "path": "public/icons/docs/lodash/SOURCE",
    "content": "http://lodash.com/favicon.ico\n"
  },
  {
    "path": "public/icons/docs/love/SOURCE",
    "content": "https://love2d.org/wiki/L%C3%B6ve_Logo_Graphics"
  },
  {
    "path": "public/icons/docs/lua/SOURCE",
    "content": "http://lua-users.org/wiki/LuaLogo\n"
  },
  {
    "path": "public/icons/docs/man/SOURCE",
    "content": "https://mirrors.edge.kernel.org/images/favicon.ico\nhttps://commons.wikimedia.org/wiki/File:Tux.svg CC0\n"
  },
  {
    "path": "public/icons/docs/mariadb/SOURCE",
    "content": "https://mariadb.org/about/logos/\n"
  },
  {
    "path": "public/icons/docs/marionette/SOURCE",
    "content": "https://github.com/marionettejs/marionettejs.com/blob/master/src/logo/logo.svg\nhttps://github.com/marionettejs/marionettejs.com"
  },
  {
    "path": "public/icons/docs/markdown/SOURCE",
    "content": "https://github.com/dcurtis/markdown-mark\n"
  },
  {
    "path": "public/icons/docs/matplotlib/SOURCE",
    "content": "https://upload.wikimedia.org/wikipedia/commons/thumb/8/84/Matplotlib_icon.svg/1024px-Matplotlib_icon.svg.png\n"
  },
  {
    "path": "public/icons/docs/meteor/SOURCE",
    "content": "https://assets.website-files.com/5dd3f8176674eb0829f184d5/5dd41eb9049df81f8773946e_meteor-logo.svg\nhttps://www.meteor.com/"
  },
  {
    "path": "public/icons/docs/minitest/SOURCE",
    "content": "https://minite.st/favicon-16x16.png\nhttps://minite.st/favicon-32x32.png\n"
  },
  {
    "path": "public/icons/docs/mocha/SOURCE",
    "content": "https://mochajs.org/\nhttps://github.com/mochajs/mocha/tree/master/assets"
  },
  {
    "path": "public/icons/docs/modernizr/SOURCE",
    "content": "https://github.com/Modernizr/Modernizr/tree/master/media\n"
  },
  {
    "path": "public/icons/docs/moment/moment.sketch/fonts",
    "content": ""
  },
  {
    "path": "public/icons/docs/moment/moment.sketch/metadata",
    "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>app</key>\n\t<string>com.bohemiancoding.sketch</string>\n\t<key>build</key>\n\t<integer>5302</integer>\n\t<key>commit</key>\n\t<string>9460a4bc62af5e9ba50dd4143578fd9401710ce5</string>\n\t<key>version</key>\n\t<integer>18</integer>\n</dict>\n</plist>\n"
  },
  {
    "path": "public/icons/docs/moment/moment.sketch/version",
    "content": "18"
  },
  {
    "path": "public/icons/docs/moment_timezone/SOURCE",
    "content": "https://momentjs.com/static/img/moment-timezone-favicon.png"
  },
  {
    "path": "public/icons/docs/nextjs/SOURCE",
    "content": "https://assets.vercel.com/image/upload/v1662130559/nextjs/Icon_dark_background.png\nhttps://github.com/vercel/next.js/blob/canary/examples/cms-enterspeed/public/favicon/favicon.ico"
  },
  {
    "path": "public/icons/docs/nginx/SOURCE",
    "content": "http://nginx.org/nginx.png\nhttp://nginx.org/"
  },
  {
    "path": "public/icons/docs/nim/SOURCE",
    "content": "https://github.com/nim-lang/website/tree/master/jekyll/assets/img"
  },
  {
    "path": "public/icons/docs/nix/SOURCE",
    "content": "https://github.com/NixOS/nixos-homepage/blob/master/logo/nixos-logo-only-hires.png\n"
  },
  {
    "path": "public/icons/docs/node/SOURCE",
    "content": "https://github.com/nodejs/nodejs.org/blob/main/static/images/logo-hexagon.png\n"
  },
  {
    "path": "public/icons/docs/npm/SOURCE",
    "content": "https://www.npmjs.com/\n"
  },
  {
    "path": "public/icons/docs/numpy/SOURCE",
    "content": "https://github.com/numpy/numpy/tree/main/doc/source/_static/favicon\n"
  },
  {
    "path": "public/icons/docs/nushell/SOURCE",
    "content": "https://www.nushell.sh/icon.png\n"
  },
  {
    "path": "public/icons/docs/ocaml/SOURCE",
    "content": "https://ocaml.org/docs/logos.html\nhttps://github.com/ocaml/ocaml-logo/tree/master/Colour/Favicon\n"
  },
  {
    "path": "public/icons/docs/octave/SOURCE",
    "content": "https://www.gnu.org/software/octave/img/octave-logo.svg\n"
  },
  {
    "path": "public/icons/docs/opengl/SOURCE",
    "content": "https://www.khronos.org/legal/trademarks/\n"
  },
  {
    "path": "public/icons/docs/openlayers/SOURCE",
    "content": "https://github.com/openlayers\nhttps://avatars.githubusercontent.com/u/240579?s=64\n"
  },
  {
    "path": "public/icons/docs/padrino/SOURCE",
    "content": "https://raw.githubusercontent.com/padrino/padrino-web/master/source/images/favicon.ico\n"
  },
  {
    "path": "public/icons/docs/pandas/SOURCE",
    "content": "https://commons.wikimedia.org/wiki/File:Pandas_mark.svg"
  },
  {
    "path": "public/icons/docs/perl/SOURCE",
    "content": "https://www.perlfoundation.org/trademarks.html"
  },
  {
    "path": "public/icons/docs/phalcon/SOURCE",
    "content": "https://github.com/phalcon/website/tree/master/public/images/icons"
  },
  {
    "path": "public/icons/docs/phaser/SOURCE",
    "content": "https://phaser.io/images/img.png\nhttps://phaser.io/"
  },
  {
    "path": "public/icons/docs/phoenix/SOURCE",
    "content": "https://github.com/phoenixframework/phoenix/blob/master/logo.png\n"
  },
  {
    "path": "public/icons/docs/php/SOURCE",
    "content": "http://php.net/download-logos.php\n"
  },
  {
    "path": "public/icons/docs/phpunit/SOURCE",
    "content": "https://github.com/Kapeli/Dash-X-Platform-Resources/blob/master/docset_icons/phpunit%402x.png\n"
  },
  {
    "path": "public/icons/docs/playwright/SOURCE",
    "content": "https://playwright.dev/img/playwright-logo.svg\n"
  },
  {
    "path": "public/icons/docs/point_cloud_library/SOURCE",
    "content": "https://github.com/PointCloudLibrary/pcl/blob/master/pcl.png\n"
  },
  {
    "path": "public/icons/docs/pony/SOURCE",
    "content": "https://raw.githubusercontent.com/ponylang/ponylang-website/master/static/images/logo.png\n"
  },
  {
    "path": "public/icons/docs/postgresql/SOURCE",
    "content": "https://www.postgresql.org/about/press/presskit93/#logos\n"
  },
  {
    "path": "public/icons/docs/prettier/SOURCE",
    "content": "https://prettier.io/icon.png\n"
  },
  {
    "path": "public/icons/docs/pug/SOURCE",
    "content": "https://github.com/pugjs/pug-www/blob/master/htdocs/images/logo.svg\n"
  },
  {
    "path": "public/icons/docs/puppeteer/SOURCE",
    "content": "https://github.com/GoogleChromeLabs/pptr.dev/tree/master/src/favicons\n"
  },
  {
    "path": "public/icons/docs/pygame/SOURCE",
    "content": "https://www.pygame.org/news/2020/10/pygame-20th-birthday"
  },
  {
    "path": "public/icons/docs/python/SOURCE",
    "content": "http://www.python.org/community/logos/\n"
  },
  {
    "path": "public/icons/docs/pytorch/SOURCE",
    "content": "https://pytorch.org/favicon.ico"
  },
  {
    "path": "public/icons/docs/q/SOURCE",
    "content": "http://kriskowal.github.io/q/q.png\n"
  },
  {
    "path": "public/icons/docs/qt/SOURCE",
    "content": "https://commons.wikimedia.org/wiki/File:Qt_logo_2016.svg\n"
  },
  {
    "path": "public/icons/docs/qunit/SOURCE",
    "content": "https://raw.githubusercontent.com/qunitjs/qunitjs.com/main/img/logo.svg\n"
  },
  {
    "path": "public/icons/docs/r/SOURCE",
    "content": "https://svn.r-project.org/R/trunk/doc/html/Rlogo.svg\n"
  },
  {
    "path": "public/icons/docs/rails/SOURCE",
    "content": "http://commons.wikimedia.org/wiki/File:Ruby_logo.svg\n"
  },
  {
    "path": "public/icons/docs/ramda/SOURCE",
    "content": "http://ramda.jcphillipps.com/logo/ramdaFilled_200x235.png"
  },
  {
    "path": "public/icons/docs/react/SOURCE",
    "content": "https://github.com/reactjs/react.dev/blob/master/public/favicon-16x16.png\nhttps://github.com/reactjs/react.dev/blob/master/public/favicon-32x32.png\n"
  },
  {
    "path": "public/icons/docs/react_bootstrap/SOURCE",
    "content": "https://github.com/react-bootstrap/react-bootstrap/blob/master/www/static/logo.svg\n"
  },
  {
    "path": "public/icons/docs/react_native/SOURCE",
    "content": "https://github.com/facebook/react-native-website/blob/main/website/static/docs/assets/favicon.png\n"
  },
  {
    "path": "public/icons/docs/react_router/SOURCE",
    "content": "https://reactrouterdotcom.fly.dev/favicon.ico\n"
  },
  {
    "path": "public/icons/docs/reactivex/SOURCE",
    "content": "https://github.com/ReactiveX/reactivex.github.io/blob/develop/assets/Rx_Icon.png\n"
  },
  {
    "path": "public/icons/docs/redis/SOURCE",
    "content": "http://redis.io/documentation\n"
  },
  {
    "path": "public/icons/docs/redux/SOURCE",
    "content": "https://redux.js.org/img/redux.svg"
  },
  {
    "path": "public/icons/docs/requests/SOURCE",
    "content": "https://requests.readthedocs.io/en/latest/_static/requests-sidebar.png\n"
  },
  {
    "path": "public/icons/docs/requirejs/SOURCE",
    "content": "http://requirejs.org/\n"
  },
  {
    "path": "public/icons/docs/rethinkdb/SOURCE",
    "content": "http://rethinkdb.com/\n"
  },
  {
    "path": "public/icons/docs/ruby/SOURCE",
    "content": "https://www.ruby-lang.org/en/about/logo/\nhttps://commons.wikimedia.org/wiki/File:Ruby_logo.svg\n"
  },
  {
    "path": "public/icons/docs/rust/SOURCE",
    "content": "https://github.com/rust-lang/rust-www/tree/8347e870e3b09824ef9137fa9146ef7d21fec3d6/logos\n"
  },
  {
    "path": "public/icons/docs/rxjs/SOURCE",
    "content": "https://github.com/ReactiveX/reactivex.github.io/blob/develop/favicon.ico\n"
  },
  {
    "path": "public/icons/docs/saltstack/SOURCE",
    "content": "https://github.com/saltstack/salt/blob/develop/doc/_static/salt-logo.svg\n"
  },
  {
    "path": "public/icons/docs/sanctuary/SOURCE",
    "content": "https://github.com/sanctuary-js/sanctuary-logo/tree/v1.1.0\n"
  },
  {
    "path": "public/icons/docs/sanctuary_def/SOURCE",
    "content": "https://github.com/sanctuary-js/sanctuary-logo/tree/v1.1.0\n"
  },
  {
    "path": "public/icons/docs/sanctuary_type_classes/SOURCE",
    "content": "https://github.com/sanctuary-js/sanctuary-logo/tree/v1.1.0\n"
  },
  {
    "path": "public/icons/docs/sass/SOURCE",
    "content": "http://sass-lang.com/assets/img/styleguide/sass-logo.zip"
  },
  {
    "path": "public/icons/docs/scikit_image/SOURCE",
    "content": "https://github.com/scikit-image/scikit-image/blob/master/doc/source/themes/scikit-image/static/img/favicon.ico"
  },
  {
    "path": "public/icons/docs/scikit_learn/SOURCE",
    "content": "http://scikit-learn.org/stable/_static/favicon.ico"
  },
  {
    "path": "public/icons/docs/sequelize/SOURCE",
    "content": "https://github.com/sequelize/sequelize/blob/master/docs/images/logo.png\n"
  },
  {
    "path": "public/icons/docs/sinon/SOURCE",
    "content": "https://github.com/sinonjs/sinon/blob/master/docs/assets/images/logo.png\n"
  },
  {
    "path": "public/icons/docs/socketio/SOURCE",
    "content": "http://socket.io/\n"
  },
  {
    "path": "public/icons/docs/spring_boot/SOURCE",
    "content": "https://spring.io/trademarks\n"
  },
  {
    "path": "public/icons/docs/sqlite/SOURCE",
    "content": "https://commons.wikimedia.org/wiki/File:SQLite370.svg"
  },
  {
    "path": "public/icons/docs/statsmodels/SOURCE",
    "content": "https://www.statsmodels.org/stable/_images/statsmodels-logo-v2-horizontal.svg"
  },
  {
    "path": "public/icons/docs/support_tables/SOURCE",
    "content": "https://caniuse.com/img/favicon-16.png\nhttps://caniuse.com/img/favicon-128.png\n"
  },
  {
    "path": "public/icons/docs/svelte/SOURCE",
    "content": "https://svelte.dev/favicon.png\n"
  },
  {
    "path": "public/icons/docs/svg/SOURCE",
    "content": "http://www.w3.org/2009/08/svg-logos.html\n"
  },
  {
    "path": "public/icons/docs/symfony/SOURCE",
    "content": "http://symfony.com/logo\n"
  },
  {
    "path": "public/icons/docs/tailwindcss/SOURCE",
    "content": "https://tailwindcss.com/favicons/favicon.ico\n"
  },
  {
    "path": "public/icons/docs/tcl_tk/SOURCE",
    "content": "https://www.tcl-lang.org/images/plume.png\n"
  },
  {
    "path": "public/icons/docs/tcllib/SOURCE",
    "content": "https://commons.wikimedia.org/wiki/File:Tcl.svg\n"
  },
  {
    "path": "public/icons/docs/tensorflow/SOURCE",
    "content": "https://www.gstatic.com/devrel-devsite/prod/vc890b700b898929d6a9586ce333c0fb7d88f26c0e62aab16b11d9d648f110bfc/tensorflow/images/lockup.svg"
  },
  {
    "path": "public/icons/docs/tensorflow_cpp/SOURCE",
    "content": "https://www.gstatic.com/devrel-devsite/prod/vc890b700b898929d6a9586ce333c0fb7d88f26c0e62aab16b11d9d648f110bfc/tensorflow/images/lockup.svg"
  },
  {
    "path": "public/icons/docs/terraform/SOURCE",
    "content": "https://www.hashicorp.com/brand"
  },
  {
    "path": "public/icons/docs/threejs/SOURCE",
    "content": "https://github.com/mrdoob/three.js/tree/dev/files"
  },
  {
    "path": "public/icons/docs/trio/SOURCE",
    "content": "https://github.com/python-trio/trio/blob/37de153f858e29df3a19db9fffcd0fb3f2308951/logo/logo-transparent-no-text.svg\n"
  },
  {
    "path": "public/icons/docs/twig/SOURCE",
    "content": "https://twig.symfony.com/images/logo.png\n"
  },
  {
    "path": "public/icons/docs/typescript/SOURCE",
    "content": "https://github.com/remojansen/logo.ts\n"
  },
  {
    "path": "public/icons/docs/underscore/SOURCE",
    "content": "http://underscorejs.org/favicon.ico\n"
  },
  {
    "path": "public/icons/docs/vagrant/SOURCE",
    "content": "https://www.vagrantup.com/vagrant-public/img/logo-hashicorp.svg\nhttp://www.vagrantup.com"
  },
  {
    "path": "public/icons/docs/varnish/SOURCE",
    "content": "https://www.varnish-software.com/branding/\n"
  },
  {
    "path": "public/icons/docs/vertx/SOURCE",
    "content": "https://avatars.githubusercontent.com/u/8124623?s=200&v=4\n"
  },
  {
    "path": "public/icons/docs/vite/SOURCE",
    "content": "https://vite.dev/logo-without-border.svg\n"
  },
  {
    "path": "public/icons/docs/vitest/SOURCE",
    "content": "https://vitest.dev/logo.svg\nhttps://vitest.dev/favicon.ico\n"
  },
  {
    "path": "public/icons/docs/vue/SOURCE",
    "content": "https://es.wikipedia.org/wiki/Archivo:Vue.js_Logo_2.svg\nhttp://vuejs.org/"
  },
  {
    "path": "public/icons/docs/vue_router/SOURCE",
    "content": "https://github.com/vuejs/vuejs.org/blob/master/assets/logo.ai\n"
  },
  {
    "path": "public/icons/docs/vueuse/SOURCE",
    "content": "https://github.com/vueuse/vueuse/blob/main/packages/public/favicon-16x16.png\nhttps://github.com/vueuse/vueuse/blob/main/packages/public/favicon-32x32.png\n"
  },
  {
    "path": "public/icons/docs/vuex/SOURCE",
    "content": "https://github.com/vuejs/vuejs.org/blob/master/assets/logo.ai\n"
  },
  {
    "path": "public/icons/docs/vulkan/SOURCE",
    "content": "01_Vulkan_Icon_RGB_Aug1.svg hand-made, credit to Anne-Sophie BOSSÉ\nUsage granted by James Riordon, Khronos Group Webmaster, until their Marketing have the time to review it\n"
  },
  {
    "path": "public/icons/docs/wagtail/SOURCE",
    "content": "https://github.com/wagtail/wagtail/blob/main/docs/logo.png\n"
  },
  {
    "path": "public/icons/docs/webpack/SOURCE",
    "content": "https://github.com/webpack/media\n"
  },
  {
    "path": "public/icons/docs/werkzeug/SOURCE",
    "content": "https://werkzeug.palletsprojects.com/en/2.3.x/_static/shortcut-icon.png\n"
  },
  {
    "path": "public/icons/docs/wordpress/SOURCE",
    "content": "https://wordpress.org/about/logos/"
  },
  {
    "path": "public/icons/docs/xpath/XPath.sketch/metadata",
    "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>app</key>\n\t<string>com.bohemiancoding.sketch</string>\n\t<key>build</key>\n\t<integer>5355</integer>\n\t<key>commit</key>\n\t<string>b7d299b0a34651d1a0e066786b75aa36168d5809</string>\n\t<key>fonts</key>\n\t<array/>\n\t<key>length</key>\n\t<integer>22373</integer>\n\t<key>version</key>\n\t<integer>18</integer>\n</dict>\n</plist>\n"
  },
  {
    "path": "public/icons/docs/xpath/XPath.sketch/version",
    "content": "18"
  },
  {
    "path": "public/icons/docs/yarn/SOURCE",
    "content": "https://raw.githubusercontent.com/yarnpkg/website/master/favicon.ico\n"
  },
  {
    "path": "public/icons/docs/yii/SOURCE",
    "content": "http://www.yiiframework.com/logo/\n"
  },
  {
    "path": "public/icons/docs/zig/SOURCE",
    "content": "https://github.com/ziglang/logo/blob/master/zig-favicon.png\nhttps://github.com/ziglang/logo/blob/master/zig-mark.svg\n"
  },
  {
    "path": "public/icons/docs/zsh/SOURCE",
    "content": "https://sourceforge.net/p/zsh/web/ci/master/tree/favicon.png\n\n"
  },
  {
    "path": "public/manifest.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/web-manifest-combined.json\",\n  \"name\": \"DevDocs\",\n  \"short_name\": \"DevDocs\",\n  \"description\": \"API Documentation Browser\",\n  \"start_url\": \"/\",\n  \"display\": \"standalone\",\n  \"background_color\": \"#EEEEEE\",\n  \"icons\": [\n    {\n      \"src\": \"/images/webapp-icon-32.png\",\n      \"sizes\": \"32x32\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"/images/webapp-icon-60.png\",\n      \"sizes\": \"60x60\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"/images/webapp-icon-80.png\",\n      \"sizes\": \"80x80\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"/images/webapp-icon-128.png\",\n      \"sizes\": \"128x128\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"/images/webapp-icon-192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"/images/webapp-icon-256.png\",\n      \"sizes\": \"256x256\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"/images/webapp-icon-512.png\",\n      \"sizes\": \"512x512\",\n      \"type\": \"image/png\"\n    }\n  ],\n  \"url_handlers\": [\n    {\n      \"origin\": \"https://devdocs.io\"\n    }\n  ]\n}\n"
  },
  {
    "path": "public/mathml.css",
    "content": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n\n/* See https://github.com/fred-wang/mathml.css */\n\n@namespace \"http://www.w3.org/1998/Math/MathML\";\n\n/* math */\nmath {\n    display: inline;\n    text-indent: 0;\n}\nmath[display=\"block\"] {\n    display: block;\n    text-align: center;\n}\n\n/* fraction */\nmfrac {\n    display: inline-block !important;\n    vertical-align: -50%;\n    border-collapse: collapse;\n    text-align: center;\n}\nmfrac > * {\n    display: block !important;\n}\nmfrac > * + * {\n    display: inline-block !important;\n    vertical-align: top;\n}\nmfrac:not([linethickness=\"0\"]) > *:first-child {\n    border-bottom: solid thin;\n}\n\n/* sub/sup scripts */\nmsub > *:nth-child(2), msubsup > *:nth-child(2),\nmmultiscripts > *:nth-child(2n+2),\nmmultiscripts > mprescripts ~ *:nth-child(2n+3) {\n    font-size: 0.8em;\n    vertical-align: sub;\n}\nmsup > *:nth-child(2), msubsup > *:nth-child(3),\nmmultiscripts > *:nth-child(2n+3),\nmmultiscripts > mprescripts ~ *:nth-child(2n+2) {\n    font-size: 0.8em;\n    vertical-align: super;\n}\nmprescripts:after {\n    content: \";\";\n}\n\n/* under/over scripts */\nmunder, mover, munderover {\n    display: inline-flex !important;\n    flex-direction: column;\n}\nmunder > *:nth-child(2), munderover > *:nth-child(2) {\n    font-size: 0.8em;\n    order: +1;\n}\nmover > *:nth-child(2), munderover > *:nth-child(3) {\n    font-size: 0.8em;\n    order: -1;\n}\nmunder {\n    vertical-align: text-top;\n}\nmover {\n    vertical-align: text-bottom;\n}\nmunderover {\n    vertical-align: middle;\n}\n\n/* roots */\nmsqrt, mroot {\n    display: inline-flex !important;\n    margin-left: .5em;\n    vertical-align: middle;\n    border-top: solid thin;\n}\nmsqrt:before, mroot:before {\n    margin-left: -.5em;\n    content: \"\\221A\";\n}\nmroot > *:nth-child(2) {\n    margin-right: .25em;\n    margin-left: -.75em;\n    font-size: 0.8em;\n    order: -1;\n}\n\n/* menclose */\nmenclose {\n  display: inline-table !important;\n  border-collapse: separate;\n  border-spacing: 0.4ex 0;\n}\nmenclose[notation*=\"top\"], menclose[notation*=\"actuarial\"] {\n  border-top: solid thin;\n}\nmenclose[notation*=\"bottom\"], menclose[notation*=\"madruwb\"] {\n    border-bottom: solid thin;\n}\nmenclose[notation*=\"right\"], menclose[notation*=\"actuarial\"],\nmenclose[notation*=\"madruwb\"] {\n    border-right: solid thin;\n}\nmenclose[notation*=\"left\"] {\n    border-left: solid thin;\n}\nmenclose[notation*=\"box\"], menclose[notation*=\"roundedbox\"],\nmenclose[notation*=\"circle\"] {\n    border: solid thin;\n}\nmenclose[notation*=\"roundedbox\"] {\n    border-radius: 15%;\n}\nmenclose[notation*=\"circle\"] {\n    border-radius: 50%;\n}\nmenclose[notation*=\"horizontalstrike\"] {\n    text-decoration: line-through;\n}\n\n/* table */\nmtable {\n    display: inline-table !important;\n    vertical-align: middle;\n    text-align: center;\n}\nmtr {\n    display: table-row !important;\n}\nmtd {\n    display: table-cell !important;\n    padding: 0 0.5ex;\n}\n\n/* token elements */\nmspace {\n    margin: .2em;\n}\nmi {\n    font-style: italic;\n}\nmo {\n    margin-right: .2em;\n    margin-left: .2em;\n}\nms:before, ms:after {\n    content:\"\\0022\";\n}\nms[lquote]:before {\n    content: attr(lquote);\n}\nms[rquote]:after {\n    content: attr(rquote);\n}\n\n/* mathvariants */\n[mathvariant=\"bold\"], [mathvariant=\"bold-italic\"],\n[mathvariant=\"bold-sans-serif\"], [mathvariant=\"sans-serif-bold-italic\"] {\n    font-weight: bold;\n    font-style: normal;\n}\n[mathvariant=\"monospace\"] {\n    font-family: monospace;\n    font-style: normal;\n}\n[mathvariant=\"sans-serif\"],\n[mathvariant=\"bold-sans-serif\"], [mathvariant=\"sans-serif-italic\"],\n[mathvariant=\"sans-serif-bold-italic\"] {\n    font-family: sans-serif;\n    font-style: normal;\n}\n[mathvariant=\"italic\"], [mathvariant=\"bold-italic\"],\n[mathvariant=\"sans-serif-italic\"], [mathvariant=\"sans-serif-bold-italic\"] {\n    font-style: italic;\n}\n[mathvariant=\"normal\"] {\n    font-style: normal;\n}\n\n/* mphantom */\nmphantom {\n    visibility: hidden;\n}\n\n/* merror */\nmerror {\n    outline: solid thin red;\n}\nmerror:before {\n    content: \"Error: \";\n}\n\n/* annotations */\nsemantics > *:first-child {\n    display: inline;\n}\nannotation, annotation-xml {\n    font-family: monospace;\n    display: none !important;\n}\nmath:active > semantics > *:first-child,\nmath:active > semantics > *:first-child {\n    display: none !important;\n}\nmath:active annotation:first-of-type {\n    display: inline !important;\n}\n"
  },
  {
    "path": "public/opensearch.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<OpenSearchDescription xmlns=\"http://a9.com/-/spec/opensearch/1.1/\" xmlns:moz=\"http://www.mozilla.org/2006/browser/search/\">\n  <ShortName>DevDocs</ShortName>\n  <Description>Search API documentation</Description>\n  <Tags>devdocs</Tags>\n  <Url type=\"text/html\" method=\"get\" template=\"https://devdocs.io/#q={searchTerms}\"/>\n  <Image height=\"16\" width=\"16\" type=\"image/vnd.microsoft.icon\">https://devdocs.io/favicon.ico</Image>\n  <Image height=\"64\" width=\"64\" type=\"image/x-icon\">https://devdocs.io/images/icon-64.png</Image>\n  <InputEncoding>UTF-8</InputEncoding>\n  <moz:SearchForm>https://devdocs.io</moz:SearchForm>\n  <Url type=\"application/opensearchdescription+xml\" rel=\"self\" template=\"https://devdocs.io/opensearch.xml\"/>\n</OpenSearchDescription>\n"
  },
  {
    "path": "public/robots.txt",
    "content": "User-agent: *\nDisallow: /settings"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"extends\": [\"github>freecodecamp/renovate-config\"],\n  \"packageRules\": [\n    {\n      \"matchPackageNames\": [\"better_errors\"],\n      \"allowedVersions\": \"!/^2\\\\.10\\\\.0$/\"\n    }\n  ]\n}\n"
  },
  {
    "path": "techstack.md",
    "content": "<!--\n&lt;--- Readme.md Snippet without images Start ---&gt;\n## Tech Stack\nfreeCodeCamp/devdocs is built on the following main stack:\n\n- [New Relic](http://newrelic.com) – Performance Monitoring\n- [Ruby](https://www.ruby-lang.org) – Languages\n- [Sinatra](http://www.sinatrarb.com/) – Microframeworks (Backend)\n- [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript) – Languages\n- [Capybara](http://jnicklas.github.io/capybara/) – Testing Frameworks\n- [GitHub Actions](https://github.com/features/actions) – Continuous Integration\n- [Docker](https://www.docker.com/) – Virtual Machine Platforms & Containers\n\nFull tech stack [here](/techstack.md)\n\n&lt;--- Readme.md Snippet without images End ---&gt;\n\n&lt;--- Readme.md Snippet with images Start ---&gt;\n## Tech Stack\nfreeCodeCamp/devdocs is built on the following main stack:\n\n- <img width='25' height='25' src='https://img.stackshare.io/service/103/default_193410db3a7e419c7b436961bf41d733c7346b59.png' alt='New Relic'/> [New Relic](http://newrelic.com) – Performance Monitoring\n- <img width='25' height='25' src='https://img.stackshare.io/service/989/ruby.png' alt='Ruby'/> [Ruby](https://www.ruby-lang.org) – Languages\n- <img width='25' height='25' src='https://img.stackshare.io/service/999/logo.png' alt='Sinatra'/> [Sinatra](http://www.sinatrarb.com/) – Microframeworks (Backend)\n- <img width='25' height='25' src='https://img.stackshare.io/service/1209/javascript.jpeg' alt='JavaScript'/> [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript) – Languages\n- <img width='25' height='25' src='https://img.stackshare.io/service/2595/capybara.png' alt='Capybara'/> [Capybara](http://jnicklas.github.io/capybara/) – Testing Frameworks\n- <img width='25' height='25' src='https://img.stackshare.io/service/11563/actions.png' alt='GitHub Actions'/> [GitHub Actions](https://github.com/features/actions) – Continuous Integration\n- <img width='25' height='25' src='https://img.stackshare.io/service/586/n4u37v9t_400x400.png' alt='Docker'/> [Docker](https://www.docker.com/) – Virtual Machine Platforms & Containers\n\nFull tech stack [here](/techstack.md)\n\n&lt;--- Readme.md Snippet with images End ---&gt;\n-->\n<div align=\"center\">\n\n# Tech Stack File\n![](https://img.stackshare.io/repo.svg \"repo\") [freeCodeCamp/devdocs](https://github.com/freeCodeCamp/devdocs)![](https://img.stackshare.io/public_badge.svg \"public\")\n<br/><br/>\n|43<br/>Tools used|01/27/24 <br/>Report generated|\n|------|------|\n</div>\n\n## <img src='https://img.stackshare.io/languages.svg'/> Languages (3)\n<table><tr>\n  <td align='center'>\n  <img width='36' height='36' src='https://img.stackshare.io/service/6727/css.png' alt='CSS 3'>\n  <br>\n  <sub><a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/CSS3\">CSS 3</a></sub>\n  <br>\n  <sub></sub>\n</td>\n\n<td align='center'>\n  <img width='36' height='36' src='https://img.stackshare.io/service/1209/javascript.jpeg' alt='JavaScript'>\n  <br>\n  <sub><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript\">JavaScript</a></sub>\n  <br>\n  <sub></sub>\n</td>\n\n<td align='center'>\n  <img width='36' height='36' src='https://img.stackshare.io/service/989/ruby.png' alt='Ruby'>\n  <br>\n  <sub><a href=\"https://www.ruby-lang.org\">Ruby</a></sub>\n  <br>\n  <sub>v3.3.0</sub>\n</td>\n\n</tr>\n</table>\n\n## <img src='https://img.stackshare.io/frameworks.svg'/> Frameworks (1)\n<table><tr>\n  <td align='center'>\n  <img width='36' height='36' src='https://img.stackshare.io/service/999/logo.png' alt='Sinatra'>\n  <br>\n  <sub><a href=\"http://www.sinatrarb.com/\">Sinatra</a></sub>\n  <br>\n  <sub></sub>\n</td>\n\n</tr>\n</table>\n\n## <img src='https://img.stackshare.io/devops.svg'/> DevOps (7)\n<table><tr>\n  <td align='center'>\n  <img width='36' height='36' src='https://img.stackshare.io/service/2595/capybara.png' alt='Capybara'>\n  <br>\n  <sub><a href=\"http://jnicklas.github.io/capybara/\">Capybara</a></sub>\n  <br>\n  <sub></sub>\n</td>\n\n<td align='center'>\n  <img width='36' height='36' src='https://img.stackshare.io/service/586/n4u37v9t_400x400.png' alt='Docker'>\n  <br>\n  <sub><a href=\"https://www.docker.com/\">Docker</a></sub>\n  <br>\n  <sub></sub>\n</td>\n\n<td align='center'>\n  <img width='36' height='36' src='https://img.stackshare.io/service/1046/git.png' alt='Git'>\n  <br>\n  <sub><a href=\"http://git-scm.com/\">Git</a></sub>\n  <br>\n  <sub></sub>\n</td>\n\n<td align='center'>\n  <img width='36' height='36' src='https://img.stackshare.io/service/11563/actions.png' alt='GitHub Actions'>\n  <br>\n  <sub><a href=\"https://github.com/features/actions\">GitHub Actions</a></sub>\n  <br>\n  <sub></sub>\n</td>\n\n<td align='center'>\n  <img width='36' height='36' src='https://img.stackshare.io/service/103/default_193410db3a7e419c7b436961bf41d733c7346b59.png' alt='New Relic'>\n  <br>\n  <sub><a href=\"http://newrelic.com\">New Relic</a></sub>\n  <br>\n  <sub></sub>\n</td>\n\n<td align='center'>\n  <img width='36' height='36' src='https://img.stackshare.io/service/12795/5jL6-BA5_400x400.jpeg' alt='RubyGems'>\n  <br>\n  <sub><a href=\"https://rubygems.org/\">RubyGems</a></sub>\n  <br>\n  <sub></sub>\n</td>\n\n<td align='center'>\n  <img width='36' height='36' src='https://img.stackshare.io/service/1120/lejvzrnlpb308aftn31u.png' alt='npm'>\n  <br>\n  <sub><a href=\"https://www.npmjs.com/\">npm</a></sub>\n  <br>\n  <sub></sub>\n</td>\n\n</tr>\n</table>\n\n\n## <img src='https://img.stackshare.io/group.svg' /> Open source packages (32)</h2>\n\n## <img width='24' height='24' src='https://img.stackshare.io/service/12795/5jL6-BA5_400x400.jpeg'/> RubyGems (32)\n\n|NAME|VERSION|LAST UPDATED|LAST UPDATED BY|LICENSE|VULNERABILITIES|\n|:------|:------|:------|:------|:------|:------|\n|[activesupport](https://rubygems.org/activesupport)|v7.1.3|11/14/22|Paul Sernatinger |MIT|N/A|\n|[better_errors](https://rubygems.org/better_errors)|v2.10.1|10/24/13|Thibaut |MIT|N/A|\n|[browser](https://rubygems.org/browser)|v5.3.1|11/14/22|Paul Sernatinger |MIT|N/A|\n|[chunky_png](https://rubygems.org/chunky_png)|v1.4.0|11/14/22|Paul Sernatinger |MIT|N/A|\n|[erubi](https://rubygems.org/erubi)|v1.12.0|11/14/22|Paul Sernatinger |MIT|N/A|\n|[html-pipeline](https://rubygems.org/html-pipeline)|v2.14.3|10/21/18|Thibaut Courouble |MIT|N/A|\n|[image_optim](https://rubygems.org/image_optim)|v0.31.3|11/14/22|Paul Sernatinger |MIT|N/A|\n|[image_optim_pack](https://rubygems.org/image_optim_pack)|v0.10.1|11/14/22|Paul Sernatinger |MIT|N/A|\n|[minitest](https://rubygems.org/minitest)|v5.21.2|01/03/15|Thibaut |MIT|N/A|\n|[newrelic_rpm](https://rubygems.org/newrelic_rpm)|v8.16.0|03/24/18|Thibaut Courouble |Apache-2.0|N/A|\n|[nokogiri](https://rubygems.org/nokogiri)|v1.16.0|10/21/18|Thibaut Courouble |MIT|N/A|\n|[progress_bar](https://rubygems.org/progress_bar)|v1.3.3|01/26/14|Thibaut |WTFPL|N/A|\n|[pry-byebug](https://rubygems.org/pry-byebug)|v3.10.1|11/14/22|Paul Sernatinger |MIT|N/A|\n|[rack](https://rubygems.org/rack)|v2.2.8|11/14/22|Paul Sernatinger |MIT|N/A|\n|[rack-ssl-enforcer](https://rubygems.org/rack-ssl-enforcer)|v0.2.9|11/14/22|Paul Sernatinger |MIT|N/A|\n|[rack-test](https://rubygems.org/rack-test)|v2.1.0|11/14/22|Paul Sernatinger |MIT|N/A|\n|[rake](https://rubygems.org/rake)|v13.1.0|11/14/22|Paul Sernatinger |MIT|N/A|\n|[redcarpet](https://rubygems.org/redcarpet)|v3.6.0|11/14/22|Paul Sernatinger |MIT|N/A|\n|[rr](https://rubygems.org/rr)|v3.1.0|10/24/13|Thibaut |MIT|N/A|\n|[sass](https://rubygems.org/sass)|v3.7.4|01/13/24|Simon Legner |MIT|N/A|\n|[selenium-webdriver](https://rubygems.org/selenium-webdriver)|N/A|01/26/14|Thibaut |Apache-2.0|N/A|\n|[sinatra-contrib](https://rubygems.org/sinatra-contrib)|v3.2.0|11/14/22|Paul Sernatinger |MIT|N/A|\n|[sprockets](https://rubygems.org/sprockets)|v3.7.2|01/13/24|Simon Legner |MIT|N/A|\n|[sprockets-helpers](https://rubygems.org/sprockets-helpers)|v1.4.0|01/13/24|Simon Legner |MIT|N/A|\n|[sprockets-sass](https://rubygems.org/sprockets-sass)|N/A|01/13/24|Simon Legner |MIT|N/A|\n|[terminal-table](https://rubygems.org/terminal-table)|v3.0.2|10/11/19|Jasper van Merle |MIT|N/A|\n|[thin](https://rubygems.org/thin)|v1.8.2|11/14/22|Paul Sernatinger |GPL-2.0+,Ruby|N/A|\n|[thor](https://rubygems.org/thor)|v1.3.0|11/14/22|Paul Sernatinger |MIT|N/A|\n|[tty-pager](https://rubygems.org/tty-pager)|v0.14.0|01/24/16|Thibaut Courouble |MIT|N/A|\n|[typhoeus](https://rubygems.org/typhoeus)|v1.4.1|11/14/22|Paul Sernatinger |MIT|N/A|\n|[yajl-ruby](https://rubygems.org/yajl-ruby)|v1.4.3|01/09/24|Nicholas La Roux |MIT|N/A|\n\n<br/>\n<div align='center'>\n\nGenerated via [Stack File](https://github.com/marketplace/stack-file)\n"
  },
  {
    "path": "techstack.yml",
    "content": "repo_name: freeCodeCamp/devdocs\nreport_id: 281e8991a46b88702952b7b18dd5e2b0\nversion: 0.1\nrepo_type: Public\ntimestamp: '2024-01-27T03:50:22+00:00'\nrequested_by: renovate[bot]\nprovider: github\nbranch: main\ndetected_tools_count: 43\ntools:\n- name: CSS 3\n  description: The latest evolution of the Cascading Style Sheets language\n  website_url: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS3\n  open_source: true\n  hosted_saas: false\n  category: Languages & Frameworks\n  sub_category: Languages\n  image_url: https://img.stackshare.io/service/6727/css.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs\n  detection_source: Repo Metadata\n- name: JavaScript\n  description: Lightweight, interpreted, object-oriented language with first-class\n    functions\n  website_url: https://developer.mozilla.org/en-US/docs/Web/JavaScript\n  open_source: true\n  hosted_saas: false\n  category: Languages & Frameworks\n  sub_category: Languages\n  image_url: https://img.stackshare.io/service/1209/javascript.jpeg\n  detection_source_url: https://github.com/freeCodeCamp/devdocs\n  detection_source: Repo Metadata\n- name: Ruby\n  description: A dynamic, interpreted, open source programming language with a focus\n    on simplicity and productivity\n  website_url: https://www.ruby-lang.org\n  version: 3.3.0\n  open_source: true\n  hosted_saas: false\n  category: Languages & Frameworks\n  sub_category: Languages\n  image_url: https://img.stackshare.io/service/989/ruby.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Repo Metadata\n  last_updated_by: Thibaut\n  last_updated_on: 2013-10-24 18:25:52.000000000 Z\n- name: Sinatra\n  description: Classy web-development dressed in a DSL\n  website_url: http://www.sinatrarb.com/\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Languages & Frameworks\n  sub_category: Microframeworks (Backend)\n  image_url: https://img.stackshare.io/service/999/logo.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile\n  detection_source: Gemfile\n  last_updated_by: Paul Sernatinger\n  last_updated_on: 2022-11-14 14:30:30.000000000 Z\n- name: Capybara\n  description: Acceptance test framework for web applications\n  website_url: http://jnicklas.github.io/capybara/\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Build, Test, Deploy\n  sub_category: Testing Frameworks\n  image_url: https://img.stackshare.io/service/2595/capybara.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile\n  detection_source: Gemfile\n  last_updated_by: Thibaut\n  last_updated_on: 2014-01-26 21:01:54.000000000 Z\n- name: Docker\n  description: Enterprise Container Platform for High-Velocity Innovation.\n  website_url: https://www.docker.com/\n  license: Apache-2.0\n  open_source: true\n  hosted_saas: false\n  category: Build, Test, Deploy\n  sub_category: Virtual Machine Platforms & Containers\n  image_url: https://img.stackshare.io/service/586/n4u37v9t_400x400.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs\n  detection_source: Repo Metadata\n- name: Git\n  description: Fast, scalable, distributed revision control system\n  website_url: http://git-scm.com/\n  open_source: true\n  hosted_saas: false\n  category: Build, Test, Deploy\n  sub_category: Version Control System\n  image_url: https://img.stackshare.io/service/1046/git.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs\n  detection_source: Repo Metadata\n- name: GitHub Actions\n  description: Automate your workflow from idea to production\n  website_url: https://github.com/features/actions\n  open_source: false\n  hosted_saas: true\n  category: Build, Test, Deploy\n  sub_category: Continuous Integration\n  image_url: https://img.stackshare.io/service/11563/actions.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/.github/workflows/build.yml\n  detection_source: \".github/workflows/build.yml\"\n  last_updated_by: renovate[bot]\n  last_updated_on: 2023-03-25 01:01:01.000000000 Z\n- name: New Relic\n  description: New Relic is the industry’s largest and most comprehensive cloud-based\n    observability platform.\n  website_url: http://newrelic.com\n  open_source: false\n  hosted_saas: true\n  category: Monitoring\n  sub_category: Performance Monitoring\n  image_url: https://img.stackshare.io/service/103/default_193410db3a7e419c7b436961bf41d733c7346b59.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile\n  detection_source: Gemfile\n  last_updated_by: Thibaut Courouble\n  last_updated_on: 2018-03-24 19:06:19.000000000 Z\n- name: RubyGems\n  description: Easily download, install, and use ruby software packages on your system\n  website_url: https://rubygems.org/\n  open_source: false\n  hosted_saas: false\n  category: Build, Test, Deploy\n  sub_category: Package Managers\n  image_url: https://img.stackshare.io/service/12795/5jL6-BA5_400x400.jpeg\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile\n  detection_source: Gemfile\n  last_updated_by: Thibaut\n  last_updated_on: 2013-10-24 18:25:52.000000000 Z\n- name: npm\n  description: The package manager for JavaScript.\n  website_url: https://www.npmjs.com/\n  open_source: false\n  hosted_saas: false\n  category: Build, Test, Deploy\n  sub_category: Front End Package Manager\n  image_url: https://img.stackshare.io/service/1120/lejvzrnlpb308aftn31u.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile\n  detection_source: Gemfile\n  last_updated_by: Thibaut\n  last_updated_on: 2013-10-24 18:25:52.000000000 Z\n- name: activesupport\n  description: A toolkit of support libraries and Ruby core extensions extracted from\n    the Rails framework\n  package_url: https://rubygems.org/activesupport\n  version: 7.1.3\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/18817/default_b17f14dbef6c1275120b34d9ec2eff5942499bd5.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Paul Sernatinger\n  last_updated_on: 2022-11-14 14:30:30.000000000 Z\n- name: better_errors\n  description: Provides a better error page for Rails and other Rack apps\n  package_url: https://rubygems.org/better_errors\n  version: 2.10.1\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/19224/default_90da4313847baa579409c050c1ebb8de5a6e0fbc.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Thibaut\n  last_updated_on: 2013-10-24 18:25:52.000000000 Z\n- name: browser\n  description: Do some browser detection with Ruby\n  package_url: https://rubygems.org/browser\n  version: 5.3.1\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/19740/default_dd31b4f280886d69f8a43459d55ecc7f1ae44486.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Paul Sernatinger\n  last_updated_on: 2022-11-14 14:30:30.000000000 Z\n- name: chunky_png\n  description: This pure Ruby library can read and write PNG images without depending\n    on an external image library, like RMagick\n  package_url: https://rubygems.org/chunky_png\n  version: 1.4.0\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/19118/default_ab0024f54be625eb1e21abc015158fc74b2e3704.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Paul Sernatinger\n  last_updated_on: 2022-11-14 14:30:30.000000000 Z\n- name: erubi\n  description: Erubi is a ERB template engine for ruby\n  package_url: https://rubygems.org/erubi\n  version: 1.12.0\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/rubygems/image.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Paul Sernatinger\n  last_updated_on: 2022-11-14 14:30:30.000000000 Z\n- name: html-pipeline\n  description: GitHub HTML processing filters and utilities\n  package_url: https://rubygems.org/html-pipeline\n  version: 2.14.3\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/19386/default_b0770ebdd12e1eb479a98261d0175b1714524f87.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Thibaut Courouble\n  last_updated_on: 2018-10-21 22:41:35.000000000 Z\n- name: image_optim\n  description: Command line tool and ruby interface to optimize\n  package_url: https://rubygems.org/image_optim\n  version: 0.31.3\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/19798/default_5acb201e8340ca5b337b8c46c2fcaff7aa0db0ec.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Paul Sernatinger\n  last_updated_on: 2022-11-14 14:30:30.000000000 Z\n- name: image_optim_pack\n  description: 'Precompiled binaries for image_optim: advpng'\n  package_url: https://rubygems.org/image_optim_pack\n  version: 0.10.1\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/rubygems/image.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Paul Sernatinger\n  last_updated_on: 2022-11-14 14:30:30.000000000 Z\n- name: minitest\n  description: Minitest provides a complete suite of testing facilities supporting\n    TDD, BDD, mocking, and benchmarking\n  package_url: https://rubygems.org/minitest\n  version: 5.21.2\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/18818/default_f36df1cfa9ff6061d7f9b4879088be8538581c49.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Thibaut\n  last_updated_on: 2015-01-03 15:38:22.000000000 Z\n- name: newrelic_rpm\n  description: New Relic is a performance management system, developed by New Relic,\n    Inc\n  package_url: https://rubygems.org/newrelic_rpm\n  version: 8.16.0\n  license: Apache-2.0\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/19246/default_518f3430d0c3ea4653c591ebd7e75da67f51cfaf.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Thibaut Courouble\n  last_updated_on: 2018-03-24 19:06:19.000000000 Z\n- name: nokogiri\n  description: Nokogiri\n  package_url: https://rubygems.org/nokogiri\n  version: 1.16.0\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/18823/default_b8fbb83e23c963442e15398c5b56262cc6267d6f.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Thibaut Courouble\n  last_updated_on: 2018-10-21 22:41:35.000000000 Z\n- name: progress_bar\n  description: 'Give people feedback about long-running tasks without overloading\n    them with information: Use a progress bar'\n  package_url: https://rubygems.org/progress_bar\n  version: 1.3.3\n  license: WTFPL\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/19696/default_2312d4d1847ef96af6bbd57ed9959234ab92321c.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Thibaut\n  last_updated_on: 2014-01-26 21:01:54.000000000 Z\n- name: pry-byebug\n  description: Combine 'pry' with 'byebug'\n  package_url: https://rubygems.org/pry-byebug\n  version: 3.10.1\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/18848/default_1c2935fa69cec14d38adad302e002464101cd71f.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Paul Sernatinger\n  last_updated_on: 2022-11-14 14:30:30.000000000 Z\n- name: rack\n  description: Rack provides a minimal, modular and adaptable interface for developing\n    web applications in Ruby\n  package_url: https://rubygems.org/rack\n  version: 2.2.8\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/18839/default_db5cfb0d85d9fd8bfb40a863581417a2a57791ab.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Paul Sernatinger\n  last_updated_on: 2022-11-14 14:30:30.000000000 Z\n- name: rack-ssl-enforcer\n  description: Rack::SslEnforcer is a simple Rack middleware to enforce ssl connections\n  package_url: https://rubygems.org/rack-ssl-enforcer\n  version: 0.2.9\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/rubygems/image.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Paul Sernatinger\n  last_updated_on: 2022-11-14 14:30:30.000000000 Z\n- name: rack-test\n  description: Rack::Test is a small, simple testing API for Rack apps\n  package_url: https://rubygems.org/rack-test\n  version: 2.1.0\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/18845/default_db5cfb0d85d9fd8bfb40a863581417a2a57791ab.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Paul Sernatinger\n  last_updated_on: 2022-11-14 14:30:30.000000000 Z\n- name: rake\n  description: Rake is a Make-like program implemented in Ruby\n  package_url: https://rubygems.org/rake\n  version: 13.1.0\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/18812/default_f582e4648f4682adb72d2b201218cda7f8e894ac.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Paul Sernatinger\n  last_updated_on: 2022-11-14 14:30:30.000000000 Z\n- name: redcarpet\n  description: A fast, safe and extensible Markdown to\n  package_url: https://rubygems.org/redcarpet\n  version: 3.6.0\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/18853/default_b87d202e13d56f87c63181fa49bc5e099c9abaac.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Paul Sernatinger\n  last_updated_on: 2022-11-14 14:30:30.000000000 Z\n- name: rr\n  description: RR is a test double framework that features a rich selection of double\n    techniques and a terse syntax\n  package_url: https://rubygems.org/rr\n  version: 3.1.0\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/18915/default_353d3fd998e68372ebd6d80dbb4c0632f387d091.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Thibaut\n  last_updated_on: 2013-10-24 18:25:52.000000000 Z\n- name: sass\n  description: Ruby Sass is deprecated! See https://sass-lang.com/ruby-sass for details\n  package_url: https://rubygems.org/sass\n  version: 3.7.4\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/18882/default_b1cb499f3e62fe773109c989f76e3365fbe49857.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Simon Legner\n  last_updated_on: 2024-01-13 21:26:48.000000000 Z\n- name: selenium-webdriver\n  description: WebDriver is a tool for writing automated tests of websites\n  package_url: https://rubygems.org/selenium-webdriver\n  license: Apache-2.0\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/18901/default_f0ad2b1ffbe10fdd866f9f8e5f812599d9e8085b.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile\n  detection_source: Gemfile\n  last_updated_by: Thibaut\n  last_updated_on: 2014-01-26 21:01:54.000000000 Z\n- name: sinatra-contrib\n  description: Collection of useful Sinatra extensions\n  package_url: https://rubygems.org/sinatra-contrib\n  version: 3.2.0\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/19010/default_4361ccfae4dee655802081d8182ddcaebd7a2b3f.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Paul Sernatinger\n  last_updated_on: 2022-11-14 14:30:30.000000000 Z\n- name: sprockets\n  description: Sprockets is a Rack-based asset packaging system that concatenates\n    and serves JavaScript\n  package_url: https://rubygems.org/sprockets\n  version: 3.7.2\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/18932/default_36bbb38c1cd5521e90d6e99982778960b00ec515.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Simon Legner\n  last_updated_on: 2024-01-13 21:26:48.000000000 Z\n- name: sprockets-helpers\n  description: Asset path helpers for Sprockets 2.x &amp;amp; 3.x applications\n  package_url: https://rubygems.org/sprockets-helpers\n  version: 1.4.0\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/19732/default_be22d39d0c697424d0da2019daf25b87c93b4c6e.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Simon Legner\n  last_updated_on: 2024-01-13 21:26:48.000000000 Z\n- name: sprockets-sass\n  description: When using Sprockets 2.0 with Sass you will eventually run into a pretty\n    big issue\n  package_url: https://rubygems.org/sprockets-sass\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/19683/default_5a5984fb57132e73bff841a1ebf058577d3c8cb9.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile\n  detection_source: Gemfile\n  last_updated_by: Simon Legner\n  last_updated_on: 2024-01-13 21:26:48.000000000 Z\n- name: terminal-table\n  description: Simple, feature rich ascii table generation library\n  package_url: https://rubygems.org/terminal-table\n  version: 3.0.2\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/18923/default_401820e785202ca0018a36ea3e12ee9a92b3efac.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Jasper van Merle\n  last_updated_on: 2019-10-11 15:55:09.000000000 Z\n- name: thin\n  description: A thin and fast web server\n  package_url: https://rubygems.org/thin\n  version: 1.8.2\n  license: GPL-2.0+,Ruby\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/18900/default_f0ad2b1ffbe10fdd866f9f8e5f812599d9e8085b.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Paul Sernatinger\n  last_updated_on: 2022-11-14 14:30:30.000000000 Z\n- name: thor\n  description: Thor is a toolkit for building powerful command-line interfaces\n  package_url: https://rubygems.org/thor\n  version: 1.3.0\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/18827/default_fa0604c1b3b2c413c942b4fa310935704d314c2f.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Paul Sernatinger\n  last_updated_on: 2022-11-14 14:30:30.000000000 Z\n- name: tty-pager\n  description: Terminal output paging in a cross-platform way supporting all major\n    ruby interpreters\n  package_url: https://rubygems.org/tty-pager\n  version: 0.14.0\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/rubygems/image.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Thibaut Courouble\n  last_updated_on: 2016-01-24 15:03:12.000000000 Z\n- name: typhoeus\n  description: Like a modern code version of the mythical beast with 100 serpent heads\n  package_url: https://rubygems.org/typhoeus\n  version: 1.4.1\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/18891/default_b7088ec778267614a78a5a472c67252ec1721d10.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Paul Sernatinger\n  last_updated_on: 2022-11-14 14:30:30.000000000 Z\n- name: yajl-ruby\n  description: Ruby C bindings to the excellent Yajl JSON stream-based parser library\n  package_url: https://rubygems.org/yajl-ruby\n  version: 1.4.3\n  license: MIT\n  open_source: true\n  hosted_saas: false\n  category: Libraries\n  sub_category: RubyGems Packages\n  image_url: https://img.stackshare.io/package/18895/default_9386886dd1c6c396a11bd4b49732afb9ec444f8d.png\n  detection_source_url: https://github.com/freeCodeCamp/devdocs/blob/main/Gemfile.lock\n  detection_source: Gemfile\n  last_updated_by: Nicholas La Roux\n  last_updated_on: 2024-01-09 12:48:51.000000000 Z\n"
  },
  {
    "path": "test/app_test.rb",
    "content": "require 'test_helper'\nrequire 'rack/test'\nrequire 'app'\n\nclass AppTest < Minitest::Spec\n  include Rack::Test::Methods\n\n  MODERN_BROWSER = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:39.0) Gecko/20100101 Firefox/39.0'\n\n  def app\n    App\n  end\n\n  before do\n    current_session.env('HTTPS', 'on')\n  end\n\n  it 'redirects to HTTPS' do\n    get 'http://example.com/test?q=1', {}, 'HTTPS' => 'off'\n    assert last_response.redirect?\n    assert_equal 'https://example.com/test?q=1', last_response['Location']\n  end\n\n  it 'returns HSTS header' do\n    get 'https://example.com/test'\n    assert_equal 'max-age=31536000; includeSubDomains', last_response['Strict-Transport-Security']\n  end\n\n  describe \"/\" do\n    it \"works\" do\n      get '/'\n      assert last_response.ok?\n    end\n\n    it \"redirects to /#q= when there is a 'q' query param\" do\n      get '/search', q: 'foo'\n      assert last_response.redirect?\n      assert_equal 'https://example.org/#q=foo', last_response['Location']\n    end\n\n    it \"redirects without the query string\" do\n      get '/', foo: 'bar'\n      assert last_response.redirect?\n      assert_equal 'https://example.org/', last_response['Location']\n    end\n  end\n\n  describe \"/[static-page]\" do\n    it \"redirects to /#/[static-page] by default\" do\n      %w(offline about news help).each do |page|\n        get \"/#{page}\", {}, 'HTTP_USER_AGENT' => MODERN_BROWSER\n        assert last_response.redirect?\n        assert_equal \"https://example.org/#/#{page}\", last_response['Location']\n      end\n    end\n\n    it \"redirects via JS cookie when a cookie exists\" do\n      %w(offline about news help).each do |page|\n        set_cookie('foo=bar')\n        get \"/#{page}\", {}, 'HTTP_USER_AGENT' => MODERN_BROWSER\n        assert last_response.redirect?\n        assert_equal 'https://example.org/', last_response['Location']\n        assert last_response['Set-Cookie'].start_with?(\"initial_path=%2F#{page}; path=/; expires=\")\n      end\n    end\n  end\n\n  describe \"/search\" do\n    it \"redirects to /#q=\" do\n      get '/search'\n      assert last_response.redirect?\n      assert_equal 'https://example.org/#q=', last_response['Location']\n\n      get '/search', q: 'foo'\n      assert last_response.redirect?\n      assert_equal 'https://example.org/#q=foo', last_response['Location']\n    end\n  end\n\n  describe \"/[doc]\" do\n    it \"renders when the doc exists and isn't enabled\" do\n      set_cookie('docs=html~5')\n      get '/html~4/', {}, 'HTTP_USER_AGENT' => MODERN_BROWSER\n      assert last_response.ok?\n    end\n\n    it \"renders when the doc exists, is a default doc, and all docs are enabled\" do\n      set_cookie('docs=')\n      get '/css/', {}, 'HTTP_USER_AGENT' => MODERN_BROWSER\n      assert last_response.ok?\n    end\n\n    it \"redirects via JS cookie when the doc exists and is enabled\" do\n      set_cookie('docs=html~5')\n      get '/html~5/', {}, 'HTTP_USER_AGENT' => MODERN_BROWSER\n      assert last_response.redirect?\n      assert_equal 'https://example.org/', last_response['Location']\n      assert last_response['Set-Cookie'].start_with?(\"initial_path=%2Fhtml%7E5%2F; path=/; expires=\")\n    end\n\n    it \"renders when the doc exists, has no version in the path, and isn't enabled\" do\n      get '/html/', {}, 'HTTP_USER_AGENT' => MODERN_BROWSER\n      assert last_response.ok?\n    end\n\n    it \"redirects via JS cookie when the doc exists, has no version in the path, and a version is enabled\" do\n      set_cookie('docs=html~5')\n      get '/html/', {}, 'HTTP_USER_AGENT' => MODERN_BROWSER\n      assert last_response.redirect?\n      assert_equal 'https://example.org/', last_response['Location']\n      assert last_response['Set-Cookie'].start_with?(\"initial_path=%2Fhtml%2F; path=/; expires=\")\n    end\n\n    it \"renders when the doc exists and is enabled, and the request is from Googlebot\" do\n      set_cookie('docs=html')\n      get '/html/', {}, 'HTTP_USER_AGENT' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +https://www.google.com/bot.html)'\n      assert last_response.ok?\n    end\n\n    it \"returns 404 when the doc doesn't exist\" do\n      get '/html~6/'\n      assert last_response.not_found?\n    end\n\n    it \"decodes '~' properly\" do\n      get '/html%7E5/'\n      assert last_response.ok?\n\n      get '/html%7E42/'\n      assert last_response.not_found?\n    end\n\n    it \"redirects with trailing slash\" do\n      get '/html'\n      assert last_response.redirect?\n      assert_equal 'https://example.org/html/', last_response['Location']\n\n      get '/html', bar: 'baz'\n      assert last_response.redirect?\n      assert_equal 'https://example.org/html/?bar=baz', last_response['Location']\n    end\n\n    it \"redirects old docs\" do\n      get '/iojs/'\n      assert last_response.redirect?\n      assert_equal 'https://example.org/node/', last_response['Location']\n    end\n  end\n\n  describe \"/[doc]-[type]\" do\n    it \"works when the doc exists\" do\n      get '/html~4-foo-bar_42/'\n      assert last_response.ok?\n      assert_includes last_response.body, 'data-doc=\"{&quot;name&quot;:&quot;HTML&quot;,&quot;slug&quot;:&quot;html~4&quot;'\n    end\n\n    it \"works when the doc has no version in the path and a version exists\" do\n      get '/html-foo-bar_42/'\n      assert last_response.ok?\n      assert_includes last_response.body, 'data-doc=\"{&quot;name&quot;:&quot;HTML&quot;,&quot;slug&quot;:&quot;html~5&quot;'\n    end\n\n    it \"returns 404 when the type is blank\" do\n      get '/css-/'\n      assert last_response.not_found?\n    end\n\n    it \"returns 404 when the type is not alpha-numeric\" do\n      get '/css-foo:bar/'\n      assert last_response.not_found?\n    end\n\n    it \"returns 404 when the doc doesn't exist\" do\n      get '/html~6-bar/'\n      assert last_response.not_found?\n    end\n\n    it \"redirects with trailing slash\" do\n      get '/css-foo'\n      assert last_response.redirect?\n      assert_equal 'https://example.org/css-foo/', last_response['Location']\n\n      get '/css-foo', bar: 'baz'\n      assert last_response.redirect?\n      assert_equal 'https://example.org/css-foo/?bar=baz', last_response['Location']\n    end\n\n    it \"redirects old docs\" do\n      get '/yii1-foo/'\n      assert last_response.redirect?\n      assert_equal 'https://example.org/yii~1.1-foo/', last_response['Location']\n    end\n  end\n\n  describe \"/[doc+type]/[path]\" do\n    it \"works when the doc exists\" do\n      get '/css/foo'\n      assert last_response.ok?\n\n      get '/css-bar/foo'\n      assert last_response.ok?\n    end\n\n    it \"returns 404 when the doc doesn't exist\" do\n      get '/foo/bar'\n      assert last_response.not_found?\n    end\n\n    it \"redirects without trailing slash\" do\n      get '/css/foo/'\n      assert last_response.redirect?\n      assert_equal 'https://example.org/css/foo', last_response['Location']\n\n      get '/css/foo/', bar: 'baz'\n      assert last_response.redirect?\n      assert_equal 'https://example.org/css/foo?bar=baz', last_response['Location']\n    end\n\n    it \"redirects old docs\" do\n      get '/python2/foo'\n      assert last_response.redirect?\n      assert_equal 'https://example.org/python~2.7/foo', last_response['Location']\n    end\n  end\n\n  describe \"/docs.json\" do\n    it \"returns to the asset path\" do\n      get '/docs.json'\n      assert last_response.redirect?\n      assert_equal 'https://example.org/assets/docs.json', last_response['Location']\n    end\n  end\n\n  describe \"/application.js\" do\n    it \"returns to the asset path\" do\n      get '/application.js'\n      assert last_response.redirect?\n      assert_equal 'https://example.org/assets/application.js', last_response['Location']\n    end\n  end\n\n  describe \"/application.css\" do\n    it \"returns to the asset path\" do\n      get '/application.css'\n      assert last_response.redirect?\n      assert_equal 'https://example.org/assets/application.css', last_response['Location']\n    end\n  end\n\n  describe \"/feed\" do\n    it \"returns an atom feed\" do\n      get '/feed'\n      assert last_response.ok?\n      assert_equal 'application/atom+xml', last_response['Content-Type']\n\n      get '/feed.atom'\n      assert last_response.ok?\n      assert_equal 'application/atom+xml', last_response['Content-Type']\n    end\n  end\n\n  describe \"/s/[link]\" do\n    it \"redirects\" do\n      %w(maxcdn shopify code-school jetbrains tw fb re).each do |link|\n        get \"/s/#{link}\"\n        assert last_response.redirect?\n      end\n    end\n  end\n\n  describe \"/ping\" do\n    it \"works\" do\n      get '/ping'\n      assert last_response.ok?\n    end\n  end\n\n  describe \"404\" do\n    it \"works\" do\n      get '/foo'\n      assert last_response.not_found?\n    end\n  end\nend\n"
  },
  {
    "path": "test/files/docs.json",
    "content": "[{\"name\":\"CSS\",\"slug\":\"css\",\"type\":\"mdn\",\"release\":null,\"mtime\":1420139788,\"db_size\":3460507,\"alias\":null},{\"name\":\"DOM\",\"slug\":\"dom\",\"type\":\"mdn\",\"release\":null,\"mtime\":1420139789,\"db_size\":11399128,\"alias\":null},{\"name\":\"DOM Events\",\"slug\":\"dom_events\",\"type\":\"mdn\",\"release\":null,\"mtime\":1420139790,\"db_size\":889020,\"alias\":null},{\"name\":\"HTML\",\"slug\":\"html~5\",\"type\":\"mdn\",\"version\":\"5\",\"mtime\":1420139791,\"db_size\":1835647,\"alias\":null},{\"name\":\"HTML\",\"slug\":\"html~4\",\"type\":\"mdn\",\"version\":\"4\",\"mtime\":1420139790,\"db_size\":1835646,\"alias\":null},{\"name\":\"HTTP\",\"slug\":\"http\",\"type\":\"rfc\",\"release\":null,\"mtime\":1420139790,\"db_size\":183083,\"alias\":null},{\"name\":\"JavaScript\",\"slug\":\"javascript\",\"type\":\"mdn\",\"release\":null,\"mtime\":1420139791,\"db_size\":4125477,\"alias\":\"js\"}]\n"
  },
  {
    "path": "test/lib/docs/core/doc_test.rb",
    "content": "require_relative '../../../test_helper'\nrequire_relative '../../../../lib/docs'\n\nclass DocsDocTest < Minitest::Spec\n  let :doc do\n    Class.new Docs::Doc do\n      self.name = 'name'\n      self.type = 'type'\n    end\n  end\n\n  let :page do\n    { path: 'path', store_path: 'store_path', output: 'output', entries: [entry] }\n  end\n\n  let :entry do\n    Docs::Entry.new 'name', 'path', 'type'\n  end\n\n  let :store do\n    Docs::NullStore.new\n  end\n\n  describe \".inherited\" do\n    it \"sets .type\" do\n      assert_equal doc.type, Class.new(doc).type\n    end\n  end\n\n  describe \".name\" do\n    it \"returns 'Doc' when the class is Docs::Doc\" do\n      assert_equal 'Doc', Docs::Doc.name\n    end\n  end\n\n  describe \".name=\" do\n    it \"stores .name\" do\n      doc.name = 'test'\n      assert_equal 'test', doc.name\n    end\n  end\n\n  describe \".slug\" do\n    it \"returns 'doc' when the class is Docs::Doc\" do\n      assert_equal 'doc', Docs::Doc.slug\n    end\n\n    it \"returns 'doc~4.2_lts' when the class is Docs::Doc and its #version is '42 LTS'\" do\n      stub(Docs::Doc).version { '4.2 LTS' }\n      assert_equal 'doc~4.2_lts', Docs::Doc.slug\n    end\n\n    it \"returns 'foo~42' when #slug has been set to 'foo' and #version to '42'\" do\n      doc.slug = 'foo'\n      doc.version = '42'\n      assert_equal 'foo~42', doc.slug\n    end\n\n    it \"returns 'foobar' when #name has been set to 'FooBar'\" do\n      doc.name = 'FooBar'\n      assert_equal 'foobar', doc.slug\n    end\n\n    it \"raises error when #name has unsluggable characters\" do\n      assert_raises do\n        doc.name = 'Foo-Bar'\n        doc.slug\n      end\n    end\n  end\n\n  describe \".slug=\" do\n    it \"stores .slug\" do\n      doc.slug = 'test'\n      assert_equal 'test', doc.slug\n    end\n  end\n\n  describe \".version=\" do\n    it \"stores .version as a string\" do\n      doc.version = 4815162342\n      assert_equal '4815162342', doc.version\n    end\n  end\n\n  describe \".release=\" do\n    it \"stores .release\" do\n      doc.release = '1'\n      assert_equal '1', doc.release\n    end\n  end\n\n  describe \".links=\" do\n    it \"stores .links\" do\n      doc.links = { test: true }\n      assert_equal({ test: true }, doc.links)\n    end\n  end\n\n  describe \".abstract\" do\n    it \"returns nil\" do\n      assert_nil doc.abstract\n    end\n  end\n\n  describe \".abstract=\" do\n    it \"stores .abstract\" do\n      doc.abstract = true\n      assert doc.abstract\n    end\n  end\n\n  describe \".path\" do\n    it \"returns .slug\" do\n      doc.slug = 'slug'\n      assert_equal 'slug', doc.path\n    end\n  end\n\n  describe \".index_path\" do\n    it \"returns .path + ::INDEX_FILENAME\" do\n      stub(doc).path { 'path' }\n      assert_equal File.join('path', Docs::Doc::INDEX_FILENAME), doc.index_path\n    end\n  end\n\n  describe \".db_path\" do\n    it \"returns .path + ::DB_FILENAME\" do\n      stub(doc).path { 'path' }\n      assert_equal File.join('path', Docs::Doc::DB_FILENAME), doc.db_path\n    end\n  end\n\n  describe \".meta_path\" do\n    it \"returns .path + ::META_FILENAME\" do\n      stub(doc).path { 'path' }\n      assert_equal File.join('path', Docs::Doc::META_FILENAME), doc.meta_path\n    end\n  end\n\n  describe \".new\" do\n    it \"raises an error when .abstract is true\" do\n      doc.abstract = true\n      assert_raises NotImplementedError do\n        doc.new\n      end\n    end\n  end\n\n  describe \".as_json\" do\n    it \"returns a hash\" do\n      assert_instance_of Hash, doc.as_json\n    end\n\n    it \"includes the doc's name, slug, type, version, and release\" do\n      assert_equal %i(name slug type), doc.as_json.keys\n\n      %w(name slug type version release links).each do |attribute|\n        eval \"stub(doc).#{attribute} { attribute }\"\n        assert_equal attribute, doc.as_json[attribute.to_sym]\n      end\n    end\n\n    it \"includes the doc's version when it's defined and nil\" do\n      refute doc.as_json.key?(:version)\n      doc.version = nil\n      assert doc.as_json.key?(:version)\n    end\n  end\n\n  describe \".store_page\" do\n    it \"builds a page\" do\n      any_instance_of(doc) do |instance|\n        stub(instance).build_page('id') { @called = true; nil }\n      end\n      doc.store_page(store, 'id') {}\n      assert @called\n    end\n\n    context \"when the page builds successfully\" do\n      before do\n        any_instance_of(doc) do |instance|\n          stub(instance).build_page { page }\n        end\n      end\n\n      context \"and it has :entries\" do\n        it \"returns true\" do\n          assert doc.store_page(store, 'id')\n        end\n\n        it \"stores a file\" do\n          mock(store).write(page[:store_path], page[:output])\n          doc.store_page(store, 'id')\n        end\n\n        it \"opens the .path directory before storing the file\" do\n          stub(doc).path { 'path' }\n          stub(store).write { assert false }\n          mock(store).open('path') do |_, &block|\n            stub(store).write\n            block.call\n          end\n          doc.store_page(store, 'id')\n        end\n      end\n\n      context \"and it doesn't have :entries\" do\n        before do\n          page[:entries] = []\n        end\n\n        it \"returns false\" do\n          refute doc.store_page(store, 'id')\n        end\n\n        it \"doesn't store a file\" do\n          dont_allow(store).write\n          doc.store_page(store, 'id')\n        end\n      end\n    end\n\n    context \"when the page doesn't build successfully\" do\n      before do\n        any_instance_of(doc) do |instance|\n          stub(instance).build_page { nil }\n        end\n      end\n\n      it \"returns false\" do\n        refute doc.store_page(store, 'id')\n      end\n\n      it \"doesn't store a file\" do\n        dont_allow(store).write\n        doc.store_page(store, 'id')\n      end\n    end\n  end\n\n  describe \".store_pages\" do\n    it \"build the pages\" do\n      any_instance_of(doc) do |instance|\n        stub(instance).build_pages { @called = true }\n      end\n      doc.store_pages(store) {}\n      assert @called\n    end\n\n    context \"when pages are built successfully\" do\n      let :pages do\n        [\n          page.deep_dup.tap { |p| page[:entries].first.tap { |e| e.name = 'one' } },\n          page.deep_dup.tap { |p| page[:entries].first.tap { |e| e.name = 'two' } }\n        ]\n      end\n\n      before do\n        any_instance_of(doc) do |instance|\n          stub(instance).build_pages { |&block| pages.each(&block) }\n        end\n      end\n\n      context \"and at least one page has :entries\" do\n        it \"returns true\" do\n          assert doc.store_pages(store)\n        end\n\n        it \"stores a file for each page that has :entries\" do\n          pages.first.merge!(entries: [], output: '')\n          mock(store).write(page[:store_path], page[:output])\n          mock(store).write('index.json', anything)\n          mock(store).write('db.json', anything)\n          mock(store).write('meta.json', anything)\n          doc.store_pages(store)\n        end\n\n        it \"stores an index that contains all the pages' entries\" do\n          stub(store).write(page[:store_path], page[:output])\n          stub(store).write('db.json', anything)\n          stub(store).write('meta.json', anything)\n          mock(store).write('index.json', anything) do |path, json|\n            json = JSON.parse(json)\n            assert_equal pages.length, json['entries'].length\n            assert_includes json['entries'], Docs::Entry.new('one', 'path', 'type').as_json.stringify_keys\n          end\n          doc.store_pages(store)\n        end\n\n        it \"stores a db that contains all the pages, indexed by path\" do\n          stub(store).write(page[:store_path], page[:output])\n          stub(store).write('index.json', anything)\n          stub(store).write('meta.json', anything)\n          mock(store).write('db.json', anything) do |path, json|\n            json = JSON.parse(json)\n            assert_equal page[:output], json[page[:path]]\n          end\n          doc.store_pages(store)\n        end\n\n        it \"stores a meta file that contains the doc's metadata\" do\n          stub(store).write(page[:store_path], page[:output])\n          stub(store).write('index.json', anything)\n          stub(store).write('db.json', anything)\n          mock(store).write('meta.json', anything) do |path, json|\n            json = JSON.parse(json)\n            assert_equal %w(name slug type mtime db_size).sort, json.keys.sort\n          end\n          doc.store_pages(store)\n        end\n\n        it \"replaces the .path directory before storing the files\" do\n          stub(doc).path { 'path' }\n          stub(store).write { assert false }\n          mock(store).replace('path') do |_, &block|\n            stub(store).write\n            block.call\n          end\n          doc.store_pages(store)\n        end\n      end\n\n      context \"and no pages have :entries\" do\n        before do\n          pages.each { |page| page[:entries] = [] }\n        end\n\n        it \"returns false\" do\n          refute doc.store_pages(store)\n        end\n\n        it \"doesn't store files\" do\n          dont_allow(store).write\n          doc.store_pages(store)\n        end\n      end\n    end\n\n    context \"when no pages are built successfully\" do\n      before do\n        any_instance_of(doc) do |instance|\n          stub(instance).build_pages\n        end\n      end\n\n      it \"returns false\" do\n        refute doc.store_pages(store)\n      end\n\n      it \"doesn't store files\" do\n        dont_allow(store).write\n        doc.store_pages(store)\n      end\n    end\n  end\n\n  describe \".versions\" do\n    it \"returns [self] if no versions have been created\" do\n      assert_equal [doc], doc.versions\n    end\n  end\n\n  describe \".version\" do\n    context \"with no args\" do\n      it \"returns @version by default\" do\n        doc.version = 'v'\n        assert_equal 'v', doc.version\n      end\n    end\n\n    context \"with args\" do\n      it \"creates a version subclass\" do\n        version = doc.version('4') { self.release = '8'; self.links = [\"https://#{self.version}\"] }\n\n        assert_equal [version], doc.versions\n\n        assert_nil doc.version\n        assert_nil doc.release\n        refute doc.version?\n\n        assert version.version?\n        assert_equal '4', version.version\n        assert_equal '8', version.release\n        assert_equal 'name', version.name\n        assert_equal 'type', version.type\n        assert_equal ['https://4'], version.links\n      end\n    end\n\n    it \"compares versions\" do\n      instance = doc.versions.first.new\n      assert_equal \"Up-to-date\", instance.outdated_state('0.0.2', '0.0.3')\n      assert_equal \"Outdated major version\", instance.outdated_state('0.2', '0.3')\n      assert_equal 'Up-to-date', instance.outdated_state('1', '1')\n      assert_equal 'Up-to-date', instance.outdated_state('1.2', '1.2')\n      assert_equal 'Up-to-date', instance.outdated_state('1.2.2', '1.2.2')\n      assert_equal 'Up-to-date', instance.outdated_state('1.2.2', '1.2.3')\n      assert_equal \"Outdated major version\", instance.outdated_state('1', '2')\n      assert_equal \"Up-to-date\", instance.outdated_state('1.0.2', '1.0.3')\n      assert_equal \"Outdated major version\", instance.outdated_state('1.2', '1.3')\n      assert_equal \"Outdated minor version\", instance.outdated_state('2.2', '2.3')\n      assert_equal \"Outdated major version\", instance.outdated_state('9', '10')\n      assert_equal \"Outdated major version\", instance.outdated_state('99', '101')\n      assert_equal 'Up-to-date', instance.outdated_state('2006-01-02', '2006-01-03')\n      assert_equal \"Outdated minor version\", instance.outdated_state('2006-01-02', '2006-02-03')\n    end\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/core/entry_index_test.rb",
    "content": "require_relative '../../../test_helper'\nrequire_relative '../../../../lib/docs'\n\nclass DocsEntryIndexTest < Minitest::Spec\n  let :entry do\n    Docs::Entry.new 'name', 'path', 'type'\n  end\n\n  let :index do\n    Docs::EntryIndex.new\n  end\n\n  describe \"#entries\" do\n    it \"returns an Array\" do\n      assert_instance_of Array, index.entries\n    end\n  end\n\n  describe \"#types\" do\n    it \"returns a hash\" do\n      assert_instance_of Hash, index.types\n    end\n  end\n\n  describe \"#add\" do\n    it \"stores an entry\" do\n      index.add(entry)\n      assert_includes index.entries, entry\n    end\n\n    it \"stores an array of entries\" do\n      entries = [\n        Docs::Entry.new('one', 'path', 'type'),\n        Docs::Entry.new('two', 'path', 'type')\n      ]\n\n      index.add(entries)\n      assert_equal entries, index.entries\n    end\n\n    it \"duplicates the entry\" do\n      index.add(entry)\n      refute_same entry, index.entries.first\n    end\n\n    it \"doesn't store the root entry\" do\n      mock(entry).root? { true }\n      index.add(entry)\n      assert_empty index.entries\n      assert_empty index.types\n    end\n\n    it \"doesn't store the same entry twice\" do\n      2.times { index.add(entry.dup) }\n      assert_equal [entry], index.entries\n    end\n\n    it \"creates and indexes the type\" do\n      index.add Docs::Entry.new('one', 'path', 'a')\n      index.add Docs::Entry.new('two', 'path', 'b')\n      index.add Docs::Entry.new('three', 'path', 'b')\n      assert_equal ['a', 'b'], index.types.keys\n      assert_instance_of Docs::Type, index.types['a']\n    end\n\n    it \"doesn't index the nil type\" do\n      entry.type = nil; index.add(entry)\n      assert_empty index.types\n    end\n\n    it \"increments the type's count\" do\n      index.add Docs::Entry.new('one', 'path', 'type')\n      index.add Docs::Entry.new('two', 'path', 'type')\n      assert_equal 2, index.types['type'].count\n    end\n  end\n\n  describe \"#empty? / #blank? / #present?\" do\n    it \"is #empty? and #blank? when no entries have been added\" do\n      assert index.empty?\n      assert index.blank?\n      refute index.present?\n    end\n\n    it \"is #present? when an entry has been added\" do\n      index.add(entry)\n      refute index.empty?\n      refute index.blank?\n      assert index.present?\n    end\n  end\n\n  describe \"#as_json\" do\n    it \"returns a Hash\" do\n      assert_instance_of Hash, index.as_json\n    end\n\n    describe \":entries\" do\n      it \"is an empty array by default\" do\n        assert_instance_of Array, index.as_json[:entries]\n      end\n\n      it \"includes the json representation of the #entries\" do\n        index.add one = Docs::Entry.new('one', 'path', 'type')\n        index.add two = Docs::Entry.new('two', 'path', 'type')\n        assert_equal [one.as_json, two.as_json], index.as_json[:entries]\n      end\n\n      it \"is sorted by name, case-insensitive\" do\n        entry.name = 'B'; index.add(entry)\n        entry.name = 'a'; index.add(entry)\n        entry.name = 'c'; index.add(entry)\n        assert_equal ['a', 'B', 'c'], index.as_json[:entries].map { |e| e[:name] }\n      end\n\n      it \"sorts numbered names\" do\n        entry.name = '4.2.2. Test'; index.add(entry)\n        entry.name = '4.20. Test'; index.add(entry)\n        entry.name = '4.3. Test'; index.add(entry)\n        entry.name = '4. Test'; index.add(entry)\n        entry.name = '2 Test'; index.add(entry)\n        entry.name = 'Test'; index.add(entry)\n        assert_equal ['4. Test', '4.2.2. Test', '4.3. Test', '4.20. Test', '2 Test', 'Test'], index.as_json[:entries].map { |e| e[:name] }\n      end\n    end\n\n    describe \":types\" do\n      it \"is an empty array by default\" do\n        assert_instance_of Array, index.as_json[:types]\n      end\n\n      it \"includes the json representation of the #types\" do\n        type = Docs::Type.new 'one', 1\n        entry.type = 'one'; index.add(entry)\n        assert_equal type.as_json, index.as_json[:types].first\n      end\n\n      it \"is sorted by name, case-insensitive\" do\n        entry.type = 'B'; index.add(entry)\n        entry.type = 'a'; index.add(entry)\n        entry.type = 'c'; index.add(entry)\n        assert_equal ['a', 'B', 'c'], index.as_json[:types].map { |e| e[:name] }\n      end\n\n      it \"sorts numbered names\" do\n        entry.type = '1.8.2. Test'; index.add(entry)\n        entry.type = '1.90. Test'; index.add(entry)\n        entry.type = '1.9. Test'; index.add(entry)\n        entry.type = '9. Test'; index.add(entry)\n        entry.type = '1 Test'; index.add(entry)\n        entry.type = 'Test'; index.add(entry)\n        assert_equal ['1.8.2. Test', '1.9. Test', '1.90. Test', '9. Test', '1 Test', 'Test'], index.as_json[:types].map { |e| e[:name] }\n      end\n    end\n  end\n\n  describe \"#to_json\" do\n    it \"returns the JSON string for #as_json\" do\n      stub(index).as_json { { entries: [1], types: [2] } }\n      assert_equal '{\"entries\":[1],\"types\":[2]}', index.to_json\n    end\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/core/filter_test.rb",
    "content": "require_relative '../../../test_helper'\nrequire_relative '../../../../lib/docs'\n\nclass DocsFilterTest < Minitest::Spec\n  include FilterTestHelper\n  self.filter_class = Docs::Filter\n\n  before do\n    context[:base_url] = 'http://example.com/path'\n    context[:url] = 'http://example.com/path/file'\n  end\n\n  describe \"#subpath\" do\n    it \"returns the #subpath_to the #current_url\" do\n      stub(filter).subpath_to(filter.current_url) { 'subpath' }\n      assert_equal 'subpath', filter.subpath\n    end\n  end\n\n  describe \"#subpath_to\" do\n    it \"returns the subpath from the #base_url to the url, ignoring case\" do\n      stub(filter.base_url).subpath_to('url', ignore_case: true) { 'subpath' }\n      assert_equal 'subpath', filter.subpath_to('url')\n    end\n  end\n\n  describe \"#slug\" do\n    def slug(subpath)\n      stub(filter).subpath { subpath }\n      filter.slug\n    end\n\n    it \"returns '' when #subpath is blank\" do\n      assert_equal '', slug('')\n    end\n\n    it \"returns '' when #subpath is '/'\" do\n      assert_equal '', slug('/')\n    end\n\n    it \"returns 'path' when #subpath is '/path'\" do\n      assert_equal 'path', slug('/path')\n    end\n\n    it \"returns 'path' when #subpath is '/path.html'\" do\n      assert_equal 'path', slug('/path.html')\n    end\n\n    it \"returns 'if..else' when #subpath is '/if..else'\" do\n      assert_equal 'if..else', slug('/if..else')\n    end\n\n    it \"returns 'dir/path' when #subpath is '/dir/path'\" do\n      assert_equal 'dir/path', slug('/dir/path')\n    end\n  end\n\n  describe \"#root_page?\" do\n    it \"returns true when #subpath is blank\" do\n      stub(filter).subpath { '' }\n      assert filter.root_page?\n    end\n\n    it \"returns true when #subpath is '/'\" do\n      stub(filter).subpath { '/' }\n      assert filter.root_page?\n    end\n\n    it \"returns true when #subpath is the root path\" do\n      context[:root_path] = '/path'\n      stub(filter).subpath { '/path' }\n      assert filter.root_page?\n    end\n\n    it \"returns false when #subpath isn't the root path\" do\n      stub(filter).subpath { '/path' }\n      refute filter.root_page?\n    end\n  end\n\n  describe \"#initial_page?\" do\n    before do\n      context[:initial_paths] = ['initial']\n      stub(filter).root_page? { false }\n    end\n\n    it \"returns true when #root_page? is true\" do\n      stub(filter).root_page? { true }\n      assert filter.initial_page?\n    end\n\n    it \"returns true when #subpath is included in :initial_paths\" do\n      stub(filter).subpath { 'initial' }\n      assert filter.initial_page?\n    end\n\n    it \"returns false otherwise\" do\n      refute filter.initial_page?\n    end\n  end\n\n  describe \"#fragment_url_string?\" do\n    it \"returns false with ''\" do\n      refute filter.fragment_url_string?('')\n    end\n\n    it \"returns true with '#'\" do\n      assert filter.fragment_url_string?('#')\n    end\n\n    it \"returns false with '/#'\" do\n      refute filter.fragment_url_string?('/#')\n    end\n\n    it \"returns false with 'http://example.com/#'\" do\n      refute filter.fragment_url_string?('http://example.com/#')\n    end\n  end\n\n  describe \"#relative_url_string?\" do\n    it \"returns true with ''\" do\n      assert filter.relative_url_string?('')\n    end\n\n    it \"returns true with 'http'\" do\n      assert filter.relative_url_string?('http')\n    end\n\n    it \"returns true with '/file'\" do\n      assert filter.relative_url_string?('/file')\n    end\n\n    it \"returns true with '?file'\" do\n      assert filter.relative_url_string?('?file')\n    end\n\n    it \"returns false with '#file'\" do\n      refute filter.relative_url_string?('#file')\n    end\n\n    it \"returns false with 'http://example.com'\" do\n      refute filter.relative_url_string?('http://example.com')\n    end\n\n    it \"returns false with 'ftp://example.com'\" do\n      refute filter.relative_url_string?('ftp://example.com')\n    end\n\n    it \"returns false with 'mailto:test@example.com'\" do\n      refute filter.relative_url_string?('mailto:test@example.com')\n    end\n\n    it \"returns false with 'data:image/gif;base64,foo'\" do\n      refute filter.relative_url_string?('data:image/gif;base64,foo')\n    end\n  end\n\n  describe \"#absolute_url_string?\" do\n    it \"returns true with 'http://example.com'\" do\n      assert filter.absolute_url_string?('http://example.com')\n    end\n\n    it \"returns true with 'ftp://example.com'\" do\n      assert filter.absolute_url_string?('ftp://example.com')\n    end\n\n    it \"returns true with 'mailto:test@example.com'\" do\n      assert filter.absolute_url_string?('mailto:test@example.com')\n    end\n\n    it \"returns false with ''\" do\n      refute filter.absolute_url_string?('')\n    end\n\n    it \"returns false with 'http'\" do\n      refute filter.absolute_url_string?('http')\n    end\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/core/instrumentable_test.rb",
    "content": "require_relative '../../../test_helper'\nrequire_relative '../../../../lib/docs'\n\nclass DocsInstrumentableTest < Minitest::Spec\n  let :extended_class do\n    Class.new.tap { |klass| klass.send :extend, Docs::Instrumentable }\n  end\n\n  let :included_class do\n    Class.new.tap { |klass| klass.send :include, Docs::Instrumentable }\n  end\n\n  it \"works when extended\" do\n    extended_class.subscribe('test') { @called = true }\n    extended_class.instrument 'test'\n    assert @called\n  end\n\n  it \"works when included\" do\n    instance = included_class.new\n    instance.subscribe('test') { @called = true }\n    instance.instrument 'test'\n    assert @called\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/core/manifest_test.rb",
    "content": "require_relative '../../../test_helper'\nrequire_relative '../../../../lib/docs'\n\nclass ManifestTest < Minitest::Spec\n  let :doc do\n    doc = Class.new Docs::Scraper\n    doc.name = 'TestDoc'\n    doc.options[:attribution] = 'foo'\n    doc\n  end\n\n  let :store do\n    Docs::NullStore.new\n  end\n\n  let :manifest do\n    Docs::Manifest.new store, [doc]\n  end\n\n  describe \"#store\" do\n    before do\n      stub(manifest).as_json\n    end\n\n    it \"stores a file\" do\n      mock(store).write.with_any_args\n      manifest.store\n    end\n\n    describe \"the file\" do\n      it \"is named ::FILENAME\" do\n        mock(store).write Docs::Manifest::FILENAME, anything\n        manifest.store\n      end\n\n      it \"contains the manifest's JSON dump\" do\n        stub(manifest).to_json { 'json' }\n        mock(store).write anything, 'json'\n        manifest.store\n      end\n    end\n  end\n\n  describe \"#as_json\" do\n    let :meta_path do\n      'meta_path'\n    end\n\n    before do\n      stub(doc).meta_path { meta_path }\n    end\n\n    it \"returns an array\" do\n      manifest = Docs::Manifest.new store, []\n      assert_instance_of Array, manifest.as_json\n    end\n\n    context \"when the doc has a meta file\" do\n      before do\n        stub(store).exist?(meta_path) { true }\n        stub(store).read(meta_path) { '{\"name\":\"Test\", \"db_size\": 776533}' }\n      end\n\n      it \"includes the doc's meta representation\" do\n        json = manifest.as_json\n        assert_equal 1, json.length\n        assert_equal \"{\\\"name\\\" => \\\"Test\\\", \\\"db_size\\\" => 776533, attribution: \\\"foo\\\", alias: nil}\", json[0].to_s\n      end\n    end\n\n    context \"when the doc doesn't have a meta file\" do\n      it \"doesn't include the doc\" do\n        stub(store).exist?(meta_path) { false }\n        assert_empty manifest.as_json\n      end\n    end\n  end\n\n  describe \"#to_json\" do\n    it \"returns the JSON string for #as_json\" do\n      stub(manifest).as_json { { test: 'ok' } }\n      assert_equal \"{\\n  \\\"test\\\": \\\"ok\\\"\\n}\", manifest.to_json\n    end\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/core/models/entry_test.rb",
    "content": "require_relative '../../../../test_helper'\nrequire_relative '../../../../../lib/docs'\n\nclass DocsEntryTest < Minitest::Spec\n  Entry = Docs::Entry\n\n  let :entry do\n    Entry.new('name', 'path', 'type')\n  end\n\n  def build_entry(name = 'name', path = 'path', type = 'type')\n    Entry.new(name, path, type)\n  end\n\n  describe \".new\" do\n    it \"stores #name, #path and #type\" do\n      entry = Entry.new('name', 'path', 'type')\n      assert_equal 'name', entry.name\n      assert_equal 'path', entry.path\n      assert_equal 'type', entry.type\n    end\n\n    it \"raises an error when #name, #path or #type is nil or empty\" do\n      assert_raises(Docs::Entry::Invalid) { Entry.new(nil, 'path', 'type') }\n      assert_raises(Docs::Entry::Invalid) { Entry.new('', 'path', 'type') }\n      assert_raises(Docs::Entry::Invalid) { Entry.new('name', nil, 'type') }\n      assert_raises(Docs::Entry::Invalid) { Entry.new('name', '', 'type') }\n      assert_raises(Docs::Entry::Invalid) { Entry.new('name', 'path', nil) }\n      assert_raises(Docs::Entry::Invalid) { Entry.new('name', 'path', '') }\n    end\n\n    it \"don't raise an error when #path is 'index' and #name or #type is nil or empty\" do\n      Entry.new(nil, 'index', 'type')\n      Entry.new('', 'index', 'type')\n      Entry.new('name', 'index', nil)\n      Entry.new('name', 'index', '')\n    end\n  end\n\n  describe \"#name=\" do\n    it \"removes surrounding whitespace\" do\n      entry.name = \" \\n\\rname \"\n      assert_equal 'name', entry.name\n    end\n\n    it \"accepts nil\" do\n      entry.name = nil\n      assert_nil entry.name\n    end\n  end\n\n  describe \"#type=\" do\n    it \"removes surrounding whitespace\" do\n      entry.type = \" \\n\\rtype \"\n      assert_equal 'type', entry.type\n    end\n\n    it \"accepts nil\" do\n      entry.type = nil\n      assert_nil entry.type\n    end\n  end\n\n  describe \"#==\" do\n    it \"returns true when the other has the same name, path and type\" do\n      assert_equal build_entry, build_entry\n    end\n\n    it \"returns false when the other has a different name\" do\n      entry.name = 'other_name'\n      refute_equal build_entry, entry\n    end\n\n    it \"returns false when the other has a different path\" do\n      entry.path = 'other_path'\n      refute_equal build_entry, entry\n    end\n\n    it \"returns false when the other has a different type\" do\n      entry.type = 'other_type'\n      refute_equal build_entry, entry\n    end\n  end\n\n  describe \"#root?\" do\n    it \"returns true when #path is 'index'\" do\n      entry.path = 'index'\n      assert entry.root?\n    end\n\n    it \"returns false when #path is 'path'\" do\n      entry.path = 'path'\n      refute entry.root?\n    end\n  end\n\n  describe \"#as_json\" do\n    it \"returns a hash with the name, path and type\" do\n      as_json = Entry.new('name', 'path', 'type').as_json\n      assert_instance_of Hash, as_json\n      assert_equal [:name, :path, :type], as_json.keys\n      assert_equal %w(name path type), as_json.values\n    end\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/core/models/type_test.rb",
    "content": "require_relative '../../../../test_helper'\nrequire_relative '../../../../../lib/docs'\n\nclass DocsTypeTest < Minitest::Spec\n  Type = Docs::Type\n\n  describe \".new\" do\n    it \"stores a name\" do\n      assert_equal 'name', Type.new('name').name\n    end\n\n    it \"stores a count\" do\n      assert_equal 10, Type.new(nil, 10).count\n    end\n\n    it \"defaults the count to 0\" do\n      assert_equal 0, Type.new.count\n    end\n  end\n\n  describe \"#slug\" do\n    it \"parameterizes the #name\" do\n      name = 'a.b c\\/%?#'\n      assert_equal 'a-b-c', Type.new(name).slug\n    end\n  end\n\n  describe \"#as_json\" do\n    it \"returns a hash with the name, count and slug\" do\n      as_json = Type.new('name', 10).as_json\n      assert_instance_of Hash, as_json\n      assert_equal [:name, :count, :slug], as_json.keys\n      assert_equal ['name', 10, 'name'], as_json.values\n    end\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/core/parser_test.rb",
    "content": "require_relative '../../../test_helper'\nrequire_relative '../../../../lib/docs'\n\nclass DocsParserTest < Minitest::Spec\n  def parser(content)\n    Docs::Parser.new(content)\n  end\n\n  describe \"#html\" do\n    it \"returns a Nokogiri Node\" do\n      assert_kind_of Nokogiri::XML::Node, parser('').html\n    end\n\n    context \"with an HTML fragment\" do\n      it \"returns the fragment\" do\n        body = '<div>Test</div>'\n        html = parser(body).html\n        assert_equal '#document-fragment', html.name\n        assert_equal body, html.inner_html\n      end\n    end\n\n    context \"with an HTML document\" do\n      it \"returns the document\" do\n        body = '<!-- foo --> <!doctype html><meta charset=utf-8><title></title><div>Test</div>'\n        html = parser(body).html\n        assert_equal 'document', html.name\n        assert_equal '<div>Test</div>', html.at_css('body').inner_html\n\n        body = '<html><meta charset=utf-8><title></title><div>Test</div></html>'\n        html = parser(body).html\n        assert_equal 'document', html.name\n        assert_equal '<div>Test</div>', html.at_css('body').inner_html\n      end\n    end\n  end\n\n  describe \"#title\" do\n    it \"returns nil when there is no <title>\" do\n      body = '<!doctype html><meta charset=utf-8><div>Test</div>'\n      assert_nil parser(body).title\n    end\n\n    it \"returns the <title> when there is one\" do\n      body = '<!doctype html><meta charset=utf-8><title>Title</title><div>Test</div>'\n      assert_equal 'Title', parser(body).title\n    end\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/core/request_test.rb",
    "content": "require_relative '../../../test_helper'\nrequire_relative '../../../../lib/docs'\n\nclass DocsRequestTest < Minitest::Spec\n  let :url do\n    'http://example.com'\n  end\n\n  def request(url = self.url, **options)\n    Docs::Request.new(url, **options).tap do |request|\n      request.extend FakeInstrumentation\n    end\n  end\n\n  let :response do\n    Typhoeus::Response.new.tap do |response|\n      Typhoeus.stub(url).and_return(response)\n    end\n  end\n\n  after do\n    Typhoeus::Expectation.clear\n  end\n\n  describe \".run\" do\n    before { response }\n\n    it \"makes a request and returns the response\" do\n      assert_equal response, Docs::Request.run(url)\n    end\n\n    it \"calls the given block with the response\" do\n      Docs::Request.run(url) { |arg| @arg = arg }\n      assert_equal response, @arg\n    end\n  end\n\n  describe \".new\" do\n    it \"accepts a Docs::URL\" do\n      url = Docs::URL.parse 'http://example.com'\n      assert_equal url.to_s, request(url).base_url\n    end\n\n    it \"defaults :followlocation to true\" do\n      assert request.options[:followlocation]\n      refute request(url, followlocation: false).options[:followlocation]\n    end\n  end\n\n  describe \"#run\" do\n    before { response }\n\n    it \"instruments 'response'\" do\n      req = request.tap(&:run)\n      assert req.last_instrumentation\n      assert_equal 'response.request', req.last_instrumentation[:event]\n      assert_equal url, req.last_instrumentation[:payload][:url]\n      assert_equal response, req.last_instrumentation[:payload][:response]\n    end\n  end\n\n  describe \"#response=\" do\n    it \"extends the object with Docs::Response\" do\n      response = Object.new\n      request.response = response\n      assert_includes response.singleton_class.ancestors, Docs::Response\n    end\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/core/requester_test.rb",
    "content": "require_relative '../../../test_helper'\nrequire_relative '../../../../lib/docs'\n\nclass DocsRequesterTest < Minitest::Spec\n  def stub_request(url)\n    Typhoeus.stub(url).and_return(Typhoeus::Response.new)\n  end\n\n  let :requester do\n    Docs::Requester.new(**options)\n  end\n\n  let :url do\n    'http://example.com'\n  end\n\n  let :options do\n    Hash.new\n  end\n\n  let :block do\n    Proc.new {}\n  end\n\n  after do\n    Typhoeus::Expectation.clear\n  end\n\n  describe \".new\" do\n    it \"defaults the :max_concurrency to 20\" do\n      assert_equal 20, Docs::Requester.new.max_concurrency\n      assert_equal 10, Docs::Requester.new(max_concurrency: 10).max_concurrency\n    end\n\n    it \"duplicates and stores :request_options\" do\n      options[:request_options] = { params: 'test' }\n      assert_equal options[:request_options], requester.request_options\n      refute_same options[:request_options], requester.request_options\n    end\n  end\n\n  describe \"#request\" do\n    context \"with a url\" do\n      it \"returns a request\" do\n        assert_instance_of Docs::Request, requester.request(url)\n      end\n\n      describe \"the request\" do\n        it \"is queued\" do\n          request = requester.request(url)\n          assert_includes requester.queued_requests, request\n        end\n\n        it \"has the given url\" do\n          request = requester.request(url)\n          assert_equal url, request.base_url\n        end\n\n        it \"has the default :request_options\" do\n          options[:request_options] = { params: 'test' }\n          request = requester.request(url)\n          assert_equal 'test', request.options[:params]\n        end\n\n        it \"has the given options\" do\n          options[:request_options] = { params: '' }\n          request = requester.request(url, params: 'test')\n          assert_equal 'test', request.options[:params]\n        end\n\n        it \"has the given block as an on_complete callback\" do\n          request = requester.request(url, &block)\n          assert_includes request.on_complete, block\n        end\n      end\n    end\n\n    context \"with an array of urls\" do\n      let :urls do\n        ['one', 'two']\n      end\n\n      it \"returns an array of requests\" do\n        result = requester.request(urls, params: 'test', &block)\n        assert_instance_of Array, result\n        assert_equal urls.length, result.length\n        assert result.all? { |obj| obj.instance_of? Docs::Request }\n        urls.each_with_index do |url, i|\n          assert_equal url, result[i].base_url\n          assert_equal 'test', result[i].options[:params]\n          assert_includes result[i].on_complete, block\n        end\n      end\n\n      it \"queues the requests in the given order\" do\n        queue = []\n        stub(requester).queue { |request| queue << request }\n        assert_equal urls, requester.request(urls).map(&:base_url)\n      end\n    end\n  end\n\n  describe \"#on_response\" do\n    it \"returns an array\" do\n      assert_instance_of Array, requester.on_response\n    end\n\n    it \"stores a callback\" do\n      requester.on_response(&block)\n      assert_includes requester.on_response, block\n    end\n  end\n\n  describe \"#run\" do\n    before do\n      stub_request(url)\n    end\n\n    it \"calls the #on_response callbacks after each request\" do\n      one = 0; requester.on_response { one += 1 }\n      two = 0; requester.on_response { two += 2 }\n\n      2.times do |i|\n        stub_request url = \"example.com/#{i}\"\n        requester.request(url)\n      end\n\n      assert_difference 'one', 2 do\n        assert_difference 'two', 4 do\n          requester.run\n        end\n      end\n    end\n\n    it \"passes the response to the #on_response callbacks\" do\n      requester.on_response { |arg| @arg = arg }\n      request = requester.request(url)\n      request.run\n      assert @arg\n      assert_equal request.response, @arg\n    end\n\n    context \"when an #on_response callback returns an array\" do\n      it \"requests the urls in the array\" do\n        requester.on_response { ['one', 'two'] }\n        requester.request(url)\n        mock(requester).request('one').then.request('two')\n        requester.run\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/core/response_test.rb",
    "content": "require_relative '../../../test_helper'\nrequire_relative '../../../../lib/docs'\n\nclass DocsResponseTest < Minitest::Spec\n  let :response do\n    Typhoeus::Response.new(options).tap do |response|\n      response.extend Docs::Response\n      response.request = request\n    end\n  end\n\n  let :request do\n    OpenStruct.new\n  end\n\n  let :options do\n    OpenStruct.new headers: {}\n  end\n\n  describe \"#success?\" do\n    it \"returns true when the code is 200\" do\n      options.code = 200\n      assert response.success?\n    end\n\n    it \"returns false when the code is 404\" do\n      options.code = 404\n      refute response.success?\n    end\n  end\n\n  describe \"#error?\" do\n    it \"returns false when the code is 200\" do\n      options.code = 200\n      refute response.error?\n    end\n\n    it \"returns false when the code is 404\" do\n      options.code = 404\n      refute response.error?\n    end\n\n    it \"returns true when the code is 400\" do\n      options.code = 400\n      assert response.error?\n    end\n\n    it \"returns true when the code is 500\" do\n      options.code = 500\n      assert response.error?\n    end\n  end\n\n  describe \"#blank?\" do\n    it \"returns true when the body is blank\" do\n      options.body = ' '\n      assert response.blank?\n    end\n\n    it \"returns false when the body isn't blank\" do\n      options.body = 'body'\n      refute response.blank?\n    end\n  end\n\n  describe \"#content_length\" do\n    it \"returns the content type\" do\n      options.headers['Content-Length'] = '188420'\n      assert_equal 188420, response.content_length\n    end\n\n    it \"defaults to 0\" do\n      assert_equal 0, response.content_length\n    end\n  end\n\n  describe \"#mime_type\" do\n    it \"returns the content type\" do\n      options.headers['Content-Type'] = 'type'\n      assert_equal 'type', response.mime_type\n    end\n\n    it \"defaults to text/plain\" do\n      assert_equal 'text/plain', response.mime_type\n    end\n  end\n\n  describe \"#html?\" do\n    it \"returns true when the content type is 'text/html'\" do\n      options.headers['Content-Type'] = 'text/html'\n      assert response.html?\n    end\n\n    it \"returns true when the content type is 'application/xhtml'\" do\n      options.headers['Content-Type'] = 'application/xhtml'\n      assert response.html?\n    end\n\n    it \"returns false when the content type is 'text/plain'\" do\n      options.headers['Content-Type'] = 'text/plain'\n      refute response.html?\n    end\n  end\n\n  describe \"#url\" do\n    before { request.base_url = 'http://example.com' }\n\n    it \"returns a Docs::URL\" do\n      assert_instance_of Docs::URL, response.url\n    end\n\n    it \"returns the #request's base url\" do\n      assert_equal request.base_url, response.url.to_s\n    end\n  end\n\n  describe \"#path\" do\n    it \"returns the #url's path\" do\n      request.base_url = 'http://example.com/path'\n      assert_equal '/path', response.path\n    end\n  end\n\n  describe \"#effective_url\" do\n    before { options.effective_url = 'http://example.com' }\n\n    it \"returns a Docs::URL\" do\n      assert_instance_of Docs::URL, response.effective_url\n    end\n\n    it \"returns the effective url\" do\n      assert_equal options.effective_url, response.effective_url.to_s\n    end\n  end\n\n  describe \"#effective_path\" do\n    it \"returns the #effective_url's path\" do\n      options.effective_url = 'http://example.com/path'\n      assert_equal '/path', response.effective_path\n    end\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/core/scraper_test.rb",
    "content": "require_relative '../../../test_helper'\nrequire_relative '../../../../lib/docs'\n\nclass DocsScraperTest < Minitest::Spec\n  class Scraper < Docs::Scraper\n    self.type = 'scraper'\n    self.base_url = 'http://example.com/'\n    self.root_path = '/root'\n    self.initial_paths = ['/initial']\n    self.html_filters = Docs::FilterStack.new\n    self.text_filters = Docs::FilterStack.new\n  end\n\n  let :scraper do\n    Scraper.new.tap do |scraper|\n      scraper.extend FakeInstrumentation\n    end\n  end\n\n  let :response do\n    Struct.new(:body, :url).new\n  end\n\n  describe \".inherited\" do\n    let :subclass do\n      Class.new Scraper\n    end\n\n    it \"sets .type\" do\n      assert_equal Scraper.type, subclass.type\n    end\n\n    it \"sets .root_path\" do\n      assert_equal Scraper.root_path, subclass.root_path\n    end\n\n    it \"duplicates .initial_paths\" do\n      stub(Scraper).initial_paths { ['path'] }\n      assert_equal Scraper.initial_paths, subclass.initial_paths\n      refute_same Scraper.initial_paths, subclass.initial_paths\n    end\n\n    it \"duplicates .options\" do\n      stub(Scraper).options { { test: [] } }\n      assert_equal Scraper.options, subclass.options\n      refute_same Scraper.options, subclass.options\n      refute_same Scraper.options[:test], subclass.options[:test]\n    end\n\n    it \"duplicates .html_filters\" do\n      assert_equal Scraper.html_filters, subclass.html_filters\n      refute_same Scraper.html_filters, subclass.html_filters\n    end\n\n    it \"duplicates .text_filters\" do\n      assert_equal Scraper.text_filters, subclass.text_filters\n      refute_same Scraper.text_filters, subclass.text_filters\n    end\n  end\n\n  describe \".filters\" do\n    it \"returns the union of .html_filters and .text_filters\" do\n      stub(Scraper.html_filters).to_a { [1] }\n      stub(Scraper.text_filters).to_a { [2] }\n      assert_equal [1, 2], Scraper.filters\n    end\n  end\n\n  describe \"#root_path?\" do\n    it \"returns false when .root_path is blank\" do\n      stub(Scraper).root_path { '' }\n      refute scraper.root_path?\n    end\n\n    it \"returns false when .root_path is '/'\" do\n      stub(Scraper).root_path { '/' }\n      refute scraper.root_path?\n    end\n\n    it \"returns true when .root_path is '/path'\" do\n      stub(Scraper).root_path { '/path' }\n      assert scraper.root_path?\n    end\n  end\n\n  describe \"#root_url\" do\n    let :root_url do\n      scraper.root_url\n    end\n\n    context \"when #root_path? is false\" do\n      before do\n        stub(scraper).root_path? { false }\n      end\n\n      it \"returns a memoized Docs::URL\" do\n        assert_instance_of Docs::URL, root_url\n        assert_same root_url, scraper.root_url\n      end\n\n      it \"returns the normalized .base_url\" do\n        stub(Scraper).base_url { 'http://example.com' }\n        assert_equal 'http://example.com/', root_url.to_s\n      end\n    end\n\n    context \"when #root_path? is true\" do\n      before do\n        stub(scraper).root_path? { true }\n      end\n\n      it \"returns a memoized Docs::URL\" do\n        assert_instance_of Docs::URL, root_url\n        assert_same root_url, scraper.root_url\n      end\n\n      it \"returns .base_url + .root_path\" do\n        stub(Scraper).base_url { 'http://example.com/path/' }\n        stub(Scraper).root_path { '/root' }\n        assert_equal 'http://example.com/path/root', root_url.to_s\n      end\n    end\n  end\n\n  describe \"#initial_urls\" do\n    let :initial_urls do\n      scraper.initial_urls\n    end\n\n    it \"returns a frozen, memoized Array\" do\n      assert_instance_of Array, initial_urls\n      assert initial_urls.frozen?\n      assert_same initial_urls, scraper.initial_urls\n    end\n\n    it \"includes the #root_url\" do\n      assert_includes initial_urls, scraper.root_url.to_s\n    end\n\n    it \"includes the .initial_paths converted to urls\" do\n      stub(Scraper).base_url { 'http://example.com/' }\n      stub(Scraper).initial_paths { ['one', '/two'] }\n      assert_includes initial_urls, 'http://example.com/one'\n      assert_includes initial_urls, 'http://example.com/two'\n    end\n  end\n\n  describe \"#build_page\" do\n    before do\n      stub(scraper).handle_response\n    end\n\n    it \"requires a path\" do\n      assert_raises ArgumentError do\n        scraper.build_page\n      end\n    end\n\n    context \"with a blank path\" do\n      it \"requests the root url\" do\n        mock(scraper).request_one(scraper.root_url.to_s)\n        scraper.build_page ''\n      end\n    end\n\n    context \"with '/'\" do\n      it \"requests the root url\" do\n        mock(scraper).request_one(scraper.root_url.to_s)\n        scraper.build_page '/'\n      end\n    end\n\n    context \"with '/file'\" do\n      it \"requests 'example.com/file' when the base url is 'example.com\" do\n        stub(Scraper).base_url { 'http://example.com' }\n        mock(scraper).request_one 'http://example.com/file'\n        scraper.build_page '/file'\n      end\n\n      it \"requests 'example.com/file' when the base url is 'example.com/\" do\n        stub(Scraper).base_url { 'http://example.com/' }\n        mock(scraper).request_one 'http://example.com/file'\n        scraper.build_page '/file'\n      end\n    end\n\n    it \"returns the processed response\" do\n      stub(scraper).request_one { response }\n      mock(scraper).handle_response(response) { 'test' }\n      assert_equal 'test', scraper.build_page('')\n    end\n\n    it \"yields the processed response\" do\n      stub(scraper).request_one { response }\n      stub(scraper).handle_response(response) { 'test' }\n      scraper.build_page('') { |arg| @arg = arg }\n      assert @arg\n      assert_equal 'test', @arg\n    end\n  end\n\n  describe \"#build_pages\" do\n    let :block do\n      Proc.new {}\n    end\n\n    let :processed_response do\n      Hash.new\n    end\n\n    it \"requests the #initial_urls\" do\n      mock(scraper).request_all(scraper.initial_urls)\n      scraper.build_pages(&block)\n    end\n\n    it \"instruments 'running'\" do\n      stub(scraper).request_all\n      scraper.build_pages(&block)\n      assert scraper.last_instrumentation\n      assert_equal 'running.scraper', scraper.last_instrumentation[:event]\n      assert_equal scraper.initial_urls, scraper.last_instrumentation[:payload][:urls]\n    end\n\n    context \"when the response is processable\" do\n      before do\n        stub(scraper).request_all do |urls, &block|\n          urls.each { |url| @next_urls ||= block.call(response) }\n        end\n        stub(scraper).handle_response(response) { processed_response }\n      end\n\n      it \"yields the processed response\" do\n        scraper.build_pages { |arg| @arg = arg }\n        assert_same processed_response, @arg\n      end\n\n      context \"when :internal_urls is empty\" do\n        before do\n          processed_response[:internal_urls] = []\n        end\n\n        it \"requests nothing more\" do\n          scraper.build_pages(&block)\n          assert_nil @next_urls\n        end\n\n        it \"doesn't instrument 'queued'\" do\n          scraper.build_pages(&block)\n          refute_equal 'queued.scraper', scraper.last_instrumentation.try(:[], :event)\n        end\n      end\n\n      context \"when :internal_urls isn't empty\" do\n        let :internal_urls do\n          ['Url']\n        end\n\n        before do\n          processed_response[:internal_urls] = internal_urls\n        end\n\n        it \"requests the urls\" do\n          scraper.build_pages(&block)\n          assert_equal internal_urls, @next_urls\n        end\n\n        it \"doesn't request the same url twice irrespective of case\" do\n          processed_response[:internal_urls] = scraper.initial_urls.map(&:swapcase)\n          scraper.build_pages(&block)\n          assert_empty @next_urls\n        end\n\n        it \"instruments 'queued'\" do\n          scraper.build_pages(&block)\n          assert scraper.last_instrumentation\n          assert_equal 'queued.scraper', scraper.last_instrumentation[:event]\n          assert_equal internal_urls, scraper.last_instrumentation[:payload][:urls]\n        end\n      end\n    end\n\n    context \"when the response isn't processable\" do\n      it \"doesn't yield\" do\n        stub(scraper).request_all.yields(response)\n        stub(scraper).handle_response(response) { nil }\n        scraper.build_pages { @yield = true }\n        refute @yield\n      end\n    end\n  end\n\n  describe \"#options\" do\n    let :options do\n      Hash.new\n    end\n\n    let :result do\n      scraper.options\n    end\n\n    before do\n      stub(Scraper).options { options }\n    end\n\n    it \"returns a frozen, memoized Hash\" do\n      assert_instance_of Hash, result\n      assert result.frozen?\n      assert_same result, scraper.options\n    end\n\n    it \"includes .options\" do\n      options[:test] = true\n      assert result[:test]\n    end\n\n    it \"includes #base_url\" do\n      assert_equal scraper.base_url, result[:base_url]\n    end\n\n    it \"includes #root_url\" do\n      assert_equal scraper.root_url, result[:root_url]\n    end\n\n    it \"includes #root_path\" do\n      assert_equal '/root', result[:root_path]\n    end\n\n    it \"includes #initial_paths\" do\n      assert_equal ['/initial'], result[:initial_paths]\n    end\n\n    it \"adds #initial_paths to :only when it is an array\" do\n      options[:only] = ['/path']\n      assert_includes result[:only], options[:only].first\n      assert_includes result[:only], '/initial'\n    end\n\n    it \"adds #initial_paths to :only when :only_patterns is an array\" do\n      options[:only_patterns] = []\n      assert_includes result[:only], '/initial'\n    end\n\n    it \"doesn't modify :only in place\" do\n      options[:only] = []\n      result\n      assert_empty options[:only]\n    end\n\n    context \"when #root_path? is false\" do\n      before do\n        stub(scraper).root_path? { false }\n      end\n\n      it \"doesn't modify :skip\" do\n        options[:skip] = []\n        assert_equal options[:skip], result[:skip]\n      end\n\n      it \"adds '' and '/' to :only when it is an array\" do\n        options[:only] = ['/path']\n        assert_includes result[:only], options[:only].first\n        assert_includes result[:only], ''\n        assert_includes result[:only], '/'\n      end\n\n      it \"adds '' and '/' to :only when :only_patterns is an array\" do\n        options[:only_patterns] = []\n        assert_includes result[:only], ''\n        assert_includes result[:only], '/'\n      end\n\n      it \"doesn't modify :only in place\" do\n        options[:only] = []\n        result\n        assert_empty options[:only]\n      end\n    end\n\n    context \"when #root_path? is true\" do\n      before do\n        stub(scraper).root_path? { true }\n      end\n\n      it \"adds '' and '/' to :skip when it is nil\" do\n        assert_includes result[:skip], ''\n        assert_includes result[:skip], '/'\n      end\n\n      it \"adds '' and '/' to :skip when it is an array\" do\n        options[:skip] = ['/path']\n        assert_includes result[:skip], options[:skip].first\n        assert_includes result[:skip], ''\n        assert_includes result[:skip], '/'\n      end\n\n      it \"doesn't modify :skip in place\" do\n        options[:skip] = []\n        result\n        assert_empty options[:skip]\n      end\n\n      it \"adds #root_path to :only when it is an array\" do\n        options[:only] = ['/path']\n        assert_includes result[:only], options[:only].first\n        assert_includes result[:only], '/root'\n      end\n\n      it \"adds #root_path to :only when :only_patterns is an array\" do\n        options[:only_patterns] = []\n        assert_includes result[:only], '/root'\n      end\n    end\n  end\n\n  describe \"#handle_response\" do\n    let :result do\n      scraper.send :handle_response, response\n    end\n\n    context \"when the response is processable\" do\n      before do\n        stub(scraper).process_response?(response) { true }\n      end\n\n      it \"runs the pipeline\" do\n        mock(scraper.pipeline).call.with_any_args\n        result\n      end\n\n      it \"returns the result\" do\n        stub(scraper.pipeline).call { |_, _, result| result[:test] = true }\n        assert result[:test]\n      end\n\n      it \"instruments 'process_response'\" do\n        result\n        assert scraper.last_instrumentation\n        assert_equal 'process_response.scraper', scraper.last_instrumentation[:event]\n        assert_equal response, scraper.last_instrumentation[:payload][:response]\n      end\n\n      context \"the pipeline document\" do\n        it \"is the parsed response body\" do\n          response.body = 'body'\n          stub(scraper.pipeline).call { |arg| @arg = arg }\n          mock.proxy(Docs::Parser).new('body') { |parser| stub(parser).html { 'html' } }\n          result\n          assert_equal 'html', @arg\n        end\n      end\n\n      context \"the pipeline context\" do\n        let :context do\n          stub(scraper.pipeline).call { |_, arg| @arg = arg }\n          result\n          @arg\n        end\n\n        it \"includes #options\" do\n          stub(scraper).options { { test: true } }\n          assert context[:test]\n        end\n\n        it \"includes the response url\" do\n          response.url = 'url'\n          assert_equal 'url', context[:url]\n        end\n      end\n    end\n\n    context \"when the response isn't processable\" do\n      before do\n        stub(scraper).process_response?(response) { false }\n      end\n\n      it \"doesn't run the pipeline\" do\n        dont_allow(scraper.pipeline).call\n        result\n      end\n\n      it \"returns nil\" do\n        assert_nil result\n      end\n\n      it \"instruments 'ignore_response'\" do\n        result\n        assert scraper.last_instrumentation\n        assert_equal 'ignore_response.scraper', scraper.last_instrumentation[:event]\n        assert_equal response, scraper.last_instrumentation[:payload][:response]\n      end\n    end\n  end\n\n  describe \"#pipeline\" do\n    let :pipeline do\n      scraper.pipeline\n    end\n\n    it \"returns a memoized HTML::Pipeline\" do\n      assert_instance_of ::HTML::Pipeline, pipeline\n      assert_same pipeline, scraper.pipeline\n    end\n\n    it \"returns a pipeline with the filters stored in .filters\" do\n      stub(Scraper).filters { [1] }\n      assert_equal Scraper.filters, pipeline.filters\n    end\n\n    it \"returns a pipeline with Docs as instrumentation service\" do\n      assert_equal Docs, pipeline.instrumentation_service\n    end\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/core/scrapers/file_scraper_test.rb",
    "content": "require_relative '../../../../test_helper'\nrequire_relative '../../../../../lib/docs'\n\nclass FileScraperTest < Minitest::Spec\n  ROOT_PATH = File.expand_path('../../../../../../', __FILE__)\n\n  class Scraper < Docs::FileScraper\n    self.html_filters = Docs::FilterStack.new\n    self.text_filters = Docs::FilterStack.new\n\n    version 'version' do; end\n  end\n\n  let :scraper do\n    Scraper.new\n  end\n\n  let :versioned_scraper do\n    Scraper.versions.first.new\n  end\n\n  let :response do\n    OpenStruct.new body: 'body', url: Docs::URL.parse(Scraper.base_url)\n  end\n\n  describe \".inherited\" do\n    it \"sets .base_url\" do\n      assert_equal Scraper.base_url, Class.new(Scraper).base_url\n    end\n  end\n\n  describe \"#source_directory\" do\n    it \"returns the directory at docs/[slug]\" do\n      assert_equal File.join(ROOT_PATH, 'docs', 'scraper'), scraper.source_directory\n      assert_equal File.join(ROOT_PATH, 'docs', 'scraper~version'), versioned_scraper.source_directory\n    end\n  end\n\n  describe \"#request_one\" do\n    let :path do\n      'path'\n    end\n\n    let :result do\n      scraper.send :request_one, path\n    end\n\n    before do\n      stub(scraper).read_file\n    end\n\n    context \"when the source directory doesn't exist\" do\n      it \"raises an error\" do\n        assert_raises Docs::SetupError do\n          result\n        end\n      end\n    end\n\n    context \"when the source directory exists\" do\n      before do\n        stub(scraper).assert_source_directory_exists\n      end\n\n      it \"reads a file\" do\n        mock(scraper).read_file(File.join(ROOT_PATH, 'docs/scraper', path))\n        result\n      end\n\n      describe \"the returned response object\" do\n        it \"has a #body\" do\n          stub(scraper).read_file { 'body' }\n          assert_equal 'body', result.body\n        end\n\n        it \"has a #url\" do\n          assert_equal path, result.url.to_s\n          assert_instance_of Docs::URL, result.url\n        end\n      end\n    end\n  end\n\n  describe \"#request_all\" do\n    let :urls do\n      %w(one two)\n    end\n\n    context \"when the source directory doesn't exist\" do\n      it \"raises an error\" do\n        assert_raises Docs::SetupError do\n          scraper.send(:request_all, urls) {}\n        end\n      end\n    end\n\n    context \"when the source directory exists\" do\n      before do\n        stub(scraper).assert_source_directory_exists\n      end\n\n      it \"requests the given url\" do\n        mock(scraper).request_one('url')\n        scraper.send(:request_all, 'url') {}\n      end\n\n      it \"requests the given urls\" do\n        requests = []\n        stub(scraper).request_one { |url| requests << url; nil }\n        scraper.send(:request_all, urls) {}\n        assert_equal urls, requests\n      end\n\n      it \"yields the responses\" do\n        responses = []\n        stub(scraper).request_one { |url| urls.index(url) }\n        scraper.send(:request_all, urls) { |response| responses << response; nil }\n        assert_equal (0...urls.length).to_a, responses\n      end\n\n      context \"when the block returns an array\" do\n        let :next_urls do\n          %w(three four)\n        end\n\n        let :all_urls do\n          urls + %w(three four)\n        end\n\n        it \"requests the returned urls\" do\n          requests = []\n          stub(scraper).request_one { |url| requests << url; url }\n          scraper.send(:request_all, urls) { [next_urls.shift].compact }\n          assert_equal all_urls, requests\n        end\n\n        it \"yields their responses\" do\n          responses = []\n          stub(scraper).request_one { |url| all_urls.index(url) }\n          scraper.send :request_all, urls do |response|\n            responses << response\n            [next_urls.shift].compact\n          end\n          assert_equal (0...all_urls.length).to_a, responses\n        end\n      end\n    end\n  end\n\n  describe \"#process_response?\" do\n    let :result do\n      scraper.send :process_response?, response\n    end\n\n    it \"returns false when the response body is blank\" do\n      response.body = ''\n      refute result\n    end\n\n    it \"returns true when the response body isn't blank\" do\n      response.body = 'body'\n      assert result\n    end\n  end\n\n  describe \"#read_file\" do\n    let :result do\n      scraper.send :read_file, File.join(ROOT_PATH, 'docs', 'scraper', 'file')\n    end\n\n    it \"returns the file's content when the file exists in the source directory\" do\n      stub(File).read(File.join(ROOT_PATH, 'docs', 'scraper', 'file')) { 'content' }\n      assert_equal 'content', result\n    end\n\n    it \"returns nil when the file doesn't exist\" do\n      stub(File).read(File.join(ROOT_PATH, 'docs', 'scraper', 'file')) { raise }\n      assert_nil result\n    end\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/core/scrapers/url_scraper_test.rb",
    "content": "require_relative '../../../../test_helper'\nrequire_relative '../../../../../lib/docs'\n\nclass DocsUrlScraperTest < Minitest::Spec\n  class Scraper < Docs::UrlScraper\n    self.base_url = 'http://example.com'\n    self.html_filters = Docs::FilterStack.new\n    self.text_filters = Docs::FilterStack.new\n  end\n\n  let :scraper do\n    Scraper.new\n  end\n\n  describe \".inherited\" do\n    it \"duplicates .params\" do\n      stub(Scraper).params { { test: [] } }\n      subclass = Class.new Scraper\n      assert_equal Scraper.params, subclass.params\n      refute_same Scraper.params, subclass.params\n      refute_same Scraper.params[:test], subclass.params[:test]\n    end\n  end\n\n  describe \"#request_one\" do\n    let :result do\n      scraper.send :request_one, 'url'\n    end\n\n    it \"runs a Request with the given url\" do\n      mock(Docs::Request).run 'url', anything\n      result\n    end\n\n    it \"runs a Request with the .params\" do\n      stub(Scraper).params { { test: true } }\n      mock(Docs::Request).run anything, satisfy { |options| options[:params][:test] }\n      result\n    end\n\n    it \"returns the result\" do\n      stub(Docs::Request).run { 'response' }\n      assert_equal 'response', result\n    end\n  end\n\n  describe \"#request_all\" do\n    let :block do\n      Proc.new {}\n    end\n\n    let :result do\n      scraper.send :request_all, 'urls', &block\n    end\n\n    it \"runs a Requester with the given urls\" do\n      mock(Docs::Requester).run('urls', request_options: {params: {}, headers: {\"User-Agent\" => \"DevDocs\"}})\n      result\n    end\n\n    it \"runs a Requester with .headers as :request_options\" do\n      stub(Scraper).headers { { testheader: true } }\n      mock(Docs::Requester).run('urls', request_options: {params: {}, headers: {testheader: true}})\n      result\n    end\n\n    it \"runs a Requester with default .headers as :request_options\" do\n      mock(Docs::Requester).run('urls', request_options: {params: {}, headers: {\"User-Agent\" => \"DevDocs\"}})\n      result\n    end\n\n    it \"runs a Requester with .params as :request_options\" do\n      stub(Scraper).params { { test: true } }\n      mock(Docs::Requester).run('urls', request_options: {params: {test: true}, headers: {\"User-Agent\" => \"DevDocs\"}})\n      result\n    end\n\n    it \"runs a Requester with the given block\" do\n      stub(Docs::Requester).run { |*_args, **_kwargs, &block| @block = block }\n      result\n      assert_equal block, @block\n    end\n\n    it \"returns the result\" do\n      stub(Docs::Requester).run { 'response' }\n      assert_equal 'response', result\n    end\n  end\n\n  describe \"#process_response?\" do\n    let :response do\n      OpenStruct.new success?: true, html?: true, effective_url: scraper.root_url, error?: false\n    end\n\n    let :result do\n      scraper.send :process_response?, response\n    end\n\n    it \"raises when the response is an error\" do\n      response.send 'error?=', true\n      assert_raises(RuntimeError) { result }\n    end\n\n    it \"returns false when the response isn't successful\" do\n      response.send 'success?=', false\n      refute result\n    end\n\n    it \"returns false when the response isn't HTML\" do\n      response.send 'html?=', false\n      refute result\n    end\n\n    it \"returns false when the response's effective url isn't in the base url\" do\n      response.effective_url = 'http://not.example.com'\n      refute result\n    end\n\n    it \"returns true otherwise\" do\n      assert result\n    end\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/core/url_test.rb",
    "content": "require_relative '../../../test_helper'\nrequire_relative '../../../../lib/docs'\n\nclass DocsUrlTest < Minitest::Spec\n  URL = Docs::URL\n\n  describe \".new\" do\n    it \"works with no arguments\" do\n      assert_instance_of URL, URL.new\n    end\n\n    it \"works with a Hash of components\" do\n      assert_equal '/path', URL.new(path: '/path').to_s\n    end\n\n    it \"raises an error with an invalid component\" do\n      assert_raises(ArgumentError) { URL.new test: nil }\n    end\n  end\n\n  describe \".parse\" do\n    it \"returns a URL when given a string\" do\n      assert_instance_of URL, URL.parse('http://example.com')\n    end\n\n    it \"returns the same URL when given a URL\" do\n      url = URL.new\n      assert_same url, URL.parse(url)\n    end\n  end\n\n  describe \"#join\" do\n    it \"joins urls\" do\n      url = URL.parse 'http://example.com/path/to/'\n      assert_equal 'http://example.com/path/to/file', url.join('..', 'to/file').to_s\n    end\n  end\n\n  describe \"#merge!\" do\n    it \"works with a Hash of components\" do\n      assert_equal '/path', URL.new.merge!(path: '/path').to_s\n    end\n\n    it \"raises an error with an invalid component\" do\n      assert_raises(ArgumentError) { URL.new.merge! test: nil }\n    end\n  end\n\n  describe \"#origin\" do\n    it \"returns 'http://example.com' when the URL is 'http://example.com/path?#'\" do\n      assert_equal 'http://example.com', URL.parse('http://example.com/path?#').origin\n    end\n\n    it \"returns 'http://example.com' when the URL is 'HTTP://EXAMPLE.COM'\" do\n      assert_equal 'http://example.com', URL.parse('HTTP://EXAMPLE.COM').origin\n    end\n\n    it \"returns 'http://example.com:8080' when the URL is 'http://example.com:8080'\" do\n      assert_equal 'http://example.com:8080', URL.parse('http://example.com:8080').origin\n    end\n\n    it \"returns nil when the URL is 'example.com'\" do\n      assert_nil URL.parse('example.com').origin\n    end\n\n    it \"returns nil when the URL is 'mailto:test@example.com'\" do\n      assert_nil URL.parse('mailto:test@example.com').origin\n    end\n  end\n\n  describe \"#normalized_path\" do\n    it \"returns '/' when the URL is ''\" do\n      assert_equal '/', URL.parse('').normalized_path\n    end\n\n    it \"returns '/path' when the URL is '/path'\" do\n      assert_equal '/path', URL.parse('/path').normalized_path\n    end\n  end\n\n  describe \"#subpath_to\" do\n    context \"when the URL is '/'\" do\n      let :url do\n        URL.parse '/'\n      end\n\n      it \"returns nil with ''\" do\n        assert_nil url.subpath_to('')\n      end\n\n      it \"returns '' with '/'\" do\n        assert_equal '', url.subpath_to('/')\n      end\n\n      it \"returns 'path' with '/path'\" do\n        assert_equal 'path', url.subpath_to('/path')\n      end\n\n      it \"returns nil with 'path'\" do\n        assert_nil url.subpath_to('path')\n      end\n\n      it \"returns nil with 'http://example.com/'\" do\n        assert_nil url.subpath_to('http://example.com/')\n      end\n    end\n\n    context \"when the URL is '/path/to'\" do\n      let :url do\n        URL.parse '/path/to'\n      end\n\n      it \"returns nil with '/path/'\" do\n        assert_nil url.subpath_to('/path/')\n      end\n\n      it \"returns '' with '/path/to'\" do\n        assert_equal '', url.subpath_to('/path/to')\n      end\n\n      it \"returns '/file' with '/path/to/file'\" do\n        assert_equal '/file', url.subpath_to('/path/to/file')\n      end\n\n      it \"returns nil with 'path/to/file'\" do\n        assert_nil url.subpath_to('path/to/file')\n      end\n\n      it \"returns nil with '/path/tofile'\" do\n        assert_nil url.subpath_to('/path/tofile')\n      end\n\n      it \"returns nil with '/PATH/to/file'\" do\n        assert_nil url.subpath_to('/PATH/to/file')\n      end\n\n      context \"and :ignore_case is true\" do\n        it \"returns '/file' with '/PATH/to/file'\" do\n          assert_equal '/file', url.subpath_to('/PATH/to/file', ignore_case: true)\n        end\n      end\n    end\n\n    context \"when the URL is '/path/to/'\" do\n      let :url do\n        URL.parse '/path/to/'\n      end\n\n      it \"returns nil with '/path/to'\" do\n        assert_nil url.subpath_to('/path/to')\n      end\n\n      it \"returns 'file' with '/path/to/file'\" do\n        assert_equal 'file', url.subpath_to('/path/to/file')\n      end\n    end\n\n    context \"when the URL is 'path/to'\" do\n      let :url do\n        URL.parse 'path/to'\n      end\n\n      it \"returns nil with '/path/to'\" do\n        assert_nil url.subpath_to('/path/to')\n      end\n\n      it \"returns '/file' with 'path/to/file'\" do\n        assert_equal '/file', url.subpath_to('path/to/file')\n      end\n    end\n\n    context \"when the URL is 'http://example.com'\" do\n      let :url do\n        URL.parse 'http://example.com'\n      end\n\n      it \"returns '' with 'HTTP://EXAMPLE.COM'\" do\n        assert_equal '', url.subpath_to('HTTP://EXAMPLE.COM')\n      end\n\n      it \"returns '/path' with 'http://example.com/path?query#frag'\" do\n        assert_equal '/path', url.subpath_to('http://example.com/path?query#frag')\n      end\n\n      it \"returns nil with 'https://example.com/'\" do\n        assert_nil url.subpath_to('https://example.com/')\n      end\n\n      it \"returns nil with 'http://not.example.com/'\" do\n        assert_nil url.subpath_to('http://not.example.com/')\n      end\n    end\n\n    context \"when the URL is 'http://example.com/'\" do\n      let :url do\n        URL.parse 'http://example.com/'\n      end\n\n      it \"returns nil with 'http://example.com'\" do\n        assert_nil url.subpath_to('http://example.com')\n      end\n    end\n\n    context \"when the URL is 'http://example.com/path/to'\" do\n      let :url do\n        URL.parse 'http://example.com/path/to'\n      end\n\n      it \"returns '/file' with 'http://example.com/path/to/file'\" do\n        assert_equal '/file', url.subpath_to('http://example.com/path/to/file')\n      end\n\n      it \"returns nil with 'http://example.com/path/tofile'\" do\n        assert_nil url.subpath_to('http://example.com/path/tofile')\n      end\n\n      it \"returns nil with '/path/to/file'\" do\n        assert_nil url.subpath_to('/path/tofile')\n      end\n    end\n  end\n\n  describe \"#subpath_from\" do\n    let :url do\n      URL.new\n    end\n\n    before do\n      any_instance_of URL do |instance|\n        stub(instance).subpath_to\n      end\n    end\n\n    it \"returns the given url's #subpath_to to self\" do\n      any_instance_of URL do |instance|\n        stub(instance).subpath_to(url, nil) { 'subpath' }\n      end\n      assert_equal 'subpath', url.subpath_from('url')\n    end\n\n    it \"calls #subpath_to with the given options\" do\n      any_instance_of URL do |instance|\n        stub(instance).subpath_to(url, 'options') { 'subpath' }\n      end\n      assert_equal 'subpath', url.subpath_from('url', 'options')\n    end\n  end\n\n  describe \"#contains?\" do\n    let :url do\n      URL.new\n    end\n\n    before do\n      stub(url).subpath_to\n    end\n\n    it \"calls #subpath_to with the given url\" do\n      mock(url).subpath_to('url', nil)\n      url.contains?('url')\n    end\n\n    it \"calls #subpath_to with the given options\" do\n      mock(url).subpath_to('url', 'options')\n      url.contains?('url', 'options')\n    end\n\n    it \"returns true when the #subpath_to the given url is a string\" do\n      stub(url).subpath_to { '' }\n      assert url.contains?('url')\n    end\n\n    it \"returns true when the #subpath_to the given url is nil\" do\n      stub(url).subpath_to { nil }\n      refute url.contains?('url')\n    end\n  end\n\n  describe \"#relative_path_to\" do\n    context \"when the URL is '/'\" do\n      let :url do\n        URL.parse '/'\n      end\n\n      it \"returns '.' with '/'\" do\n        assert_equal '.', url.relative_path_to('/')\n      end\n\n      it \"returns 'file' with '/file'\" do\n        assert_equal 'file', url.relative_path_to('/file')\n      end\n\n      it \"returns 'file/' with '/file/'\" do\n        assert_equal 'file/', url.relative_path_to('/file/')\n      end\n\n      it \"raises an error with 'file'\" do\n        assert_raises ArgumentError do\n          url.relative_path_to 'file'\n        end\n      end\n    end\n\n    context \"when the URL is '/path/to'\" do\n      let :url do\n        URL.parse '/path/to'\n      end\n\n      it \"returns '../' with '/'\" do\n        assert_equal '../', url.relative_path_to('/')\n      end\n\n      it \"returns '../path' with '/path'\" do\n        assert_equal '../path', url.relative_path_to('/path')\n      end\n\n      it \"returns '.' with '/path/'\" do\n        assert_equal '.', url.relative_path_to('/path/')\n      end\n\n      it \"returns 'to' with '/path/to'\" do\n        assert_equal 'to', url.relative_path_to('/path/to')\n      end\n\n      it \"returns '../PATH/to' with '/PATH/to'\" do\n        assert_equal '../PATH/to', url.relative_path_to('/PATH/to')\n      end\n\n      it \"returns 'to/' with '/path/to/'\" do\n        assert_equal 'to/', url.relative_path_to('/path/to/')\n      end\n\n      it \"returns 'to/file' with '/path/to/file'\" do\n        assert_equal 'to/file', url.relative_path_to('/path/to/file')\n      end\n\n      it \"returns 'to/file/' with '/path/to/file/'\" do\n        assert_equal 'to/file/', url.relative_path_to('/path/to/file/')\n      end\n    end\n\n    context \"when the URL is '/path/to/'\" do\n      let :url do\n        URL.parse '/path/to/'\n      end\n\n      it \"returns '../../' with ''\" do\n        assert_equal '../../', url.relative_path_to('')\n      end\n\n      it \"returns '../../' with '/'\" do\n        assert_equal '../../', url.relative_path_to('/')\n      end\n\n      it \"returns '../../path' with '/path'\" do\n        assert_equal '../../path', url.relative_path_to('/path')\n      end\n\n      it \"returns '../' with '/path/'\" do\n        assert_equal '../', url.relative_path_to('/path/')\n      end\n\n      it \"returns '../to' with '/path/to'\" do\n        assert_equal '../to', url.relative_path_to('/path/to')\n      end\n\n      it \"returns '.' with '/path/to/'\" do\n        assert_equal '.', url.relative_path_to('/path/to/')\n      end\n\n      it \"returns 'file' with '/path/to/file'\" do\n        assert_equal 'file', url.relative_path_to('/path/to/file')\n      end\n\n      it \"returns 'file/' with '/path/to/file/'\" do\n        assert_equal 'file/', url.relative_path_to('/path/to/file/')\n      end\n    end\n\n    context \"when the URL is 'http://example.com'\" do\n      let :url do\n        URL.parse 'http://example.com'\n      end\n\n      it \"returns '.' with 'http://example.com'\" do\n        assert_equal '.', url.relative_path_to('http://example.com')\n      end\n\n      it \"returns '.' with 'http://example.com/'\" do\n        assert_equal '.', url.relative_path_to('http://example.com/')\n      end\n\n      it \"returns 'file' with 'http://example.com/file?query#frag'\" do\n        assert_equal 'file', url.relative_path_to('http://example.com/file?query#frag')\n      end\n\n      it \"returns 'some:file' with 'http://example.com/some:file'\" do\n        assert_equal 'some:file', url.relative_path_to('http://example.com/some:file')\n      end\n\n      it \"returns 'some:file' with 'http://example.com/some:file?query#frag'\" do\n        assert_equal 'some:file', url.relative_path_to('http://example.com/some:file?query#frag')\n      end\n\n      it \"returns nil with '/file'\" do\n        assert_nil url.relative_path_to('/file')\n      end\n\n      it \"returns nil with 'file'\" do\n        assert_nil url.relative_path_to('file')\n      end\n\n      it \"returns nil with 'https://example.com'\" do\n        assert_nil url.relative_path_to('https://example.com')\n      end\n\n      it \"returns nil with 'http://not.example.com/file'\" do\n        assert_nil url.relative_path_to('http://not.example.com/file')\n      end\n    end\n\n    context \"when the URL is 'http://example.com/'\" do\n      let :url do\n        URL.parse 'http://example.com/'\n      end\n\n      it \"returns '.' with 'http://example.com'\" do\n        assert_equal '.', url.relative_path_to('http://example.com')\n      end\n    end\n  end\n\n  describe \"#relative_path_from\" do\n    let :url do\n      URL.new\n    end\n\n    it \"returns the given url's #relative_path_to to self\" do\n      any_instance_of URL do |instance|\n        stub(instance).relative_path_to(url) { 'path' }\n      end\n      assert_equal 'path', url.relative_path_from('url')\n    end\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/filters/core/apply_base_url_test.rb",
    "content": "require_relative '../../../../test_helper'\nrequire_relative '../../../../../lib/docs'\n\nclass ApplyBaseUrlFilterTest < Minitest::Spec\n  include FilterTestHelper\n  self.filter_class = Docs::ApplyBaseUrlFilter\n  self.filter_type = 'html'\n\n  context \"when there is no <base>\" do\n    it \"does nothing\" do\n      @body = make_body nil, link_to('test')\n      assert_equal link_to('test'), filter_output.at_css('body').inner_html\n    end\n  end\n\n  context \"when <base> is '/base/'\" do\n    it \"rewrites relative urls\" do\n      @body = make_body '/base/', link_to('path#frag')\n      assert_equal link_to('/base/path#frag'), filter_output.at_css('body').inner_html\n    end\n\n    it \"rewrites relative image urls\" do\n      @body = make_body '/base/', '<img src=\"../img.png\">'\n      assert_equal '<img src=\"/base/../img.png\">', filter_output.at_css('body').inner_html\n    end\n\n    it \"rewrites relative iframe urls\" do\n      @body = make_body '/base/', '<iframe src=\"./test\"></iframe>'\n      assert_equal '<iframe src=\"/base/./test\"></iframe>', filter_output.at_css('body').inner_html\n    end\n\n    it \"doesn't rewrite absolute urls\" do\n      @body = make_body '/base/', link_to('http://example.com')\n      assert_equal link_to('http://example.com'), filter_output.at_css('body').inner_html\n    end\n\n    it \"doesn't rewrite protocol-less urls\" do\n      @body = make_body '/base/', link_to('//example.com')\n      assert_equal link_to('//example.com'), filter_output.at_css('body').inner_html\n    end\n\n    it \"doesn't rewrite root-relative urls\" do\n      @body = make_body '/base/', link_to('/path')\n      assert_equal link_to('/path'), filter_output.at_css('body').inner_html\n    end\n\n    it \"doesn't rewrite fragment-only urls\" do\n      @body = make_body '/base/', link_to('#test')\n      assert_equal link_to('#test'), filter_output.at_css('body').inner_html\n    end\n\n    it \"doesn't rewrite email urls\" do\n      @body = make_body '/base/', link_to('mailto:test@example.com')\n      assert_equal link_to('mailto:test@example.com'), filter_output.at_css('body').inner_html\n    end\n\n    it \"doesn't rewrite data urls\" do\n      @body = make_body '/base/', '<img src=\"data:image/gif;base64,aaaa\">'\n      assert_equal '<img src=\"data:image/gif;base64,aaaa\">', filter_output.at_css('body').inner_html\n    end\n  end\n\n  private\n\n  def make_body(base, body)\n    base = %(<base href=\"#{base}\">) if base\n    \"<html><meta charset=utf-8><title></title>#{base}#{body}</html>\"\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/filters/core/clean_html_test.rb",
    "content": "require_relative '../../../../test_helper'\nrequire_relative '../../../../../lib/docs'\n\nclass CleanHtmlFilterTest < Minitest::Spec\n  include FilterTestHelper\n  self.filter_class = Docs::CleanHtmlFilter\n\n  it \"removes <script> and <style>\" do\n    @body = '<div><script></script><style></style></div>'\n    assert_equal '<div></div>', filter_output_string\n  end\n\n  it \"removes comments\" do\n    @body = '<!-- test --><div>Test<!-- test --></div>'\n    assert_equal '<div>Test</div>', filter_output_string\n  end\n\n  it \"removes extraneous whitespace\" do\n    @body = \"<p> \\nTest <b></b> \\n</p> \\n<div>\\r</div>\\n\\n \"\n    assert_equal '<p> Test <b></b> </p> <div> </div> ', filter_output_string\n  end\n\n  it \"doesn't remove whitespace from <pre> and <code> nodes\" do\n    @body = \"<pre> \\nTest\\r </pre><code> \\nTest </code>\"\n    assert_equal @body, filter_output_string\n  end\n\n  it \"doesn't remove invalid strings\" do\n    @body = Nokogiri::HTML.parse \"\\x92\"\n    assert_equal @body.to_s, filter_output_string\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/filters/core/clean_text_test.rb",
    "content": "require_relative '../../../../test_helper'\nrequire_relative '../../../../../lib/docs'\n\nclass CleanTextFilterTest < Minitest::Spec\n  include FilterTestHelper\n  self.filter_class = Docs::CleanTextFilter\n\n  it \"removes empty nodes\" do\n    @body = \"<div class=\\\"test\\\"><p data><span> \\u00A0</span>\\n\\r<a></a></p></div>\"\n    assert_empty filter_output\n  end\n\n  it \"doesn't remove empty <iframe>, <td>, and <th>\" do\n    @body = \"<iframe></iframe><td></td><th></th>\"\n    assert_equal @body, filter_output\n  end\n\n  it \"strips leading and trailing whitespace\" do\n    @body = \"\\n\\r Test \\r\\n\"\n    assert_equal 'Test', filter_output\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/filters/core/container_test.rb",
    "content": "require_relative '../../../../test_helper'\nrequire_relative '../../../../../lib/docs'\n\nclass ContainerFilterTest < Minitest::Spec\n  include FilterTestHelper\n  self.filter_class = Docs::ContainerFilter\n  self.filter_type = 'html'\n\n  before do\n    @body = '<div>Test</div>'\n  end\n\n  context \"when context[:container] is a CSS selector\" do\n    before { context[:container] = '.main' }\n\n    it \"returns the element when it exists\" do\n      @body = '<div><div class=\"main\">Main</div></div><div></div>'\n      assert_equal 'Main', filter_output.inner_html\n    end\n\n    it \"raises an error when the element doesn't exist\" do\n      assert_raises Docs::ContainerFilter::ContainerNotFound do\n        filter.call\n      end\n    end\n  end\n\n  context \"when context[:container] is a block\" do\n    it \"calls the block with itself\" do\n      context[:container] = ->(arg) { @arg = arg; nil }\n      filter.call\n      assert_equal filter, @arg\n    end\n\n    context \"and the block returns a CSS selector\" do\n      before { context[:container] = ->(_) { '.main' } }\n\n      it \"returns the element when it exists\" do\n        @body = '<div><div class=\"main\">Main</div></div>'\n        assert_equal 'Main', filter_output.inner_html\n      end\n\n      it \"raises an error when the element doesn't exist\" do\n        assert_raises Docs::ContainerFilter::ContainerNotFound do\n          filter.call\n        end\n      end\n    end\n\n    context \"and the block returns nil\" do\n      before { context[:container] = ->(_) { nil } }\n\n      it \"returns the document\" do\n        assert_equal @body, filter_output.inner_html\n      end\n    end\n  end\n\n  context \"when context[:container] is nil\" do\n    context \"and the document is an HTML fragment\" do\n      it \"returns the document\" do\n        assert_equal @body, filter_output.inner_html\n      end\n    end\n\n    context \"and the document is an HTML document\" do\n      it \"returns the <body>\" do\n        @body = '<html><meta charset=utf-8><title></title><div>Test</div></html>'\n        assert_equal '<div>Test</div>', filter_output.inner_html\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/filters/core/entries_test.rb",
    "content": "require_relative '../../../../test_helper'\nrequire_relative '../../../../../lib/docs'\n\nclass EntriesFilterTest < Minitest::Spec\n  include FilterTestHelper\n  self.filter_class = Docs::EntriesFilter\n\n  before do\n    stub(filter).root_page? { false }\n  end\n\n  describe \":entries\" do\n    before do\n      stub(filter).name { 'name' }\n      stub(filter).path { 'path' }\n      stub(filter).type { 'type' }\n    end\n\n    let :entries do\n      filter_result[:entries]\n    end\n\n    it \"is an array\" do\n      assert_instance_of Array, entries\n    end\n\n    it \"includes the default entry when #include_default_entry? is true\" do\n      stub(filter).include_default_entry? { true }\n      refute_empty entries\n    end\n\n    it \"doesn't include the default entry when #include_default_entry? is false\" do\n      stub(filter).include_default_entry? { false }\n      assert_empty entries\n    end\n\n    it \"always includes the default entry when #root_page? is true\" do\n      stub(filter).include_default_entry? { false }\n      stub(filter).root_page? { true }\n      refute_empty entries\n    end\n\n    describe \"the default entry\" do\n      it \"has the #name, #path and #type\" do\n        assert_equal 'name', entries.first.name\n        assert_equal 'path', entries.first.path\n        assert_equal 'type', entries.first.type\n      end\n    end\n\n    it \"includes the #additional_entries\" do\n      stub(filter).additional_entries { [['name']] }\n      assert_equal 2, entries.length\n    end\n\n    describe \"an additional entry\" do\n      it \"has the given name\" do\n        stub(filter).additional_entries { [['test']] }\n        assert_equal 'test', entries.last.name\n      end\n\n      it \"has a default path equal to #path\" do\n        stub(filter).additional_entries { [['test']] }\n        assert_equal 'path', entries.last.path\n      end\n\n      it \"has a path with the given fragment\" do\n        stub(filter).additional_entries { [['test', 'frag']] }\n        assert_equal 'path#frag', entries.last.path\n      end\n\n      it \"has a path with the given path\" do\n        stub(filter).additional_entries { [['test', 'custom_path#frag']] }\n        assert_equal 'custom_path#frag', entries.last.path\n      end\n\n      it \"has the given type\" do\n        stub(filter).additional_entries { [['test', nil, 'test']] }\n        assert_equal 'test', entries.last.type\n      end\n\n      it \"has a default type equal to #type\" do\n        stub(filter).additional_entries { [['test']] }\n        assert_equal 'type', entries.last.type\n      end\n\n      it \"has a type equal to #type when the given type is nil\" do\n        stub(filter).additional_entries { [['test', nil, nil]] }\n        assert_equal 'type', entries.last.type\n      end\n    end\n  end\n\n  describe \"#name\" do\n    context \"when #root_page? is true\" do\n      it \"returns nil\" do\n        stub(filter).root_page? { true }\n        assert_nil filter.name\n      end\n    end\n\n    context \"when #root_page? is false\" do\n      before do\n        stub(filter).root_page? { false }\n        stub(filter).get_name { 'name' }\n      end\n\n      it \"returns #get_name\" do\n        assert_equal 'name', filter.name\n      end\n\n      it \"is memoized\" do\n        assert_same filter.name, filter.name\n      end\n    end\n  end\n\n  describe \"#get_name\" do\n    it \"returns 'file-name' when #slug is 'file-name'\" do\n      stub(filter).slug { 'file-name' }\n      assert_equal 'file-name', filter.get_name\n    end\n\n    it \"returns 'file name' when #slug is '_file__name_'\" do\n      stub(filter).slug { '_file__name_' }\n      assert_equal 'file name', filter.get_name\n    end\n\n    it \"returns 'file.name' when #slug is 'file/name'\" do\n      stub(filter).slug { 'file/name' }\n      assert_equal 'file.name', filter.get_name\n    end\n  end\n\n  describe \"#type\" do\n    context \"when #root_page? is true\" do\n      it \"returns nil\" do\n        stub(filter).root_page? { true }\n        assert_nil filter.type\n      end\n    end\n\n    context \"when #root_page? is false\" do\n      before do\n        stub(filter).root_page? { false }\n        stub(filter).get_type { 'type' }\n      end\n\n      it \"returns #get_type\" do\n        assert_equal 'type', filter.type\n      end\n\n      it \"is memoized\" do\n        assert_same filter.type, filter.type\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/filters/core/inner_html_test.rb",
    "content": "require_relative '../../../../test_helper'\nrequire_relative '../../../../../lib/docs'\n\nclass InnerHtmlFilterTest < Minitest::Spec\n  include FilterTestHelper\n  self.filter_class = Docs::InnerHtmlFilter\n\n  it \"returns the document as a string\" do\n    @body = Nokogiri::HTML.fragment('<p>Test</p>')\n    assert_equal '<p>Test</p>', filter_output\n  end\n\n  it \"returns a valid string\" do\n    invalid_string = \"\\x92\"\n    @body = Nokogiri::HTML.parse(invalid_string)\n    assert filter_output.valid_encoding?\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/filters/core/internal_urls_test.rb",
    "content": "require_relative '../../../../test_helper'\nrequire_relative '../../../../../lib/docs'\n\nclass InternalUrlsFilterTest < Minitest::Spec\n  include FilterTestHelper\n  self.filter_class = Docs::InternalUrlsFilter\n\n  before do\n    context[:base_url] = context[:root_url] = context[:url] = 'http://example.com/dir'\n  end\n\n  let :internal_urls do\n    filter_result[:internal_urls]\n  end\n\n  describe \":internal_urls\" do\n    it \"is an array\" do\n      assert_instance_of Array, internal_urls\n    end\n\n    it \"includes urls contained in the base url\" do\n      @body = link_to(url = 'http://example.com/dir/path')\n      assert_includes internal_urls, url\n    end\n\n    it \"doesn't include urls not contained in the base url\" do\n      @body = link_to 'http://example.com/dir-2/path'\n      assert_empty internal_urls\n    end\n\n    it \"includes urls irrespective of case\" do\n      context[:base_url] = 'http://example.com/Dir'\n      @body = link_to 'HTTP://example.com/diR/path'\n      assert_equal 1, internal_urls.length\n    end\n\n    it \"doesn't include relative urls\" do\n      @body = link_to 'http'\n      assert_empty internal_urls\n    end\n\n    it \"doesn't include ftp urls\" do\n      @body = link_to 'ftp://example.com/dir/path'\n      assert_empty internal_urls\n    end\n\n    it \"doesn't include invalid urls\" do\n      @body = link_to 'http://example.com/dir/%path'\n      assert_empty internal_urls\n    end\n\n    it \"retains query strings\" do\n      @body = link_to(url = 'http://example.com/dir?query')\n      assert_includes internal_urls, url\n    end\n\n    it \"removes fragments\" do\n      @body = link_to 'http://example.com/dir#frag'\n      assert_includes internal_urls, 'http://example.com/dir'\n    end\n\n    it \"doesn't have duplicates\" do\n      @body = link_to('http://example.com/dir/path') * 2\n      assert_equal 1, internal_urls.length\n    end\n\n    it \"normalizes the urls\" do\n      @body = link_to(url = 'HTTP://EXAMPLE.COM/dir')\n      assert_includes internal_urls, url.downcase\n    end\n\n    it \"doesn't include urls included in context[:skip]\" do\n      context[:skip] = ['/path']\n      @body = link_to 'http://example.com/dir/Path'\n      assert_empty internal_urls\n    end\n\n    it \"doesn't include urls matching context[:skip_patterns]\" do\n      context[:skip_patterns] = [/\\A\\/path.*/]\n      @body = link_to 'http://example.com/dir/path.html'\n      assert_empty internal_urls\n    end\n\n    it \"includes urls that don't match context[:skip_patterns]\" do\n      context[:skip_patterns] = [/\\A\\/path.*/]\n      @body = link_to(url = 'http://example.com/dir/file')\n      assert_includes internal_urls, url\n    end\n\n    it \"includes urls included in context[:only]\" do\n      context[:only] = ['/path']\n      @body = link_to(url = 'http://example.com/dir/Path')\n      assert_includes internal_urls, url\n    end\n\n    it \"doesn't include urls not included in context[:only]\" do\n      context[:only] = []\n      @body = link_to 'http://example.com/dir/Path'\n      assert_empty internal_urls\n    end\n\n    it \"includes urls matching context[:only_patterns]\" do\n      context[:only_patterns] = [/file/]\n      @body = link_to(url = 'http://example.com/dir/file')\n      assert_includes internal_urls, url\n    end\n\n    it \"doesn't include urls that don't match context[:only_patterns]\" do\n      context[:only_patterns] = []\n      @body = link_to 'http://example.com/dir/file'\n      assert_empty internal_urls\n    end\n  end\n\n  context \"when the base url is 'example.com'\" do\n    before do\n      context[:base_url] = 'http://example.com'\n      context[:root_url] = 'http://example.com/'\n    end\n\n    context \"and the url is 'example.com/file'\" do\n      before { context[:url] = 'http://example.com/file' }\n\n      it \"replaces 'example.com' with '.'\" do\n        @body = link_to 'http://example.com'\n        assert_equal link_to('.'), filter_output_string\n      end\n\n      it \"replaces 'example.com/' with '.'\" do\n        @body = link_to 'http://example.com/'\n        assert_equal link_to('.'), filter_output_string\n      end\n\n      it \"replaces 'example.com/test' with 'test'\" do\n        @body = link_to 'http://example.com/test'\n        assert_equal link_to('test'), filter_output_string\n      end\n\n      it \"replaces 'example.com/test/' with 'test/'\" do\n        @body = link_to 'http://example.com/test/'\n        assert_equal link_to('test/'), filter_output_string\n      end\n\n      it \"retains query strings\" do\n        @body = link_to 'http://example.com/?query'\n        assert_equal link_to('.?query'), filter_output_string\n      end\n\n      it \"retains fragments\" do\n        @body = link_to 'http://example.com/#frag'\n        assert_equal link_to('.#frag'), filter_output_string\n      end\n\n      it \"doesn't replace 'https://example.com'\" do\n        @body = link_to 'https://example.com'\n        assert_equal @body, filter_output_string\n      end\n\n      it \"doesn't replace 'http://not.example.com'\" do\n        @body = link_to 'http://not.example.com'\n        assert_equal @body, filter_output_string\n      end\n\n      context \"and the root url is 'example.com/root/path'\" do\n        it \"replaces 'example.com/root/path' with '.'\" do\n          context[:root_url] = 'http://example.com/root/path'\n          @body = link_to 'http://example.com/root/path'\n          assert_equal link_to('.'), filter_output_string\n        end\n      end\n    end\n  end\n\n  context \"when the base url is 'example.com/dir'\" do\n    before do\n      context[:base_url] = context[:root_url] = 'http://example.com/dir'\n    end\n\n    context \"and the url is 'example.com/dir'\" do\n      before { context[:url] = 'http://example.com/dir' }\n\n      it \"replaces 'example.com/dir' with '.'\" do\n        @body = link_to 'http://example.com/dir'\n        assert_equal link_to('.'), filter_output_string\n      end\n\n      it \"replaces 'example.com/dir/' with '.'\" do\n        @body = link_to 'http://example.com/dir/'\n        assert_equal link_to('.'), filter_output_string\n      end\n\n      it \"replaces 'example.com/dir/test' with 'test'\" do\n        @body = link_to 'http://example.com/dir/test'\n        assert_equal link_to('test'), filter_output_string\n      end\n\n      it \"doesn't replace 'example.com/'\" do\n        @body = link_to 'http://example.com/'\n        assert_equal @body, filter_output_string\n      end\n    end\n\n    context \"and the url is 'example.com/dir/file'\" do\n      before { context[:url] = 'http://example.com/dir/file' }\n\n      it \"replaces 'example.com/dir' with '.'\" do\n        @body = link_to 'http://example.com/dir'\n        assert_equal link_to('.'), filter_output_string\n      end\n\n      it \"replaces 'example.com/dir/' with '.'\" do\n        @body = link_to 'http://example.com/dir/'\n        assert_equal link_to('.'), filter_output_string\n      end\n    end\n  end\n\n  context \"when the base url is 'example.com/dir/'\" do\n    before do\n      context[:base_url] = context[:root_url] = 'http://example.com/dir/'\n    end\n\n    context \"and the url is 'example.com/dir/file'\" do\n      before { context[:url] = 'http://example.com/dir/file' }\n\n      it \"replaces 'example.com/dir/' with '.'\" do\n        @body = link_to 'http://example.com/dir/'\n        assert_equal link_to('.'), filter_output_string\n      end\n\n      it \"doesn't replace 'example.com/dir'\" do\n        @body = link_to 'http://example.com/dir'\n        assert_equal @body, filter_output_string\n      end\n    end\n  end\n\n  context \"context[:trailing_slash]\" do\n    before do\n      @body = link_to('http://example.com/dir/path/') + link_to('http://example.com/dir/path')\n    end\n\n    context \"when it is true\" do\n      before do\n        context[:trailing_slash] = true\n      end\n\n      it \"adds a trailing slash to :internal_urls\" do\n        assert_equal ['http://example.com/dir/path/'], internal_urls\n      end\n\n      it \"adds a trailing slash to replaced urls\" do\n        assert_equal link_to('path/') * 2, filter_output_string\n      end\n    end\n\n    context \"when it is false\" do\n      before do\n        context[:trailing_slash] = false\n      end\n\n      it \"removes the trailing slash from :internal_urls\" do\n        assert_equal ['http://example.com/dir/path'], internal_urls\n      end\n\n      it \"removes the trailing slash from replaced urls\" do\n        assert_equal link_to('path') * 2, filter_output_string\n      end\n\n      it \"doesn't remove the leading slash\" do\n        url = context[:base_url] = context[:root_url] = 'http://example.com/'\n        @body = link_to(url)\n        assert_equal [url], internal_urls\n      end\n    end\n  end\n\n  context \"context[:skip_links]\" do\n    before do\n      @body = link_to context[:url]\n    end\n\n    context \"when it is true\" do\n      before do\n        context[:skip_links] = true\n      end\n\n      it \"doesn't set :internal_urls\" do\n        refute internal_urls\n      end\n\n      it \"doesn't replace urls\" do\n        assert_equal @body, filter_output_string\n      end\n    end\n\n    context \"when it is a block\" do\n      it \"calls the block with the filter instance\" do\n        context[:skip_links] = ->(arg) { @arg = arg; nil }\n        filter.call\n        assert_equal filter, @arg\n      end\n\n      context \"and the block returns true\" do\n        before do\n          context[:skip_links] = ->(_) { true }\n        end\n\n        it \"doesn't set :internal_urls\" do\n          refute internal_urls\n        end\n\n        it \"doesn't replace urls\" do\n          assert_equal @body, filter_output_string\n        end\n      end\n\n      context \"and the block returns false\" do\n        before do\n          context[:skip_links] = ->(_) { false }\n        end\n\n        it \"sets :internal_urls\" do\n          assert internal_urls\n        end\n\n        it \"replaces urls\" do\n          refute_equal @body, filter_output_string\n        end\n      end\n    end\n  end\n\n  context \"context[:follow_links]\" do\n    before do\n      @body = link_to context[:url]\n    end\n\n    context \"when it is false\" do\n      before do\n        context[:follow_links] = false\n      end\n\n      it \"doesn't set :internal_urls\" do\n        refute internal_urls\n      end\n\n      it \"replaces urls\" do\n        refute_equal @body, filter_output_string\n      end\n    end\n\n    context \"when it is a block\" do\n      it \"calls the block with the filter instance\" do\n        context[:follow_links] = ->(arg) { @arg = arg; nil }\n        filter.call\n        assert_equal filter, @arg\n      end\n\n      context \"and the block returns false\" do\n        before do\n          context[:follow_links] = ->(_) { false }\n        end\n\n        it \"doesn't set :internal_urls\" do\n          refute internal_urls\n        end\n\n        it \"replaces urls\" do\n          refute_equal @body, filter_output_string\n        end\n      end\n\n      context \"and the block returns true\" do\n        before do\n          context[:follow_links] = ->(_) { true }\n        end\n\n        it \"sets :internal_urls\" do\n          assert internal_urls\n        end\n      end\n    end\n  end\n\n  context \"context[:skip_link] is a block\" do\n    before do\n      @body = link_to context[:url]\n    end\n\n    it \"calls the block with each link\" do\n      context[:skip_link] = ->(arg) { @arg = arg.try(:to_html); nil }\n      filter.call\n      assert_equal @body, @arg\n    end\n\n    context \"and the block returns true\" do\n      before do\n        context[:skip_link] = ->(_) { true }\n      end\n\n      it \"doesn't include the link's url in :internal_urls\" do\n        assert internal_urls.empty?\n      end\n\n      it \"doesn't replace the link's url\" do\n        assert_equal @body, filter_output_string\n      end\n    end\n\n    context \"and the block returns false\" do\n      before do\n        context[:skip_link] = ->(_) { false }\n      end\n\n      it \"includes the link's url in :internal_urls\" do\n        refute internal_urls.empty?\n      end\n\n      it \"replaces the link's url\" do\n        refute_equal @body, filter_output_string\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/filters/core/normalize_paths_test.rb",
    "content": "require_relative '../../../../test_helper'\nrequire_relative '../../../../../lib/docs'\n\nclass NormalizePathsFilterTest < Minitest::Spec\n  include FilterTestHelper\n  self.filter_class = Docs::NormalizePathsFilter\n\n  describe \"#path\" do\n    it \"returns 'index' when the page is the root page\" do\n      mock(filter).root_page? { true }\n      assert_equal 'index', filter.path\n    end\n\n    it \"returns 'test/index' when #subpath is 'test/'\" do\n      stub(filter).subpath { 'test/' }\n      assert_equal 'test/index', filter.path\n    end\n\n    it \"returns 'test' when #subpath is '/test'\" do\n      stub(filter).subpath { '/test' }\n      assert_equal 'test', filter.path\n    end\n  end\n\n  describe \"#store_path\" do\n    it \"returns 'index.html' when #path is 'index'\" do\n      stub(filter).path { 'index' }\n      assert_equal 'index.html', filter.store_path\n    end\n\n    it \"returns 'index.html' when #path is 'index.html'\" do\n      stub(filter).path { 'index.html' }\n      assert_equal 'index.html', filter.store_path\n    end\n\n    it \"returns 'page.ext.html' when #path is 'page.ext'\" do\n      stub(filter).path { 'page.ext' }\n      assert_equal 'page.ext.html', filter.store_path\n    end\n  end\n\n  describe \"#normalize_path\" do\n    it \"returns 'index' with '.'\" do\n      assert_equal 'index', filter.normalize_path('.')\n    end\n\n    it \"returns 'test' with 'TEST'\" do\n      assert_equal 'test', filter.normalize_path('TEST')\n    end\n\n    it \"returns 'test/index' with 'test/'\" do\n      assert_equal 'test/index', filter.normalize_path('test/')\n    end\n\n    it \"returns 'test' with 'test.html'\" do\n      assert_equal 'test', filter.normalize_path('test.html')\n    end\n  end\n\n  before do\n    stub(filter).subpath { '' }\n  end\n\n  it \"rewrites relative urls\" do\n    @body = link_to 'TEST/'\n    assert_equal link_to('test/index'), filter_output_string\n  end\n\n  it \"doesn't rewrite absolute urls\" do\n    @body = link_to 'http://example.com'\n    assert_equal @body, filter_output_string\n  end\n\n  it \"retains query strings\" do\n    @body = link_to 'TEST/?query'\n    assert_equal link_to('test/index?query'), filter_output_string\n  end\n\n  it \"retains fragments\" do\n    @body = link_to 'TEST/#frag'\n    assert_equal link_to('test/index#frag'), filter_output_string\n  end\n\n  it \"doesn't rewrite mailto urls\" do\n    @body = link_to 'mailto:'\n    assert_equal @body, filter_output_string\n  end\n\n  it \"doesn't rewrite ftp urls\" do\n    @body = link_to 'ftp://example.com'\n    assert_equal @body, filter_output_string\n  end\n\n  it \"doesn't rewrite invalid urls\" do\n    @body = link_to '.%'\n    assert_equal @body, filter_output_string\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/filters/core/normalize_urls_test.rb",
    "content": "require_relative '../../../../test_helper'\nrequire_relative '../../../../../lib/docs'\n\nclass NormalizeUrlsFilterTest < Minitest::Spec\n  include FilterTestHelper\n  self.filter_class = Docs::NormalizeUrlsFilter\n\n  before do\n    context[:url] = 'http://example.com/dir/file'\n  end\n\n  it \"rewrites relative urls\" do\n    @body = link_to './path'\n    assert_equal link_to('http://example.com/dir/path'), filter_output_string\n  end\n\n  it \"rewrites root-relative urls\" do\n    @body = link_to '/path'\n    assert_equal link_to('http://example.com/path'), filter_output_string\n  end\n\n  it \"rewrites relative image urls\" do\n    @body = '<img src=\"/image.png\">'\n    assert_equal '<img src=\"http://example.com/image.png\">', filter_output_string\n  end\n\n  it \"rewrites relative iframe urls\" do\n    @body = '<iframe src=\"/path\"></iframe>'\n    assert_equal '<iframe src=\"http://example.com/path\"></iframe>', filter_output_string\n  end\n\n  it \"rewrites protocol-less urls\" do\n    @body = link_to '//example.com/'\n    assert_equal link_to('http://example.com/'), filter_output_string\n  end\n\n  it \"rewrites empty urls\" do\n    @body = link_to ''\n    assert_equal link_to(context[:url]), filter_output_string\n  end\n\n  it \"rewrites invalid link urls\" do\n    @body = link_to '%'\n    assert_equal link_to('#'), filter_output_string\n  end\n\n  it \"rewrites invalid image urls\" do\n    @body = '<img src=\"%\">'\n    assert_equal '<img src=\"#\">', filter_output_string\n  end\n\n  it \"doesn't rewrite invalid iframe urls\" do\n    @body = '<iframe src=\"%\"></iframe>'\n    assert_equal @body, filter_output_string\n  end\n\n  it \"repairs un-encoded spaces\" do\n    @body = link_to 'http://example.com/#foo bar '\n    assert_equal link_to('http://example.com/#foo%20bar'), filter_output_string\n  end\n\n  it \"retains query strings\" do\n    @body = link_to'path?query'\n    assert_equal link_to('http://example.com/dir/path?query'), filter_output_string\n  end\n\n  it \"retains fragments\" do\n    @body = link_to 'path#frag'\n    assert_equal link_to('http://example.com/dir/path#frag'), filter_output_string\n  end\n\n  it \"doesn't rewrite absolute urls\" do\n    @body = link_to 'http://not.example.com/path'\n    assert_equal @body, filter_output_string\n  end\n\n  it \"doesn't rewrite fragment-only urls\" do\n    @body = link_to '#frag'\n    assert_equal @body, filter_output_string\n  end\n\n  it \"doesn't rewrite email urls\" do\n    @body = link_to 'mailto:test@example.com'\n    assert_equal @body, filter_output_string\n  end\n\n  it \"doesn't rewrite data image urls\" do\n    @body = '<img src=\"data:image/gif;base64,aaaa\">'\n    assert_equal @body, filter_output_string\n  end\n\n  context \"when context[:replace_paths] is a hash\" do\n    before do\n      context[:base_url] = 'http://example.com/dir/'\n      @body = link_to 'http://example.com/dir/path?query#frag'\n    end\n\n    it \"fixes each absolute url whose subpath is found in the hash\" do\n      context[:replace_paths] = { 'path' => 'fixed' }\n      @body += link_to 'path?query#frag'\n      assert_equal link_to('http://example.com/dir/fixed?query#frag') * 2, filter_output_string\n    end\n\n    it \"doesn't fix urls whose subpath isn't found in the hash\" do\n      context[:replace_paths] = { 'dir/path' => 'fixed', '/dir/path' => 'fixed' }\n      assert_equal @body, filter_output_string\n    end\n\n    it \"doesn't fix urls whose subpath isn't found in the hash\" do\n      context[:replace_paths] = {}\n      @body = link_to 'http://example.com/dir/path'\n      assert_equal @body, filter_output_string\n    end\n  end\n\n  context \"when context[:replace_urls] is a hash\" do\n    before do\n      @body = link_to 'http://example.com/path?#'\n    end\n\n    it \"replaces each absolute url found in the hash\" do\n      context[:replace_urls] = { 'http://example.com/path?#' => 'fixed' }\n      @body += link_to '/path?#'\n      assert_equal link_to('fixed') * 2, filter_output_string\n    end\n\n    it \"doesn't replace urls not found in the hash\" do\n      context[:replace_urls] = {}\n      assert_equal @body, filter_output_string\n    end\n  end\n\n  context \"when context[:fix_urls_before_parse] is a block\" do\n    before do\n      @body = link_to 'foo[bar]'\n    end\n\n    it \"calls the block with each absolute url\" do\n      context[:fix_urls_before_parse] = ->(arg) { (@args ||= []).push(arg); nil }\n      @body += link_to 'foo[bar]'\n      filter.call\n      assert_equal ['foo[bar]'] * 2, @args\n    end\n\n    it \"replaces the url with the block's return value\" do\n      context[:fix_urls_before_parse] = ->(url) { '/fixed' }\n      assert_equal link_to('http://example.com/fixed'), filter_output_string\n    end\n  end\n\n  context \"when context[:fix_urls] is a block\" do\n    before do\n      @body = link_to 'http://example.com/path?#'\n    end\n\n    it \"calls the block with each absolute url\" do\n      context[:fix_urls] = ->(arg) { (@args ||= []).push(arg); nil }\n      @body += link_to '/path?#'\n      filter.call\n      assert_equal ['http://example.com/path?#'] * 2, @args\n    end\n\n    it \"replaces the url with the block's return value\" do\n      context[:fix_urls] = ->(url) { url == 'http://example.com/path?#' ? 'fixed' : url }\n      assert_equal link_to('fixed'), filter_output_string\n    end\n\n    it \"doesn't replace the url when the block returns nil\" do\n      context[:fix_urls] = ->(_) { nil }\n      assert_equal @body, filter_output_string\n    end\n\n    it \"skips fragment-only urls\" do\n      context[:fix_urls] = ->(_) { @called = true }\n      @body = link_to '#frag'\n      filter.call\n      refute @called\n    end\n  end\n\n  context \"when context[:redirections] is a hash\" do\n    before do\n      @body = link_to 'http://example.com/path?query#frag'\n    end\n\n    it \"replaces the path of matching urls, case-insensitive\" do\n      @body = link_to('http://example.com/PATH?query#frag') + link_to('http://example.com/path/two')\n      context[:redirections] = { '/path' => '/fixed' }\n      expected = link_to('http://example.com/fixed?query#frag') + link_to('http://example.com/path/two')\n      assert_equal expected, filter_output_string\n    end\n\n    it \"does a multi pass with context[:fix_urls]\" do\n      @body = link_to('http://example.com/path')\n      context[:fix_urls] = ->(url) do\n        url.sub! 'example.com', 'example.org'\n        url.sub! '/Fixed', '/fixed'\n        url\n      end\n      context[:redirections] = { '/path' => '/Fixed' }\n      assert_equal link_to('http://example.org/fixed'), filter_output_string\n    end\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/filters/core/parse_cf_email_test.rb",
    "content": "require_relative '../../../../test_helper'\nrequire_relative '../../../../../lib/docs'\n\nclass ParseCfEmailFilterTest < Minitest::Spec\n  include FilterTestHelper\n  self.filter_class = Docs::ParseCfEmailFilter\n\n  before do\n    context[:url] = 'http://example.com/dir/file'\n  end\n\n  it 'rewrites parses CloudFlare mail addresses' do\n    href = 'b3dddad0d6d2ddd7c0dadec3dfd6f3d6cbd2dec3dfd69dd0dcde'\n    @body = %(<a class=\"__cf_email__\" data-cfemail=\"#{href}\">Link</a>)\n    assert_equal 'niceandsimple@example.com', filter_output_string\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/filters/core/title_test.rb",
    "content": "require_relative '../../../../test_helper'\nrequire_relative '../../../../../lib/docs'\n\nclass TitleFilterTest < Minitest::Spec\n  include FilterTestHelper\n  self.filter_class = Docs::TitleFilter\n\n  before do\n    @body = '<div>Test</div>'\n  end\n\n  def output_with_title(title)\n    \"<h1>#{title}</h1>#{@body}\"\n  end\n\n  context \"when result[:entries] is empty\" do\n    it \"does nothing\" do\n      assert_equal @body, filter_output.inner_html\n    end\n\n    context \"and context[:title] is a string\" do\n      it \"prepends a heading containing the title\" do\n        context[:title] = 'title'\n        assert_equal output_with_title('title'), filter_output.inner_html\n      end\n    end\n  end\n\n  context \"when result[:entries] is an array\" do\n    before do\n      result[:entries] = [OpenStruct.new(name: 'name'), OpenStruct.new(name: 'name2')]\n    end\n\n    it \"prepends a heading containing the first entry's name\" do\n      assert_equal output_with_title('name'), filter_output.inner_html\n    end\n\n    context \"and context[:title] is a string\" do\n      it \"prepends a heading containing the title\" do\n        context[:title] = 'title'\n        assert_equal output_with_title('title'), filter_output.inner_html\n      end\n    end\n\n    context \"and context[:title] is nil\" do\n      it \"prepends a heading containing the first entry's name\" do\n        context[:title] = nil\n        assert_equal output_with_title('name'), filter_output.inner_html\n      end\n    end\n\n    context \"and context[:title] is false\" do\n      it \"does nothing\" do\n        context[:title] = false\n        assert_equal @body, filter_output.inner_html\n      end\n    end\n  end\n\n  context \"when context[:root_title] is a string\" do\n    before do\n      context[:root_title] = 'root'\n    end\n\n    context \"and context[:title] is a string\" do\n      before do\n        context[:title] = 'title'\n      end\n\n      it \"prepends a heading containing the root title when #root_page? is true\" do\n        stub(filter).root_page? { true }\n        assert_equal output_with_title('root'), filter_output.inner_html\n      end\n\n      it \"prepends a heading containing the title when #root_page? is false\" do\n        stub(filter).root_page? { false }\n        assert_equal output_with_title('title'), filter_output.inner_html\n      end\n    end\n  end\n\n  context \"when context[:title] is a string\" do\n    before do\n      context[:title] = 'title'\n    end\n\n    context \"and context[:root_title] is false\" do\n      it \"does nothing when #root_page? is true\" do\n        context[:root_title] = false\n        stub(filter).root_page? { true }\n        assert_equal @body, filter_output.inner_html\n      end\n    end\n  end\n\n  context \"when context[:title] is a block\" do\n    it \"calls the block with itself\" do\n      context[:title] = ->(arg) { @arg = arg; nil }\n      filter.call\n      assert_equal filter, @arg\n    end\n\n    it \"prepends a heading tag containing the title returned by the block\" do\n      context[:title] = ->(_) { 'title' }\n      assert_equal output_with_title('title'), filter_output.inner_html\n    end\n\n    it \"does nothing when the block returns nil\" do\n      context[:title] = ->(_) { nil }\n      assert_equal @body, filter_output.inner_html\n    end\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/storage/abstract_store_test.rb",
    "content": "require_relative '../../../test_helper'\nrequire_relative '../../../../lib/docs'\n\nclass DocsAbstractStoreTest < Minitest::Spec\n  InvalidPathError = Docs::AbstractStore::InvalidPathError\n  LockError = Docs::AbstractStore::LockError\n\n  let :path do\n    '/'\n  end\n\n  let :store do\n    Docs::AbstractStore.new(@path || path).tap do |store|\n      store.extend FakeInstrumentation\n    end\n  end\n\n  describe \".new\" do\n    it \"raises an error with a relative path\" do\n      assert_raises ArgumentError do\n        Docs::AbstractStore.new 'path'\n      end\n    end\n\n    it \"sets #root_path\" do\n      @path = '/path'\n      assert_equal @path, store.root_path\n    end\n\n    it \"expands #root_path\" do\n      @path = '/path/..'\n      assert_equal '/', store.root_path\n    end\n\n    it \"sets #working_path\" do\n      assert_equal store.root_path, store.working_path\n    end\n  end\n\n  describe \"#root_path\" do\n    it \"can't be overwritten\" do\n      @path = '/path'\n      store.root_path << '/..'\n      assert_equal '/path', store.root_path\n    end\n  end\n\n  describe \"#working_path\" do\n    it \"can't be overwritten\" do\n      @path = '/path'\n      store.working_path << '/..'\n      assert_equal '/path', store.working_path\n    end\n  end\n\n  describe \"#open\" do\n    it \"raises an error when the store is locked\" do\n      assert_raises LockError do\n        store.send :lock, &-> { store.open 'dir' }\n      end\n    end\n\n    context \"with a relative path\" do\n      it \"updates #working_path relative to #root_path\" do\n        2.times { store.open 'dir' }\n        assert_equal File.join(path, 'dir'), store.working_path\n      end\n\n      it \"expands the new #working_path\" do\n        store.open './dir/../'\n        assert_equal path, store.working_path\n      end\n\n      it \"raises an error when the new #working_path is outside of #root_path\" do\n        @path = '/dir'\n        assert_raises InvalidPathError do\n          store.open '../dir2'\n        end\n      end\n    end\n\n    context \"with an absolute path\" do\n      it \"updates #working_path\" do\n        store.open File.join(path, 'dir')\n        assert_equal File.join(path, 'dir'), store.working_path\n      end\n\n      it \"expands the new #working_path\" do\n        store.open File.join(path, 'dir/..')\n        assert_equal path, store.working_path\n      end\n\n      it \"raises an error when the new #working_path is outside of #root_path\" do\n        @path = '/dir'\n        assert_raises InvalidPathError do\n          store.open '/dir2'\n        end\n      end\n    end\n\n    context \"with a block\" do\n      it \"calls the block\" do\n        store.open('dir') { @called = true }\n        assert @called\n      end\n\n      it \"returns the block's return value\" do\n        assert_equal 1, store.open('dir') { 1 }\n      end\n\n      it \"updates #working_path while calling the block\" do\n        store.open 'dir' do\n          assert_equal File.join(path, 'dir'), store.working_path\n        end\n      end\n\n      it \"resets #working_path to its previous value afterward\" do\n        store.open('dir')\n        store.open('dir2') {}\n        assert_equal File.join(path, 'dir'), store.working_path\n      end\n\n      it \"resets #working_path even when the block fails\" do\n        assert_raises RuntimeError do\n          store.open('dir') { raise }\n        end\n        assert_equal path, store.working_path\n      end\n    end\n  end\n\n  describe \"#close\" do\n    it \"resets #working_path to #root_path\" do\n      2.times { store.open 'dir' }\n      store.close\n      assert_equal path, store.working_path\n    end\n\n    it \"raises an error when the store is locked\" do\n      assert_raises LockError do\n        store.send :lock, &-> { store.close }\n      end\n    end\n  end\n\n  describe \"#expand_path\" do\n    context \"when #working_path is '/'\" do\n      before do\n        store.open '/'\n      end\n\n      it \"returns '/path' with './path'\" do\n        assert_equal '/path', store.expand_path('./path')\n      end\n\n      it \"returns '/path' with '/path'\" do\n        assert_equal '/path', store.expand_path('/path')\n      end\n    end\n\n    context \"when #working_path is '/dir'\" do\n      before do\n        store.open '/dir'\n      end\n\n      it \"returns '/dir/path' with './path'\" do\n        assert_equal '/dir/path', store.expand_path('./path')\n      end\n\n      it \"returns '/dir/path' with 'path/../path'\" do\n        assert_equal '/dir/path', store.expand_path('path/../path')\n      end\n\n      it \"returns '/dir/path' with '/dir/path'\" do\n        assert_equal '/dir/path', store.expand_path('/dir/path')\n      end\n\n      it \"raises an error with '..'\" do\n        assert_raises InvalidPathError do\n          store.expand_path '..'\n        end\n      end\n\n      it \"raises an error with '/'\" do\n        assert_raises InvalidPathError do\n          store.expand_path '/'\n        end\n      end\n    end\n  end\n\n  describe \"#read\" do\n    it \"raises an error with a path outside of #working_path\" do\n      @path = '/path'\n      assert_raises InvalidPathError do\n        store.read '../file'\n      end\n    end\n\n    it \"returns nil when the file doesn't exist\" do\n      dont_allow(store).read_file\n      stub(store).file_exist?('/file') { false }\n      assert_nil store.read('file')\n    end\n\n    it \"returns #read_file when the file exists\" do\n      stub(store).read_file('/file') { 1 }\n      stub(store).file_exist?('/file') { true }\n      assert_equal 1, store.read('file')\n    end\n  end\n\n  describe \"#write\" do\n    it \"raises an error with a path outside of #working_path\" do\n      @path = '/path'\n      assert_raises InvalidPathError do\n        store.write '../file', ''\n      end\n    end\n\n    context \"when the file doesn't exist\" do\n      before do\n        stub(store).file_exist?('/file') { false }\n        stub(store).create_file\n      end\n\n      it \"returns #create_file\" do\n        stub(store).create_file('/file', '') { 1 }\n        assert_equal 1, store.write('file', '')\n      end\n\n      it \"instrument 'create'\" do\n        store.write 'file', ''\n        assert store.last_instrumentation\n        assert_equal 'create.store', store.last_instrumentation[:event]\n        assert_equal '/file', store.last_instrumentation[:payload][:path]\n      end\n    end\n\n    context \"when the file exists\" do\n      before do\n        stub(store).file_exist?('/file') { true }\n        stub(store).update_file\n      end\n\n      it \"returns #update_file\" do\n        stub(store).update_file('/file', '') { 1 }\n        assert_equal 1, store.write('file', '')\n      end\n\n      it \"instruments 'update'\" do\n        store.write 'file', ''\n        assert store.last_instrumentation\n        assert_equal 'update.store', store.last_instrumentation[:event]\n        assert_equal '/file', store.last_instrumentation[:payload][:path]\n      end\n    end\n  end\n\n  describe \"#delete\" do\n    it \"raises an error with a path outside og #working_path\" do\n      @path = '/path'\n      assert_raises InvalidPathError do\n        store.delete '../file'\n      end\n    end\n\n    it \"returns nil when the file doesn't exist\" do\n      dont_allow(store).delete_file\n      stub(store).file_exist?('/file') { false }\n      assert_nil store.delete('file')\n    end\n\n    context \"when the file exists\" do\n      before do\n        stub(store).file_exist?('/file') { true }\n        stub(store).delete_file\n      end\n\n      it \"calls #delete_file\" do\n        mock(store).delete_file('/file')\n        store.delete 'file'\n      end\n\n      it \"returns true\" do\n        assert store.delete('file')\n      end\n\n      it \"instruments 'destroy'\" do\n        store.delete 'file'\n        assert store.last_instrumentation\n        assert_equal 'destroy.store', store.last_instrumentation[:event]\n        assert_equal '/file', store.last_instrumentation[:payload][:path]\n      end\n    end\n  end\n\n  describe \"exist?\" do\n    it \"raises an error with a path outside of #working_path\" do\n      @path = '/path'\n      assert_raises InvalidPathError do\n        store.exist? '../file'\n      end\n    end\n\n    it \"returns #file_exist?\" do\n      stub(store).file_exist?('/file') { 1 }\n      assert_equal 1, store.exist?('file')\n    end\n  end\n\n  describe \"mtime\" do\n    it \"raises an error with a path outside of #working_path\" do\n      @path = '/path'\n      assert_raises InvalidPathError do\n        store.mtime '../file'\n      end\n    end\n\n    it \"returns nil when the file doesn't exist\" do\n      stub(store).file_exist?('/file') { false }\n      dont_allow(store).file_mtime\n      assert_nil store.mtime('file')\n    end\n\n    it \"returns #file_mtime when the file exists\" do\n      stub(store).file_exist?('/file') { true }\n      stub(store).file_mtime('/file') { 1 }\n      assert_equal 1, store.mtime('file')\n    end\n  end\n\n  describe \"#size\" do\n    it \"raises an error with a path outside of #working_path\" do\n      @path = '/path'\n      assert_raises InvalidPathError do\n        store.size '../file'\n      end\n    end\n\n    it \"returns nil when the file doesn't exist\" do\n      stub(store).file_exist?('/file') { false }\n      dont_allow(store).file_size\n      assert_nil store.size('file')\n    end\n\n    it \"returns #file_size when the file exists\" do\n      stub(store).file_exist?('/file') { true }\n      stub(store).file_size('/file') { 1 }\n      assert_equal 1, store.size('file')\n    end\n  end\n\n  describe \"#each\" do\n    it \"calls #list_files with #working_path\" do\n      store.open 'dir'\n      block = Proc.new {}\n      mock(store).list_files(File.join(path, 'dir'), &block)\n      store.each(&block)\n    end\n  end\n\n  describe \"#replace\" do\n    before do\n      stub(store).file_exist?\n      stub(store).create_file\n      stub(store).delete_file\n    end\n\n    def stub_paths(*paths)\n      stub(store).each { |&block| paths.each(&block) }\n    end\n\n    it \"calls the block\" do\n      store.replace { @called = true }\n      assert @called\n    end\n\n    it \"returns the block's return value\" do\n      assert_equal 1, store.replace { 1 }\n    end\n\n    it \"locks the store while calling the block\" do\n      assert_raises LockError do\n        store.replace { store.open('dir') }\n      end\n      store.open 'dir'\n    end\n\n    context \"with a path\" do\n      it \"opens the path while calling the block\" do\n        store.replace 'dir' do\n          assert_equal File.join(path, 'dir'), store.working_path\n        end\n      end\n    end\n\n    context \"when the block writes no files\" do\n      it \"doesn't delete files\" do\n        stub_paths '/', '/file'\n        dont_allow(store).delete_file\n        store.replace {}\n      end\n    end\n\n    context \"when the block writes files\" do\n      it \"deletes untouched files\" do\n        stub_paths '/', '/dir', '/dir/file', '/dir/file2', '/dir2'\n        mock(store).delete_file('/dir/file2').then.delete_file('/dir2')\n        store.replace { store.write 'dir/file', '' }\n      end\n\n      it \"doesn't delete touched files\" do\n        stub_paths '/', '/dir', '/dir/(file)'\n        dont_allow(store).delete_file\n        store.replace { store.write 'dir/(file)', '' }\n      end\n    end\n\n    context \"when the block fails\" do\n      it \"doesn't delete files\" do\n        stub_paths '/', '/file'\n        dont_allow(store).delete_file\n        assert_raises RuntimeError do\n          store.replace { store.write 'file2', ''; raise }\n        end\n      end\n\n      it \"unlocks the store afterward\" do\n        assert_raises RuntimeError do\n          store.replace { raise }\n        end\n        store.open 'dir'\n      end\n    end\n\n    context \"when called multiple times\" do\n      before do\n        stub_paths '/', '/file'\n      end\n\n      it \"deletes untouched files that were touched the previous time\" do\n        store.replace { store.write 'file', '' }\n        mock(store).delete_file '/file'\n        store.replace { store.write 'file2', '' }\n      end\n\n      it \"deletes untouched files that were touched and failed the previous time\" do\n        assert_raises RuntimeError do\n          store.replace { store.write 'file', ''; raise }\n        end\n        mock(store).delete_file '/file'\n        store.replace { store.write 'file2', '' }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/lib/docs/storage/file_store_test.rb",
    "content": "require_relative '../../../test_helper'\nrequire_relative '../../../../lib/docs'\n\nclass DocsFileStoreTest < Minitest::Spec\n  let :store do\n    Docs::FileStore.new(tmp_path)\n  end\n\n  after do\n    FileUtils.rm_rf \"#{tmp_path}/.\"\n  end\n\n  def expand_path(path)\n    File.join(tmp_path, path)\n  end\n\n  def read(path)\n    File.read expand_path(path)\n  end\n\n  def write(path, content)\n    File.write expand_path(path), content\n  end\n\n  def exists?(path)\n    File.exist? expand_path(path)\n  end\n\n  def touch(path)\n    FileUtils.touch expand_path(path)\n  end\n\n  def mkpath(path)\n    FileUtils.mkpath expand_path(path)\n  end\n\n  describe \"#read\" do\n    it \"reads a file\" do\n      write 'file', 'content'\n      assert_equal 'content', store.read('file')\n    end\n  end\n\n  describe \"#write\" do\n    context \"with a string\" do\n      it \"creates the file when it doesn't exist\" do\n        store.write 'file', 'content'\n        assert exists?('file')\n        assert_equal 'content', read('file')\n      end\n\n      it \"updates the file when it exists\" do\n        touch 'file'\n        store.write 'file', 'content'\n        assert_equal 'content', read('file')\n      end\n    end\n\n    context \"with a Tempfile\" do\n      let :file do\n        Tempfile.new('tmp').tap do |file|\n          file.write 'content'\n          file.close\n        end\n      end\n\n      it \"creates the file when it doesn't exist\" do\n        store.write 'file', file\n        assert exists?('file')\n        assert_equal 'content', read('file')\n      end\n\n      it \"updates the file when it exists\" do\n        touch 'file'\n        store.write 'file', file\n        assert_equal 'content', read('file')\n      end\n    end\n\n    it \"recursively creates directories\" do\n      store.write '1/2/file', ''\n      assert exists?('1/2/file')\n    end\n  end\n\n  describe \"#delete\" do\n    it \"deletes a file\" do\n      touch 'file'\n      store.delete 'file'\n      refute exists?('file')\n    end\n\n    it \"deletes a directory\" do\n      mkpath '1/2'\n      touch '1/2/file'\n      store.delete '1'\n      refute exists?('1/2/exist')\n      refute exists?('1/2')\n      refute exists?('1')\n    end\n  end\n\n  describe \"#exist?\" do\n    it \"returns true when the file exists\" do\n      touch 'file'\n      assert store.exist?('file')\n    end\n\n    it \"returns false when the file doesn't exist\" do\n      refute store.exist?('file')\n    end\n  end\n\n  describe \"#mtime\" do\n    it \"returns the file modification time\" do\n      touch 'file'\n      created_at = Time.now.round - 86400\n      modified_at = created_at + 1\n      File.utime created_at, modified_at, expand_path('file')\n      assert_equal modified_at, store.mtime('file')\n    end\n  end\n\n  describe \"#size\" do\n    it \"returns the file's size\" do\n      write 'file', 'content'\n      assert_equal File.size(expand_path('file')), store.size('file')\n    end\n  end\n\n  describe \"#each\" do\n    let :paths do\n      paths = []\n      store.each { |path| paths << path.remove(tmp_path) }\n      paths\n    end\n\n    it \"yields file paths\" do\n      touch 'file'\n      assert_equal ['/file'], paths\n    end\n\n    it \"yields directory paths\" do\n      mkpath 'dir'\n      assert_equal ['/dir'], paths\n    end\n\n    it \"yields file paths recursively\" do\n      mkpath 'dir'\n      touch 'dir/file'\n      assert_includes paths, '/dir/file'\n    end\n\n    it \"yields directory paths recursively\" do\n      mkpath 'dir/dir'\n      assert_includes paths, '/dir/dir'\n    end\n\n    it \"doesn't yield file paths that start with '.'\" do\n      touch '.file'\n      assert_empty paths\n    end\n\n    it \"doesn't yield directory paths that start with '.'\" do\n      mkpath '.dir'\n      assert_empty paths\n    end\n\n    it \"yields directories before what's inside them\" do\n      mkpath 'dir'\n      touch 'dir/file'\n      assert paths.index('/dir') < paths.index('/dir/file')\n    end\n\n    context \"when the block deletes the directory\" do\n      it \"stops yielding what was inside it\" do\n        mkpath 'dir'\n        touch 'dir/file'\n        store.each do |path|\n          (@paths ||= []) << path\n          FileUtils.rm_rf(path) if path == expand_path('dir')\n        end\n        refute_includes @paths, expand_path('dir/file')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/support/fake_instrumentation.rb",
    "content": "module FakeInstrumentation\n  def instrument(event, payload = nil)\n    (@instrumentations ||= []) << { event: event, payload: payload }\n    yield payload if block_given?\n  end\n\n  def instrumentations\n    @instrumentations\n  end\n\n  def last_instrumentation\n    @instrumentations.try :last\n  end\nend\n"
  },
  {
    "path": "test/support/filter_test_helper.rb",
    "content": "module FilterTestHelper\n  extend ActiveSupport::Concern\n\n  included do\n    class_attribute :filter_class\n    class_attribute :filter_type\n  end\n\n  def filter\n    @filter ||= filter_class.new prepare_body(@body || ''), context, result\n  end\n\n  def filter_output\n    @filter_output ||= begin\n      filter.instance_variable_set :@html, prepare_body(@body) if @body\n      filter.call\n    end\n  end\n\n  def filter_output_string\n    @filter_output_string ||= filter_output.to_s\n  end\n\n  def filter_result\n    @filter_result ||= filter_output && result\n  end\n\n  class Context < Hash\n    def []=(key, value)\n      super key, key.to_s.end_with?('url') ? Docs::URL.parse(value) : value\n    end\n  end\n\n  def context\n    @context ||= Context.new\n  end\n\n  def result\n    @result ||= {}\n  end\n\n  def link_to(href)\n    %(<a href=\"#{href}\">Link</a>)\n  end\n\n  def prepare_body(body)\n    if self.class.filter_type == 'html'\n      Docs::Parser.new(body).html\n    else\n      body\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_helper.rb",
    "content": "ENV['RACK_ENV'] = 'test'\n\nrequire 'bundler/setup'\nBundler.require :test\n\n$LOAD_PATH.unshift 'lib'\n\nrequire 'minitest/autorun'\nrequire 'minitest/pride'\nrequire 'active_support'\nrequire 'active_support/core_ext'\nrequire 'active_support/testing/assertions'\nrequire 'rr'\n\nDir[File.dirname(__FILE__) + '/support/*.rb'].each do |file|\n  autoload File.basename(file, '.rb').camelize, file\nend\n\nActiveSupport::TestCase.test_order = :random\n\nclass Minitest::Spec\n  include ActiveSupport::Testing::Assertions\n\n  module DSL\n    def context(*args, &block)\n      describe(*args, &block)\n    end\n  end\nend\n\ndef tmp_path\n  $tmp_path ||= mk_tmp\nend\n\ndef mk_tmp\n  File.expand_path('../tmp', __FILE__).tap do |path|\n    FileUtils.mkdir(path)\n  end\nend\n\ndef rm_tmp\n  FileUtils.rm_rf $tmp_path if $tmp_path\nend\n\nMinitest.after_run do\n  rm_tmp\nend\n"
  },
  {
    "path": "views/app.erb",
    "content": "<div class=\"_app\" role=\"application\">\n  <header class=\"_header\" role=\"banner\">\n    <button type=\"button\" aria-label=\"Toggle navigation\" class=\"_header-btn\" data-toggle-sidebar hidden>\n      <svg viewBox=\"0 0 24 24\"><path d=\"M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z\"/></svg>\n    </button>\n    <form class=\"_search\" role=\"search\">\n      <svg><use xlink:href=\"#icon-search\"/></svg>\n      <input type=\"search\" name=\"q\" class=\"_search-input\" placeholder=\"Search&hellip;\" autocomplete=\"off\" autocapitalize=\"off\" autocorrect=\"off\" spellcheck=\"false\" maxlength=\"30\" aria-label=\"Search\">\n      <button type=\"reset\" class=\"_search-clear\" title=\"Clear search\"><svg><use xlink:href=\"#icon-close\"/></svg>Clear search</button>\n      <div class=\"_search-tag\"></div>\n    </form>\n    <button type=\"button\" aria-label=\"Back\" class=\"_header-btn\" data-back hidden>\n      <svg viewBox=\"0 0 24 24\"><path d=\"M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z\"/></svg>\n    </button>\n    <button type=\"button\" aria-label=\"Forward\" class=\"_header-btn _forward-btn\" data-forward hidden>\n      <svg viewBox=\"0 0 24 24\"><path d=\"M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z\"/></svg>\n    </button>\n    <button type=\"button\" aria-label=\"Toggle menu\" title=\"Toggle menu\" class=\"_header-btn _menu-btn\" data-toggle-menu>\n      <svg viewBox=\"0 0 24 24\"><path d=\"M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"/></svg>\n    </button>\n    <nav class=\"_menu\" role=\"navigation\">\n      <h1 class=\"_menu-title\"><a href=\"/\" class=\"_menu-title-link\">DevDocs</a></h1>\n      <a href=\"/settings\" class=\"_menu-link\">Preferences</a>\n      <a href=\"/offline\" class=\"_menu-link\">Offline Data</a>\n      <a href=\"/news\" class=\"_menu-link\">Changelog</a>\n      <a href=\"/help\" class=\"_menu-link\">Guide</a>\n      <a href=\"/about\" class=\"_menu-link\">About</a>\n      <a href=\"https://github.com/freeCodeCamp/devdocs/issues/new/choose\" class=\"_menu-link\">Report a bug</a>\n    </nav>\n  </header>\n  <section class=\"_sidebar\" tabindex=\"-1\">\n    <div class=\"_list\" role=\"navigation\"></div>\n  </section>\n  <div class=\"_container\" role=\"document\">\n    <main class=\"_content _content-loading\" role=\"main\"></main>\n  </div>\n  <form class=\"_settings\" id=\"settings\">\n    <div class=\"_header\">\n      <button type=\"button\" aria-label=\"Back\" class=\"_settings-btn _settings-btn-back\" data-back>\n        <svg viewBox=\"0 0 24 24\"><path d=\"M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z\"/></svg> Back\n      </button>\n      <button type=\"submit\" class=\"_settings-btn _settings-btn-save\">\n        <svg viewBox=\"0 0 24 24\"><path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\"/></svg> Apply\n      </button>\n      <nav class=\"_settings-tabs\">\n        <button type=\"button\" class=\"_settings-tab active\" data-tab=\"doc-picker\" hidden>Docs</button><button type=\"button\" class=\"_settings-tab\" data-tab=\"settings\" hidden>Settings</button>\n      </nav>\n    </div>\n    <div class=\"_sidebar\" tabindex=\"-1\"></div>\n  </form>\n</div>\n<svg style=\"display:none\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n  <defs>\n    <symbol id=\"icon-search\" viewBox=\"0 0 24 24\"><path d=\"M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z\"/></symbol>\n    <symbol id=\"icon-dir\" viewBox=\"0 0 20 20\"><path d=\"M15 10c0 .3-.305.515-.305.515l-8.56 5.303c-.625.41-1.135.106-1.135-.67V4.853c0-.777.51-1.078 1.135-.67l8.56 5.305S15 9.702 15 10z\"/></symbol>\n    <symbol id=\"icon-close\" viewBox=\"0 0 24 24\"><path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"/></symbol>\n    <symbol id=\"icon-copy\" viewBox=\"0 0 14 16\"><path d=\"M2 13h4v1H2v-1zm5-6H2v1h5V7zm2 3V8l-3 3 3 3v-2h5v-2H9zM4.5 9H2v1h2.5V9zM2 12h2.5v-1H2v1zm9 1h1v2c-.02.28-.11.52-.3.7-.19.18-.42.28-.7.3H1c-.55 0-1-.45-1-1V4c0-.55.45-1 1-1h3c0-1.11.89-2 2-2 1.11 0 2 .89 2 2h3c.55 0 1 .45 1 1v5h-1V6H1v9h10v-2zM2 5h8c0-.55-.45-1-1-1H8c-.55 0-1-.45-1-1s-.45-1-1-1-1 .45-1 1-.45 1-1 1H3c-.55 0-1 .45-1 1z\"/></symbol>\n    <symbol id=\"icon-external-link\" viewBox=\"0 0 24 24\"><path d=\"M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z\"/></symbol>\n  </defs>\n</svg>\n"
  },
  {
    "path": "views/index.erb",
    "content": "<!DOCTYPE html>\n<html prefix=\"og: http://ogp.me/ns#\" lang=\"en\" class=\"_booting\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,shrink-to-fit=no\">\n  <meta name=\"description\" content=\"Fast, offline, and free documentation browser for developers. Search 100+ docs in one web app: HTML, CSS, JavaScript, PHP, Ruby, Python, Go, C, C++…\">\n  <meta property=\"application-name\" content=\"DevDocs\">\n  <meta property=\"og:title\" content=\"DevDocs\">\n  <meta property=\"og:description\" content=\"Fast, offline, and free documentation browser for developers. Search 100+ docs in one web app including HTML, CSS, JavaScript, PHP, Ruby, Python, Go, C, C++, and many more.\">\n  <meta property=\"og:type\" content=\"website\">\n  <meta property=\"og:url\" content=\"<%= canonical_origin %>\">\n  <meta property=\"og:image\" content=\"/images/icon-320.png\">\n  <meta property=\"twitter:title\" content=\"DevDocs API Documentation\">\n  <meta property=\"twitter:description\" content=\"Fast, offline, and free documentation browser for developers. Search 100+ docs in one web app: HTML, CSS, JavaScript, PHP, Ruby, Python, Go, C, C++…\">\n  <meta property=\"twitter:card\" content=\"summary_large_image\">\n  <meta property=\"twitter:url\" content=\"<%= canonical_origin %>\">\n  <meta property=\"twitter:image\" content=\"/images/icon-320.png\">\n  <meta name=\"apple-mobile-web-app-title\" content=\"DevDocs\">\n  <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n  <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black\">\n  <meta name=\"format-detection\" content=\"telephone=no\">\n  <meta name=\"theme-color\" content=\"#eee\">\n  <meta name=\"robots\" content=\"noodp\">\n  <title>DevDocs API Documentation</title>\n  <link rel=\"canonical\" href=\"<%= canonical_origin %>\">\n  <link rel=\"manifest\" href=\"/manifest.json\">\n  <link rel=\"search\" type=\"application/opensearchdescription+xml\" href=\"/opensearch.xml\" title=\"DevDocs Search\">\n  <link rel=\"alternate\" href=\"<%= canonical_origin %>/feed\" title=\"DevDocs\" type=\"application/atom+xml\">\n  <link rel=\"icon\" type=\"image/x-icon\" href=\"/favicon.ico\">\n  <link rel=\"fluid-icon\" href=\"/images/fluid-icon.png\" title=\"DevDocs\">\n  <link rel=\"apple-touch-icon\" sizes=\"72x72\" href=\"/images/apple-icon-72.png\">\n  <link rel=\"apple-touch-icon\" sizes=\"76x76\" href=\"/images/apple-icon-76.png\">\n  <link rel=\"apple-touch-icon\" sizes=\"114x114\" href=\"/images/apple-icon-114.png\">\n  <link rel=\"apple-touch-icon\" sizes=\"120x120\" href=\"/images/apple-icon-120.png\">\n  <link rel=\"apple-touch-icon\" sizes=\"144x144\" href=\"/images/apple-icon-144.png\">\n  <link rel=\"apple-touch-icon\" sizes=\"152x152\" href=\"/images/apple-icon-152.png\">\n  <link rel=\"apple-touch-icon\" sizes=\"160x160\" href=\"/images/apple-icon-160.png\">\n  <link rel=\"mask-icon\" href=\"/images/webkit-mask-icon.svg\" color=\"#398df0\">\n  <%= stylesheet_tag 'application' %>\n</head>\n<body>\n<noscript class=\"_fail\">DevDocs requires JavaScript to run.</noscript>\n<%= erb :app -%>\n<%= javascript_tag 'application' %>\n<%= javascript_tag 'docs' %><% unless App.production? %>\n<%= javascript_tag 'debug' %><% end %>\n</body>\n</html>\n"
  },
  {
    "path": "views/other.erb",
    "content": "<!DOCTYPE html>\n<html lang=\"en\" class=\"_booting\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n  <% if doc_index_page? %><meta name=\"description\" content=\"<%= @doc['name'] %> <%= @doc['release'] %> API documentation with instant search, offline support, keyboard shortcuts, mobile version, and more.\"><% else %><meta name=\"robots\" content=\"noindex\"><% end %>\n  <meta name=\"format-detection\" content=\"telephone=no\">\n  <meta name=\"theme-color\" content=\"#eee\">\n  <meta property=\"og:image\" content=\"/images/icon-320.png\">\n  <title>DevDocs<%= \" &mdash; #{@doc['full_name']} documentation\" if doc_index_page? %></title>\n  <link rel=\"canonical\" href=\"<%= canonical_origin %><%= request.path %>\">\n  <link rel=\"manifest\" href=\"/manifest.json\">\n  <link rel=\"icon\" type=\"image/x-icon\" href=\"/favicon.ico\">\n  <link rel=\"search\" type=\"application/opensearchdescription+xml\" href=\"/opensearch.xml\" title=\"Search DevDocs\">\n  <%= stylesheet_tag 'application' %>\n</head>\n<body data-doc=\"<%= CGI::escape_html @doc.to_json %>\">\n<noscript class=\"_fail\">DevDocs requires JavaScript to run.</noscript>\n<%= erb :app -%>\n<%= javascript_tag 'application' %><% unless App.production? %>\n<%= javascript_tag 'debug' %><% end %>\n</body>\n</html>\n"
  },
  {
    "path": "views/service-worker.js.erb",
    "content": "<%# The name of the cache to store responses in %>\n<%# If the cache name changes DevDocs is assumed to be updated %>\nconst cacheName = '<%= service_worker_cache_name %>';\n\n<%# Url's to cache when the service worker is installed %>\nconst urlsToCache = [\n  '/',\n  '/favicon.ico',\n  '/manifest.json',\n  '<%= service_worker_asset_urls.join \"',\\n  '\" %>',\n  '<%= doc_index_urls.join \"',\\n  '\" %>',\n];\n\n<%# Set-up the cache %>\nself.addEventListener('install', event => {\n  self.skipWaiting();\n\n  event.waitUntil(\n    caches.open(cacheName).then(cache => cache.addAll(urlsToCache)),\n  );\n});\n\n<%# Remove old caches %>\nself.addEventListener('activate', event => {\n  event.waitUntil((async () => {\n    const keys = await caches.keys();\n    const jobs = keys.map(key => key !== cacheName ? caches.delete(key) : Promise.resolve());\n    return Promise.all(jobs);\n  })());\n});\n\n<%# Handle HTTP requests %>\nself.addEventListener('fetch', event => {\n  event.respondWith((async () => {\n    const cachedResponse = await caches.match(event.request);\n    if (cachedResponse) return cachedResponse;\n\n    try {\n      const response = await fetch(event.request);\n      return response;\n    } catch (err) {\n      const url = new URL(event.request.url);\n\n      const pathname = url.pathname;\n      const filename = pathname.substr(1 + pathname.lastIndexOf('/')).split(/\\#|\\?/g)[0];\n      const extensions = ['.html', '.css', '.js', '.json', '.png', '.ico', '.svg', '.xml'];\n\n      <%# Attempt to return the index page from the cache if the user is visiting a url like devdocs.io/offline or devdocs.io/javascript/global_objects/array/find %>\n      <%# The index page will make sure the correct documentation or a proper offline page is shown  %>\n      if (url.origin === location.origin && !extensions.some(ext => filename.endsWith(ext))) {\n        const cachedIndex = await caches.match('/');\n        if (cachedIndex) return cachedIndex;\n      }\n\n      throw err;\n    }\n  })());\n});\n"
  },
  {
    "path": "views/unsupported.erb",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>DevDocs &mdash; API Documentation Browser</title>\n  <%= stylesheet_tag 'application' %>\n</head>\n<body>\n  <div class=\"_fail\">\n    <h1 class=\"_fail-title\">Your browser is unsupported, sorry.</h1>\n    <p class=\"_fail-text\">DevDocs is an API documentation browser which supports the following browsers:</p>\n    <ul class=\"_fail-list\">\n      <li>Recent versions of Firefox, Chrome, or Opera</li>\n      <li>Safari 11.1+</li>\n      <li>Edge 17+</li>\n      <li>iOS 11.3+</li>\n    </ul>\n    <p class=\"_fail-text\">\n      If you're unable to upgrade, we apologize.\n      We decided to prioritize speed and new features over support for older browsers.\n    </p>\n    <p class=\"_fail-text\">\n      &mdash; <a href=\"https://twitter.com/DevDocs\">@DevDocs</a>\n    </p>\n  </div>\n</body>\n</html>\n"
  }
]