[
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "// For format details, see https://aka.ms/devcontainer.json. For config options, see the\n// README at: https://github.com/devcontainers/templates/tree/main/src/ruby\n{\n\t\"name\": \"sdoc\",\n\t// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile\n\t\"image\": \"ghcr.io/rails/devcontainer/images/ruby:3.4.5\",\n\t\"features\": {\n\t\t\"ghcr.io/devcontainers/features/github-cli:1\": {}\n\t}\n\n\t// Features to add to the dev container. More info: https://containers.dev/features.\n\t// \"features\": {},\n\n\t// Use 'forwardPorts' to make a list of ports inside the container available locally.\n\t// \"forwardPorts\": [],\n\n\t// Use 'postCreateCommand' to run commands after the container is created.\n\t// \"postCreateCommand\": \"ruby --version\",\n\n\t// Configure tool-specific properties.\n\t// \"customizations\": {},\n\n\t// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.\n\t// \"remoteUser\": \"root\"\n}\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Tests\n\non:\n  push:\n  pull_request:\n    branches: [ main ]\n  schedule:\n    - cron: '0 0 * * *'\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        ruby:\n          - '2.7'\n          - '3.0'\n          - '3.1'\n          - '3.2'\n          - '3.3'\n          - '3.4'\n          - 'ruby-head'\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Set up Ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: ${{ matrix.ruby }}\n          bundler-cache: true\n\n      - name: Test\n        run: |\n          bundle exec rake\n        continue-on-error: true\n"
  },
  {
    "path": ".gitignore",
    "content": "*.gem\n.bundle\npkg\ndoc\n/test.rb\nGemfile.lock\n/.rake_tasks~\n/*.gem\n/rails/\n/ruby/\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "Main (3.0.0.alpha)\n==================\n\n* [#198](https://github.com/rails/sdoc/pull/198) Bump version to 3.0.0.alpha [@zzak](https://github.com/zzak)\n* [#197](https://github.com/rails/sdoc/pull/197) Remove sdoc-merge and sdoc template [@zzak](https://github.com/zzak)\n* [#195](https://github.com/rails/sdoc/pull/195) Replace turbolinks with turbo v7.3.0 [@zzak](https://github.com/zzak)\n* [#200](https://github.com/rails/sdoc/pull/200) Add canonical url link to all pages [@zzak](https://github.com/zzak)\n* [#222](https://github.com/rails/sdoc/pull/222) Add NameList for building a static file of classes and methods [@zzak](https://github.com/zzak)\n* [#206](https://github.com/rails/sdoc/pull/206) Move file links to the footer [@p8](https://github.com/p8)\n* [#237](https://github.com/rails/sdoc/pull/237) Hide scrollbar for unscrollable code snippets [@jonathanhefner](https://github.com/jonathanhefner)\n* [#239](https://github.com/rails/sdoc/pull/239) Replace highlight.js with Rouge gem [@jonathanhefner](https://github.com/jonathanhefner)\n* [#242](https://github.com/rails/sdoc/pull/242) Use Rouge to highlight method source code [@jonathanhefner](https://github.com/jonathanhefner)\n* [#243](https://github.com/rails/sdoc/pull/243) Enable horizontal scrollbar for method source code [@jonathanhefner](https://github.com/jonathanhefner)\n* [#251](https://github.com/rails/sdoc/pull/251) Move alias listing closer to method name [@jonathanhefner](https://github.com/jonathanhefner)\n* [#252](https://github.com/rails/sdoc/pull/252) Replace source code toggler JavaScript with `<details>` [@jonathanhefner](https://github.com/jonathanhefner)\n* [#253](https://github.com/rails/sdoc/pull/253) Version links to Rails guides [@jonathanhefner](https://github.com/jonathanhefner)\n* [#254](https://github.com/rails/sdoc/pull/254) Unlink unintentional autolinked code ref links [@jonathanhefner](https://github.com/jonathanhefner)\n* [#255](https://github.com/rails/sdoc/pull/255) Style unstyled code ref links [@jonathanhefner](https://github.com/jonathanhefner)\n* [#260](https://github.com/rails/sdoc/pull/260) Include site title in page titles [@jonathanhefner](https://github.com/jonathanhefner)\n* [#261](https://github.com/rails/sdoc/pull/261) Include project name and version in `og:title` [@jonathanhefner](https://github.com/jonathanhefner)\n* [#262](https://github.com/rails/sdoc/pull/262) Refine meta description [@jonathanhefner](https://github.com/jonathanhefner)\n* [#265](https://github.com/rails/sdoc/pull/265) Add required Open Graph meta tags [@jonathanhefner](https://github.com/jonathanhefner)\n* [#266](https://github.com/rails/sdoc/pull/266) Add `article:modified_time` meta tag [@jonathanhefner](https://github.com/jonathanhefner)\n* [#268](https://github.com/rails/sdoc/pull/268) Add initial dark mode support [@p8](https://github.com/p8)\n* [#279](https://github.com/rails/sdoc/pull/279) Redesign \"Appears in\" file list [@jonathanhefner](https://github.com/jonathanhefner)\n* [#280](https://github.com/rails/sdoc/pull/280) Redesign banner and mobile menu bar [@jonathanhefner](https://github.com/jonathanhefner)\n* [#283](https://github.com/rails/sdoc/pull/283), [#336](https://github.com/rails/sdoc/pull/336) Rework typography [@jonathanhefner](https://github.com/jonathanhefner)\n* [#286](https://github.com/rails/sdoc/pull/286) Render breadcrumb links for module's parents [@jonathanhefner](https://github.com/jonathanhefner)\n* [#287](https://github.com/rails/sdoc/pull/287) Generate uniform `<h1>` headings for all modules [@jonathanhefner](https://github.com/jonathanhefner)\n* [#291](https://github.com/rails/sdoc/pull/291) Remove keyboard shortcuts that use meta keys [@jonathanhefner](https://github.com/jonathanhefner)\n* [#294](https://github.com/rails/sdoc/pull/294) Remove version badge [@jonathanhefner](https://github.com/jonathanhefner)\n* [#298](https://github.com/rails/sdoc/pull/298) Rewrite search [@jonathanhefner](https://github.com/jonathanhefner)\n* [#315](https://github.com/rails/sdoc/pull/315) Replace navigation tree [@jonathanhefner](https://github.com/jonathanhefner)\n* [#319](https://github.com/rails/sdoc/pull/319) Rework method spotlight effect [@jonathanhefner](https://github.com/jonathanhefner)\n* [#320](https://github.com/rails/sdoc/pull/320) Remove jQuery [@jonathanhefner](https://github.com/jonathanhefner)\n* [#322](https://github.com/rails/sdoc/pull/322) Use `type=\"search\"` for search input [@jonathanhefner](https://github.com/jonathanhefner)\n* [#323](https://github.com/rails/sdoc/pull/323) Update favicon [@jonathanhefner](https://github.com/jonathanhefner)\n* [#345](https://github.com/rails/sdoc/pull/345) Version explicit links to `api.rubyonrails.org` [@jonathanhefner](https://github.com/jonathanhefner)\n* [#356](https://github.com/rails/sdoc/pull/356) Redesign \"Constants\" section [@jonathanhefner](https://github.com/jonathanhefner)\n* [#357](https://github.com/rails/sdoc/pull/357) Support permalinking constants [@jonathanhefner](https://github.com/jonathanhefner)\n* [#358](https://github.com/rails/sdoc/pull/358) Add constants to search index [@jonathanhefner](https://github.com/jonathanhefner)\n* [#370](https://github.com/rails/sdoc/pull/370) Add attributes to search index [@earlopain](https://github.com/earlopain)\n\n2.6.1\n=====\n\n* [#194](https://github.com/rails/sdoc/pull/194) Remove `text-align: justify` on paragraphs [@zzak](https://github.com/zzak)\n\n2.6.0\n=====\n\n* #193 Add support for Ruby 3.2 as File.exists? has been removed [@sato11](https://github.com/sato11)\n\n2.5.0\n=====\n\n* #192 Add padding and rounded borders to code examples [@p8](https://github.com/p8)\n* #191 Add css class for constant [@p8](https://github.com/p8)\n* #189 `index_path` should handle a nil `main_page` [@ghiculescu](https://github.com/ghiculescu)\n\n2.4.0\n=====\n\n* #187 Allow setting CSS based version badge [@p8](https://github.com/p8)\n\n2.3.2\n=====\n\n* #184 Add support for rdoc 6.4.0 [@p8](https://github.com/p8)\n\n2.3.1\n=====\n\n* #183 Remove unsupported browser detection  [@p8](https://github.com/p8)\n* #182 Use window.location instead of Turbolinks.visit if protocol is 'file:' [@p8](https://github.com/p8)\n\n2.3.0\n=====\n\n* #178 Don't use rdoc 6.4.0 for now [@p8](https://github.com/p8)\n* #177 Remove rake version constraint for ruby head [@p8](https://github.com/p8)\n* #176 Make sidepanel work with relative paths/URLs [@p8](https://github.com/p8)\n* #175 Avoid displaying source toggler for ghost methods [Robin Dupret](https://github.com/robin850)\n* #174 Suppress unused variable warnings [Masataka Pocke Kuwabara](https://github.com/pocke)\n\n2.2.0\n=====\n\n* #161 Add 'skip to content' link and improve shortcut keys [@MikeRogers0](https://github.com/MikeRogers0)\n* #170 Fix link hovers in headings [@tlatsas](https://github.com/tlatsas)\n* #169 Fix clearing search results [@mikdiet](https://github.com/mikdiet)\n* #167 Update Merge script to work with sdoc v2 [@mikdiet](https://github.com/mikdiet)\n* #160 Remove outline from reset stylesheet [@p8](https://github.com/p8)\n* #159 Remove TAB override in panel [@p8](https://github.com/p8)\n* #157 Move to GitHub action for tests [@MikeRogers0](https://github.com/MikeRogers0)\n* #155 Fix Ctrl+C copying [Jan Schär](https://github.com/jscissr)\n\n2.1.0\n=====\n\n* #154 Make panel responsive for mobile [@MikeRogers0](https://github.com/MikeRogers0) and [@p8](https://github.com/p8)\n* #153 Add viewport metatag to views for improved Lighthouse score. [@MikeRogers0](https://github.com/MikeRogers0)\n* #150 Use semantic headers for better SEO [@p8](https://github.com/p8)\n\n2.0.4\n=====\n\n* #149 Using HTML5 doctype across all HTML files. [@MikeRogers0](https://github.com/MikeRogers0)\n* #148 Fix overflow CSS property of panel elements. [@cveneziani](https://github.com/cveneziani)\n\n2.0.3\n=====\n\n* #147 Use @options.title for the index [@p8](https://github.com/p8)\n\n2.0.2\n=====\n\n* Remove accidental rack inclusion in gemspec\n\n2.0.1\n=====\n\n* #142 Fix arrow icons for selected panel items [@p8](https://github.com/p8)\n* #141 Always use only one metatag for keywords [@p8](https://github.com/p8)\n* #140 Use h2 instead of h1 for banner header [@p8](https://github.com/p8)\n\n2.0.0\n=====\n\n* #137 Replace frames based implementation with a css [@p8](https://github.com/p8)\n* #132 Deprecate safe_level of ERB.new in Ruby 2.6\n\n1.1.0\n=====\n\n* #138 - Fix panel header overflow on Chrome\n* 39e6cae9 - Display version using `-v` or `--version` flags\n\n1.0.0\n=====\n\n* #110 - Strip out HTML tags from search results description\n* #109 - Add basic SEO tags\n* #108 - Tiny refresh of the Rails theme\n* e6f02b91 - Remove the jQuery effect library\n* 73ace366 - Remove the `--without-search` option\n* b1d429f2 - Produce HTML 5 output\n* 38d06095 - Support only RDoc 5 and up\n* #96 - Require at least Ruby 1.9.3\n\n0.4.2\n=====\n\n[Compare v0.4.1...v0.4.2](https://github.com/voloko/sdoc/compare/v0.4.1...v0.4.2)\n\n0.4.1\n=====\n\n[Compare v0.4.0...v0.4.1](https://github.com/voloko/sdoc/compare/v0.4.0...v0.4.1)\n\nBreaking Changes\n----------------\n\nNone.\n\nEnhancements\n------------\n\n- 65e46cb2 Unordered lists inside ordered ones render ordered\n- SDoc::VERSION\n  - 2fe1a7b8 Move version to separate file, remove require_relative from gemspec\n  - 97e1eda8 Push ./lib to $LOAD_PATH for require SDoc::VERSION\n  - ad0a7e1e Initialize SDoc namespace in main file\n\nBug Fixes\n---------\n\n- 926ff732 Remove redundancy < 5.0 from rdoc dependency specification\n- db99e402 Remove code tags styling under pre elements\n- a1d7e211 Follow up of #68\n- bffc93ef Relax JSON dependency to ~> 1.7, >= 1.7.7\n- 404dceb9 GH-72: Extra `<p>` tags appear in results snippet\n\n0.4.0\n=====\n\n[Compare v0.3.20...v0.4.0](https://github.com/voloko/sdoc/compare/v0.3.20...v0.4.0)\n\nNo friendly log for this version yet, but PRs are welcome!\n"
  },
  {
    "path": "Gemfile",
    "content": "source \"https://rubygems.org\"\n\ngemspec\n\ngem \"rack\", \"~> 3.0\"\ngem \"rake\"\ngem \"hoe\"\ngem \"minitest\"\ngem \"puma\"\n\nif ENV[\"rdoc\"] == \"master\"\n  gem \"rdoc\", :github => \"ruby/rdoc\"\nend\n\ngem \"importmap-rails\"\ngem \"railties\"\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2014 Vladimir Kolesnikov, and Nathan Broadbent\nCopyright (c) 2014-2017 Zachary Scott\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\n\nDarkfish RDoc HTML Generator\n\nCopyright (c) 2007, 2008, Michael Granger. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice,\n  this list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\n* Neither the name of the author/s, nor the names of the project's\n  contributors may be used to endorse or promote products derived from this\n  software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\nRDoc is copyrighted free software.\n\nYou can redistribute it and/or modify it under either the terms of the GPL\nversion 2 (see the file GPL), or the conditions below:\n\n  1. You may make and give away verbatim copies of the source form of the\n     software without restriction, provided that you duplicate all of the\n     original copyright notices and associated disclaimers.\n\n  2. You may modify your copy of the software in any way, provided that\n     you do at least ONE of the following:\n\n       a) place your modifications in the Public Domain or otherwise\n          make them Freely Available, such as by posting said\n          modifications to Usenet or an equivalent medium, or by allowing\n          the author to include your modifications in the software.\n\n       b) use the modified software only within your corporation or\n          organization.\n\n       c) give non-standard binaries non-standard names, with\n          instructions on where to get the original software distribution.\n\n       d) make other distribution arrangements with the author.\n\n  3. You may distribute the software in object code or binary form,\n     provided that you do at least ONE of the following:\n\n       a) distribute the binaries and library files of the software,\n          together with instructions (in the manual page or equivalent)\n          on where to get the original distribution.\n\n       b) accompany the distribution with the machine-readable source of\n          the software.\n\n       c) give non-standard binaries non-standard names, with\n          instructions on where to get the original software distribution.\n\n       d) make other distribution arrangements with the author.\n\n  4. You may modify and include the part of the software into any other\n     software (possibly commercial).  But some files in the distribution\n     are not written by the author, so that they are not under these terms.\n\n     For the list of those files and their copying conditions, see the\n     file LEGAL.\n\n  5. The scripts and library files supplied as input to or produced as\n     output from the software do not automatically fall under the\n     copyright of the software, but belong to whomever generated them,\n     and may be sold commercially, and may be aggregated with this\n     software.\n\n  6. THIS SOFTWARE IS PROVIDED \"AS IS\" AND WITHOUT ANY EXPRESS OR\n     IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED\n     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n     PURPOSE.\n"
  },
  {
    "path": "README.md",
    "content": "# SDoc\n\n[![Tests](https://github.com/rails/sdoc/actions/workflows/test.yml/badge.svg)](https://github.com/rails/sdoc/actions/workflows/test.yml)\n\n**Powering http://api.rubyonrails.org/**\n\n### What is sdoc?\n\nSDoc is an HTML template built on top of the RDoc documentation generator for Ruby code.\n\n### Getting Started\n\n```bash\n# Install the gem\ngem install sdoc\n\n# Generate documentation for 'projectdir'\nsdoc projectdir\n```\n\n### sdoc\n\n`sdoc` is simply a wrapper for the `rdoc` command line tool. See `sdoc --help` for more details.\n\nWhen using the `sdoc` command, `--fmt` is set to `shtml` by default. The default template (or `-T` option) is set to `shtml`, but you can also use the `direct` template when generating documentation.\n\nExample:\n\n```bash\nsdoc -o doc/rails -T direct rails\n```\n\n### Rake Task\n\nIf you want, you can setup a task in your `Rakefile` for generating your project's documentation via the `rake rdoc` command.\n\n```ruby\n# Rakefile\nrequire 'sdoc' # and use your RDoc task the same way you used it before\nrequire 'rdoc/task' # ensure this file is also required in order to use `RDoc::Task`\n\nRDoc::Task.new do |rdoc|\n  rdoc.rdoc_dir = 'doc/rdoc'      # name of output directory\n  rdoc.options << '--format=sdoc' # explicitly set the sdoc generator\n  rdoc.template = 'rails'         # template used on api.rubyonrails.org\nend\n```\n\nNOTE: If you don't set `template` the default \"sdoc\" template is chosen, with a lighter color scheme.\n\nNow you can execute this command with `rake rdoc`, to compile the documentation for the current project directory.\n\nAlternatively you can pass this command a path to the project you wish to compile: `rake rdoc path/to/project`.\n\n### RDoc\n\nAs mentioned before, SDoc is built on top of the RDoc project.\n\nIf you notice any bugs in the output of your documentation, it may be RDoc's fault and should be [reported upstream](https://github.com/ruby/rdoc/issues/new).\n\nAn example of an SDoc bug would be:\n\n* Error or warning in JavaScript or HTML found in your browser\n* Generation fails with some exception (likely due to incompatibility with RDoc)\n\nPlease feel free to still report issues here for both projects, especially if you aren't sure. We will try to redirect to the proper place if necessary.\n\n## Contributing\n\nIf you'd like to contribute you can generate the Rails main branch documentation by running:\n\n```bash\nbundle exec rake test:rails\n```\n\nYou can generate the Ruby default branch documentation by running:\n\n```bash\nbundle exec rake test:ruby\n```\n\nThe generated documentation will be put into `doc/public` directory.\nTo view the just generated documentation start up a rack application by running:\n\n```bash\nbundle exec rackup config.ru\n```\n\nThen open http://localhost:9292 in the browser to view the documentation.\n\n### Who?\n\n* Vladimir Kolesnikov ([voloko](https://github.com/voloko))\n* Nathan Broadbent ([ndbroadbent](https://github.com/ndbroadbent))\n* Petrik de Heus ([p8](https://github.com/p8))\n* ([zzak](https://github.com/zzak))\n"
  },
  {
    "path": "Rakefile",
    "content": "require 'bundler'\nBundler::GemHelper.install_tasks\n\nrequire 'rake/testtask'\n\nRake::TestTask.new do |t|\n  t.pattern = \"spec/*_spec.rb\"\n  t.libs << \"spec\"\nend\n\ntask :default => :test\ntask :spec => :test\n\nrequire 'sdoc'\nrequire 'rdoc/task'\nrequire 'rails/api/task'\n\nrails = File.expand_path \"rails\"\nruby = File.expand_path \"ruby\"\n\ndirectory rails do\n  sh \"git clone --depth=1 https://github.com/rails/rails\"\nend\n\ndirectory ruby do\n  sh \"git clone --depth=1 https://github.com/ruby/ruby\"\nend\n\nclass RailsTask < Rails::API::EdgeTask\n  def configure_sdoc\n    options << \"--root\" << \"rails\"\n    super\n    self.title = nil # Use default title for local testing.\n  end\n\n  def rails_version\n    Dir.chdir \"rails\" do\n      super\n    end\n  end\n\n  def api_dir\n    \"doc/rails\"\n  end\n\n  def component_root_dir(component)\n    File.join(\"rails\", component)\n  end\n\n  def setup_horo_variables\n    super\n\n    ENV[\"HORO_BADGE_VERSION\"] ||= \"edge\" if ENV[\"HORO_PROJECT_VERSION\"]&.include?(\"@\")\n\n    if ENV['NETLIFY']\n      ENV['HORO_CANONICAL_URL'] = ENV.fetch('DEPLOY_PRIME_URL', 'https://edgeapi.rubyonrails.org')\n    end\n  end\nend\n\nnamespace :test do\n  desc 'Deletes all generated test documentation'\n  task :reset_docs do\n    FileUtils.remove_dir(File.expand_path('doc'), force: true)\n  end\n\n  desc 'Generates test rails documentation'\n  task :rails => [rails, :generate_rails] do\n    FileUtils.mv(\n      File.expand_path('rails/doc/rails'),\n      File.expand_path('doc/public')\n    )\n  end\n\n  RailsTask.new(:generate_rails)\n\n  desc 'Generates test ruby documentation'\n  task :ruby => [ruby, :generate_ruby] do\n    FileUtils.mv(\n      File.expand_path('doc/ruby'),\n      File.expand_path('doc/public')\n    )\n  end\n\n  RDoc::Task.new(:generate_ruby) do |rdoc|\n    rdoc.rdoc_dir = 'doc/ruby'\n    rdoc.generator = 'sdoc'\n    rdoc.template = 'rails'\n    rdoc.title = 'Ruby'\n    rdoc.main = 'ruby/README.md'\n\n    rdoc.rdoc_files.include(\"ruby/\")\n  end\nend\n\nASSETS_PATH = \"lib/rdoc/generator/template/rails/resources\"\n\ndesc \"Download and vendor JavaScript assets\"\ntask :vendor_javascript do\n  module Importmap; end\n  require \"importmap/packager\"\n\n  packager = Importmap::Packager.new(vendor_path: \"#{ASSETS_PATH}/js\")\n  imports = packager.import(\"@hotwired/turbo\", from: \"unpkg\")\n  imports.each do |package, url|\n    puts %(Vendoring \"#{package}\" to #{packager.vendor_path}/#{package}.js via download from #{url})\n    packager.download(package, url)\n  end\nend\n"
  },
  {
    "path": "bin/sdoc",
    "content": "#!/usr/bin/env ruby\nrequire 'sdoc'\n\nbegin\n  ARGV.unshift('--format=sdoc') if ARGV.grep(/\\A(-f|--fmt|--format|-r|-R|--ri|--ri-site)\\b/).empty?\n  r = RDoc::RDoc.new\n  r.document ARGV\nrescue SystemExit\n  raise\nrescue Exception => e\n  if $DEBUG_RDOC then\n    $stderr.puts e.message\n    $stderr.puts \"#{e.backtrace.join \"\\n\\t\"}\"\n    $stderr.puts\n  elsif Interrupt === e then\n    $stderr.puts\n    $stderr.puts 'Interrupted'\n  else\n    $stderr.puts \"uh-oh! RDoc had a problem:\"\n    $stderr.puts e.message\n    $stderr.puts\n    $stderr.puts \"run with --debug for full backtrace\"\n  end\n\n  exit 1\nend\n"
  },
  {
    "path": "config.ru",
    "content": "# Rack application for serving the documentation locally.\n# After generating the documentation run:\n#\n#   bundle exec rackup config.ru\n#\nrequire 'bundler/setup'\n\nroot = \"doc/public\"\nunless Dir.exist?(root)\n  puts <<~MESSAGE\n    Could not find any docs in #{root}.\n    Run the following command to generate sample documentation:\n      bundle exec rake test:rails\n  MESSAGE\n  exit\nend\n\nrequire \"rack/static\"\nuse Rack::Static,\n  :urls => [\"/files\", \"/images\", \"/js\", \"/css\", \"/panel\", \"/i\", \"/fonts\", \"/classes\", \"/ruby\", \"/rails\"],\n  :root => root\nrun lambda { |env|\n  [\n    200,\n    {\n      'content-type'  => 'text/html',\n      'cache-control' => 'public, max-age=86400'\n    },\n    File.open(\"#{root}/index.html\", File::RDONLY)\n  ]\n}\n"
  },
  {
    "path": "lib/rdoc/discover.rb",
    "content": "begin\n  gem 'rdoc', '>= 5.0'\n  require_relative \"../sdoc\" unless defined?(SDoc)\nrescue Gem::LoadError\nend\n"
  },
  {
    "path": "lib/rdoc/generator/template/rails/_context.rhtml",
    "content": "<div id=\"context\">\n  <% unless (description = @context.description).empty? %>\n    <div class=\"description\">\n      <%= description %>\n    </div>\n  <% end %>\n\n\n  <%# File only: requires %>\n  <% unless @context.requires.empty? %>\n    <h2 class=\"content__divider\">Required Files</h2>\n    <ul>\n      <% @context.requires.each do |req| %>\n        <li><%= full_name_for req.name %></li>\n      <% end %>\n    </ul>\n  <% end %>\n\n\n  <%# Module only: ancestors %>\n  <% unless @context.is_a?(RDoc::TopLevel) || (ancestors = module_ancestors(@context)).empty? %>\n    <h2 class=\"content__divider\">Inherits From</h2>\n    <ul>\n      <% ancestors.each do |kind, ancestor| %>\n        <li>\n          <span class=\"kind\"><%= kind %></span>\n          <%= ancestor.is_a?(String) ? full_name_for(ancestor) : link_to(ancestor) %>\n        </li>\n      <% end %>\n    </ul>\n  <% end %>\n\n\n  <% sections = @context.sections.select { |s| s.title }.sort_by{ |s| s.title.to_s } %>\n  <% unless sections.empty? then %>\n    <!-- Sections -->\n    <h2 class=\"content__divider\">Sections</h2>\n    <ul>\n      <% sections.each do |section| %>\n        <li><%= link_to h(section.title), \"##{section.aref}\" %></li>\n      <% end %>\n    </ul>\n  <% end %>\n\n\n\n  <% @context.each_section do |section, constants, attributes| %>\n\n    <% if section.title then %>\n      <div class=\"content__section-title\" id=\"<%= h section.aref %>\">\n        <%= h section.title %>\n      </div>\n    <% end %>\n\n    <%= description_for section %>\n\n    <!-- Constants -->\n    <% unless constants.empty? %>\n      <h2 class=\"content__divider\">Constants</h2>\n      <% constants.each do |constant| %>\n        <div class=\"constant\" id=\"<%= constant.aref %>\">\n          <div class=\"constant__name permalink-container\">\n            <h3><%= short_name_for constant %></h3>\n            <%= link_to %(<img src=\"/i/link.svg\" alt=\"Permalink\">), constant,\n                  class: \"permalink\", title: \"Permalink\" %>\n          </div>\n          <%= description_for constant %>\n          <div class=\"constant__source\">\n            <pre class=\"source-code\"><code class=\"ruby\"><%= h constant.value %></code></pre>\n          </div>\n        </div>\n      <% end %>\n    <% end %>\n\n    <% unless attributes.empty? %>\n      <!-- Section attributes -->\n      <h2 class=\"content__divider\">Attributes</h2>\n      <table border='0' cellpadding='5'>\n        <% attributes.each do |attrib| %>\n          <tr valign='top' id='<%= attrib.aref %>'>\n            <td class='attr-rw'>\n              [<%= attrib.rw %>]\n            </td>\n            <td class='attr-name'><%= short_name_for attrib %></td>\n            <td class='attr-desc'><%= attrib.description.strip %></td>\n          </tr>\n        <% end %>\n      </table>\n    <% end %>\n\n    <!-- Methods -->\n    <%\n      @context.methods_by_type(section).each do |type, visibilities|\n        next if visibilities.empty?\n\n        visibilities.each do |visibility, methods|\n          next if methods.empty?\n    %>\n      <h2 class=\"content__divider\"><%= visibility.to_s.capitalize %> <%= type %> methods</h2>\n      <% methods.each do |method| %>\n        <div class=\"method\" id=\"<%= method.aref %>\">\n          <div class=\"method__signature permalink-container\">\n            <h3><%= method_signature method %></h3>\n            <%= link_to %(<img src=\"/i/link.svg\" alt=\"Permalink\">), method,\n                  class: \"permalink\", title: \"Permalink\" %>\n          </div>\n\n          <% unless method.aliases.empty? %>\n            <p class=\"method__aka\">\n              Also aliased as:\n              <%# Sometimes a parent cannot be determined. See ruby/rdoc@85ebfe13dc. %>\n              <%= method.aliases.map { |aka| link_to_if aka.parent, short_name_for(aka), aka }.join(\", \") %>.\n            </p>\n          <% end %>\n\n          <% if alias_for = method.is_alias_for then %>\n            <p class=\"method__aka\">\n              Alias for:\n              <%= link_to short_name_for(alias_for), alias_for %>.\n            </p>\n          <% end %>\n\n          <%= description_for method %>\n\n          <% source_code, source_url = method_source_code_and_url(method) %>\n          <% if source_code %>\n            <details class=\"method__source\">\n              <summary>\n                <span class=\"label\">Source code</span>\n                <% if source_url %>\n                  <%= link_to_external \"GitHub\", source_url %>\n                <% end %>\n              </summary>\n\n              <pre class=\"source-code\"><code class=\"ruby\"><%= source_code %></code></pre>\n            </details>\n          <% elsif source_url %>\n            <p class=\"method__source\"><%= link_to_external \"GitHub\", source_url %></p>\n          <% end %>\n        </div>\n      <% end %><%# methods.each %>\n    <% end %><%# visibilities.each %>\n    <% end %><%# context.methods_by_type %>\n  <% end %><%# context.each_section %>\n\n  <% unless @context.classes_and_modules.empty? %>\n    <!-- Namespace -->\n    <h2 id=\"namespace\" class=\"content__divider\">Namespace</h2>\n    <ul>\n      <% @context.classes_and_modules.sort.each do |mod| %>\n        <li><%= link_to mod %></li>\n      <% end %>\n    </ul>\n  <% end %>\n</div>\n"
  },
  {
    "path": "lib/rdoc/generator/template/rails/_file_nav.rhtml",
    "content": "<% if @context.text? %>\n  <div class=\"nav__heading\">Outline</div>\n  <div class=\"nav__outline\">\n    <%= outline(@context) %>\n  </div>\n<% else %>\n  <% @context.each_classmodule do |rdoc_module| %>\n    <%= button_to_search rdoc_module %>\n  <% end %>\n<% end %>\n"
  },
  {
    "path": "lib/rdoc/generator/template/rails/_head.rhtml",
    "content": "<%= base_tag_for_context(@context) %>\n\n<link rel=\"preload\" href=\"/fonts/Bitter-Roman.woff2\" as=\"font\" type=\"font/woff2\" crossorigin>\n<link rel=\"preload\" href=\"/fonts/Bitter-Italic.woff2\" as=\"font\" type=\"font/woff2\" crossorigin>\n<link rel=\"preload\" href=\"/fonts/Jost-Roman.woff2\" as=\"font\" type=\"font/woff2\" crossorigin>\n<link rel=\"preload\" href=\"/fonts/Jost-Italic.woff2\" as=\"font\" type=\"font/woff2\" crossorigin>\n<link rel=\"preload\" href=\"/fonts/RobotoMono-Roman.woff2\" as=\"font\" type=\"font/woff2\" crossorigin>\n\n<link rel=\"modulepreload\" href=\"/js/search-index.js\">\n<link rel=\"modulepreload\" href=\"/js/search.js\">\n\n<link rel=\"stylesheet\" href=\"/css/main.css\" type=\"text/css\" media=\"screen\" />\n<link rel=\"stylesheet\" href=\"/css/highlight.css\" type=\"text/css\" media=\"screen\" />\n\n<script src=\"/js/main.js\" type=\"module\"></script>\n<script src=\"/js/@hotwired--turbo.js\" type=\"module\"></script>\n\n<% if canonical = canonical_url(@context) %>\n<link rel=\"canonical\" href=\"<%= canonical %>\" />\n<meta property=\"og:url\" content=\"<%= canonical %>\">\n<meta property=\"og:image\" content=\"<%= canonical_url(\"/i/logo.svg\") %>\">\n<% end %>\n<meta property=\"og:type\" content=\"article\">\n<meta property=\"article:modified_time\" content=\"<%= og_modified_time %>\">\n<% if description = page_description(@context.description) %>\n<meta name=\"description\" property=\"og:description\" content=\"<%= description %>\">\n<% end %>\n"
  },
  {
    "path": "lib/rdoc/generator/template/rails/_index_nav.rhtml",
    "content": "<div class=\"nav__heading\">Outline</div>\n<div class=\"nav__outline\">\n  <%= outline(@context) %>\n</div>\n\n<% unless (modules = top_modules(@context.store)).empty? %>\n  <div class=\"nav__heading\">Modules</div>\n  <ul class=\"nav__list\">\n    <% modules.each do |rdoc_module| %>\n      <li><%= link_to rdoc_module %></li>\n    <% end %>\n  </ul>\n<% end %>\n\n<% unless (extensions = core_extensions(@context.store)).empty? %>\n  <div class=\"nav__heading\">Core Extensions</div>\n  <ul class=\"nav__list\">\n    <% extensions.each do |rdoc_module| %>\n      <li><%= link_to rdoc_module %></li>\n    <% end %>\n  </ul>\n<% end %>\n"
  },
  {
    "path": "lib/rdoc/generator/template/rails/_module_nav.rhtml",
    "content": "<% unless @context.classes_and_modules.empty? %>\n  <a href=\"#namespace\" class=\"namespace-link\"><%= short_name_for(@context) %> namespace</a>\n<% end %>\n<%= button_to_search @context, display_name: short_name_for(@context) %>\n\n<% if outline = outline(@context) %>\n  <div class=\"nav__heading\">Outline</div>\n  <div class=\"nav__outline\">\n    <%= outline %>\n  </div>\n<% end %>\n\n<% unless (constants = @context.constants).empty? %>\n  <div class=\"nav__heading\">Constants</div>\n  <ul class=\"nav__list\">\n    <% constants.sort.each do |rdoc_constant| %>\n      <li><%= link_to short_name_for(rdoc_constant), rdoc_constant %></li>\n    <% end %>\n  </ul>\n<% end %>\n\n<% unless (methods = module_methods(@context)).empty? %>\n  <div class=\"nav__heading\">Methods</div>\n  <ul class=\"nav__list\">\n    <% methods.each do |rdoc_method| %>\n      <li><%= link_to short_name_for(rdoc_method), rdoc_method,\n                class: \"ref-link nav__method-link#{\"--singleton\" if rdoc_method.singleton}\" %></li>\n    <% end %>\n  </ul>\n<% end %>\n"
  },
  {
    "path": "lib/rdoc/generator/template/rails/_panel.rhtml",
    "content": "<nav id=\"panel\" class=\"panel\">\n  <div class=\"banner\">\n    <div class=\"banner__segment\">\n      <label class=\"banner__menu-button\" for=\"panel__state\"></label>\n    </div>\n    <div class=\"banner__segment\">\n      <a class=\"banner__logo\" href=\"/\"><img src=\"/i/logo.svg\" alt=\"<%= project_name %>\"></a>\n      <span class=\"banner__version\" title=\"<%= project_git_head %>\"><%= project_version %></span>\n    </div>\n  </div>\n\n  <input id=\"panel__state\" type=\"checkbox\">\n\n  <div class=\"panel__tray\">\n    <input id=\"search\" class=\"panel__search\" type=\"search\"\n      tabindex=\"-1\" autocomplete=\"off\" autosave=\"searchdoc\"\n      placeholder=\"Search (/) for a class, method, ...\"\n      data-turbo-permanent>\n\n    <div id=\"results\" class=\"panel__results\" data-turbo-permanent></div>\n\n    <div class=\"panel__nav content\">\n      <% yield if block_given? %>\n    </div>\n  </div>\n</nav>\n"
  },
  {
    "path": "lib/rdoc/generator/template/rails/class.rhtml",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"<%= @options.charset %>\">\n    <title><%= page_title @context.full_name %></title>\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <% inline \"_head.rhtml\" %>\n\n    <meta property=\"og:title\" value=\"<%= og_title @context.full_name %>\">\n</head>\n\n<body>\n    <a class=\"sr-only sr-only-focusable\" href=\"#context\" data-turbo=\"false\">Skip to Content</a>\n    <a class=\"sr-only sr-only-focusable\" href=\"#search\" data-turbo=\"false\">Skip to Search</a>\n\n    <% inline(\"_panel.rhtml\") { inline(\"_module_nav.rhtml\") } %>\n\n    <main id=\"content\" class=\"content\">\n        <hgroup class=\"content__title\">\n          <h1><span class=\"kind\"><%= @context.type %></span> <%= module_breadcrumbs @context %></h1>\n        </hgroup>\n\n        <% inline \"_context.rhtml\" %>\n\n        <h2 class=\"content__divider\">Definition files</h2>\n        <%= more_less_ul @context.in_files.map { |file| link_to file }, 5..9 %>\n    </main>\n  </body>\n</html>\n"
  },
  {
    "path": "lib/rdoc/generator/template/rails/file.rhtml",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"<%= @options.charset %>\">\n    <title><%= page_title @context.name %></title>\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <% inline \"_head.rhtml\" %>\n\n    <meta property=\"og:title\" value=\"<%= og_title @context.name %>\">\n</head>\n\n<body>\n    <a class=\"sr-only sr-only-focusable\" href=\"#context\" data-turbo=\"false\">Skip to Content</a>\n    <a class=\"sr-only sr-only-focusable\" href=\"#search\" data-turbo=\"false\">Skip to Search</a>\n\n    <% inline(\"_panel.rhtml\") { inline(\"_file_nav.rhtml\") } %>\n\n    <main id=\"content\" class=\"content\">\n        <div class=\"content__title\">\n          <%= \"<h1>\" if @context.comment.empty? %>\n            <%= full_name_for @context %>\n          <%= \"</h1>\" if @context.comment.empty? %>\n        </div>\n\n        <% if source_url = github_url(@context.relative_name) %>\n          <p class=\"content__source-link\"><%= link_to_external \"View on GitHub\", source_url %></p>\n        <% end %>\n\n        <% inline \"_context.rhtml\" %>\n    </main>\n  </body>\n</html>\n"
  },
  {
    "path": "lib/rdoc/generator/template/rails/index.rhtml",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"<%= @options.charset %>\">\n    <title><%= page_title %></title>\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <% inline \"_head.rhtml\" %>\n</head>\n\n<body>\n    <a class=\"sr-only sr-only-focusable\" href=\"#context\" data-turbo=\"false\">Skip to Content</a>\n    <a class=\"sr-only sr-only-focusable\" href=\"#search\" data-turbo=\"false\">Skip to Search</a>\n\n    <% inline(\"_panel.rhtml\") { inline(\"_index_nav.rhtml\") } %>\n\n    <main id=\"content\" class=\"content\">\n        <% inline \"_context.rhtml\" %>\n    </main>\n  </body>\n</html>\n"
  },
  {
    "path": "lib/rdoc/generator/template/rails/resources/css/highlight.css",
    "content": "/* From https://github.com/rails/rails/blob/a3b26873ebf80da6e218cf58b2c9c6669f2d2f1e/guides/assets/stylesheets/highlight.css */\n\n.highlight table td { padding: 5px; }\n.highlight table pre { margin: 0; }\n.highlight .cm {\n  color: #6c6c65;\n  font-style: italic;\n}\n.highlight .cp {\n  color: #6c6c6c;\n  font-weight: bold;\n}\n.highlight .c1 {\n  color: #6c6c65;\n  font-style: italic;\n}\n.highlight .cs {\n  color: #6c6c6c;\n  font-weight: bold;\n  font-style: italic;\n}\n.highlight .c, .highlight .ch, .highlight .cd, .highlight .cpf {\n  color: #6c6c65;\n  font-style: italic;\n}\n.highlight .err {\n  color: #a61717;\n  background-color: #e3d2d2;\n}\n.highlight .gd {\n  color: #000000;\n  background-color: #ffdddd;\n}\n.highlight .ge {\n  color: #000000;\n  font-style: italic;\n}\n.highlight .gr {\n  color: #aa0000;\n}\n.highlight .gh {\n  color: #6c6c6c;\n}\n.highlight .gi {\n  color: #000000;\n  background-color: #ddffdd;\n}\n.highlight .go {\n  color: #6c6c6c;\n}\n.highlight .gp {\n  color: #555555;\n}\n.highlight .gs {\n  font-weight: bold;\n}\n.highlight .gu {\n  color: #6c6c6c;\n}\n.highlight .gt {\n  color: #aa0000;\n}\n.highlight .kc {\n  color: #000000;\n  font-weight: bold;\n}\n.highlight .kd {\n  color: #000000;\n  font-weight: bold;\n}\n.highlight .kn {\n  color: #000000;\n  font-weight: bold;\n}\n.highlight .kp {\n  color: #000000;\n  font-weight: bold;\n}\n.highlight .kr {\n  color: #000000;\n  font-weight: bold;\n}\n.highlight .kt {\n  color: #445588;\n  font-weight: bold;\n}\n.highlight .k, .highlight .kv {\n  color: #0073a6;\n}\n.highlight .mf {\n  color: #007979;\n}\n.highlight .mh {\n  color: #007979;\n}\n.highlight .il {\n  color: #007979;\n}\n.highlight .mi {\n  color: #007979;\n}\n.highlight .mo {\n  color: #007979;\n}\n.highlight .m, .highlight .mb, .highlight .mx {\n  color: #007979;\n}\n.highlight .sb {\n  color: #d51144;\n}\n.highlight .sc {\n  color: #d51144;\n}\n.highlight .sd {\n  color: #d51144;\n}\n.highlight .s2 {\n  color: #d51144;\n}\n.highlight .se {\n  color: #d51144;\n}\n.highlight .sh {\n  color: #d51144;\n}\n.highlight .si {\n  color: #d51144;\n}\n.highlight .sx {\n  color: #d51144;\n}\n.highlight .sr {\n  color: #007e26;\n}\n.highlight .s1 {\n  color: #d51144;\n}\n.highlight .ss {\n  color: #990073;\n}\n.highlight .s, .highlight .sa, .highlight .dl {\n  color: #d51144;\n}\n.highlight .na {\n  color: #007979;\n}\n.highlight .bp {\n  color: #6c6c6c;\n}\n.highlight .nb {\n  color: #0074a1;\n}\n.highlight .nc {\n  color: #445588;\n  font-weight: bold;\n}\n.highlight .no {\n  color: #007979;\n}\n.highlight .nd {\n  color: #3c5d5d;\n  font-weight: bold;\n}\n.highlight .ni {\n  color: #800080;\n}\n.highlight .ne {\n  color: #990000;\n  font-weight: bold;\n}\n.highlight .nf, .highlight .fm {\n  color: #990000;\n  font-weight: bold;\n}\n.highlight .nl {\n  color: #990000;\n  font-weight: bold;\n}\n.highlight .nn {\n  color: #555555;\n}\n.highlight .nt {\n  color: #000080;\n}\n.highlight .vc {\n  color: #007979;\n}\n.highlight .vg {\n  color: #007979;\n}\n.highlight .vi {\n  color: #005cc5;\n}\n.highlight .nv, .highlight .vm {\n  color: #007979;\n}\n.highlight .ow {\n  color: #000000;\n  font-weight: bold;\n}\n.highlight .o {\n  color: #000000;\n  font-weight: bold;\n}\n.highlight .w {\n  color: #6c6c6c;\n}\n.highlight {\n  background-color: transparent;\n}\n\n@media (prefers-color-scheme: dark) {\n  .highlight pre { background-color: #272822; }\n  .highlight .hll { background-color: #272822; }\n  .highlight .c { color: #75715e } /* Comment */\n  .highlight .err { color: #960050; background-color: #1e0010 } /* Error */\n  .highlight .k { color: #66d9ef } /* Keyword */\n  .highlight .l { color: #ae81ff } /* Literal */\n  .highlight .n { color: #f8f8f2 } /* Name */\n  .highlight .o { color: #f92672 } /* Operator */\n  .highlight .p { color: #f8f8f2 } /* Punctuation */\n  .highlight .cm { color: #75715e } /* Comment.Multiline */\n  .highlight .cp { color: #75715e } /* Comment.Preproc */\n  .highlight .c1 { color: #75715e } /* Comment.Single */\n  .highlight .cs { color: #75715e } /* Comment.Special */\n  .highlight .ge { font-style: italic } /* Generic.Emph */\n  .highlight .gs { font-weight: bold } /* Generic.Strong */\n  .highlight .kc { color: #66d9ef } /* Keyword.Constant */\n  .highlight .kd { color: #66d9ef } /* Keyword.Declaration */\n  .highlight .kn { color: #f92672 } /* Keyword.Namespace */\n  .highlight .kp { color: #66d9ef } /* Keyword.Pseudo */\n  .highlight .kr { color: #66d9ef } /* Keyword.Reserved */\n  .highlight .kt { color: #66d9ef } /* Keyword.Type */\n  .highlight .ld { color: #e6db74 } /* Literal.Date */\n  .highlight .m { color: #ae81ff } /* Literal.Number */\n  .highlight .s { color: #e6db74 } /* Literal.String */\n  .highlight .na { color: #a6e22e } /* Name.Attribute */\n  .highlight .nb { color: #f8f8f2 } /* Name.Builtin */\n  .highlight .nc { color: #a6e22e } /* Name.Class */\n  .highlight .no { color: #66d9ef } /* Name.Constant */\n  .highlight .nd { color: #a6e22e } /* Name.Decorator */\n  .highlight .ni { color: #f8f8f2 } /* Name.Entity */\n  .highlight .ne { color: #a6e22e } /* Name.Exception */\n  .highlight .nf { color: #a6e22e } /* Name.Function */\n  .highlight .nl { color: #f8f8f2 } /* Name.Label */\n  .highlight .nn { color: #f8f8f2 } /* Name.Namespace */\n  .highlight .nx { color: #a6e22e } /* Name.Other */\n  .highlight .py { color: #f8f8f2 } /* Name.Property */\n  .highlight .nt { color: #f92672 } /* Name.Tag */\n  .highlight .nv { color: #f8f8f2 } /* Name.Variable */\n  .highlight .ow { color: #f92672 } /* Operator.Word */\n  .highlight .w { color: #f8f8f2 } /* Text.Whitespace */\n  .highlight .mf { color: #ae81ff } /* Literal.Number.Float */\n  .highlight .mh { color: #ae81ff } /* Literal.Number.Hex */\n  .highlight .mi { color: #ae81ff } /* Literal.Number.Integer */\n  .highlight .mo { color: #ae81ff } /* Literal.Number.Oct */\n  .highlight .sb { color: #e6db74 } /* Literal.String.Backtick */\n  .highlight .sc { color: #e6db74 } /* Literal.String.Char */\n  .highlight .sd { color: #e6db74 } /* Literal.String.Doc */\n  .highlight .s2 { color: #e6db74 } /* Literal.String.Double */\n  .highlight .se { color: #ae81ff } /* Literal.String.Escape */\n  .highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */\n  .highlight .si { color: #e6db74 } /* Literal.String.Interpol */\n  .highlight .sx { color: #e6db74 } /* Literal.String.Other */\n  .highlight .sr { color: #e6db74 } /* Literal.String.Regex */\n  .highlight .s1 { color: #e6db74 } /* Literal.String.Single */\n  .highlight .ss { color: #e6db74 } /* Literal.String.Symbol */\n  .highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */\n  .highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */\n  .highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */\n  .highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */\n  .highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */\n\n  .highlight .gh { } /* Generic Heading & Diff Header */\n  .highlight .gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */\n  .highlight .gd { color: #f92672; background-color: unset; } /* Generic.Deleted & Diff Deleted */\n  .highlight .gi { color: #a6e22e; background-color: unset; } /* Generic.Inserted & Diff Inserted */\n}\n"
  },
  {
    "path": "lib/rdoc/generator/template/rails/resources/css/main.css",
    "content": "@font-face {\n  font-family: \"Bitter\";\n  src: url(\"../fonts/Bitter-Roman.woff2\") format(\"woff2\");\n  font-weight: 100 900;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: \"Bitter\";\n  src: url(\"../fonts/Bitter-Italic.woff2\") format(\"woff2\");\n  font-style: italic;\n  font-weight: 100 900;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: \"Jost\";\n  src: url(\"../fonts/Jost-Roman.woff2\") format(\"woff2\");\n  font-weight: 100 900;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: \"Jost\";\n  src: url(\"../fonts/Jost-Italic.woff2\") format(\"woff2\");\n  font-style: italic;\n  font-weight: 100 900;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: \"RobotoMono\";\n  src: url(\"../fonts/RobotoMono-Roman.woff2\") format(\"woff2\");\n  font-weight: 100 800;\n  font-display: swap;\n}\n\n* { box-sizing: border-box; }\n\nbody {\n  margin: 0;\n  min-height: 100dvh;\n}\n\n:root {\n  font-size: 18px;\n\n  --line-height: 1.5;\n  --space: 1.25rem;\n  --space-xs: calc(var(--space) * 0.25);\n  --space-sm: calc(var(--space) * 0.5);\n  --space-lg: calc(var(--space) * 1.75);\n  --space-xl: calc(var(--space) * 3);\n\n  --body-font: \"Bitter\", serif;\n  --heading-font: \"Jost\", sans-serif;\n  --monospace-font: \"RobotoMono\", monospace;\n\n  --brand-color: #b61d1d;\n\n  --text-color: #3b3b3b;\n  --link-color: var(--brand-color);\n  --link-hover-color: var(--text-color);\n  --icon-color: #777777;\n\n  --body-bg: #ffffff;\n  --code-bg: #f1f1f1;\n  --source-code-bg: #f1f1e1;\n}\n\n@media (min-width: 600px) {\n  :root {\n    font-size: 20px;\n  }\n}\n\n@media (prefers-color-scheme: dark) {\n  :root {\n    --text-color: #dddddd;\n    --link-color: #ee3f3f;\n\n    --body-bg: #0c0c0c;\n    --code-bg: #1b1b1b;\n    --source-code-bg: #000000;\n  }\n}\n\nbody {\n  font-family: var(--body-font);\n  line-height: var(--line-height);\n\n  background: var(--body-bg);\n  color: var(--text-color);\n}\n\npre, code, .kind {\n  font-family: var(--monospace-font);\n  font-size: 0.95em;\n}\n\n:where(pre, code, .kind) > :is(pre, code, .kind) {\n  font-size: unset;\n}\n\n.kind::after {\n  content: \" \"; /* Ensure trailing space has width of 1 monospace char */\n}\n\na {\n  color: var(--link-color);\n}\n\n@media (hover: hover) {\n  a:hover {\n    color: var(--link-hover-color);\n  }\n}\n\n.external-link {\n  padding-left: 1.3em;\n  background: url('../i/external-link.svg') no-repeat;\n  background-size: 1.1em;\n}\n\n.ref-link {\n  text-decoration: none;\n}\n\n.query-button {\n  border: none;\n  text-align: left;\n  font-family: inherit;\n  font-size: 1em;\n  color: var(--link-color);\n}\n\n@media (hover: hover) {\n  .query-button:hover {\n    color: var(--link-hover-color);\n  }\n}\n\nblockquote {\n  padding: var(--space-xs) 0 var(--space-xs) var(--space);\n  border-left: var(--space-sm) solid color-mix(in srgb, currentColor 15%, transparent);\n}\n\ntable {\n  border-collapse: collapse;\n}\n\ntd, th\n{\n    padding: 0 0.7em 0.3em 0;\n}\n\nth\n{\n    font-weight: bold;\n}\n\n.attr-rw {\n  padding-right: 1em;\n  text-align: center;\n  color: #055;\n}\n\n.attr-name {\n  font-weight: bold;\n  padding-right: 1em;\n}\n\n.attr-desc {\n}\n\n.attr-value {\n  font-family: monospace;\n  padding-left: 1em;\n  font-size: 1.15em;\n}\n\n.sr-only {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  padding: 0;\n  overflow: hidden;\n  clip: rect(0,0,0,0);\n  white-space: nowrap;\n  border: 0;\n}\n.sr-only-focusable:active,\n.sr-only-focusable:focus {\n  position: fixed;\n  top: 10%;\n  width: auto;\n  height: auto;\n  overflow: visible;\n  clip: auto;\n  white-space: normal;\n  padding: 2rem;\n  border: 4px solid #990000;\n  border-radius: 1rem;\n  box-shadow: 0 0.5rem 1rem rgb(0 0 0 / 15%) !important;\n  left: 40%;\n  z-index: 100;\n  background: #fff;\n  font-size: 2rem;\n}\n\n\n/*\n * Generic content\n */\n\n:where(.content) *,\n:where(.content) :is(br, wbr) {\n  margin: 0;\n}\n\n:where(.content) * + * {\n  margin-top: var(--space);\n}\n\n:where(.content) :is(ol, ul, dd) {\n  padding: 0 0 0 1.25em;\n}\n\n:where(.content) * + :is(li, dd) {\n  margin-top: var(--space-sm);\n}\n\n/* Increase top margin for list items when any item has more than one paragraph */\n:where(.content :is(ol, ul):has(> li > p:not(:only-child))) * + li {\n  margin-top: var(--space);\n}\n\n:where(.content) :is(hgroup, h1, h2, h3, h4, h5, h6) {\n  font-family: var(--heading-font);\n  font-weight: bold;\n  line-height: 1.125;\n}\n\n:where(.content :is(hgroup, h1, h2, h3, h4, h5, h6)) :is(code, .kind) {\n  font-size: 0.85em;\n  font-weight: normal;\n}\n\n:where(.content) code {\n  font-style: normal;\n}\n\n\n/*\n * Description section\n */\n\n .description * + :is(h1, h2) {\n  margin-top: var(--space-xl);\n}\n\n.description * + :is(h3, h4, h5, h6) {\n  margin-top: var(--space-lg);\n}\n\n.description h1 {\n  font-size: 2em;\n}\n\n.description h2 {\n  font-size: 1.6em;\n}\n\n.description h3 {\n  font-size: 1.3em;\n}\n\n.description h4 {\n  font-size: 1.2em;\n  font-style: italic;\n}\n\n.description h5 {\n  font-size: 1.1em;\n}\n\n.description h6 {\n  font-size: 1em;\n  font-style: italic;\n}\n\n.description pre, pre.source-code {\n  padding: 0.5ch 1ch;\n  border-radius: 6px;\n  overflow-x: auto;\n}\n\n:is(.description p, p.description) code {\n  padding: 0 0.15em;\n  border-radius: 3px;\n}\n\n.description pre, :is(.description p, p.description) code {\n  background: var(--code-bg);\n}\n\npre.source-code {\n  background: var(--source-code-bg);\n}\n\n@media (hover: hover) {\n  .description a:hover code {\n    box-shadow: 0 0 0 1px;\n  }\n}\n\n.description dt {\n  font-weight: bold;\n}\n\n\n/*\n * Navigation panel\n */\n\nhtml {\n  --panel-width: 100dvw;\n  --banner-height: 3.25rem;\n  --search-height: var(--space-lg);\n  scroll-padding-top: calc(var(--banner-height) + 1rem);\n}\n\n#panel {\n  position: sticky;\n  top: 0;\n  left: 0;\n  z-index: 90;\n  width: var(--panel-width);\n}\n\n#panel__state {\n  display: none;\n}\n\n\n/*\n * Navigation panel - Banner\n */\n\n.banner {\n  height: var(--banner-height);\n  background-color: var(--brand-color);\n  box-shadow: 1px 0px var(--brand-color); /* Match .panel_tray width on desktop */\n}\n\n.banner__segment {\n  display: inline-block;\n  height: 100%;\n  padding: calc(0.15 * var(--banner-height));\n  filter: brightness(0) invert(1);\n}\n\n.banner__segment:first-child {\n  border-right: 1px solid;\n}\n\n.banner__menu-button {\n  display: inline-block;\n  height: 100%;\n  aspect-ratio : 1 / 1;\n  background: url('../i/menu.svg') no-repeat center;\n  background-size: 100%;\n}\n\n#panel:has(#panel__state:checked) .banner__menu-button {\n  background-image: url('../i/close.svg');\n}\n\n.banner__logo img {\n  height: 100%;\n}\n\n.banner__version {\n  font-size: 1.25rem;\n  font-style: italic;\n\n  position: relative;\n  bottom: 0.1em;\n}\n\n\n/*\n * Navigation panel - Tray\n */\n\n.panel__tray {\n  background: var(--body-bg);\n  position: absolute;\n\n  overflow-y: hidden;\n  transition: height 0.2s ease-in-out;\n  height: 0;\n}\n\n#panel__state:checked ~ .panel__tray {\n  height: 100dvh;\n}\n\n.panel__results, .panel__nav {\n  position: relative;\n  height: calc(100dvh - var(--banner-height) - var(--search-height));\n  width: var(--panel-width);\n  transition: opacity 0.15s ease-in-out;\n\n  overflow-y: auto;\n  overscroll-behavior: contain;\n}\n\n/* Force scrolling in order to always contain scroll events (on mobile) */\n:is(.panel__results, .panel__nav)::after {\n  content: \"\";\n  height: 1px;\n  width: 1px;\n  position: absolute;\n  bottom: -1px;\n  z-index: -1;\n}\n\n.panel__results:not(.active),\n.panel__search:placeholder-shown ~ .panel__results,\n.panel__search:not(:placeholder-shown) ~ .panel__results.active ~ .panel__nav {\n  /* `display: none` disables animations, so simulate it instead */\n  max-height: 0;\n  max-width: 0;\n  padding: 0;\n\n  opacity: 0;\n}\n\n\n/*\n * Navigation panel - Search input\n */\n\n.panel__search {\n  height: var(--search-height);\n  width: 100%;\n\n  background: url('../i/search.svg') no-repeat;\n  background-size: 1.3em;\n  background-position: 0.5em center;\n  padding-left: calc(0.5em + 1.3em + 0.5em);\n\n  color: inherit;\n\n  border: 0;\n  box-shadow: 1px 1px 4px color-mix(in srgb, currentColor 50%, transparent) inset;\n}\n\n/* Hide native magnifying glass icon in Webkit-based browsers (in favor of our icon) */\n.panel__search::-webkit-search-results-button {\n  -webkit-appearance: none;\n}\n\n/* Display \"clear search\" button consistently across (Webkit-based) browsers */\n.panel__search::-webkit-search-cancel-button {\n  -webkit-appearance: none;\n\n  margin-right: 0.5em;\n  height: 1.3em;\n  width: 1.3em;\n  background: url('../i/close.svg') no-repeat;\n  background-size: contain;\n}\n\n.panel__search:not(:focus)::-webkit-search-cancel-button {\n  display: none;\n}\n\n\n/*\n * Navigation panel - Search results\n */\n\n.panel__results {\n  padding: var(--space-sm) var(--space-xs);\n  scroll-padding: var(--space-sm) 0;\n}\n\n\n.panel__results::before {\n  display: block;\n  text-align: center;\n  text-wrap: balance;\n  padding-bottom: var(--space-sm);\n\n  font-size: 0.85em;\n  font-style: italic;\n  color: color-mix(in srgb, currentColor 70%, transparent);\n}\n\n.panel__results.active::before {\n  content: \"TIP: Prefix query with \\\"#\\\" or \\\".\\\" to search for methods.\";\n}\n\n/* Hide TIP when one of the first three search results is a method. */\n.panel__results:has(.results__result:nth-child(n+1):nth-child(-n+3) .result__member:not(:empty))::before {\n  display: none;\n}\n\n.panel__results:empty::before {\n  content: \"No results.\";\n}\n\n\n.results__result:not(:first-child) {\n  margin-top: var(--space);\n}\n\n.results__result {\n  border-left: 0.5ch solid color-mix(in srgb, currentColor 10%, transparent);\n  border-radius: 0.5ch;\n  padding-left: var(--space-xs);\n}\n\n/* Assume physical keyboard exists when not `pointer: coarse` */\n@media not (pointer: coarse) {\n  .results__result.cursor {\n    border-color: var(--link-color);\n  }\n}\n\n@media (hover: hover) {\n  .results__result:has(.result__link:hover) {\n    border-color: var(--link-hover-color);\n  }\n}\n\n\n.result__link {\n  display: flex;\n  flex-direction: column;\n}\n\n.result__member {\n  display: flex;\n}\n\n.result__member::before {\n  content: \"\\221F\";\n  font-size: 1.25em;\n  margin: -0.3em 0.2em;\n  color: var(--text-color);\n}\n\n.result__summary {\n  margin: var(--space-xs) 0 0 0;\n}\n\n:is(.result__member, .result__summary):empty {\n  display: none;\n}\n\n\n/*\n * Navigation panel - Page nav\n */\n\n.panel__nav {\n  padding: var(--space);\n  padding-right: var(--space-xs);\n}\n\n:where(.panel__nav) * + :is(ul, ol, li) {\n  margin-top: var(--space-xs);\n}\n\n\n.panel__nav :is(.namespace-link, .query-button) {\n  display: block;\n  word-break: break-word;\n}\n\n.panel__nav * + :is(.namespace-link, .query-button) {\n  margin-top: var(--space-sm);\n}\n\n.panel__nav .namespace-link {\n  background: url('../i/list.svg') no-repeat;\n  background-position-y: 3px;\n  background-size: 1.1em;\n  padding: 0 0 0 1.3em;\n\n  text-decoration: none;\n}\n\n.panel__nav .query-button {\n  background: url('../i/filter.svg') no-repeat;\n  background-position-y: 2px;\n  background-size: 1.1em;\n  padding: 0 0 0 1.3em;\n}\n\n\n.nav__heading {\n  font-family: var(--heading-font);\n  font-weight: bold;\n  font-size: 1.6em;\n  line-height: 1;\n}\n\n* + .nav__heading {\n  margin-top: var(--space-lg);\n}\n\n.nav__heading + * {\n  margin-top: var(--space-sm);\n}\n\n\n.nav__outline ul {\n  padding-left: 1em;\n}\n\n.nav__outline ul ul ul {\n  display: none; /* Only show two levels deep */\n}\n\n.nav__outline li {\n  word-break: break-word;\n}\n\n\n.nav__list {\n  padding: 0;\n  list-style: none;\n}\n\n.nav__list li {\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n\n.nav__method-link code::before {\n  content: \"#\";\n}\n\n.nav__method-link--singleton code::before {\n  content: \"::\";\n}\n\n\n/*\n * Navigation panel on desktop\n */\n\n@media (min-width: 600px) {\n  html {\n    --panel-width: 300px;\n    scroll-padding-top: 1rem;\n  }\n\n  .banner__segment:first-child {\n    display: none;\n  }\n\n  .panel__tray {\n    height: unset;\n    box-shadow: 1px 0 color-mix(in srgb, currentColor 25%, transparent);\n  }\n\n  .panel__results.active {\n    width: 60ch;\n  }\n\n  :is(.panel__results, .panel__nav) {\n    font-size: 0.85em;\n  }\n\n  :is(.panel__results, .panel__nav)::after {\n    display: none;\n  }\n}\n\n\n/*\n * Main content\n */\n\n#content {\n  padding: 1em;\n  overflow-x: auto;\n}\n\n@media (min-width: 600px) {\n  #content {\n    margin-top: calc(-1 * var(--banner-height));\n    margin-left: var(--panel-width);\n    padding: var(--space) var(--space-lg);\n    max-width: calc(75ch + 2 * var(--space-lg));\n  }\n}\n\n.content__title h1 {\n  font-size: 1.75em;\n\n  margin-left: 1em;\n  text-indent: -1em;\n}\n\n.content__title p {\n  font-size: 2em;\n  font-style: italic;\n\n  margin-top: 0;\n}\n\n.content__title ~ #context > .description {\n  margin-top: var(--space-lg);\n}\n\n.content__source-link {\n  margin-top: var(--space-xs);\n}\n\n.content__section-title {\n  margin: var(--space-xl) 0 0 0;\n\n  font-size: 1.8em;\n  font-weight: bold;\n}\n\n.content__divider {\n  margin: var(--space-xl) 0 0 0;\n\n  font-size: 1.6em;\n  font-weight: bold;\n  text-transform: capitalize;\n\n  border-bottom: 1px solid;\n}\n\n.content__section-title + .content__divider,\n.content__divider + :is(*, div /* increase selector specificity */) {\n  margin-top: var(--space);\n}\n\n\n/*\n * Constant\n */\n\n.constant {\n  margin-top: var(--space-lg);\n}\n\n.constant__name {\n  padding-bottom: var(--space-xs);\n  border-bottom: 2px solid var(--code-bg);\n\n  display: flex;\n  align-items: baseline;\n  gap: 0.5em;\n}\n\n.constant__name > * {\n  margin-top: 0;\n}\n\n.constant__name code {\n  font-size: 1rem;\n  font-weight: bold;\n}\n\n.constant__name + *, .constant__source {\n  margin-top: var(--space-sm);\n}\n\n\n/*\n * Method\n */\n\n.method {\n  margin-top: var(--space-xl);\n}\n\n\n.method__signature {\n  padding-bottom: var(--space-xs);\n  border-bottom: 2px solid var(--code-bg);\n\n  display: flex;\n  align-items: baseline;\n  gap: 0.5em;\n}\n\n.method__signature > * {\n  margin-top: 0;\n}\n\n.method__signature code {\n  font-size: 1rem;\n  white-space: pre-wrap;\n}\n\n.method__signature code .returns {\n  font-family: var(--body-font);\n  font-size: 1.1em;\n}\n\n.method__signature + * {\n  margin-top: var(--space-sm);\n}\n\n.method__aka {\n  font-style: italic;\n}\n\n\n.method__source {\n  width: fit-content; /* Reduce clickable area when collapsed */\n}\n\n.method__source[open] {\n  width: auto;\n}\n\n.method__source summary {\n  cursor: pointer;\n}\n\n.method__source summary::marker {\n  color: var(--icon-color);\n}\n\n.method__source summary .label {\n  margin-left: -0.2em;\n  color: var(--link-color);\n}\n\n@media (hover: hover) {\n  .method__source summary .label:hover {\n    color: var(--link-hover-color);\n  }\n}\n\n.method__source summary .external-link {\n  margin-left: 1em;\n}\n\n.method__source pre {\n  margin-top: var(--space-xs);\n}\n\n\n/*\n * Permalink\n */\n\n.permalink img {\n  height: 1.05em;\n  vertical-align: middle;\n}\n\n@media (hover: hover) {\n  .permalink:hover img {\n    filter: brightness(0) invert(1);\n    mix-blend-mode: difference;\n  }\n}\n\n.permalink-container {\n  position: relative;\n}\n\n:is(.target .permalink-container, .target.permalink-container)::before {\n  content: \" \";\n  white-space: pre;\n\n  position: absolute;\n  left: calc(-0.5ch - var(--space-sm));\n  border-left: 0.5ch solid var(--link-color);\n  border-radius: 0.5ch;\n  height: 100%;\n}\n\n\n/*\n * More-Less widget\n */\n\ndetails.more-less {\n  margin-top: 0;\n  padding-bottom: calc(var(--line-height) * 1em + var(--space-sm));\n  position: relative;\n}\n\ndetails.more-less > ul {\n  margin-top: var(--space-sm);\n}\n\ndetails.more-less summary {\n  position: absolute;\n  bottom: 0;\n\n  font-weight: bold;\n  color: var(--icon-color);\n  padding-left: 0.25em;\n}\n\n@media (hover: hover) {\n  details.more-less summary:hover {\n    cursor: pointer;\n    color: inherit;\n  }\n}\n\ndetails.more-less summary {\n  list-style-type: none;\n}\ndetails.more-less summary::-webkit-details-marker {\n  display: none;\n}\n\ndetails.more-less summary::before {\n  content: \"+\";\n}\n\ndetails.more-less[open] summary::before {\n  content: \"-\";\n}\n\ndetails.more-less:not([open]) .more-less__less,\ndetails.more-less[open] .more-less__more {\n  display: none;\n}\n"
  },
  {
    "path": "lib/rdoc/generator/template/rails/resources/js/.gitattributes",
    "content": "@hotwired--turbo.js binary\n"
  },
  {
    "path": "lib/rdoc/generator/template/rails/resources/js/@hotwired--turbo.js",
    "content": "/*\nTurbo 7.3.0\nCopyright © 2023 37signals LLC\n */\n(function () {\n    if (window.Reflect === undefined ||\n        window.customElements === undefined ||\n        window.customElements.polyfillWrapFlushCallback) {\n        return;\n    }\n    const BuiltInHTMLElement = HTMLElement;\n    const wrapperForTheName = {\n        HTMLElement: function HTMLElement() {\n            return Reflect.construct(BuiltInHTMLElement, [], this.constructor);\n        },\n    };\n    window.HTMLElement = wrapperForTheName[\"HTMLElement\"];\n    HTMLElement.prototype = BuiltInHTMLElement.prototype;\n    HTMLElement.prototype.constructor = HTMLElement;\n    Object.setPrototypeOf(HTMLElement, BuiltInHTMLElement);\n})();\n\n/**\n * The MIT License (MIT)\n * \n * Copyright (c) 2019 Javan Makhmali\n * \n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n * \n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n(function(prototype) {\n  if (typeof prototype.requestSubmit == \"function\") return\n\n  prototype.requestSubmit = function(submitter) {\n    if (submitter) {\n      validateSubmitter(submitter, this);\n      submitter.click();\n    } else {\n      submitter = document.createElement(\"input\");\n      submitter.type = \"submit\";\n      submitter.hidden = true;\n      this.appendChild(submitter);\n      submitter.click();\n      this.removeChild(submitter);\n    }\n  };\n\n  function validateSubmitter(submitter, form) {\n    submitter instanceof HTMLElement || raise(TypeError, \"parameter 1 is not of type 'HTMLElement'\");\n    submitter.type == \"submit\" || raise(TypeError, \"The specified element is not a submit button\");\n    submitter.form == form || raise(DOMException, \"The specified element is not owned by this form element\", \"NotFoundError\");\n  }\n\n  function raise(errorConstructor, message, name) {\n    throw new errorConstructor(\"Failed to execute 'requestSubmit' on 'HTMLFormElement': \" + message + \".\", name)\n  }\n})(HTMLFormElement.prototype);\n\nconst submittersByForm = new WeakMap();\nfunction findSubmitterFromClickTarget(target) {\n    const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;\n    const candidate = element ? element.closest(\"input, button\") : null;\n    return (candidate === null || candidate === void 0 ? void 0 : candidate.type) == \"submit\" ? candidate : null;\n}\nfunction clickCaptured(event) {\n    const submitter = findSubmitterFromClickTarget(event.target);\n    if (submitter && submitter.form) {\n        submittersByForm.set(submitter.form, submitter);\n    }\n}\n(function () {\n    if (\"submitter\" in Event.prototype)\n        return;\n    let prototype = window.Event.prototype;\n    if (\"SubmitEvent\" in window && /Apple Computer/.test(navigator.vendor)) {\n        prototype = window.SubmitEvent.prototype;\n    }\n    else if (\"SubmitEvent\" in window) {\n        return;\n    }\n    addEventListener(\"click\", clickCaptured, true);\n    Object.defineProperty(prototype, \"submitter\", {\n        get() {\n            if (this.type == \"submit\" && this.target instanceof HTMLFormElement) {\n                return submittersByForm.get(this.target);\n            }\n        },\n    });\n})();\n\nvar FrameLoadingStyle;\n(function (FrameLoadingStyle) {\n    FrameLoadingStyle[\"eager\"] = \"eager\";\n    FrameLoadingStyle[\"lazy\"] = \"lazy\";\n})(FrameLoadingStyle || (FrameLoadingStyle = {}));\nclass FrameElement extends HTMLElement {\n    static get observedAttributes() {\n        return [\"disabled\", \"complete\", \"loading\", \"src\"];\n    }\n    constructor() {\n        super();\n        this.loaded = Promise.resolve();\n        this.delegate = new FrameElement.delegateConstructor(this);\n    }\n    connectedCallback() {\n        this.delegate.connect();\n    }\n    disconnectedCallback() {\n        this.delegate.disconnect();\n    }\n    reload() {\n        return this.delegate.sourceURLReloaded();\n    }\n    attributeChangedCallback(name) {\n        if (name == \"loading\") {\n            this.delegate.loadingStyleChanged();\n        }\n        else if (name == \"complete\") {\n            this.delegate.completeChanged();\n        }\n        else if (name == \"src\") {\n            this.delegate.sourceURLChanged();\n        }\n        else {\n            this.delegate.disabledChanged();\n        }\n    }\n    get src() {\n        return this.getAttribute(\"src\");\n    }\n    set src(value) {\n        if (value) {\n            this.setAttribute(\"src\", value);\n        }\n        else {\n            this.removeAttribute(\"src\");\n        }\n    }\n    get loading() {\n        return frameLoadingStyleFromString(this.getAttribute(\"loading\") || \"\");\n    }\n    set loading(value) {\n        if (value) {\n            this.setAttribute(\"loading\", value);\n        }\n        else {\n            this.removeAttribute(\"loading\");\n        }\n    }\n    get disabled() {\n        return this.hasAttribute(\"disabled\");\n    }\n    set disabled(value) {\n        if (value) {\n            this.setAttribute(\"disabled\", \"\");\n        }\n        else {\n            this.removeAttribute(\"disabled\");\n        }\n    }\n    get autoscroll() {\n        return this.hasAttribute(\"autoscroll\");\n    }\n    set autoscroll(value) {\n        if (value) {\n            this.setAttribute(\"autoscroll\", \"\");\n        }\n        else {\n            this.removeAttribute(\"autoscroll\");\n        }\n    }\n    get complete() {\n        return !this.delegate.isLoading;\n    }\n    get isActive() {\n        return this.ownerDocument === document && !this.isPreview;\n    }\n    get isPreview() {\n        var _a, _b;\n        return (_b = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.documentElement) === null || _b === void 0 ? void 0 : _b.hasAttribute(\"data-turbo-preview\");\n    }\n}\nfunction frameLoadingStyleFromString(style) {\n    switch (style.toLowerCase()) {\n        case \"lazy\":\n            return FrameLoadingStyle.lazy;\n        default:\n            return FrameLoadingStyle.eager;\n    }\n}\n\nfunction expandURL(locatable) {\n    return new URL(locatable.toString(), document.baseURI);\n}\nfunction getAnchor(url) {\n    let anchorMatch;\n    if (url.hash) {\n        return url.hash.slice(1);\n    }\n    else if ((anchorMatch = url.href.match(/#(.*)$/))) {\n        return anchorMatch[1];\n    }\n}\nfunction getAction(form, submitter) {\n    const action = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute(\"formaction\")) || form.getAttribute(\"action\") || form.action;\n    return expandURL(action);\n}\nfunction getExtension(url) {\n    return (getLastPathComponent(url).match(/\\.[^.]*$/) || [])[0] || \"\";\n}\nfunction isHTML(url) {\n    return !!getExtension(url).match(/^(?:|\\.(?:htm|html|xhtml|php))$/);\n}\nfunction isPrefixedBy(baseURL, url) {\n    const prefix = getPrefix(url);\n    return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);\n}\nfunction locationIsVisitable(location, rootLocation) {\n    return isPrefixedBy(location, rootLocation) && isHTML(location);\n}\nfunction getRequestURL(url) {\n    const anchor = getAnchor(url);\n    return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href;\n}\nfunction toCacheKey(url) {\n    return getRequestURL(url);\n}\nfunction urlsAreEqual(left, right) {\n    return expandURL(left).href == expandURL(right).href;\n}\nfunction getPathComponents(url) {\n    return url.pathname.split(\"/\").slice(1);\n}\nfunction getLastPathComponent(url) {\n    return getPathComponents(url).slice(-1)[0];\n}\nfunction getPrefix(url) {\n    return addTrailingSlash(url.origin + url.pathname);\n}\nfunction addTrailingSlash(value) {\n    return value.endsWith(\"/\") ? value : value + \"/\";\n}\n\nclass FetchResponse {\n    constructor(response) {\n        this.response = response;\n    }\n    get succeeded() {\n        return this.response.ok;\n    }\n    get failed() {\n        return !this.succeeded;\n    }\n    get clientError() {\n        return this.statusCode >= 400 && this.statusCode <= 499;\n    }\n    get serverError() {\n        return this.statusCode >= 500 && this.statusCode <= 599;\n    }\n    get redirected() {\n        return this.response.redirected;\n    }\n    get location() {\n        return expandURL(this.response.url);\n    }\n    get isHTML() {\n        return this.contentType && this.contentType.match(/^(?:text\\/([^\\s;,]+\\b)?html|application\\/xhtml\\+xml)\\b/);\n    }\n    get statusCode() {\n        return this.response.status;\n    }\n    get contentType() {\n        return this.header(\"Content-Type\");\n    }\n    get responseText() {\n        return this.response.clone().text();\n    }\n    get responseHTML() {\n        if (this.isHTML) {\n            return this.response.clone().text();\n        }\n        else {\n            return Promise.resolve(undefined);\n        }\n    }\n    header(name) {\n        return this.response.headers.get(name);\n    }\n}\n\nfunction activateScriptElement(element) {\n    if (element.getAttribute(\"data-turbo-eval\") == \"false\") {\n        return element;\n    }\n    else {\n        const createdScriptElement = document.createElement(\"script\");\n        const cspNonce = getMetaContent(\"csp-nonce\");\n        if (cspNonce) {\n            createdScriptElement.nonce = cspNonce;\n        }\n        createdScriptElement.textContent = element.textContent;\n        createdScriptElement.async = false;\n        copyElementAttributes(createdScriptElement, element);\n        return createdScriptElement;\n    }\n}\nfunction copyElementAttributes(destinationElement, sourceElement) {\n    for (const { name, value } of sourceElement.attributes) {\n        destinationElement.setAttribute(name, value);\n    }\n}\nfunction createDocumentFragment(html) {\n    const template = document.createElement(\"template\");\n    template.innerHTML = html;\n    return template.content;\n}\nfunction dispatch(eventName, { target, cancelable, detail } = {}) {\n    const event = new CustomEvent(eventName, {\n        cancelable,\n        bubbles: true,\n        composed: true,\n        detail,\n    });\n    if (target && target.isConnected) {\n        target.dispatchEvent(event);\n    }\n    else {\n        document.documentElement.dispatchEvent(event);\n    }\n    return event;\n}\nfunction nextAnimationFrame() {\n    return new Promise((resolve) => requestAnimationFrame(() => resolve()));\n}\nfunction nextEventLoopTick() {\n    return new Promise((resolve) => setTimeout(() => resolve(), 0));\n}\nfunction nextMicrotask() {\n    return Promise.resolve();\n}\nfunction parseHTMLDocument(html = \"\") {\n    return new DOMParser().parseFromString(html, \"text/html\");\n}\nfunction unindent(strings, ...values) {\n    const lines = interpolate(strings, values).replace(/^\\n/, \"\").split(\"\\n\");\n    const match = lines[0].match(/^\\s+/);\n    const indent = match ? match[0].length : 0;\n    return lines.map((line) => line.slice(indent)).join(\"\\n\");\n}\nfunction interpolate(strings, values) {\n    return strings.reduce((result, string, i) => {\n        const value = values[i] == undefined ? \"\" : values[i];\n        return result + string + value;\n    }, \"\");\n}\nfunction uuid() {\n    return Array.from({ length: 36 })\n        .map((_, i) => {\n        if (i == 8 || i == 13 || i == 18 || i == 23) {\n            return \"-\";\n        }\n        else if (i == 14) {\n            return \"4\";\n        }\n        else if (i == 19) {\n            return (Math.floor(Math.random() * 4) + 8).toString(16);\n        }\n        else {\n            return Math.floor(Math.random() * 15).toString(16);\n        }\n    })\n        .join(\"\");\n}\nfunction getAttribute(attributeName, ...elements) {\n    for (const value of elements.map((element) => element === null || element === void 0 ? void 0 : element.getAttribute(attributeName))) {\n        if (typeof value == \"string\")\n            return value;\n    }\n    return null;\n}\nfunction hasAttribute(attributeName, ...elements) {\n    return elements.some((element) => element && element.hasAttribute(attributeName));\n}\nfunction markAsBusy(...elements) {\n    for (const element of elements) {\n        if (element.localName == \"turbo-frame\") {\n            element.setAttribute(\"busy\", \"\");\n        }\n        element.setAttribute(\"aria-busy\", \"true\");\n    }\n}\nfunction clearBusyState(...elements) {\n    for (const element of elements) {\n        if (element.localName == \"turbo-frame\") {\n            element.removeAttribute(\"busy\");\n        }\n        element.removeAttribute(\"aria-busy\");\n    }\n}\nfunction waitForLoad(element, timeoutInMilliseconds = 2000) {\n    return new Promise((resolve) => {\n        const onComplete = () => {\n            element.removeEventListener(\"error\", onComplete);\n            element.removeEventListener(\"load\", onComplete);\n            resolve();\n        };\n        element.addEventListener(\"load\", onComplete, { once: true });\n        element.addEventListener(\"error\", onComplete, { once: true });\n        setTimeout(resolve, timeoutInMilliseconds);\n    });\n}\nfunction getHistoryMethodForAction(action) {\n    switch (action) {\n        case \"replace\":\n            return history.replaceState;\n        case \"advance\":\n        case \"restore\":\n            return history.pushState;\n    }\n}\nfunction isAction(action) {\n    return action == \"advance\" || action == \"replace\" || action == \"restore\";\n}\nfunction getVisitAction(...elements) {\n    const action = getAttribute(\"data-turbo-action\", ...elements);\n    return isAction(action) ? action : null;\n}\nfunction getMetaElement(name) {\n    return document.querySelector(`meta[name=\"${name}\"]`);\n}\nfunction getMetaContent(name) {\n    const element = getMetaElement(name);\n    return element && element.content;\n}\nfunction setMetaContent(name, content) {\n    let element = getMetaElement(name);\n    if (!element) {\n        element = document.createElement(\"meta\");\n        element.setAttribute(\"name\", name);\n        document.head.appendChild(element);\n    }\n    element.setAttribute(\"content\", content);\n    return element;\n}\nfunction findClosestRecursively(element, selector) {\n    var _a;\n    if (element instanceof Element) {\n        return (element.closest(selector) ||\n            findClosestRecursively(element.assignedSlot || ((_a = element.getRootNode()) === null || _a === void 0 ? void 0 : _a.host), selector));\n    }\n}\n\nvar FetchMethod;\n(function (FetchMethod) {\n    FetchMethod[FetchMethod[\"get\"] = 0] = \"get\";\n    FetchMethod[FetchMethod[\"post\"] = 1] = \"post\";\n    FetchMethod[FetchMethod[\"put\"] = 2] = \"put\";\n    FetchMethod[FetchMethod[\"patch\"] = 3] = \"patch\";\n    FetchMethod[FetchMethod[\"delete\"] = 4] = \"delete\";\n})(FetchMethod || (FetchMethod = {}));\nfunction fetchMethodFromString(method) {\n    switch (method.toLowerCase()) {\n        case \"get\":\n            return FetchMethod.get;\n        case \"post\":\n            return FetchMethod.post;\n        case \"put\":\n            return FetchMethod.put;\n        case \"patch\":\n            return FetchMethod.patch;\n        case \"delete\":\n            return FetchMethod.delete;\n    }\n}\nclass FetchRequest {\n    constructor(delegate, method, location, body = new URLSearchParams(), target = null) {\n        this.abortController = new AbortController();\n        this.resolveRequestPromise = (_value) => { };\n        this.delegate = delegate;\n        this.method = method;\n        this.headers = this.defaultHeaders;\n        this.body = body;\n        this.url = location;\n        this.target = target;\n    }\n    get location() {\n        return this.url;\n    }\n    get params() {\n        return this.url.searchParams;\n    }\n    get entries() {\n        return this.body ? Array.from(this.body.entries()) : [];\n    }\n    cancel() {\n        this.abortController.abort();\n    }\n    async perform() {\n        const { fetchOptions } = this;\n        this.delegate.prepareRequest(this);\n        await this.allowRequestToBeIntercepted(fetchOptions);\n        try {\n            this.delegate.requestStarted(this);\n            const response = await fetch(this.url.href, fetchOptions);\n            return await this.receive(response);\n        }\n        catch (error) {\n            if (error.name !== \"AbortError\") {\n                if (this.willDelegateErrorHandling(error)) {\n                    this.delegate.requestErrored(this, error);\n                }\n                throw error;\n            }\n        }\n        finally {\n            this.delegate.requestFinished(this);\n        }\n    }\n    async receive(response) {\n        const fetchResponse = new FetchResponse(response);\n        const event = dispatch(\"turbo:before-fetch-response\", {\n            cancelable: true,\n            detail: { fetchResponse },\n            target: this.target,\n        });\n        if (event.defaultPrevented) {\n            this.delegate.requestPreventedHandlingResponse(this, fetchResponse);\n        }\n        else if (fetchResponse.succeeded) {\n            this.delegate.requestSucceededWithResponse(this, fetchResponse);\n        }\n        else {\n            this.delegate.requestFailedWithResponse(this, fetchResponse);\n        }\n        return fetchResponse;\n    }\n    get fetchOptions() {\n        var _a;\n        return {\n            method: FetchMethod[this.method].toUpperCase(),\n            credentials: \"same-origin\",\n            headers: this.headers,\n            redirect: \"follow\",\n            body: this.isSafe ? null : this.body,\n            signal: this.abortSignal,\n            referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href,\n        };\n    }\n    get defaultHeaders() {\n        return {\n            Accept: \"text/html, application/xhtml+xml\",\n        };\n    }\n    get isSafe() {\n        return this.method === FetchMethod.get;\n    }\n    get abortSignal() {\n        return this.abortController.signal;\n    }\n    acceptResponseType(mimeType) {\n        this.headers[\"Accept\"] = [mimeType, this.headers[\"Accept\"]].join(\", \");\n    }\n    async allowRequestToBeIntercepted(fetchOptions) {\n        const requestInterception = new Promise((resolve) => (this.resolveRequestPromise = resolve));\n        const event = dispatch(\"turbo:before-fetch-request\", {\n            cancelable: true,\n            detail: {\n                fetchOptions,\n                url: this.url,\n                resume: this.resolveRequestPromise,\n            },\n            target: this.target,\n        });\n        if (event.defaultPrevented)\n            await requestInterception;\n    }\n    willDelegateErrorHandling(error) {\n        const event = dispatch(\"turbo:fetch-request-error\", {\n            target: this.target,\n            cancelable: true,\n            detail: { request: this, error: error },\n        });\n        return !event.defaultPrevented;\n    }\n}\n\nclass AppearanceObserver {\n    constructor(delegate, element) {\n        this.started = false;\n        this.intersect = (entries) => {\n            const lastEntry = entries.slice(-1)[0];\n            if (lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.isIntersecting) {\n                this.delegate.elementAppearedInViewport(this.element);\n            }\n        };\n        this.delegate = delegate;\n        this.element = element;\n        this.intersectionObserver = new IntersectionObserver(this.intersect);\n    }\n    start() {\n        if (!this.started) {\n            this.started = true;\n            this.intersectionObserver.observe(this.element);\n        }\n    }\n    stop() {\n        if (this.started) {\n            this.started = false;\n            this.intersectionObserver.unobserve(this.element);\n        }\n    }\n}\n\nclass StreamMessage {\n    static wrap(message) {\n        if (typeof message == \"string\") {\n            return new this(createDocumentFragment(message));\n        }\n        else {\n            return message;\n        }\n    }\n    constructor(fragment) {\n        this.fragment = importStreamElements(fragment);\n    }\n}\nStreamMessage.contentType = \"text/vnd.turbo-stream.html\";\nfunction importStreamElements(fragment) {\n    for (const element of fragment.querySelectorAll(\"turbo-stream\")) {\n        const streamElement = document.importNode(element, true);\n        for (const inertScriptElement of streamElement.templateElement.content.querySelectorAll(\"script\")) {\n            inertScriptElement.replaceWith(activateScriptElement(inertScriptElement));\n        }\n        element.replaceWith(streamElement);\n    }\n    return fragment;\n}\n\nvar FormSubmissionState;\n(function (FormSubmissionState) {\n    FormSubmissionState[FormSubmissionState[\"initialized\"] = 0] = \"initialized\";\n    FormSubmissionState[FormSubmissionState[\"requesting\"] = 1] = \"requesting\";\n    FormSubmissionState[FormSubmissionState[\"waiting\"] = 2] = \"waiting\";\n    FormSubmissionState[FormSubmissionState[\"receiving\"] = 3] = \"receiving\";\n    FormSubmissionState[FormSubmissionState[\"stopping\"] = 4] = \"stopping\";\n    FormSubmissionState[FormSubmissionState[\"stopped\"] = 5] = \"stopped\";\n})(FormSubmissionState || (FormSubmissionState = {}));\nvar FormEnctype;\n(function (FormEnctype) {\n    FormEnctype[\"urlEncoded\"] = \"application/x-www-form-urlencoded\";\n    FormEnctype[\"multipart\"] = \"multipart/form-data\";\n    FormEnctype[\"plain\"] = \"text/plain\";\n})(FormEnctype || (FormEnctype = {}));\nfunction formEnctypeFromString(encoding) {\n    switch (encoding.toLowerCase()) {\n        case FormEnctype.multipart:\n            return FormEnctype.multipart;\n        case FormEnctype.plain:\n            return FormEnctype.plain;\n        default:\n            return FormEnctype.urlEncoded;\n    }\n}\nclass FormSubmission {\n    static confirmMethod(message, _element, _submitter) {\n        return Promise.resolve(confirm(message));\n    }\n    constructor(delegate, formElement, submitter, mustRedirect = false) {\n        this.state = FormSubmissionState.initialized;\n        this.delegate = delegate;\n        this.formElement = formElement;\n        this.submitter = submitter;\n        this.formData = buildFormData(formElement, submitter);\n        this.location = expandURL(this.action);\n        if (this.method == FetchMethod.get) {\n            mergeFormDataEntries(this.location, [...this.body.entries()]);\n        }\n        this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);\n        this.mustRedirect = mustRedirect;\n    }\n    get method() {\n        var _a;\n        const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute(\"formmethod\")) || this.formElement.getAttribute(\"method\") || \"\";\n        return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get;\n    }\n    get action() {\n        var _a;\n        const formElementAction = typeof this.formElement.action === \"string\" ? this.formElement.action : null;\n        if ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.hasAttribute(\"formaction\")) {\n            return this.submitter.getAttribute(\"formaction\") || \"\";\n        }\n        else {\n            return this.formElement.getAttribute(\"action\") || formElementAction || \"\";\n        }\n    }\n    get body() {\n        if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {\n            return new URLSearchParams(this.stringFormData);\n        }\n        else {\n            return this.formData;\n        }\n    }\n    get enctype() {\n        var _a;\n        return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute(\"formenctype\")) || this.formElement.enctype);\n    }\n    get isSafe() {\n        return this.fetchRequest.isSafe;\n    }\n    get stringFormData() {\n        return [...this.formData].reduce((entries, [name, value]) => {\n            return entries.concat(typeof value == \"string\" ? [[name, value]] : []);\n        }, []);\n    }\n    async start() {\n        const { initialized, requesting } = FormSubmissionState;\n        const confirmationMessage = getAttribute(\"data-turbo-confirm\", this.submitter, this.formElement);\n        if (typeof confirmationMessage === \"string\") {\n            const answer = await FormSubmission.confirmMethod(confirmationMessage, this.formElement, this.submitter);\n            if (!answer) {\n                return;\n            }\n        }\n        if (this.state == initialized) {\n            this.state = requesting;\n            return this.fetchRequest.perform();\n        }\n    }\n    stop() {\n        const { stopping, stopped } = FormSubmissionState;\n        if (this.state != stopping && this.state != stopped) {\n            this.state = stopping;\n            this.fetchRequest.cancel();\n            return true;\n        }\n    }\n    prepareRequest(request) {\n        if (!request.isSafe) {\n            const token = getCookieValue(getMetaContent(\"csrf-param\")) || getMetaContent(\"csrf-token\");\n            if (token) {\n                request.headers[\"X-CSRF-Token\"] = token;\n            }\n        }\n        if (this.requestAcceptsTurboStreamResponse(request)) {\n            request.acceptResponseType(StreamMessage.contentType);\n        }\n    }\n    requestStarted(_request) {\n        var _a;\n        this.state = FormSubmissionState.waiting;\n        (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute(\"disabled\", \"\");\n        this.setSubmitsWith();\n        dispatch(\"turbo:submit-start\", {\n            target: this.formElement,\n            detail: { formSubmission: this },\n        });\n        this.delegate.formSubmissionStarted(this);\n    }\n    requestPreventedHandlingResponse(request, response) {\n        this.result = { success: response.succeeded, fetchResponse: response };\n    }\n    requestSucceededWithResponse(request, response) {\n        if (response.clientError || response.serverError) {\n            this.delegate.formSubmissionFailedWithResponse(this, response);\n        }\n        else if (this.requestMustRedirect(request) && responseSucceededWithoutRedirect(response)) {\n            const error = new Error(\"Form responses must redirect to another location\");\n            this.delegate.formSubmissionErrored(this, error);\n        }\n        else {\n            this.state = FormSubmissionState.receiving;\n            this.result = { success: true, fetchResponse: response };\n            this.delegate.formSubmissionSucceededWithResponse(this, response);\n        }\n    }\n    requestFailedWithResponse(request, response) {\n        this.result = { success: false, fetchResponse: response };\n        this.delegate.formSubmissionFailedWithResponse(this, response);\n    }\n    requestErrored(request, error) {\n        this.result = { success: false, error };\n        this.delegate.formSubmissionErrored(this, error);\n    }\n    requestFinished(_request) {\n        var _a;\n        this.state = FormSubmissionState.stopped;\n        (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute(\"disabled\");\n        this.resetSubmitterText();\n        dispatch(\"turbo:submit-end\", {\n            target: this.formElement,\n            detail: Object.assign({ formSubmission: this }, this.result),\n        });\n        this.delegate.formSubmissionFinished(this);\n    }\n    setSubmitsWith() {\n        if (!this.submitter || !this.submitsWith)\n            return;\n        if (this.submitter.matches(\"button\")) {\n            this.originalSubmitText = this.submitter.innerHTML;\n            this.submitter.innerHTML = this.submitsWith;\n        }\n        else if (this.submitter.matches(\"input\")) {\n            const input = this.submitter;\n            this.originalSubmitText = input.value;\n            input.value = this.submitsWith;\n        }\n    }\n    resetSubmitterText() {\n        if (!this.submitter || !this.originalSubmitText)\n            return;\n        if (this.submitter.matches(\"button\")) {\n            this.submitter.innerHTML = this.originalSubmitText;\n        }\n        else if (this.submitter.matches(\"input\")) {\n            const input = this.submitter;\n            input.value = this.originalSubmitText;\n        }\n    }\n    requestMustRedirect(request) {\n        return !request.isSafe && this.mustRedirect;\n    }\n    requestAcceptsTurboStreamResponse(request) {\n        return !request.isSafe || hasAttribute(\"data-turbo-stream\", this.submitter, this.formElement);\n    }\n    get submitsWith() {\n        var _a;\n        return (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute(\"data-turbo-submits-with\");\n    }\n}\nfunction buildFormData(formElement, submitter) {\n    const formData = new FormData(formElement);\n    const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute(\"name\");\n    const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute(\"value\");\n    if (name) {\n        formData.append(name, value || \"\");\n    }\n    return formData;\n}\nfunction getCookieValue(cookieName) {\n    if (cookieName != null) {\n        const cookies = document.cookie ? document.cookie.split(\"; \") : [];\n        const cookie = cookies.find((cookie) => cookie.startsWith(cookieName));\n        if (cookie) {\n            const value = cookie.split(\"=\").slice(1).join(\"=\");\n            return value ? decodeURIComponent(value) : undefined;\n        }\n    }\n}\nfunction responseSucceededWithoutRedirect(response) {\n    return response.statusCode == 200 && !response.redirected;\n}\nfunction mergeFormDataEntries(url, entries) {\n    const searchParams = new URLSearchParams();\n    for (const [name, value] of entries) {\n        if (value instanceof File)\n            continue;\n        searchParams.append(name, value);\n    }\n    url.search = searchParams.toString();\n    return url;\n}\n\nclass Snapshot {\n    constructor(element) {\n        this.element = element;\n    }\n    get activeElement() {\n        return this.element.ownerDocument.activeElement;\n    }\n    get children() {\n        return [...this.element.children];\n    }\n    hasAnchor(anchor) {\n        return this.getElementForAnchor(anchor) != null;\n    }\n    getElementForAnchor(anchor) {\n        return anchor ? this.element.querySelector(`[id='${anchor}'], a[name='${anchor}']`) : null;\n    }\n    get isConnected() {\n        return this.element.isConnected;\n    }\n    get firstAutofocusableElement() {\n        const inertDisabledOrHidden = \"[inert], :disabled, [hidden], details:not([open]), dialog:not([open])\";\n        for (const element of this.element.querySelectorAll(\"[autofocus]\")) {\n            if (element.closest(inertDisabledOrHidden) == null)\n                return element;\n            else\n                continue;\n        }\n        return null;\n    }\n    get permanentElements() {\n        return queryPermanentElementsAll(this.element);\n    }\n    getPermanentElementById(id) {\n        return getPermanentElementById(this.element, id);\n    }\n    getPermanentElementMapForSnapshot(snapshot) {\n        const permanentElementMap = {};\n        for (const currentPermanentElement of this.permanentElements) {\n            const { id } = currentPermanentElement;\n            const newPermanentElement = snapshot.getPermanentElementById(id);\n            if (newPermanentElement) {\n                permanentElementMap[id] = [currentPermanentElement, newPermanentElement];\n            }\n        }\n        return permanentElementMap;\n    }\n}\nfunction getPermanentElementById(node, id) {\n    return node.querySelector(`#${id}[data-turbo-permanent]`);\n}\nfunction queryPermanentElementsAll(node) {\n    return node.querySelectorAll(\"[id][data-turbo-permanent]\");\n}\n\nclass FormSubmitObserver {\n    constructor(delegate, eventTarget) {\n        this.started = false;\n        this.submitCaptured = () => {\n            this.eventTarget.removeEventListener(\"submit\", this.submitBubbled, false);\n            this.eventTarget.addEventListener(\"submit\", this.submitBubbled, false);\n        };\n        this.submitBubbled = ((event) => {\n            if (!event.defaultPrevented) {\n                const form = event.target instanceof HTMLFormElement ? event.target : undefined;\n                const submitter = event.submitter || undefined;\n                if (form &&\n                    submissionDoesNotDismissDialog(form, submitter) &&\n                    submissionDoesNotTargetIFrame(form, submitter) &&\n                    this.delegate.willSubmitForm(form, submitter)) {\n                    event.preventDefault();\n                    event.stopImmediatePropagation();\n                    this.delegate.formSubmitted(form, submitter);\n                }\n            }\n        });\n        this.delegate = delegate;\n        this.eventTarget = eventTarget;\n    }\n    start() {\n        if (!this.started) {\n            this.eventTarget.addEventListener(\"submit\", this.submitCaptured, true);\n            this.started = true;\n        }\n    }\n    stop() {\n        if (this.started) {\n            this.eventTarget.removeEventListener(\"submit\", this.submitCaptured, true);\n            this.started = false;\n        }\n    }\n}\nfunction submissionDoesNotDismissDialog(form, submitter) {\n    const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute(\"formmethod\")) || form.getAttribute(\"method\");\n    return method != \"dialog\";\n}\nfunction submissionDoesNotTargetIFrame(form, submitter) {\n    if ((submitter === null || submitter === void 0 ? void 0 : submitter.hasAttribute(\"formtarget\")) || form.hasAttribute(\"target\")) {\n        const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute(\"formtarget\")) || form.target;\n        for (const element of document.getElementsByName(target)) {\n            if (element instanceof HTMLIFrameElement)\n                return false;\n        }\n        return true;\n    }\n    else {\n        return true;\n    }\n}\n\nclass View {\n    constructor(delegate, element) {\n        this.resolveRenderPromise = (_value) => { };\n        this.resolveInterceptionPromise = (_value) => { };\n        this.delegate = delegate;\n        this.element = element;\n    }\n    scrollToAnchor(anchor) {\n        const element = this.snapshot.getElementForAnchor(anchor);\n        if (element) {\n            this.scrollToElement(element);\n            this.focusElement(element);\n        }\n        else {\n            this.scrollToPosition({ x: 0, y: 0 });\n        }\n    }\n    scrollToAnchorFromLocation(location) {\n        this.scrollToAnchor(getAnchor(location));\n    }\n    scrollToElement(element) {\n        element.scrollIntoView();\n    }\n    focusElement(element) {\n        if (element instanceof HTMLElement) {\n            if (element.hasAttribute(\"tabindex\")) {\n                element.focus();\n            }\n            else {\n                element.setAttribute(\"tabindex\", \"-1\");\n                element.focus();\n                element.removeAttribute(\"tabindex\");\n            }\n        }\n    }\n    scrollToPosition({ x, y }) {\n        this.scrollRoot.scrollTo(x, y);\n    }\n    scrollToTop() {\n        this.scrollToPosition({ x: 0, y: 0 });\n    }\n    get scrollRoot() {\n        return window;\n    }\n    async render(renderer) {\n        const { isPreview, shouldRender, newSnapshot: snapshot } = renderer;\n        if (shouldRender) {\n            try {\n                this.renderPromise = new Promise((resolve) => (this.resolveRenderPromise = resolve));\n                this.renderer = renderer;\n                await this.prepareToRenderSnapshot(renderer);\n                const renderInterception = new Promise((resolve) => (this.resolveInterceptionPromise = resolve));\n                const options = { resume: this.resolveInterceptionPromise, render: this.renderer.renderElement };\n                const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);\n                if (!immediateRender)\n                    await renderInterception;\n                await this.renderSnapshot(renderer);\n                this.delegate.viewRenderedSnapshot(snapshot, isPreview);\n                this.delegate.preloadOnLoadLinksForView(this.element);\n                this.finishRenderingSnapshot(renderer);\n            }\n            finally {\n                delete this.renderer;\n                this.resolveRenderPromise(undefined);\n                delete this.renderPromise;\n            }\n        }\n        else {\n            this.invalidate(renderer.reloadReason);\n        }\n    }\n    invalidate(reason) {\n        this.delegate.viewInvalidated(reason);\n    }\n    async prepareToRenderSnapshot(renderer) {\n        this.markAsPreview(renderer.isPreview);\n        await renderer.prepareToRender();\n    }\n    markAsPreview(isPreview) {\n        if (isPreview) {\n            this.element.setAttribute(\"data-turbo-preview\", \"\");\n        }\n        else {\n            this.element.removeAttribute(\"data-turbo-preview\");\n        }\n    }\n    async renderSnapshot(renderer) {\n        await renderer.render();\n    }\n    finishRenderingSnapshot(renderer) {\n        renderer.finishRendering();\n    }\n}\n\nclass FrameView extends View {\n    missing() {\n        this.element.innerHTML = `<strong class=\"turbo-frame-error\">Content missing</strong>`;\n    }\n    get snapshot() {\n        return new Snapshot(this.element);\n    }\n}\n\nclass LinkInterceptor {\n    constructor(delegate, element) {\n        this.clickBubbled = (event) => {\n            if (this.respondsToEventTarget(event.target)) {\n                this.clickEvent = event;\n            }\n            else {\n                delete this.clickEvent;\n            }\n        };\n        this.linkClicked = ((event) => {\n            if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {\n                if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {\n                    this.clickEvent.preventDefault();\n                    event.preventDefault();\n                    this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);\n                }\n            }\n            delete this.clickEvent;\n        });\n        this.willVisit = ((_event) => {\n            delete this.clickEvent;\n        });\n        this.delegate = delegate;\n        this.element = element;\n    }\n    start() {\n        this.element.addEventListener(\"click\", this.clickBubbled);\n        document.addEventListener(\"turbo:click\", this.linkClicked);\n        document.addEventListener(\"turbo:before-visit\", this.willVisit);\n    }\n    stop() {\n        this.element.removeEventListener(\"click\", this.clickBubbled);\n        document.removeEventListener(\"turbo:click\", this.linkClicked);\n        document.removeEventListener(\"turbo:before-visit\", this.willVisit);\n    }\n    respondsToEventTarget(target) {\n        const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;\n        return element && element.closest(\"turbo-frame, html\") == this.element;\n    }\n}\n\nclass LinkClickObserver {\n    constructor(delegate, eventTarget) {\n        this.started = false;\n        this.clickCaptured = () => {\n            this.eventTarget.removeEventListener(\"click\", this.clickBubbled, false);\n            this.eventTarget.addEventListener(\"click\", this.clickBubbled, false);\n        };\n        this.clickBubbled = (event) => {\n            if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {\n                const target = (event.composedPath && event.composedPath()[0]) || event.target;\n                const link = this.findLinkFromClickTarget(target);\n                if (link && doesNotTargetIFrame(link)) {\n                    const location = this.getLocationForLink(link);\n                    if (this.delegate.willFollowLinkToLocation(link, location, event)) {\n                        event.preventDefault();\n                        this.delegate.followedLinkToLocation(link, location);\n                    }\n                }\n            }\n        };\n        this.delegate = delegate;\n        this.eventTarget = eventTarget;\n    }\n    start() {\n        if (!this.started) {\n            this.eventTarget.addEventListener(\"click\", this.clickCaptured, true);\n            this.started = true;\n        }\n    }\n    stop() {\n        if (this.started) {\n            this.eventTarget.removeEventListener(\"click\", this.clickCaptured, true);\n            this.started = false;\n        }\n    }\n    clickEventIsSignificant(event) {\n        return !((event.target && event.target.isContentEditable) ||\n            event.defaultPrevented ||\n            event.which > 1 ||\n            event.altKey ||\n            event.ctrlKey ||\n            event.metaKey ||\n            event.shiftKey);\n    }\n    findLinkFromClickTarget(target) {\n        return findClosestRecursively(target, \"a[href]:not([target^=_]):not([download])\");\n    }\n    getLocationForLink(link) {\n        return expandURL(link.getAttribute(\"href\") || \"\");\n    }\n}\nfunction doesNotTargetIFrame(anchor) {\n    if (anchor.hasAttribute(\"target\")) {\n        for (const element of document.getElementsByName(anchor.target)) {\n            if (element instanceof HTMLIFrameElement)\n                return false;\n        }\n        return true;\n    }\n    else {\n        return true;\n    }\n}\n\nclass FormLinkClickObserver {\n    constructor(delegate, element) {\n        this.delegate = delegate;\n        this.linkInterceptor = new LinkClickObserver(this, element);\n    }\n    start() {\n        this.linkInterceptor.start();\n    }\n    stop() {\n        this.linkInterceptor.stop();\n    }\n    willFollowLinkToLocation(link, location, originalEvent) {\n        return (this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) &&\n            link.hasAttribute(\"data-turbo-method\"));\n    }\n    followedLinkToLocation(link, location) {\n        const form = document.createElement(\"form\");\n        const type = \"hidden\";\n        for (const [name, value] of location.searchParams) {\n            form.append(Object.assign(document.createElement(\"input\"), { type, name, value }));\n        }\n        const action = Object.assign(location, { search: \"\" });\n        form.setAttribute(\"data-turbo\", \"true\");\n        form.setAttribute(\"action\", action.href);\n        form.setAttribute(\"hidden\", \"\");\n        const method = link.getAttribute(\"data-turbo-method\");\n        if (method)\n            form.setAttribute(\"method\", method);\n        const turboFrame = link.getAttribute(\"data-turbo-frame\");\n        if (turboFrame)\n            form.setAttribute(\"data-turbo-frame\", turboFrame);\n        const turboAction = getVisitAction(link);\n        if (turboAction)\n            form.setAttribute(\"data-turbo-action\", turboAction);\n        const turboConfirm = link.getAttribute(\"data-turbo-confirm\");\n        if (turboConfirm)\n            form.setAttribute(\"data-turbo-confirm\", turboConfirm);\n        const turboStream = link.hasAttribute(\"data-turbo-stream\");\n        if (turboStream)\n            form.setAttribute(\"data-turbo-stream\", \"\");\n        this.delegate.submittedFormLinkToLocation(link, location, form);\n        document.body.appendChild(form);\n        form.addEventListener(\"turbo:submit-end\", () => form.remove(), { once: true });\n        requestAnimationFrame(() => form.requestSubmit());\n    }\n}\n\nclass Bardo {\n    static async preservingPermanentElements(delegate, permanentElementMap, callback) {\n        const bardo = new this(delegate, permanentElementMap);\n        bardo.enter();\n        await callback();\n        bardo.leave();\n    }\n    constructor(delegate, permanentElementMap) {\n        this.delegate = delegate;\n        this.permanentElementMap = permanentElementMap;\n    }\n    enter() {\n        for (const id in this.permanentElementMap) {\n            const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];\n            this.delegate.enteringBardo(currentPermanentElement, newPermanentElement);\n            this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);\n        }\n    }\n    leave() {\n        for (const id in this.permanentElementMap) {\n            const [currentPermanentElement] = this.permanentElementMap[id];\n            this.replaceCurrentPermanentElementWithClone(currentPermanentElement);\n            this.replacePlaceholderWithPermanentElement(currentPermanentElement);\n            this.delegate.leavingBardo(currentPermanentElement);\n        }\n    }\n    replaceNewPermanentElementWithPlaceholder(permanentElement) {\n        const placeholder = createPlaceholderForPermanentElement(permanentElement);\n        permanentElement.replaceWith(placeholder);\n    }\n    replaceCurrentPermanentElementWithClone(permanentElement) {\n        const clone = permanentElement.cloneNode(true);\n        permanentElement.replaceWith(clone);\n    }\n    replacePlaceholderWithPermanentElement(permanentElement) {\n        const placeholder = this.getPlaceholderById(permanentElement.id);\n        placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement);\n    }\n    getPlaceholderById(id) {\n        return this.placeholders.find((element) => element.content == id);\n    }\n    get placeholders() {\n        return [...document.querySelectorAll(\"meta[name=turbo-permanent-placeholder][content]\")];\n    }\n}\nfunction createPlaceholderForPermanentElement(permanentElement) {\n    const element = document.createElement(\"meta\");\n    element.setAttribute(\"name\", \"turbo-permanent-placeholder\");\n    element.setAttribute(\"content\", permanentElement.id);\n    return element;\n}\n\nclass Renderer {\n    constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {\n        this.activeElement = null;\n        this.currentSnapshot = currentSnapshot;\n        this.newSnapshot = newSnapshot;\n        this.isPreview = isPreview;\n        this.willRender = willRender;\n        this.renderElement = renderElement;\n        this.promise = new Promise((resolve, reject) => (this.resolvingFunctions = { resolve, reject }));\n    }\n    get shouldRender() {\n        return true;\n    }\n    get reloadReason() {\n        return;\n    }\n    prepareToRender() {\n        return;\n    }\n    finishRendering() {\n        if (this.resolvingFunctions) {\n            this.resolvingFunctions.resolve();\n            delete this.resolvingFunctions;\n        }\n    }\n    async preservingPermanentElements(callback) {\n        await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);\n    }\n    focusFirstAutofocusableElement() {\n        const element = this.connectedSnapshot.firstAutofocusableElement;\n        if (elementIsFocusable(element)) {\n            element.focus();\n        }\n    }\n    enteringBardo(currentPermanentElement) {\n        if (this.activeElement)\n            return;\n        if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {\n            this.activeElement = this.currentSnapshot.activeElement;\n        }\n    }\n    leavingBardo(currentPermanentElement) {\n        if (currentPermanentElement.contains(this.activeElement) && this.activeElement instanceof HTMLElement) {\n            this.activeElement.focus();\n            this.activeElement = null;\n        }\n    }\n    get connectedSnapshot() {\n        return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;\n    }\n    get currentElement() {\n        return this.currentSnapshot.element;\n    }\n    get newElement() {\n        return this.newSnapshot.element;\n    }\n    get permanentElementMap() {\n        return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);\n    }\n}\nfunction elementIsFocusable(element) {\n    return element && typeof element.focus == \"function\";\n}\n\nclass FrameRenderer extends Renderer {\n    static renderElement(currentElement, newElement) {\n        var _a;\n        const destinationRange = document.createRange();\n        destinationRange.selectNodeContents(currentElement);\n        destinationRange.deleteContents();\n        const frameElement = newElement;\n        const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();\n        if (sourceRange) {\n            sourceRange.selectNodeContents(frameElement);\n            currentElement.appendChild(sourceRange.extractContents());\n        }\n    }\n    constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {\n        super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);\n        this.delegate = delegate;\n    }\n    get shouldRender() {\n        return true;\n    }\n    async render() {\n        await nextAnimationFrame();\n        this.preservingPermanentElements(() => {\n            this.loadFrameElement();\n        });\n        this.scrollFrameIntoView();\n        await nextAnimationFrame();\n        this.focusFirstAutofocusableElement();\n        await nextAnimationFrame();\n        this.activateScriptElements();\n    }\n    loadFrameElement() {\n        this.delegate.willRenderFrame(this.currentElement, this.newElement);\n        this.renderElement(this.currentElement, this.newElement);\n    }\n    scrollFrameIntoView() {\n        if (this.currentElement.autoscroll || this.newElement.autoscroll) {\n            const element = this.currentElement.firstElementChild;\n            const block = readScrollLogicalPosition(this.currentElement.getAttribute(\"data-autoscroll-block\"), \"end\");\n            const behavior = readScrollBehavior(this.currentElement.getAttribute(\"data-autoscroll-behavior\"), \"auto\");\n            if (element) {\n                element.scrollIntoView({ block, behavior });\n                return true;\n            }\n        }\n        return false;\n    }\n    activateScriptElements() {\n        for (const inertScriptElement of this.newScriptElements) {\n            const activatedScriptElement = activateScriptElement(inertScriptElement);\n            inertScriptElement.replaceWith(activatedScriptElement);\n        }\n    }\n    get newScriptElements() {\n        return this.currentElement.querySelectorAll(\"script\");\n    }\n}\nfunction readScrollLogicalPosition(value, defaultValue) {\n    if (value == \"end\" || value == \"start\" || value == \"center\" || value == \"nearest\") {\n        return value;\n    }\n    else {\n        return defaultValue;\n    }\n}\nfunction readScrollBehavior(value, defaultValue) {\n    if (value == \"auto\" || value == \"smooth\") {\n        return value;\n    }\n    else {\n        return defaultValue;\n    }\n}\n\nclass ProgressBar {\n    static get defaultCSS() {\n        return unindent `\n      .turbo-progress-bar {\n        position: fixed;\n        display: block;\n        top: 0;\n        left: 0;\n        height: 3px;\n        background: #0076ff;\n        z-index: 2147483647;\n        transition:\n          width ${ProgressBar.animationDuration}ms ease-out,\n          opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;\n        transform: translate3d(0, 0, 0);\n      }\n    `;\n    }\n    constructor() {\n        this.hiding = false;\n        this.value = 0;\n        this.visible = false;\n        this.trickle = () => {\n            this.setValue(this.value + Math.random() / 100);\n        };\n        this.stylesheetElement = this.createStylesheetElement();\n        this.progressElement = this.createProgressElement();\n        this.installStylesheetElement();\n        this.setValue(0);\n    }\n    show() {\n        if (!this.visible) {\n            this.visible = true;\n            this.installProgressElement();\n            this.startTrickling();\n        }\n    }\n    hide() {\n        if (this.visible && !this.hiding) {\n            this.hiding = true;\n            this.fadeProgressElement(() => {\n                this.uninstallProgressElement();\n                this.stopTrickling();\n                this.visible = false;\n                this.hiding = false;\n            });\n        }\n    }\n    setValue(value) {\n        this.value = value;\n        this.refresh();\n    }\n    installStylesheetElement() {\n        document.head.insertBefore(this.stylesheetElement, document.head.firstChild);\n    }\n    installProgressElement() {\n        this.progressElement.style.width = \"0\";\n        this.progressElement.style.opacity = \"1\";\n        document.documentElement.insertBefore(this.progressElement, document.body);\n        this.refresh();\n    }\n    fadeProgressElement(callback) {\n        this.progressElement.style.opacity = \"0\";\n        setTimeout(callback, ProgressBar.animationDuration * 1.5);\n    }\n    uninstallProgressElement() {\n        if (this.progressElement.parentNode) {\n            document.documentElement.removeChild(this.progressElement);\n        }\n    }\n    startTrickling() {\n        if (!this.trickleInterval) {\n            this.trickleInterval = window.setInterval(this.trickle, ProgressBar.animationDuration);\n        }\n    }\n    stopTrickling() {\n        window.clearInterval(this.trickleInterval);\n        delete this.trickleInterval;\n    }\n    refresh() {\n        requestAnimationFrame(() => {\n            this.progressElement.style.width = `${10 + this.value * 90}%`;\n        });\n    }\n    createStylesheetElement() {\n        const element = document.createElement(\"style\");\n        element.type = \"text/css\";\n        element.textContent = ProgressBar.defaultCSS;\n        if (this.cspNonce) {\n            element.nonce = this.cspNonce;\n        }\n        return element;\n    }\n    createProgressElement() {\n        const element = document.createElement(\"div\");\n        element.className = \"turbo-progress-bar\";\n        return element;\n    }\n    get cspNonce() {\n        return getMetaContent(\"csp-nonce\");\n    }\n}\nProgressBar.animationDuration = 300;\n\nclass HeadSnapshot extends Snapshot {\n    constructor() {\n        super(...arguments);\n        this.detailsByOuterHTML = this.children\n            .filter((element) => !elementIsNoscript(element))\n            .map((element) => elementWithoutNonce(element))\n            .reduce((result, element) => {\n            const { outerHTML } = element;\n            const details = outerHTML in result\n                ? result[outerHTML]\n                : {\n                    type: elementType(element),\n                    tracked: elementIsTracked(element),\n                    elements: [],\n                };\n            return Object.assign(Object.assign({}, result), { [outerHTML]: Object.assign(Object.assign({}, details), { elements: [...details.elements, element] }) });\n        }, {});\n    }\n    get trackedElementSignature() {\n        return Object.keys(this.detailsByOuterHTML)\n            .filter((outerHTML) => this.detailsByOuterHTML[outerHTML].tracked)\n            .join(\"\");\n    }\n    getScriptElementsNotInSnapshot(snapshot) {\n        return this.getElementsMatchingTypeNotInSnapshot(\"script\", snapshot);\n    }\n    getStylesheetElementsNotInSnapshot(snapshot) {\n        return this.getElementsMatchingTypeNotInSnapshot(\"stylesheet\", snapshot);\n    }\n    getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {\n        return Object.keys(this.detailsByOuterHTML)\n            .filter((outerHTML) => !(outerHTML in snapshot.detailsByOuterHTML))\n            .map((outerHTML) => this.detailsByOuterHTML[outerHTML])\n            .filter(({ type }) => type == matchedType)\n            .map(({ elements: [element] }) => element);\n    }\n    get provisionalElements() {\n        return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {\n            const { type, tracked, elements } = this.detailsByOuterHTML[outerHTML];\n            if (type == null && !tracked) {\n                return [...result, ...elements];\n            }\n            else if (elements.length > 1) {\n                return [...result, ...elements.slice(1)];\n            }\n            else {\n                return result;\n            }\n        }, []);\n    }\n    getMetaValue(name) {\n        const element = this.findMetaElementByName(name);\n        return element ? element.getAttribute(\"content\") : null;\n    }\n    findMetaElementByName(name) {\n        return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {\n            const { elements: [element], } = this.detailsByOuterHTML[outerHTML];\n            return elementIsMetaElementWithName(element, name) ? element : result;\n        }, undefined);\n    }\n}\nfunction elementType(element) {\n    if (elementIsScript(element)) {\n        return \"script\";\n    }\n    else if (elementIsStylesheet(element)) {\n        return \"stylesheet\";\n    }\n}\nfunction elementIsTracked(element) {\n    return element.getAttribute(\"data-turbo-track\") == \"reload\";\n}\nfunction elementIsScript(element) {\n    const tagName = element.localName;\n    return tagName == \"script\";\n}\nfunction elementIsNoscript(element) {\n    const tagName = element.localName;\n    return tagName == \"noscript\";\n}\nfunction elementIsStylesheet(element) {\n    const tagName = element.localName;\n    return tagName == \"style\" || (tagName == \"link\" && element.getAttribute(\"rel\") == \"stylesheet\");\n}\nfunction elementIsMetaElementWithName(element, name) {\n    const tagName = element.localName;\n    return tagName == \"meta\" && element.getAttribute(\"name\") == name;\n}\nfunction elementWithoutNonce(element) {\n    if (element.hasAttribute(\"nonce\")) {\n        element.setAttribute(\"nonce\", \"\");\n    }\n    return element;\n}\n\nclass PageSnapshot extends Snapshot {\n    static fromHTMLString(html = \"\") {\n        return this.fromDocument(parseHTMLDocument(html));\n    }\n    static fromElement(element) {\n        return this.fromDocument(element.ownerDocument);\n    }\n    static fromDocument({ head, body }) {\n        return new this(body, new HeadSnapshot(head));\n    }\n    constructor(element, headSnapshot) {\n        super(element);\n        this.headSnapshot = headSnapshot;\n    }\n    clone() {\n        const clonedElement = this.element.cloneNode(true);\n        const selectElements = this.element.querySelectorAll(\"select\");\n        const clonedSelectElements = clonedElement.querySelectorAll(\"select\");\n        for (const [index, source] of selectElements.entries()) {\n            const clone = clonedSelectElements[index];\n            for (const option of clone.selectedOptions)\n                option.selected = false;\n            for (const option of source.selectedOptions)\n                clone.options[option.index].selected = true;\n        }\n        for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type=\"password\"]')) {\n            clonedPasswordInput.value = \"\";\n        }\n        return new PageSnapshot(clonedElement, this.headSnapshot);\n    }\n    get headElement() {\n        return this.headSnapshot.element;\n    }\n    get rootLocation() {\n        var _a;\n        const root = (_a = this.getSetting(\"root\")) !== null && _a !== void 0 ? _a : \"/\";\n        return expandURL(root);\n    }\n    get cacheControlValue() {\n        return this.getSetting(\"cache-control\");\n    }\n    get isPreviewable() {\n        return this.cacheControlValue != \"no-preview\";\n    }\n    get isCacheable() {\n        return this.cacheControlValue != \"no-cache\";\n    }\n    get isVisitable() {\n        return this.getSetting(\"visit-control\") != \"reload\";\n    }\n    getSetting(name) {\n        return this.headSnapshot.getMetaValue(`turbo-${name}`);\n    }\n}\n\nvar TimingMetric;\n(function (TimingMetric) {\n    TimingMetric[\"visitStart\"] = \"visitStart\";\n    TimingMetric[\"requestStart\"] = \"requestStart\";\n    TimingMetric[\"requestEnd\"] = \"requestEnd\";\n    TimingMetric[\"visitEnd\"] = \"visitEnd\";\n})(TimingMetric || (TimingMetric = {}));\nvar VisitState;\n(function (VisitState) {\n    VisitState[\"initialized\"] = \"initialized\";\n    VisitState[\"started\"] = \"started\";\n    VisitState[\"canceled\"] = \"canceled\";\n    VisitState[\"failed\"] = \"failed\";\n    VisitState[\"completed\"] = \"completed\";\n})(VisitState || (VisitState = {}));\nconst defaultOptions = {\n    action: \"advance\",\n    historyChanged: false,\n    visitCachedSnapshot: () => { },\n    willRender: true,\n    updateHistory: true,\n    shouldCacheSnapshot: true,\n    acceptsStreamResponse: false,\n};\nvar SystemStatusCode;\n(function (SystemStatusCode) {\n    SystemStatusCode[SystemStatusCode[\"networkFailure\"] = 0] = \"networkFailure\";\n    SystemStatusCode[SystemStatusCode[\"timeoutFailure\"] = -1] = \"timeoutFailure\";\n    SystemStatusCode[SystemStatusCode[\"contentTypeMismatch\"] = -2] = \"contentTypeMismatch\";\n})(SystemStatusCode || (SystemStatusCode = {}));\nclass Visit {\n    constructor(delegate, location, restorationIdentifier, options = {}) {\n        this.identifier = uuid();\n        this.timingMetrics = {};\n        this.followedRedirect = false;\n        this.historyChanged = false;\n        this.scrolled = false;\n        this.shouldCacheSnapshot = true;\n        this.acceptsStreamResponse = false;\n        this.snapshotCached = false;\n        this.state = VisitState.initialized;\n        this.delegate = delegate;\n        this.location = location;\n        this.restorationIdentifier = restorationIdentifier || uuid();\n        const { action, historyChanged, referrer, snapshot, snapshotHTML, response, visitCachedSnapshot, willRender, updateHistory, shouldCacheSnapshot, acceptsStreamResponse, } = Object.assign(Object.assign({}, defaultOptions), options);\n        this.action = action;\n        this.historyChanged = historyChanged;\n        this.referrer = referrer;\n        this.snapshot = snapshot;\n        this.snapshotHTML = snapshotHTML;\n        this.response = response;\n        this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);\n        this.visitCachedSnapshot = visitCachedSnapshot;\n        this.willRender = willRender;\n        this.updateHistory = updateHistory;\n        this.scrolled = !willRender;\n        this.shouldCacheSnapshot = shouldCacheSnapshot;\n        this.acceptsStreamResponse = acceptsStreamResponse;\n    }\n    get adapter() {\n        return this.delegate.adapter;\n    }\n    get view() {\n        return this.delegate.view;\n    }\n    get history() {\n        return this.delegate.history;\n    }\n    get restorationData() {\n        return this.history.getRestorationDataForIdentifier(this.restorationIdentifier);\n    }\n    get silent() {\n        return this.isSamePage;\n    }\n    start() {\n        if (this.state == VisitState.initialized) {\n            this.recordTimingMetric(TimingMetric.visitStart);\n            this.state = VisitState.started;\n            this.adapter.visitStarted(this);\n            this.delegate.visitStarted(this);\n        }\n    }\n    cancel() {\n        if (this.state == VisitState.started) {\n            if (this.request) {\n                this.request.cancel();\n            }\n            this.cancelRender();\n            this.state = VisitState.canceled;\n        }\n    }\n    complete() {\n        if (this.state == VisitState.started) {\n            this.recordTimingMetric(TimingMetric.visitEnd);\n            this.state = VisitState.completed;\n            this.followRedirect();\n            if (!this.followedRedirect) {\n                this.adapter.visitCompleted(this);\n                this.delegate.visitCompleted(this);\n            }\n        }\n    }\n    fail() {\n        if (this.state == VisitState.started) {\n            this.state = VisitState.failed;\n            this.adapter.visitFailed(this);\n        }\n    }\n    changeHistory() {\n        var _a;\n        if (!this.historyChanged && this.updateHistory) {\n            const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? \"replace\" : this.action;\n            const method = getHistoryMethodForAction(actionForHistory);\n            this.history.update(method, this.location, this.restorationIdentifier);\n            this.historyChanged = true;\n        }\n    }\n    issueRequest() {\n        if (this.hasPreloadedResponse()) {\n            this.simulateRequest();\n        }\n        else if (this.shouldIssueRequest() && !this.request) {\n            this.request = new FetchRequest(this, FetchMethod.get, this.location);\n            this.request.perform();\n        }\n    }\n    simulateRequest() {\n        if (this.response) {\n            this.startRequest();\n            this.recordResponse();\n            this.finishRequest();\n        }\n    }\n    startRequest() {\n        this.recordTimingMetric(TimingMetric.requestStart);\n        this.adapter.visitRequestStarted(this);\n    }\n    recordResponse(response = this.response) {\n        this.response = response;\n        if (response) {\n            const { statusCode } = response;\n            if (isSuccessful(statusCode)) {\n                this.adapter.visitRequestCompleted(this);\n            }\n            else {\n                this.adapter.visitRequestFailedWithStatusCode(this, statusCode);\n            }\n        }\n    }\n    finishRequest() {\n        this.recordTimingMetric(TimingMetric.requestEnd);\n        this.adapter.visitRequestFinished(this);\n    }\n    loadResponse() {\n        if (this.response) {\n            const { statusCode, responseHTML } = this.response;\n            this.render(async () => {\n                if (this.shouldCacheSnapshot)\n                    this.cacheSnapshot();\n                if (this.view.renderPromise)\n                    await this.view.renderPromise;\n                if (isSuccessful(statusCode) && responseHTML != null) {\n                    await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender, this);\n                    this.performScroll();\n                    this.adapter.visitRendered(this);\n                    this.complete();\n                }\n                else {\n                    await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this);\n                    this.adapter.visitRendered(this);\n                    this.fail();\n                }\n            });\n        }\n    }\n    getCachedSnapshot() {\n        const snapshot = this.view.getCachedSnapshotForLocation(this.location) || this.getPreloadedSnapshot();\n        if (snapshot && (!getAnchor(this.location) || snapshot.hasAnchor(getAnchor(this.location)))) {\n            if (this.action == \"restore\" || snapshot.isPreviewable) {\n                return snapshot;\n            }\n        }\n    }\n    getPreloadedSnapshot() {\n        if (this.snapshotHTML) {\n            return PageSnapshot.fromHTMLString(this.snapshotHTML);\n        }\n    }\n    hasCachedSnapshot() {\n        return this.getCachedSnapshot() != null;\n    }\n    loadCachedSnapshot() {\n        const snapshot = this.getCachedSnapshot();\n        if (snapshot) {\n            const isPreview = this.shouldIssueRequest();\n            this.render(async () => {\n                this.cacheSnapshot();\n                if (this.isSamePage) {\n                    this.adapter.visitRendered(this);\n                }\n                else {\n                    if (this.view.renderPromise)\n                        await this.view.renderPromise;\n                    await this.view.renderPage(snapshot, isPreview, this.willRender, this);\n                    this.performScroll();\n                    this.adapter.visitRendered(this);\n                    if (!isPreview) {\n                        this.complete();\n                    }\n                }\n            });\n        }\n    }\n    followRedirect() {\n        var _a;\n        if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {\n            this.adapter.visitProposedToLocation(this.redirectedToLocation, {\n                action: \"replace\",\n                response: this.response,\n                shouldCacheSnapshot: false,\n                willRender: false,\n            });\n            this.followedRedirect = true;\n        }\n    }\n    goToSamePageAnchor() {\n        if (this.isSamePage) {\n            this.render(async () => {\n                this.cacheSnapshot();\n                this.performScroll();\n                this.changeHistory();\n                this.adapter.visitRendered(this);\n            });\n        }\n    }\n    prepareRequest(request) {\n        if (this.acceptsStreamResponse) {\n            request.acceptResponseType(StreamMessage.contentType);\n        }\n    }\n    requestStarted() {\n        this.startRequest();\n    }\n    requestPreventedHandlingResponse(_request, _response) { }\n    async requestSucceededWithResponse(request, response) {\n        const responseHTML = await response.responseHTML;\n        const { redirected, statusCode } = response;\n        if (responseHTML == undefined) {\n            this.recordResponse({\n                statusCode: SystemStatusCode.contentTypeMismatch,\n                redirected,\n            });\n        }\n        else {\n            this.redirectedToLocation = response.redirected ? response.location : undefined;\n            this.recordResponse({ statusCode: statusCode, responseHTML, redirected });\n        }\n    }\n    async requestFailedWithResponse(request, response) {\n        const responseHTML = await response.responseHTML;\n        const { redirected, statusCode } = response;\n        if (responseHTML == undefined) {\n            this.recordResponse({\n                statusCode: SystemStatusCode.contentTypeMismatch,\n                redirected,\n            });\n        }\n        else {\n            this.recordResponse({ statusCode: statusCode, responseHTML, redirected });\n        }\n    }\n    requestErrored(_request, _error) {\n        this.recordResponse({\n            statusCode: SystemStatusCode.networkFailure,\n            redirected: false,\n        });\n    }\n    requestFinished() {\n        this.finishRequest();\n    }\n    performScroll() {\n        if (!this.scrolled && !this.view.forceReloaded) {\n            if (this.action == \"restore\") {\n                this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();\n            }\n            else {\n                this.scrollToAnchor() || this.view.scrollToTop();\n            }\n            if (this.isSamePage) {\n                this.delegate.visitScrolledToSamePageLocation(this.view.lastRenderedLocation, this.location);\n            }\n            this.scrolled = true;\n        }\n    }\n    scrollToRestoredPosition() {\n        const { scrollPosition } = this.restorationData;\n        if (scrollPosition) {\n            this.view.scrollToPosition(scrollPosition);\n            return true;\n        }\n    }\n    scrollToAnchor() {\n        const anchor = getAnchor(this.location);\n        if (anchor != null) {\n            this.view.scrollToAnchor(anchor);\n            return true;\n        }\n    }\n    recordTimingMetric(metric) {\n        this.timingMetrics[metric] = new Date().getTime();\n    }\n    getTimingMetrics() {\n        return Object.assign({}, this.timingMetrics);\n    }\n    getHistoryMethodForAction(action) {\n        switch (action) {\n            case \"replace\":\n                return history.replaceState;\n            case \"advance\":\n            case \"restore\":\n                return history.pushState;\n        }\n    }\n    hasPreloadedResponse() {\n        return typeof this.response == \"object\";\n    }\n    shouldIssueRequest() {\n        if (this.isSamePage) {\n            return false;\n        }\n        else if (this.action == \"restore\") {\n            return !this.hasCachedSnapshot();\n        }\n        else {\n            return this.willRender;\n        }\n    }\n    cacheSnapshot() {\n        if (!this.snapshotCached) {\n            this.view.cacheSnapshot(this.snapshot).then((snapshot) => snapshot && this.visitCachedSnapshot(snapshot));\n            this.snapshotCached = true;\n        }\n    }\n    async render(callback) {\n        this.cancelRender();\n        await new Promise((resolve) => {\n            this.frame = requestAnimationFrame(() => resolve());\n        });\n        await callback();\n        delete this.frame;\n    }\n    cancelRender() {\n        if (this.frame) {\n            cancelAnimationFrame(this.frame);\n            delete this.frame;\n        }\n    }\n}\nfunction isSuccessful(statusCode) {\n    return statusCode >= 200 && statusCode < 300;\n}\n\nclass BrowserAdapter {\n    constructor(session) {\n        this.progressBar = new ProgressBar();\n        this.showProgressBar = () => {\n            this.progressBar.show();\n        };\n        this.session = session;\n    }\n    visitProposedToLocation(location, options) {\n        this.navigator.startVisit(location, (options === null || options === void 0 ? void 0 : options.restorationIdentifier) || uuid(), options);\n    }\n    visitStarted(visit) {\n        this.location = visit.location;\n        visit.loadCachedSnapshot();\n        visit.issueRequest();\n        visit.goToSamePageAnchor();\n    }\n    visitRequestStarted(visit) {\n        this.progressBar.setValue(0);\n        if (visit.hasCachedSnapshot() || visit.action != \"restore\") {\n            this.showVisitProgressBarAfterDelay();\n        }\n        else {\n            this.showProgressBar();\n        }\n    }\n    visitRequestCompleted(visit) {\n        visit.loadResponse();\n    }\n    visitRequestFailedWithStatusCode(visit, statusCode) {\n        switch (statusCode) {\n            case SystemStatusCode.networkFailure:\n            case SystemStatusCode.timeoutFailure:\n            case SystemStatusCode.contentTypeMismatch:\n                return this.reload({\n                    reason: \"request_failed\",\n                    context: {\n                        statusCode,\n                    },\n                });\n            default:\n                return visit.loadResponse();\n        }\n    }\n    visitRequestFinished(_visit) {\n        this.progressBar.setValue(1);\n        this.hideVisitProgressBar();\n    }\n    visitCompleted(_visit) { }\n    pageInvalidated(reason) {\n        this.reload(reason);\n    }\n    visitFailed(_visit) { }\n    visitRendered(_visit) { }\n    formSubmissionStarted(_formSubmission) {\n        this.progressBar.setValue(0);\n        this.showFormProgressBarAfterDelay();\n    }\n    formSubmissionFinished(_formSubmission) {\n        this.progressBar.setValue(1);\n        this.hideFormProgressBar();\n    }\n    showVisitProgressBarAfterDelay() {\n        this.visitProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay);\n    }\n    hideVisitProgressBar() {\n        this.progressBar.hide();\n        if (this.visitProgressBarTimeout != null) {\n            window.clearTimeout(this.visitProgressBarTimeout);\n            delete this.visitProgressBarTimeout;\n        }\n    }\n    showFormProgressBarAfterDelay() {\n        if (this.formProgressBarTimeout == null) {\n            this.formProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay);\n        }\n    }\n    hideFormProgressBar() {\n        this.progressBar.hide();\n        if (this.formProgressBarTimeout != null) {\n            window.clearTimeout(this.formProgressBarTimeout);\n            delete this.formProgressBarTimeout;\n        }\n    }\n    reload(reason) {\n        var _a;\n        dispatch(\"turbo:reload\", { detail: reason });\n        window.location.href = ((_a = this.location) === null || _a === void 0 ? void 0 : _a.toString()) || window.location.href;\n    }\n    get navigator() {\n        return this.session.navigator;\n    }\n}\n\nclass CacheObserver {\n    constructor() {\n        this.selector = \"[data-turbo-temporary]\";\n        this.deprecatedSelector = \"[data-turbo-cache=false]\";\n        this.started = false;\n        this.removeTemporaryElements = ((_event) => {\n            for (const element of this.temporaryElements) {\n                element.remove();\n            }\n        });\n    }\n    start() {\n        if (!this.started) {\n            this.started = true;\n            addEventListener(\"turbo:before-cache\", this.removeTemporaryElements, false);\n        }\n    }\n    stop() {\n        if (this.started) {\n            this.started = false;\n            removeEventListener(\"turbo:before-cache\", this.removeTemporaryElements, false);\n        }\n    }\n    get temporaryElements() {\n        return [...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation];\n    }\n    get temporaryElementsWithDeprecation() {\n        const elements = document.querySelectorAll(this.deprecatedSelector);\n        if (elements.length) {\n            console.warn(`The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`);\n        }\n        return [...elements];\n    }\n}\n\nclass FrameRedirector {\n    constructor(session, element) {\n        this.session = session;\n        this.element = element;\n        this.linkInterceptor = new LinkInterceptor(this, element);\n        this.formSubmitObserver = new FormSubmitObserver(this, element);\n    }\n    start() {\n        this.linkInterceptor.start();\n        this.formSubmitObserver.start();\n    }\n    stop() {\n        this.linkInterceptor.stop();\n        this.formSubmitObserver.stop();\n    }\n    shouldInterceptLinkClick(element, _location, _event) {\n        return this.shouldRedirect(element);\n    }\n    linkClickIntercepted(element, url, event) {\n        const frame = this.findFrameElement(element);\n        if (frame) {\n            frame.delegate.linkClickIntercepted(element, url, event);\n        }\n    }\n    willSubmitForm(element, submitter) {\n        return (element.closest(\"turbo-frame\") == null &&\n            this.shouldSubmit(element, submitter) &&\n            this.shouldRedirect(element, submitter));\n    }\n    formSubmitted(element, submitter) {\n        const frame = this.findFrameElement(element, submitter);\n        if (frame) {\n            frame.delegate.formSubmitted(element, submitter);\n        }\n    }\n    shouldSubmit(form, submitter) {\n        var _a;\n        const action = getAction(form, submitter);\n        const meta = this.element.ownerDocument.querySelector(`meta[name=\"turbo-root\"]`);\n        const rootLocation = expandURL((_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : \"/\");\n        return this.shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);\n    }\n    shouldRedirect(element, submitter) {\n        const isNavigatable = element instanceof HTMLFormElement\n            ? this.session.submissionIsNavigatable(element, submitter)\n            : this.session.elementIsNavigatable(element);\n        if (isNavigatable) {\n            const frame = this.findFrameElement(element, submitter);\n            return frame ? frame != element.closest(\"turbo-frame\") : false;\n        }\n        else {\n            return false;\n        }\n    }\n    findFrameElement(element, submitter) {\n        const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute(\"data-turbo-frame\")) || element.getAttribute(\"data-turbo-frame\");\n        if (id && id != \"_top\") {\n            const frame = this.element.querySelector(`#${id}:not([disabled])`);\n            if (frame instanceof FrameElement) {\n                return frame;\n            }\n        }\n    }\n}\n\nclass History {\n    constructor(delegate) {\n        this.restorationIdentifier = uuid();\n        this.restorationData = {};\n        this.started = false;\n        this.pageLoaded = false;\n        this.onPopState = (event) => {\n            if (this.shouldHandlePopState()) {\n                const { turbo } = event.state || {};\n                if (turbo) {\n                    this.location = new URL(window.location.href);\n                    const { restorationIdentifier } = turbo;\n                    this.restorationIdentifier = restorationIdentifier;\n                    this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier);\n                }\n            }\n        };\n        this.onPageLoad = async (_event) => {\n            await nextMicrotask();\n            this.pageLoaded = true;\n        };\n        this.delegate = delegate;\n    }\n    start() {\n        if (!this.started) {\n            addEventListener(\"popstate\", this.onPopState, false);\n            addEventListener(\"load\", this.onPageLoad, false);\n            this.started = true;\n            this.replace(new URL(window.location.href));\n        }\n    }\n    stop() {\n        if (this.started) {\n            removeEventListener(\"popstate\", this.onPopState, false);\n            removeEventListener(\"load\", this.onPageLoad, false);\n            this.started = false;\n        }\n    }\n    push(location, restorationIdentifier) {\n        this.update(history.pushState, location, restorationIdentifier);\n    }\n    replace(location, restorationIdentifier) {\n        this.update(history.replaceState, location, restorationIdentifier);\n    }\n    update(method, location, restorationIdentifier = uuid()) {\n        const state = { turbo: { restorationIdentifier } };\n        method.call(history, state, \"\", location.href);\n        this.location = location;\n        this.restorationIdentifier = restorationIdentifier;\n    }\n    getRestorationDataForIdentifier(restorationIdentifier) {\n        return this.restorationData[restorationIdentifier] || {};\n    }\n    updateRestorationData(additionalData) {\n        const { restorationIdentifier } = this;\n        const restorationData = this.restorationData[restorationIdentifier];\n        this.restorationData[restorationIdentifier] = Object.assign(Object.assign({}, restorationData), additionalData);\n    }\n    assumeControlOfScrollRestoration() {\n        var _a;\n        if (!this.previousScrollRestoration) {\n            this.previousScrollRestoration = (_a = history.scrollRestoration) !== null && _a !== void 0 ? _a : \"auto\";\n            history.scrollRestoration = \"manual\";\n        }\n    }\n    relinquishControlOfScrollRestoration() {\n        if (this.previousScrollRestoration) {\n            history.scrollRestoration = this.previousScrollRestoration;\n            delete this.previousScrollRestoration;\n        }\n    }\n    shouldHandlePopState() {\n        return this.pageIsLoaded();\n    }\n    pageIsLoaded() {\n        return this.pageLoaded || document.readyState == \"complete\";\n    }\n}\n\nclass Navigator {\n    constructor(delegate) {\n        this.delegate = delegate;\n    }\n    proposeVisit(location, options = {}) {\n        if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {\n            if (locationIsVisitable(location, this.view.snapshot.rootLocation)) {\n                this.delegate.visitProposedToLocation(location, options);\n            }\n            else {\n                window.location.href = location.toString();\n            }\n        }\n    }\n    startVisit(locatable, restorationIdentifier, options = {}) {\n        this.stop();\n        this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({ referrer: this.location }, options));\n        this.currentVisit.start();\n    }\n    submitForm(form, submitter) {\n        this.stop();\n        this.formSubmission = new FormSubmission(this, form, submitter, true);\n        this.formSubmission.start();\n    }\n    stop() {\n        if (this.formSubmission) {\n            this.formSubmission.stop();\n            delete this.formSubmission;\n        }\n        if (this.currentVisit) {\n            this.currentVisit.cancel();\n            delete this.currentVisit;\n        }\n    }\n    get adapter() {\n        return this.delegate.adapter;\n    }\n    get view() {\n        return this.delegate.view;\n    }\n    get history() {\n        return this.delegate.history;\n    }\n    formSubmissionStarted(formSubmission) {\n        if (typeof this.adapter.formSubmissionStarted === \"function\") {\n            this.adapter.formSubmissionStarted(formSubmission);\n        }\n    }\n    async formSubmissionSucceededWithResponse(formSubmission, fetchResponse) {\n        if (formSubmission == this.formSubmission) {\n            const responseHTML = await fetchResponse.responseHTML;\n            if (responseHTML) {\n                const shouldCacheSnapshot = formSubmission.isSafe;\n                if (!shouldCacheSnapshot) {\n                    this.view.clearSnapshotCache();\n                }\n                const { statusCode, redirected } = fetchResponse;\n                const action = this.getActionForFormSubmission(formSubmission);\n                const visitOptions = {\n                    action,\n                    shouldCacheSnapshot,\n                    response: { statusCode, responseHTML, redirected },\n                };\n                this.proposeVisit(fetchResponse.location, visitOptions);\n            }\n        }\n    }\n    async formSubmissionFailedWithResponse(formSubmission, fetchResponse) {\n        const responseHTML = await fetchResponse.responseHTML;\n        if (responseHTML) {\n            const snapshot = PageSnapshot.fromHTMLString(responseHTML);\n            if (fetchResponse.serverError) {\n                await this.view.renderError(snapshot, this.currentVisit);\n            }\n            else {\n                await this.view.renderPage(snapshot, false, true, this.currentVisit);\n            }\n            this.view.scrollToTop();\n            this.view.clearSnapshotCache();\n        }\n    }\n    formSubmissionErrored(formSubmission, error) {\n        console.error(error);\n    }\n    formSubmissionFinished(formSubmission) {\n        if (typeof this.adapter.formSubmissionFinished === \"function\") {\n            this.adapter.formSubmissionFinished(formSubmission);\n        }\n    }\n    visitStarted(visit) {\n        this.delegate.visitStarted(visit);\n    }\n    visitCompleted(visit) {\n        this.delegate.visitCompleted(visit);\n    }\n    locationWithActionIsSamePage(location, action) {\n        const anchor = getAnchor(location);\n        const currentAnchor = getAnchor(this.view.lastRenderedLocation);\n        const isRestorationToTop = action === \"restore\" && typeof anchor === \"undefined\";\n        return (action !== \"replace\" &&\n            getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) &&\n            (isRestorationToTop || (anchor != null && anchor !== currentAnchor)));\n    }\n    visitScrolledToSamePageLocation(oldURL, newURL) {\n        this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);\n    }\n    get location() {\n        return this.history.location;\n    }\n    get restorationIdentifier() {\n        return this.history.restorationIdentifier;\n    }\n    getActionForFormSubmission({ submitter, formElement }) {\n        return getVisitAction(submitter, formElement) || \"advance\";\n    }\n}\n\nvar PageStage;\n(function (PageStage) {\n    PageStage[PageStage[\"initial\"] = 0] = \"initial\";\n    PageStage[PageStage[\"loading\"] = 1] = \"loading\";\n    PageStage[PageStage[\"interactive\"] = 2] = \"interactive\";\n    PageStage[PageStage[\"complete\"] = 3] = \"complete\";\n})(PageStage || (PageStage = {}));\nclass PageObserver {\n    constructor(delegate) {\n        this.stage = PageStage.initial;\n        this.started = false;\n        this.interpretReadyState = () => {\n            const { readyState } = this;\n            if (readyState == \"interactive\") {\n                this.pageIsInteractive();\n            }\n            else if (readyState == \"complete\") {\n                this.pageIsComplete();\n            }\n        };\n        this.pageWillUnload = () => {\n            this.delegate.pageWillUnload();\n        };\n        this.delegate = delegate;\n    }\n    start() {\n        if (!this.started) {\n            if (this.stage == PageStage.initial) {\n                this.stage = PageStage.loading;\n            }\n            document.addEventListener(\"readystatechange\", this.interpretReadyState, false);\n            addEventListener(\"pagehide\", this.pageWillUnload, false);\n            this.started = true;\n        }\n    }\n    stop() {\n        if (this.started) {\n            document.removeEventListener(\"readystatechange\", this.interpretReadyState, false);\n            removeEventListener(\"pagehide\", this.pageWillUnload, false);\n            this.started = false;\n        }\n    }\n    pageIsInteractive() {\n        if (this.stage == PageStage.loading) {\n            this.stage = PageStage.interactive;\n            this.delegate.pageBecameInteractive();\n        }\n    }\n    pageIsComplete() {\n        this.pageIsInteractive();\n        if (this.stage == PageStage.interactive) {\n            this.stage = PageStage.complete;\n            this.delegate.pageLoaded();\n        }\n    }\n    get readyState() {\n        return document.readyState;\n    }\n}\n\nclass ScrollObserver {\n    constructor(delegate) {\n        this.started = false;\n        this.onScroll = () => {\n            this.updatePosition({ x: window.pageXOffset, y: window.pageYOffset });\n        };\n        this.delegate = delegate;\n    }\n    start() {\n        if (!this.started) {\n            addEventListener(\"scroll\", this.onScroll, false);\n            this.onScroll();\n            this.started = true;\n        }\n    }\n    stop() {\n        if (this.started) {\n            removeEventListener(\"scroll\", this.onScroll, false);\n            this.started = false;\n        }\n    }\n    updatePosition(position) {\n        this.delegate.scrollPositionChanged(position);\n    }\n}\n\nclass StreamMessageRenderer {\n    render({ fragment }) {\n        Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), () => document.documentElement.appendChild(fragment));\n    }\n    enteringBardo(currentPermanentElement, newPermanentElement) {\n        newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));\n    }\n    leavingBardo() { }\n}\nfunction getPermanentElementMapForFragment(fragment) {\n    const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);\n    const permanentElementMap = {};\n    for (const permanentElementInDocument of permanentElementsInDocument) {\n        const { id } = permanentElementInDocument;\n        for (const streamElement of fragment.querySelectorAll(\"turbo-stream\")) {\n            const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);\n            if (elementInStream) {\n                permanentElementMap[id] = [permanentElementInDocument, elementInStream];\n            }\n        }\n    }\n    return permanentElementMap;\n}\n\nclass StreamObserver {\n    constructor(delegate) {\n        this.sources = new Set();\n        this.started = false;\n        this.inspectFetchResponse = ((event) => {\n            const response = fetchResponseFromEvent(event);\n            if (response && fetchResponseIsStream(response)) {\n                event.preventDefault();\n                this.receiveMessageResponse(response);\n            }\n        });\n        this.receiveMessageEvent = (event) => {\n            if (this.started && typeof event.data == \"string\") {\n                this.receiveMessageHTML(event.data);\n            }\n        };\n        this.delegate = delegate;\n    }\n    start() {\n        if (!this.started) {\n            this.started = true;\n            addEventListener(\"turbo:before-fetch-response\", this.inspectFetchResponse, false);\n        }\n    }\n    stop() {\n        if (this.started) {\n            this.started = false;\n            removeEventListener(\"turbo:before-fetch-response\", this.inspectFetchResponse, false);\n        }\n    }\n    connectStreamSource(source) {\n        if (!this.streamSourceIsConnected(source)) {\n            this.sources.add(source);\n            source.addEventListener(\"message\", this.receiveMessageEvent, false);\n        }\n    }\n    disconnectStreamSource(source) {\n        if (this.streamSourceIsConnected(source)) {\n            this.sources.delete(source);\n            source.removeEventListener(\"message\", this.receiveMessageEvent, false);\n        }\n    }\n    streamSourceIsConnected(source) {\n        return this.sources.has(source);\n    }\n    async receiveMessageResponse(response) {\n        const html = await response.responseHTML;\n        if (html) {\n            this.receiveMessageHTML(html);\n        }\n    }\n    receiveMessageHTML(html) {\n        this.delegate.receivedMessageFromStream(StreamMessage.wrap(html));\n    }\n}\nfunction fetchResponseFromEvent(event) {\n    var _a;\n    const fetchResponse = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.fetchResponse;\n    if (fetchResponse instanceof FetchResponse) {\n        return fetchResponse;\n    }\n}\nfunction fetchResponseIsStream(response) {\n    var _a;\n    const contentType = (_a = response.contentType) !== null && _a !== void 0 ? _a : \"\";\n    return contentType.startsWith(StreamMessage.contentType);\n}\n\nclass ErrorRenderer extends Renderer {\n    static renderElement(currentElement, newElement) {\n        const { documentElement, body } = document;\n        documentElement.replaceChild(newElement, body);\n    }\n    async render() {\n        this.replaceHeadAndBody();\n        this.activateScriptElements();\n    }\n    replaceHeadAndBody() {\n        const { documentElement, head } = document;\n        documentElement.replaceChild(this.newHead, head);\n        this.renderElement(this.currentElement, this.newElement);\n    }\n    activateScriptElements() {\n        for (const replaceableElement of this.scriptElements) {\n            const parentNode = replaceableElement.parentNode;\n            if (parentNode) {\n                const element = activateScriptElement(replaceableElement);\n                parentNode.replaceChild(element, replaceableElement);\n            }\n        }\n    }\n    get newHead() {\n        return this.newSnapshot.headSnapshot.element;\n    }\n    get scriptElements() {\n        return document.documentElement.querySelectorAll(\"script\");\n    }\n}\n\nclass PageRenderer extends Renderer {\n    static renderElement(currentElement, newElement) {\n        if (document.body && newElement instanceof HTMLBodyElement) {\n            document.body.replaceWith(newElement);\n        }\n        else {\n            document.documentElement.appendChild(newElement);\n        }\n    }\n    get shouldRender() {\n        return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;\n    }\n    get reloadReason() {\n        if (!this.newSnapshot.isVisitable) {\n            return {\n                reason: \"turbo_visit_control_is_reload\",\n            };\n        }\n        if (!this.trackedElementsAreIdentical) {\n            return {\n                reason: \"tracked_element_mismatch\",\n            };\n        }\n    }\n    async prepareToRender() {\n        await this.mergeHead();\n    }\n    async render() {\n        if (this.willRender) {\n            await this.replaceBody();\n        }\n    }\n    finishRendering() {\n        super.finishRendering();\n        if (!this.isPreview) {\n            this.focusFirstAutofocusableElement();\n        }\n    }\n    get currentHeadSnapshot() {\n        return this.currentSnapshot.headSnapshot;\n    }\n    get newHeadSnapshot() {\n        return this.newSnapshot.headSnapshot;\n    }\n    get newElement() {\n        return this.newSnapshot.element;\n    }\n    async mergeHead() {\n        const mergedHeadElements = this.mergeProvisionalElements();\n        const newStylesheetElements = this.copyNewHeadStylesheetElements();\n        this.copyNewHeadScriptElements();\n        await mergedHeadElements;\n        await newStylesheetElements;\n    }\n    async replaceBody() {\n        await this.preservingPermanentElements(async () => {\n            this.activateNewBody();\n            await this.assignNewBody();\n        });\n    }\n    get trackedElementsAreIdentical() {\n        return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature;\n    }\n    async copyNewHeadStylesheetElements() {\n        const loadingElements = [];\n        for (const element of this.newHeadStylesheetElements) {\n            loadingElements.push(waitForLoad(element));\n            document.head.appendChild(element);\n        }\n        await Promise.all(loadingElements);\n    }\n    copyNewHeadScriptElements() {\n        for (const element of this.newHeadScriptElements) {\n            document.head.appendChild(activateScriptElement(element));\n        }\n    }\n    async mergeProvisionalElements() {\n        const newHeadElements = [...this.newHeadProvisionalElements];\n        for (const element of this.currentHeadProvisionalElements) {\n            if (!this.isCurrentElementInElementList(element, newHeadElements)) {\n                document.head.removeChild(element);\n            }\n        }\n        for (const element of newHeadElements) {\n            document.head.appendChild(element);\n        }\n    }\n    isCurrentElementInElementList(element, elementList) {\n        for (const [index, newElement] of elementList.entries()) {\n            if (element.tagName == \"TITLE\") {\n                if (newElement.tagName != \"TITLE\") {\n                    continue;\n                }\n                if (element.innerHTML == newElement.innerHTML) {\n                    elementList.splice(index, 1);\n                    return true;\n                }\n            }\n            if (newElement.isEqualNode(element)) {\n                elementList.splice(index, 1);\n                return true;\n            }\n        }\n        return false;\n    }\n    removeCurrentHeadProvisionalElements() {\n        for (const element of this.currentHeadProvisionalElements) {\n            document.head.removeChild(element);\n        }\n    }\n    copyNewHeadProvisionalElements() {\n        for (const element of this.newHeadProvisionalElements) {\n            document.head.appendChild(element);\n        }\n    }\n    activateNewBody() {\n        document.adoptNode(this.newElement);\n        this.activateNewBodyScriptElements();\n    }\n    activateNewBodyScriptElements() {\n        for (const inertScriptElement of this.newBodyScriptElements) {\n            const activatedScriptElement = activateScriptElement(inertScriptElement);\n            inertScriptElement.replaceWith(activatedScriptElement);\n        }\n    }\n    async assignNewBody() {\n        await this.renderElement(this.currentElement, this.newElement);\n    }\n    get newHeadStylesheetElements() {\n        return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);\n    }\n    get newHeadScriptElements() {\n        return this.newHeadSnapshot.getScriptElementsNotInSnapshot(this.currentHeadSnapshot);\n    }\n    get currentHeadProvisionalElements() {\n        return this.currentHeadSnapshot.provisionalElements;\n    }\n    get newHeadProvisionalElements() {\n        return this.newHeadSnapshot.provisionalElements;\n    }\n    get newBodyScriptElements() {\n        return this.newElement.querySelectorAll(\"script\");\n    }\n}\n\nclass SnapshotCache {\n    constructor(size) {\n        this.keys = [];\n        this.snapshots = {};\n        this.size = size;\n    }\n    has(location) {\n        return toCacheKey(location) in this.snapshots;\n    }\n    get(location) {\n        if (this.has(location)) {\n            const snapshot = this.read(location);\n            this.touch(location);\n            return snapshot;\n        }\n    }\n    put(location, snapshot) {\n        this.write(location, snapshot);\n        this.touch(location);\n        return snapshot;\n    }\n    clear() {\n        this.snapshots = {};\n    }\n    read(location) {\n        return this.snapshots[toCacheKey(location)];\n    }\n    write(location, snapshot) {\n        this.snapshots[toCacheKey(location)] = snapshot;\n    }\n    touch(location) {\n        const key = toCacheKey(location);\n        const index = this.keys.indexOf(key);\n        if (index > -1)\n            this.keys.splice(index, 1);\n        this.keys.unshift(key);\n        this.trim();\n    }\n    trim() {\n        for (const key of this.keys.splice(this.size)) {\n            delete this.snapshots[key];\n        }\n    }\n}\n\nclass PageView extends View {\n    constructor() {\n        super(...arguments);\n        this.snapshotCache = new SnapshotCache(10);\n        this.lastRenderedLocation = new URL(location.href);\n        this.forceReloaded = false;\n    }\n    renderPage(snapshot, isPreview = false, willRender = true, visit) {\n        const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);\n        if (!renderer.shouldRender) {\n            this.forceReloaded = true;\n        }\n        else {\n            visit === null || visit === void 0 ? void 0 : visit.changeHistory();\n        }\n        return this.render(renderer);\n    }\n    renderError(snapshot, visit) {\n        visit === null || visit === void 0 ? void 0 : visit.changeHistory();\n        const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);\n        return this.render(renderer);\n    }\n    clearSnapshotCache() {\n        this.snapshotCache.clear();\n    }\n    async cacheSnapshot(snapshot = this.snapshot) {\n        if (snapshot.isCacheable) {\n            this.delegate.viewWillCacheSnapshot();\n            const { lastRenderedLocation: location } = this;\n            await nextEventLoopTick();\n            const cachedSnapshot = snapshot.clone();\n            this.snapshotCache.put(location, cachedSnapshot);\n            return cachedSnapshot;\n        }\n    }\n    getCachedSnapshotForLocation(location) {\n        return this.snapshotCache.get(location);\n    }\n    get snapshot() {\n        return PageSnapshot.fromElement(this.element);\n    }\n}\n\nclass Preloader {\n    constructor(delegate) {\n        this.selector = \"a[data-turbo-preload]\";\n        this.delegate = delegate;\n    }\n    get snapshotCache() {\n        return this.delegate.navigator.view.snapshotCache;\n    }\n    start() {\n        if (document.readyState === \"loading\") {\n            return document.addEventListener(\"DOMContentLoaded\", () => {\n                this.preloadOnLoadLinksForView(document.body);\n            });\n        }\n        else {\n            this.preloadOnLoadLinksForView(document.body);\n        }\n    }\n    preloadOnLoadLinksForView(element) {\n        for (const link of element.querySelectorAll(this.selector)) {\n            this.preloadURL(link);\n        }\n    }\n    async preloadURL(link) {\n        const location = new URL(link.href);\n        if (this.snapshotCache.has(location)) {\n            return;\n        }\n        try {\n            const response = await fetch(location.toString(), { headers: { \"VND.PREFETCH\": \"true\", Accept: \"text/html\" } });\n            const responseText = await response.text();\n            const snapshot = PageSnapshot.fromHTMLString(responseText);\n            this.snapshotCache.put(location, snapshot);\n        }\n        catch (_) {\n        }\n    }\n}\n\nclass Session {\n    constructor() {\n        this.navigator = new Navigator(this);\n        this.history = new History(this);\n        this.preloader = new Preloader(this);\n        this.view = new PageView(this, document.documentElement);\n        this.adapter = new BrowserAdapter(this);\n        this.pageObserver = new PageObserver(this);\n        this.cacheObserver = new CacheObserver();\n        this.linkClickObserver = new LinkClickObserver(this, window);\n        this.formSubmitObserver = new FormSubmitObserver(this, document);\n        this.scrollObserver = new ScrollObserver(this);\n        this.streamObserver = new StreamObserver(this);\n        this.formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement);\n        this.frameRedirector = new FrameRedirector(this, document.documentElement);\n        this.streamMessageRenderer = new StreamMessageRenderer();\n        this.drive = true;\n        this.enabled = true;\n        this.progressBarDelay = 500;\n        this.started = false;\n        this.formMode = \"on\";\n    }\n    start() {\n        if (!this.started) {\n            this.pageObserver.start();\n            this.cacheObserver.start();\n            this.formLinkClickObserver.start();\n            this.linkClickObserver.start();\n            this.formSubmitObserver.start();\n            this.scrollObserver.start();\n            this.streamObserver.start();\n            this.frameRedirector.start();\n            this.history.start();\n            this.preloader.start();\n            this.started = true;\n            this.enabled = true;\n        }\n    }\n    disable() {\n        this.enabled = false;\n    }\n    stop() {\n        if (this.started) {\n            this.pageObserver.stop();\n            this.cacheObserver.stop();\n            this.formLinkClickObserver.stop();\n            this.linkClickObserver.stop();\n            this.formSubmitObserver.stop();\n            this.scrollObserver.stop();\n            this.streamObserver.stop();\n            this.frameRedirector.stop();\n            this.history.stop();\n            this.started = false;\n        }\n    }\n    registerAdapter(adapter) {\n        this.adapter = adapter;\n    }\n    visit(location, options = {}) {\n        const frameElement = options.frame ? document.getElementById(options.frame) : null;\n        if (frameElement instanceof FrameElement) {\n            frameElement.src = location.toString();\n            frameElement.loaded;\n        }\n        else {\n            this.navigator.proposeVisit(expandURL(location), options);\n        }\n    }\n    connectStreamSource(source) {\n        this.streamObserver.connectStreamSource(source);\n    }\n    disconnectStreamSource(source) {\n        this.streamObserver.disconnectStreamSource(source);\n    }\n    renderStreamMessage(message) {\n        this.streamMessageRenderer.render(StreamMessage.wrap(message));\n    }\n    clearCache() {\n        this.view.clearSnapshotCache();\n    }\n    setProgressBarDelay(delay) {\n        this.progressBarDelay = delay;\n    }\n    setFormMode(mode) {\n        this.formMode = mode;\n    }\n    get location() {\n        return this.history.location;\n    }\n    get restorationIdentifier() {\n        return this.history.restorationIdentifier;\n    }\n    historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) {\n        if (this.enabled) {\n            this.navigator.startVisit(location, restorationIdentifier, {\n                action: \"restore\",\n                historyChanged: true,\n            });\n        }\n        else {\n            this.adapter.pageInvalidated({\n                reason: \"turbo_disabled\",\n            });\n        }\n    }\n    scrollPositionChanged(position) {\n        this.history.updateRestorationData({ scrollPosition: position });\n    }\n    willSubmitFormLinkToLocation(link, location) {\n        return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);\n    }\n    submittedFormLinkToLocation() { }\n    willFollowLinkToLocation(link, location, event) {\n        return (this.elementIsNavigatable(link) &&\n            locationIsVisitable(location, this.snapshot.rootLocation) &&\n            this.applicationAllowsFollowingLinkToLocation(link, location, event));\n    }\n    followedLinkToLocation(link, location) {\n        const action = this.getActionForLink(link);\n        const acceptsStreamResponse = link.hasAttribute(\"data-turbo-stream\");\n        this.visit(location.href, { action, acceptsStreamResponse });\n    }\n    allowsVisitingLocationWithAction(location, action) {\n        return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);\n    }\n    visitProposedToLocation(location, options) {\n        extendURLWithDeprecatedProperties(location);\n        this.adapter.visitProposedToLocation(location, options);\n    }\n    visitStarted(visit) {\n        if (!visit.acceptsStreamResponse) {\n            markAsBusy(document.documentElement);\n        }\n        extendURLWithDeprecatedProperties(visit.location);\n        if (!visit.silent) {\n            this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);\n        }\n    }\n    visitCompleted(visit) {\n        clearBusyState(document.documentElement);\n        this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());\n    }\n    locationWithActionIsSamePage(location, action) {\n        return this.navigator.locationWithActionIsSamePage(location, action);\n    }\n    visitScrolledToSamePageLocation(oldURL, newURL) {\n        this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);\n    }\n    willSubmitForm(form, submitter) {\n        const action = getAction(form, submitter);\n        return (this.submissionIsNavigatable(form, submitter) &&\n            locationIsVisitable(expandURL(action), this.snapshot.rootLocation));\n    }\n    formSubmitted(form, submitter) {\n        this.navigator.submitForm(form, submitter);\n    }\n    pageBecameInteractive() {\n        this.view.lastRenderedLocation = this.location;\n        this.notifyApplicationAfterPageLoad();\n    }\n    pageLoaded() {\n        this.history.assumeControlOfScrollRestoration();\n    }\n    pageWillUnload() {\n        this.history.relinquishControlOfScrollRestoration();\n    }\n    receivedMessageFromStream(message) {\n        this.renderStreamMessage(message);\n    }\n    viewWillCacheSnapshot() {\n        var _a;\n        if (!((_a = this.navigator.currentVisit) === null || _a === void 0 ? void 0 : _a.silent)) {\n            this.notifyApplicationBeforeCachingSnapshot();\n        }\n    }\n    allowsImmediateRender({ element }, options) {\n        const event = this.notifyApplicationBeforeRender(element, options);\n        const { defaultPrevented, detail: { render }, } = event;\n        if (this.view.renderer && render) {\n            this.view.renderer.renderElement = render;\n        }\n        return !defaultPrevented;\n    }\n    viewRenderedSnapshot(_snapshot, _isPreview) {\n        this.view.lastRenderedLocation = this.history.location;\n        this.notifyApplicationAfterRender();\n    }\n    preloadOnLoadLinksForView(element) {\n        this.preloader.preloadOnLoadLinksForView(element);\n    }\n    viewInvalidated(reason) {\n        this.adapter.pageInvalidated(reason);\n    }\n    frameLoaded(frame) {\n        this.notifyApplicationAfterFrameLoad(frame);\n    }\n    frameRendered(fetchResponse, frame) {\n        this.notifyApplicationAfterFrameRender(fetchResponse, frame);\n    }\n    applicationAllowsFollowingLinkToLocation(link, location, ev) {\n        const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);\n        return !event.defaultPrevented;\n    }\n    applicationAllowsVisitingLocation(location) {\n        const event = this.notifyApplicationBeforeVisitingLocation(location);\n        return !event.defaultPrevented;\n    }\n    notifyApplicationAfterClickingLinkToLocation(link, location, event) {\n        return dispatch(\"turbo:click\", {\n            target: link,\n            detail: { url: location.href, originalEvent: event },\n            cancelable: true,\n        });\n    }\n    notifyApplicationBeforeVisitingLocation(location) {\n        return dispatch(\"turbo:before-visit\", {\n            detail: { url: location.href },\n            cancelable: true,\n        });\n    }\n    notifyApplicationAfterVisitingLocation(location, action) {\n        return dispatch(\"turbo:visit\", { detail: { url: location.href, action } });\n    }\n    notifyApplicationBeforeCachingSnapshot() {\n        return dispatch(\"turbo:before-cache\");\n    }\n    notifyApplicationBeforeRender(newBody, options) {\n        return dispatch(\"turbo:before-render\", {\n            detail: Object.assign({ newBody }, options),\n            cancelable: true,\n        });\n    }\n    notifyApplicationAfterRender() {\n        return dispatch(\"turbo:render\");\n    }\n    notifyApplicationAfterPageLoad(timing = {}) {\n        return dispatch(\"turbo:load\", {\n            detail: { url: this.location.href, timing },\n        });\n    }\n    notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {\n        dispatchEvent(new HashChangeEvent(\"hashchange\", {\n            oldURL: oldURL.toString(),\n            newURL: newURL.toString(),\n        }));\n    }\n    notifyApplicationAfterFrameLoad(frame) {\n        return dispatch(\"turbo:frame-load\", { target: frame });\n    }\n    notifyApplicationAfterFrameRender(fetchResponse, frame) {\n        return dispatch(\"turbo:frame-render\", {\n            detail: { fetchResponse },\n            target: frame,\n            cancelable: true,\n        });\n    }\n    submissionIsNavigatable(form, submitter) {\n        if (this.formMode == \"off\") {\n            return false;\n        }\n        else {\n            const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true;\n            if (this.formMode == \"optin\") {\n                return submitterIsNavigatable && form.closest('[data-turbo=\"true\"]') != null;\n            }\n            else {\n                return submitterIsNavigatable && this.elementIsNavigatable(form);\n            }\n        }\n    }\n    elementIsNavigatable(element) {\n        const container = findClosestRecursively(element, \"[data-turbo]\");\n        const withinFrame = findClosestRecursively(element, \"turbo-frame\");\n        if (this.drive || withinFrame) {\n            if (container) {\n                return container.getAttribute(\"data-turbo\") != \"false\";\n            }\n            else {\n                return true;\n            }\n        }\n        else {\n            if (container) {\n                return container.getAttribute(\"data-turbo\") == \"true\";\n            }\n            else {\n                return false;\n            }\n        }\n    }\n    getActionForLink(link) {\n        return getVisitAction(link) || \"advance\";\n    }\n    get snapshot() {\n        return this.view.snapshot;\n    }\n}\nfunction extendURLWithDeprecatedProperties(url) {\n    Object.defineProperties(url, deprecatedLocationPropertyDescriptors);\n}\nconst deprecatedLocationPropertyDescriptors = {\n    absoluteURL: {\n        get() {\n            return this.toString();\n        },\n    },\n};\n\nclass Cache {\n    constructor(session) {\n        this.session = session;\n    }\n    clear() {\n        this.session.clearCache();\n    }\n    resetCacheControl() {\n        this.setCacheControl(\"\");\n    }\n    exemptPageFromCache() {\n        this.setCacheControl(\"no-cache\");\n    }\n    exemptPageFromPreview() {\n        this.setCacheControl(\"no-preview\");\n    }\n    setCacheControl(value) {\n        setMetaContent(\"turbo-cache-control\", value);\n    }\n}\n\nconst StreamActions = {\n    after() {\n        this.targetElements.forEach((e) => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling); });\n    },\n    append() {\n        this.removeDuplicateTargetChildren();\n        this.targetElements.forEach((e) => e.append(this.templateContent));\n    },\n    before() {\n        this.targetElements.forEach((e) => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e); });\n    },\n    prepend() {\n        this.removeDuplicateTargetChildren();\n        this.targetElements.forEach((e) => e.prepend(this.templateContent));\n    },\n    remove() {\n        this.targetElements.forEach((e) => e.remove());\n    },\n    replace() {\n        this.targetElements.forEach((e) => e.replaceWith(this.templateContent));\n    },\n    update() {\n        this.targetElements.forEach((targetElement) => {\n            targetElement.innerHTML = \"\";\n            targetElement.append(this.templateContent);\n        });\n    },\n};\n\nconst session = new Session();\nconst cache = new Cache(session);\nconst { navigator: navigator$1 } = session;\nfunction start() {\n    session.start();\n}\nfunction registerAdapter(adapter) {\n    session.registerAdapter(adapter);\n}\nfunction visit(location, options) {\n    session.visit(location, options);\n}\nfunction connectStreamSource(source) {\n    session.connectStreamSource(source);\n}\nfunction disconnectStreamSource(source) {\n    session.disconnectStreamSource(source);\n}\nfunction renderStreamMessage(message) {\n    session.renderStreamMessage(message);\n}\nfunction clearCache() {\n    console.warn(\"Please replace `Turbo.clearCache()` with `Turbo.cache.clear()`. The top-level function is deprecated and will be removed in a future version of Turbo.`\");\n    session.clearCache();\n}\nfunction setProgressBarDelay(delay) {\n    session.setProgressBarDelay(delay);\n}\nfunction setConfirmMethod(confirmMethod) {\n    FormSubmission.confirmMethod = confirmMethod;\n}\nfunction setFormMode(mode) {\n    session.setFormMode(mode);\n}\n\nvar Turbo = /*#__PURE__*/Object.freeze({\n    __proto__: null,\n    navigator: navigator$1,\n    session: session,\n    cache: cache,\n    PageRenderer: PageRenderer,\n    PageSnapshot: PageSnapshot,\n    FrameRenderer: FrameRenderer,\n    start: start,\n    registerAdapter: registerAdapter,\n    visit: visit,\n    connectStreamSource: connectStreamSource,\n    disconnectStreamSource: disconnectStreamSource,\n    renderStreamMessage: renderStreamMessage,\n    clearCache: clearCache,\n    setProgressBarDelay: setProgressBarDelay,\n    setConfirmMethod: setConfirmMethod,\n    setFormMode: setFormMode,\n    StreamActions: StreamActions\n});\n\nclass TurboFrameMissingError extends Error {\n}\n\nclass FrameController {\n    constructor(element) {\n        this.fetchResponseLoaded = (_fetchResponse) => { };\n        this.currentFetchRequest = null;\n        this.resolveVisitPromise = () => { };\n        this.connected = false;\n        this.hasBeenLoaded = false;\n        this.ignoredAttributes = new Set();\n        this.action = null;\n        this.visitCachedSnapshot = ({ element }) => {\n            const frame = element.querySelector(\"#\" + this.element.id);\n            if (frame && this.previousFrameElement) {\n                frame.replaceChildren(...this.previousFrameElement.children);\n            }\n            delete this.previousFrameElement;\n        };\n        this.element = element;\n        this.view = new FrameView(this, this.element);\n        this.appearanceObserver = new AppearanceObserver(this, this.element);\n        this.formLinkClickObserver = new FormLinkClickObserver(this, this.element);\n        this.linkInterceptor = new LinkInterceptor(this, this.element);\n        this.restorationIdentifier = uuid();\n        this.formSubmitObserver = new FormSubmitObserver(this, this.element);\n    }\n    connect() {\n        if (!this.connected) {\n            this.connected = true;\n            if (this.loadingStyle == FrameLoadingStyle.lazy) {\n                this.appearanceObserver.start();\n            }\n            else {\n                this.loadSourceURL();\n            }\n            this.formLinkClickObserver.start();\n            this.linkInterceptor.start();\n            this.formSubmitObserver.start();\n        }\n    }\n    disconnect() {\n        if (this.connected) {\n            this.connected = false;\n            this.appearanceObserver.stop();\n            this.formLinkClickObserver.stop();\n            this.linkInterceptor.stop();\n            this.formSubmitObserver.stop();\n        }\n    }\n    disabledChanged() {\n        if (this.loadingStyle == FrameLoadingStyle.eager) {\n            this.loadSourceURL();\n        }\n    }\n    sourceURLChanged() {\n        if (this.isIgnoringChangesTo(\"src\"))\n            return;\n        if (this.element.isConnected) {\n            this.complete = false;\n        }\n        if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {\n            this.loadSourceURL();\n        }\n    }\n    sourceURLReloaded() {\n        const { src } = this.element;\n        this.ignoringChangesToAttribute(\"complete\", () => {\n            this.element.removeAttribute(\"complete\");\n        });\n        this.element.src = null;\n        this.element.src = src;\n        return this.element.loaded;\n    }\n    completeChanged() {\n        if (this.isIgnoringChangesTo(\"complete\"))\n            return;\n        this.loadSourceURL();\n    }\n    loadingStyleChanged() {\n        if (this.loadingStyle == FrameLoadingStyle.lazy) {\n            this.appearanceObserver.start();\n        }\n        else {\n            this.appearanceObserver.stop();\n            this.loadSourceURL();\n        }\n    }\n    async loadSourceURL() {\n        if (this.enabled && this.isActive && !this.complete && this.sourceURL) {\n            this.element.loaded = this.visit(expandURL(this.sourceURL));\n            this.appearanceObserver.stop();\n            await this.element.loaded;\n            this.hasBeenLoaded = true;\n        }\n    }\n    async loadResponse(fetchResponse) {\n        if (fetchResponse.redirected || (fetchResponse.succeeded && fetchResponse.isHTML)) {\n            this.sourceURL = fetchResponse.response.url;\n        }\n        try {\n            const html = await fetchResponse.responseHTML;\n            if (html) {\n                const document = parseHTMLDocument(html);\n                const pageSnapshot = PageSnapshot.fromDocument(document);\n                if (pageSnapshot.isVisitable) {\n                    await this.loadFrameResponse(fetchResponse, document);\n                }\n                else {\n                    await this.handleUnvisitableFrameResponse(fetchResponse);\n                }\n            }\n        }\n        finally {\n            this.fetchResponseLoaded = () => { };\n        }\n    }\n    elementAppearedInViewport(element) {\n        this.proposeVisitIfNavigatedWithAction(element, element);\n        this.loadSourceURL();\n    }\n    willSubmitFormLinkToLocation(link) {\n        return this.shouldInterceptNavigation(link);\n    }\n    submittedFormLinkToLocation(link, _location, form) {\n        const frame = this.findFrameElement(link);\n        if (frame)\n            form.setAttribute(\"data-turbo-frame\", frame.id);\n    }\n    shouldInterceptLinkClick(element, _location, _event) {\n        return this.shouldInterceptNavigation(element);\n    }\n    linkClickIntercepted(element, location) {\n        this.navigateFrame(element, location);\n    }\n    willSubmitForm(element, submitter) {\n        return element.closest(\"turbo-frame\") == this.element && this.shouldInterceptNavigation(element, submitter);\n    }\n    formSubmitted(element, submitter) {\n        if (this.formSubmission) {\n            this.formSubmission.stop();\n        }\n        this.formSubmission = new FormSubmission(this, element, submitter);\n        const { fetchRequest } = this.formSubmission;\n        this.prepareRequest(fetchRequest);\n        this.formSubmission.start();\n    }\n    prepareRequest(request) {\n        var _a;\n        request.headers[\"Turbo-Frame\"] = this.id;\n        if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute(\"data-turbo-stream\")) {\n            request.acceptResponseType(StreamMessage.contentType);\n        }\n    }\n    requestStarted(_request) {\n        markAsBusy(this.element);\n    }\n    requestPreventedHandlingResponse(_request, _response) {\n        this.resolveVisitPromise();\n    }\n    async requestSucceededWithResponse(request, response) {\n        await this.loadResponse(response);\n        this.resolveVisitPromise();\n    }\n    async requestFailedWithResponse(request, response) {\n        await this.loadResponse(response);\n        this.resolveVisitPromise();\n    }\n    requestErrored(request, error) {\n        console.error(error);\n        this.resolveVisitPromise();\n    }\n    requestFinished(_request) {\n        clearBusyState(this.element);\n    }\n    formSubmissionStarted({ formElement }) {\n        markAsBusy(formElement, this.findFrameElement(formElement));\n    }\n    formSubmissionSucceededWithResponse(formSubmission, response) {\n        const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);\n        frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);\n        frame.delegate.loadResponse(response);\n        if (!formSubmission.isSafe) {\n            session.clearCache();\n        }\n    }\n    formSubmissionFailedWithResponse(formSubmission, fetchResponse) {\n        this.element.delegate.loadResponse(fetchResponse);\n        session.clearCache();\n    }\n    formSubmissionErrored(formSubmission, error) {\n        console.error(error);\n    }\n    formSubmissionFinished({ formElement }) {\n        clearBusyState(formElement, this.findFrameElement(formElement));\n    }\n    allowsImmediateRender({ element: newFrame }, options) {\n        const event = dispatch(\"turbo:before-frame-render\", {\n            target: this.element,\n            detail: Object.assign({ newFrame }, options),\n            cancelable: true,\n        });\n        const { defaultPrevented, detail: { render }, } = event;\n        if (this.view.renderer && render) {\n            this.view.renderer.renderElement = render;\n        }\n        return !defaultPrevented;\n    }\n    viewRenderedSnapshot(_snapshot, _isPreview) { }\n    preloadOnLoadLinksForView(element) {\n        session.preloadOnLoadLinksForView(element);\n    }\n    viewInvalidated() { }\n    willRenderFrame(currentElement, _newElement) {\n        this.previousFrameElement = currentElement.cloneNode(true);\n    }\n    async loadFrameResponse(fetchResponse, document) {\n        const newFrameElement = await this.extractForeignFrameElement(document.body);\n        if (newFrameElement) {\n            const snapshot = new Snapshot(newFrameElement);\n            const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);\n            if (this.view.renderPromise)\n                await this.view.renderPromise;\n            this.changeHistory();\n            await this.view.render(renderer);\n            this.complete = true;\n            session.frameRendered(fetchResponse, this.element);\n            session.frameLoaded(this.element);\n            this.fetchResponseLoaded(fetchResponse);\n        }\n        else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {\n            this.handleFrameMissingFromResponse(fetchResponse);\n        }\n    }\n    async visit(url) {\n        var _a;\n        const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams(), this.element);\n        (_a = this.currentFetchRequest) === null || _a === void 0 ? void 0 : _a.cancel();\n        this.currentFetchRequest = request;\n        return new Promise((resolve) => {\n            this.resolveVisitPromise = () => {\n                this.resolveVisitPromise = () => { };\n                this.currentFetchRequest = null;\n                resolve();\n            };\n            request.perform();\n        });\n    }\n    navigateFrame(element, url, submitter) {\n        const frame = this.findFrameElement(element, submitter);\n        frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);\n        this.withCurrentNavigationElement(element, () => {\n            frame.src = url;\n        });\n    }\n    proposeVisitIfNavigatedWithAction(frame, element, submitter) {\n        this.action = getVisitAction(submitter, element, frame);\n        if (this.action) {\n            const pageSnapshot = PageSnapshot.fromElement(frame).clone();\n            const { visitCachedSnapshot } = frame.delegate;\n            frame.delegate.fetchResponseLoaded = (fetchResponse) => {\n                if (frame.src) {\n                    const { statusCode, redirected } = fetchResponse;\n                    const responseHTML = frame.ownerDocument.documentElement.outerHTML;\n                    const response = { statusCode, redirected, responseHTML };\n                    const options = {\n                        response,\n                        visitCachedSnapshot,\n                        willRender: false,\n                        updateHistory: false,\n                        restorationIdentifier: this.restorationIdentifier,\n                        snapshot: pageSnapshot,\n                    };\n                    if (this.action)\n                        options.action = this.action;\n                    session.visit(frame.src, options);\n                }\n            };\n        }\n    }\n    changeHistory() {\n        if (this.action) {\n            const method = getHistoryMethodForAction(this.action);\n            session.history.update(method, expandURL(this.element.src || \"\"), this.restorationIdentifier);\n        }\n    }\n    async handleUnvisitableFrameResponse(fetchResponse) {\n        console.warn(`The response (${fetchResponse.statusCode}) from <turbo-frame id=\"${this.element.id}\"> is performing a full page visit due to turbo-visit-control.`);\n        await this.visitResponse(fetchResponse.response);\n    }\n    willHandleFrameMissingFromResponse(fetchResponse) {\n        this.element.setAttribute(\"complete\", \"\");\n        const response = fetchResponse.response;\n        const visit = async (url, options = {}) => {\n            if (url instanceof Response) {\n                this.visitResponse(url);\n            }\n            else {\n                session.visit(url, options);\n            }\n        };\n        const event = dispatch(\"turbo:frame-missing\", {\n            target: this.element,\n            detail: { response, visit },\n            cancelable: true,\n        });\n        return !event.defaultPrevented;\n    }\n    handleFrameMissingFromResponse(fetchResponse) {\n        this.view.missing();\n        this.throwFrameMissingError(fetchResponse);\n    }\n    throwFrameMissingError(fetchResponse) {\n        const message = `The response (${fetchResponse.statusCode}) did not contain the expected <turbo-frame id=\"${this.element.id}\"> and will be ignored. To perform a full page visit instead, set turbo-visit-control to reload.`;\n        throw new TurboFrameMissingError(message);\n    }\n    async visitResponse(response) {\n        const wrapped = new FetchResponse(response);\n        const responseHTML = await wrapped.responseHTML;\n        const { location, redirected, statusCode } = wrapped;\n        return session.visit(location, { response: { redirected, statusCode, responseHTML } });\n    }\n    findFrameElement(element, submitter) {\n        var _a;\n        const id = getAttribute(\"data-turbo-frame\", submitter, element) || this.element.getAttribute(\"target\");\n        return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;\n    }\n    async extractForeignFrameElement(container) {\n        let element;\n        const id = CSS.escape(this.id);\n        try {\n            element = activateElement(container.querySelector(`turbo-frame#${id}`), this.sourceURL);\n            if (element) {\n                return element;\n            }\n            element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.sourceURL);\n            if (element) {\n                await element.loaded;\n                return await this.extractForeignFrameElement(element);\n            }\n        }\n        catch (error) {\n            console.error(error);\n            return new FrameElement();\n        }\n        return null;\n    }\n    formActionIsVisitable(form, submitter) {\n        const action = getAction(form, submitter);\n        return locationIsVisitable(expandURL(action), this.rootLocation);\n    }\n    shouldInterceptNavigation(element, submitter) {\n        const id = getAttribute(\"data-turbo-frame\", submitter, element) || this.element.getAttribute(\"target\");\n        if (element instanceof HTMLFormElement && !this.formActionIsVisitable(element, submitter)) {\n            return false;\n        }\n        if (!this.enabled || id == \"_top\") {\n            return false;\n        }\n        if (id) {\n            const frameElement = getFrameElementById(id);\n            if (frameElement) {\n                return !frameElement.disabled;\n            }\n        }\n        if (!session.elementIsNavigatable(element)) {\n            return false;\n        }\n        if (submitter && !session.elementIsNavigatable(submitter)) {\n            return false;\n        }\n        return true;\n    }\n    get id() {\n        return this.element.id;\n    }\n    get enabled() {\n        return !this.element.disabled;\n    }\n    get sourceURL() {\n        if (this.element.src) {\n            return this.element.src;\n        }\n    }\n    set sourceURL(sourceURL) {\n        this.ignoringChangesToAttribute(\"src\", () => {\n            this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;\n        });\n    }\n    get loadingStyle() {\n        return this.element.loading;\n    }\n    get isLoading() {\n        return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;\n    }\n    get complete() {\n        return this.element.hasAttribute(\"complete\");\n    }\n    set complete(value) {\n        this.ignoringChangesToAttribute(\"complete\", () => {\n            if (value) {\n                this.element.setAttribute(\"complete\", \"\");\n            }\n            else {\n                this.element.removeAttribute(\"complete\");\n            }\n        });\n    }\n    get isActive() {\n        return this.element.isActive && this.connected;\n    }\n    get rootLocation() {\n        var _a;\n        const meta = this.element.ownerDocument.querySelector(`meta[name=\"turbo-root\"]`);\n        const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : \"/\";\n        return expandURL(root);\n    }\n    isIgnoringChangesTo(attributeName) {\n        return this.ignoredAttributes.has(attributeName);\n    }\n    ignoringChangesToAttribute(attributeName, callback) {\n        this.ignoredAttributes.add(attributeName);\n        callback();\n        this.ignoredAttributes.delete(attributeName);\n    }\n    withCurrentNavigationElement(element, callback) {\n        this.currentNavigationElement = element;\n        callback();\n        delete this.currentNavigationElement;\n    }\n}\nfunction getFrameElementById(id) {\n    if (id != null) {\n        const element = document.getElementById(id);\n        if (element instanceof FrameElement) {\n            return element;\n        }\n    }\n}\nfunction activateElement(element, currentURL) {\n    if (element) {\n        const src = element.getAttribute(\"src\");\n        if (src != null && currentURL != null && urlsAreEqual(src, currentURL)) {\n            throw new Error(`Matching <turbo-frame id=\"${element.id}\"> element has a source URL which references itself`);\n        }\n        if (element.ownerDocument !== document) {\n            element = document.importNode(element, true);\n        }\n        if (element instanceof FrameElement) {\n            element.connectedCallback();\n            element.disconnectedCallback();\n            return element;\n        }\n    }\n}\n\nclass StreamElement extends HTMLElement {\n    static async renderElement(newElement) {\n        await newElement.performAction();\n    }\n    async connectedCallback() {\n        try {\n            await this.render();\n        }\n        catch (error) {\n            console.error(error);\n        }\n        finally {\n            this.disconnect();\n        }\n    }\n    async render() {\n        var _a;\n        return ((_a = this.renderPromise) !== null && _a !== void 0 ? _a : (this.renderPromise = (async () => {\n            const event = this.beforeRenderEvent;\n            if (this.dispatchEvent(event)) {\n                await nextAnimationFrame();\n                await event.detail.render(this);\n            }\n        })()));\n    }\n    disconnect() {\n        try {\n            this.remove();\n        }\n        catch (_a) { }\n    }\n    removeDuplicateTargetChildren() {\n        this.duplicateChildren.forEach((c) => c.remove());\n    }\n    get duplicateChildren() {\n        var _a;\n        const existingChildren = this.targetElements.flatMap((e) => [...e.children]).filter((c) => !!c.id);\n        const newChildrenIds = [...(((_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children) || [])].filter((c) => !!c.id).map((c) => c.id);\n        return existingChildren.filter((c) => newChildrenIds.includes(c.id));\n    }\n    get performAction() {\n        if (this.action) {\n            const actionFunction = StreamActions[this.action];\n            if (actionFunction) {\n                return actionFunction;\n            }\n            this.raise(\"unknown action\");\n        }\n        this.raise(\"action attribute is missing\");\n    }\n    get targetElements() {\n        if (this.target) {\n            return this.targetElementsById;\n        }\n        else if (this.targets) {\n            return this.targetElementsByQuery;\n        }\n        else {\n            this.raise(\"target or targets attribute is missing\");\n        }\n    }\n    get templateContent() {\n        return this.templateElement.content.cloneNode(true);\n    }\n    get templateElement() {\n        if (this.firstElementChild === null) {\n            const template = this.ownerDocument.createElement(\"template\");\n            this.appendChild(template);\n            return template;\n        }\n        else if (this.firstElementChild instanceof HTMLTemplateElement) {\n            return this.firstElementChild;\n        }\n        this.raise(\"first child element must be a <template> element\");\n    }\n    get action() {\n        return this.getAttribute(\"action\");\n    }\n    get target() {\n        return this.getAttribute(\"target\");\n    }\n    get targets() {\n        return this.getAttribute(\"targets\");\n    }\n    raise(message) {\n        throw new Error(`${this.description}: ${message}`);\n    }\n    get description() {\n        var _a, _b;\n        return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : \"<turbo-stream>\";\n    }\n    get beforeRenderEvent() {\n        return new CustomEvent(\"turbo:before-stream-render\", {\n            bubbles: true,\n            cancelable: true,\n            detail: { newStream: this, render: StreamElement.renderElement },\n        });\n    }\n    get targetElementsById() {\n        var _a;\n        const element = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);\n        if (element !== null) {\n            return [element];\n        }\n        else {\n            return [];\n        }\n    }\n    get targetElementsByQuery() {\n        var _a;\n        const elements = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.querySelectorAll(this.targets);\n        if (elements.length !== 0) {\n            return Array.prototype.slice.call(elements);\n        }\n        else {\n            return [];\n        }\n    }\n}\n\nclass StreamSourceElement extends HTMLElement {\n    constructor() {\n        super(...arguments);\n        this.streamSource = null;\n    }\n    connectedCallback() {\n        this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src);\n        connectStreamSource(this.streamSource);\n    }\n    disconnectedCallback() {\n        if (this.streamSource) {\n            disconnectStreamSource(this.streamSource);\n        }\n    }\n    get src() {\n        return this.getAttribute(\"src\") || \"\";\n    }\n}\n\nFrameElement.delegateConstructor = FrameController;\nif (customElements.get(\"turbo-frame\") === undefined) {\n    customElements.define(\"turbo-frame\", FrameElement);\n}\nif (customElements.get(\"turbo-stream\") === undefined) {\n    customElements.define(\"turbo-stream\", StreamElement);\n}\nif (customElements.get(\"turbo-stream-source\") === undefined) {\n    customElements.define(\"turbo-stream-source\", StreamSourceElement);\n}\n\n(() => {\n    let element = document.currentScript;\n    if (!element)\n        return;\n    if (element.hasAttribute(\"data-turbo-suppress-warning\"))\n        return;\n    element = element.parentElement;\n    while (element) {\n        if (element == document.body) {\n            return console.warn(unindent `\n        You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!\n\n        Load your application’s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.\n\n        For more information, see: https://turbo.hotwired.dev/handbook/building#working-with-script-elements\n\n        ——\n        Suppress this warning by adding a \"data-turbo-suppress-warning\" attribute to: %s\n      `, element.outerHTML);\n        }\n        element = element.parentElement;\n    }\n})();\n\nwindow.Turbo = Turbo;\nstart();\n\nexport { FrameElement, FrameLoadingStyle, FrameRenderer, PageRenderer, PageSnapshot, StreamActions, StreamElement, StreamSourceElement, cache, clearCache, connectStreamSource, disconnectStreamSource, navigator$1 as navigator, registerAdapter, renderStreamMessage, session, setConfirmMethod, setFormMode, setProgressBarDelay, start, visit };\n"
  },
  {
    "path": "lib/rdoc/generator/template/rails/resources/js/main.js",
    "content": "import { Search } from \"./search.js\";\n\ndocument.addEventListener(\"turbo:load\", () => {\n  const searchInput = document.getElementById(\"search\");\n  const searchOutput = document.getElementById(\"results\");\n  const search = new Search(searchInput, searchOutput, (url, module, member, summary) =>\n    `<div class=\"results__result\">\n      <a class=\"ref-link result__link\" href=\"${url}\">\n        <code class=\"result__module\">${module.replaceAll(\"::\", \"::<wbr>\")}</code>\n        <code class=\"result__member\">${member || \"\"}</code>\n      </a>\n      <p class=\"result__summary description\">${summary || \"\"}</p>\n    </div>`\n  );\n\n  // Handle query button clicks.\n  document.addEventListener(\"click\", ({ target }) => {\n    const query = target.closest(\".query-button\")?.dataset?.query;\n    if (query) {\n      search.query = query;\n      search.focus();\n    }\n  });\n\n  const query = new URL(document.location).searchParams.get(\"q\");\n  if (query) {\n    search.feelingLucky(query);\n  }\n}, { once: true });\n\n\n// Hide menu on mobile when navigating to a named anchor on the current page.\n// For example, when clicking on a method in the method list.\nwindow.addEventListener(\"hashchange\", () => {\n  document.getElementById(\"panel__state\").checked = false;\n});\n\n\n// Because search results are in a `data-turbo-permanent` element, manually blur\n// to hide them when navigating.\ndocument.addEventListener(\"turbo:click\", ({ target }) => {\n  if (document.getElementById(\"results\").contains(target)) {\n    target.blur();\n  }\n});\n\n\n// Keep scroll position for search results across Turbo page loads.\n(function() {\n  var scrollTop = 0;\n\n  addEventListener(\"turbo:before-render\", function() {\n    scrollTop = document.getElementById(\"results\").scrollTop\n  })\n\n  addEventListener(\"turbo:render\", function() {\n    document.getElementById(\"results\").scrollTop = scrollTop\n  })\n})();\n\n\n// Turbo Drive interferes with the browser designating the `:target` element for\n// CSS (see https://github.com/hotwired/turbo/issues/592), so add an explicit\n// class instead.\n(function() {\n  const retarget = (url) => {\n    document.querySelector(\".target\")?.classList?.remove(\"target\");\n    if (url.hash) {\n      document.getElementById(url.hash.substring(1))?.classList?.add(\"target\");\n    }\n  };\n\n  // Unlike normal navigation, Turbo Drive fires the `hashchange` _before_\n  // `location` is changed, so we must use the `newURL` property.\n  window.addEventListener(\"hashchange\", ({ newURL }) => retarget(new URL(newURL)));\n  document.addEventListener(\"turbo:load\", event => retarget(location));\n})();\n"
  },
  {
    "path": "lib/rdoc/generator/template/rails/resources/js/search.js",
    "content": "import searchIndex from \"./search-index.js\";\n\nexport class Search {\n  constructor(inputEl, outputEl, resultRenderer) {\n    Object.assign(this, { inputEl, outputEl, resultRenderer });\n\n    this.inputEl.addEventListener(\"input\", event => this.search());\n    this.inputEl.addEventListener(\"focusin\", event => this.handleFocusIn(event));\n    this.inputEl.addEventListener(\"focusout\", event => this.handleFocusOut(event));\n    this.outputEl.addEventListener(\"focusout\", event => this.handleFocusOut(event));\n    document.addEventListener(\"keydown\", event => this.handleKey(event));\n\n    // Ensure `tabindex` attribute is set. (When it is not set, the `tabIndex`\n    // property returns a default value instead of null / undefined.)\n    this.outputEl.tabIndex = this.outputEl.tabIndex;\n\n    this.active = document.activeElement === this.inputEl;\n  }\n\n  search() {\n    const bitPositions = this.compileQuery(this.query);\n    let worst;\n    this.clearResults();\n\n    for (const entry of searchIndex.entries) {\n      const score = this.computeScore(bitPositions, entry[0], entry[1]);\n      worst ??= this.worstResult;\n\n      if (score > worst.score) {\n        worst.score = score;\n        worst.entry = entry;\n        worst = null;\n      }\n    }\n\n    this.renderResults();\n  }\n\n  compileQuery(query) {\n    query = ` ${query} `;\n    const bitPositions = [];\n\n    for (let i = 0, upto = query.length - 2; i < upto; i += 1) {\n      const ngram = query.substring(i, i + 3);\n      const position = searchIndex.ngrams[ngram];\n\n      if (position) {\n        bitPositions.push(position);\n      }\n    }\n    return bitPositions;\n  }\n\n  computeScore(bitPositions, bytes, tiebreakerBonus) {\n    let score = 0;\n\n    for (let i = 0, len = bitPositions.length; i < len; i += 1) {\n      const position = bitPositions[i] | 0;\n      const byte = bytes[position / 8 | 0] | 0;\n      const mask = 1 << (position % 8) | 0;\n\n      if (byte & mask) {\n        score += searchIndex.weights[position] + tiebreakerBonus;\n      }\n    }\n\n    return score;\n  }\n\n  static maxResults = 20;\n  results = Array(Search.maxResults).fill().map(() => ({}));\n\n  clearResults() {\n    for (const result of this.results) {\n      result.score = 0;\n      result.entry = null;\n    }\n  }\n\n  get worstResult() {\n    return this.results.reduce((worst, result) => result.score < worst.score ? result : worst);\n  }\n\n  renderResults() {\n    this.results.sort((a, b) => b.score - a.score);\n\n    let html = \"\";\n\n    for (const { score, entry } of this.results) {\n      if (score > 0) {\n        html += this.resultRenderer(entry[2], entry[3], entry[4], entry[5]);\n      }\n    }\n\n    this.outputEl.innerHTML = html;\n    this.cursorEl = this.outputEl.firstElementChild;\n  }\n\n  feelingLucky(query) {\n    this.query = query;\n    this.clickCursor();\n  }\n\n  focus() {\n    this.inputEl.focus();\n  }\n\n  blur() {\n    this.inputEl.blur();\n  }\n\n  get active() {\n    return this.inputEl.classList.contains(\"active\");\n  }\n\n  set active(value) {\n    this.inputEl.classList.toggle(\"active\", value);\n    this.outputEl.classList.toggle(\"active\", value);\n  }\n\n  get query() {\n    return this.inputEl.value;\n  }\n\n  set query(value) {\n    this.inputEl.value = value;\n    this.search();\n  }\n\n  get cursorEl() {\n    return this._cursorEl;\n  }\n\n  set cursorEl(el) {\n    this._cursorEl?.classList?.remove(\"cursor\");\n    el?.classList?.add(\"cursor\");\n    el?.scrollIntoView({ block: \"nearest\" });\n    this._cursorEl = el;\n  }\n\n  incrementCursor() {\n    if (this.cursorEl?.nextElementSibling) {\n      this.cursorEl = this.cursorEl.nextElementSibling;\n    }\n  }\n\n  decrementCursor() {\n    if (this.cursorEl?.previousElementSibling) {\n      this.cursorEl = this.cursorEl.previousElementSibling;\n    }\n  }\n\n  clickCursor() {\n    this.cursorEl?.querySelector(\"a[href]\")?.click();\n    setTimeout(() => this.blur(), 0);\n  }\n\n  handleFocusIn() {\n    this.active = true;\n  }\n\n  handleFocusOut({ relatedTarget }) {\n    this.active = this.inputEl === relatedTarget || this.outputEl.contains(relatedTarget);\n  }\n\n  static activeKeyMap = {\n    \"ArrowDown\": \"incrementCursor\",\n    \"ArrowUp\": \"decrementCursor\",\n    \"Enter\": \"clickCursor\",\n    \"Escape\": \"blur\"\n  };\n\n  static idleKeyMap = {\n    \"/\": \"focus\"\n  };\n\n  handleKey(event) {\n    const handler = (this.active ? Search.activeKeyMap : Search.idleKeyMap)[event.key];\n    if (handler) {\n      this[handler]();\n      event.preventDefault();\n    }\n  }\n}\n"
  },
  {
    "path": "lib/sdoc/generator.rb",
    "content": "require 'pathname'\nrequire 'fileutils'\nrequire 'json'\n\nrequire \"rdoc\"\nrequire_relative \"rdoc_monkey_patches\"\n\nrequire \"sdoc/postprocessor\"\nrequire \"sdoc/renderer\"\nrequire \"sdoc/search_index\"\nrequire \"sdoc/version\"\n\nclass RDoc::Options\n  attr_writer :core_ext_pattern\n\n  def core_ext_pattern\n    @core_ext_pattern ||= /core_ext/\n  end\n\n  attr_accessor :github\nend\n\nclass RDoc::Generator::SDoc\n  RDoc::RDoc.add_generator self\n\n  DESCRIPTION = 'Searchable HTML documentation'\n\n  FILE_DIR = \"files\"\n  CLASS_DIR = \"classes\"\n\n  RESOURCES_DIR = File.join('resources', '.')\n\n  attr_reader :options\n\n  ##\n  # The RDoc::Store that is the source of the generated content\n\n  attr_reader :store\n\n  def self.setup_options(options)\n    opt = options.option_parser\n\n    opt.separator nil\n    opt.separator \"SDoc generator options:\"\n\n    opt.separator nil\n    opt.on(\"--core-ext=PATTERN\", Regexp, \"Regexp pattern indicating files that define core extensions. \" \\\n      \"Defaults to 'core_ext'.\") do |pattern|\n      options.core_ext_pattern = pattern\n    end\n\n    opt.separator nil\n    opt.on(\"--github\", \"-g\",\n            \"Generate links to github.\") do |value|\n      options.github = true\n    end\n\n    opt.separator nil\n    opt.on(\"--version\", \"-v\", \"Output current version\") do\n      puts SDoc::VERSION\n      exit\n    end\n\n    options.title = [\n      ENV[\"HORO_PROJECT_NAME\"],\n      ENV[\"HORO_BADGE_VERSION\"] || ENV[\"HORO_PROJECT_VERSION\"],\n      \"API documentation\"\n    ].compact.join(\" \")\n\n    if options.respond_to?(:class_module_path_prefix)\n      options.class_module_path_prefix = CLASS_DIR\n    end\n\n    if options.respond_to?(:file_path_prefix)\n      options.file_path_prefix = FILE_DIR\n    end\n  end\n\n  def initialize(store, options)\n    @store   = store\n    @options = options\n    if @options.respond_to?('diagram=')\n      @options.diagram = false\n    end\n    @options.pipe = true\n\n    @original_dir = Pathname.pwd\n    @template_dir = Pathname(options.template_dir)\n    @output_dir = Pathname(@options.op_dir).expand_path(options.root)\n  end\n\n  def generate\n    @files = @store.all_files.sort\n    @classes = @store.all_classes_and_modules.sort\n\n    FileUtils.mkdir_p(@output_dir)\n    copy_resources\n    generate_search_index\n    generate_index_file\n    generate_file_files\n    generate_class_files\n  end\n\n  # For compatibility with RDoc < 6.13.0\n  def class_dir\n    CLASS_DIR\n  end\n\n  # For compatibility with RDoc < 6.13.0\n  def file_dir\n    FILE_DIR\n  end\n\n  ### Determines index page based on @options.main_page (or lack thereof)\n  def index\n    @index ||= begin\n      path = @original_dir.join(@options.main_page || @options.files.first || \"\").expand_path\n      file = @files.find { |file| @options.root.join(file.full_name) == path }\n      raise \"Could not find main page #{path.to_s.inspect} among rendered files\" if !file\n\n      file = file.dup\n      file.path = \"\"\n\n      file\n    end\n  end\n\n  protected\n  ### Output progress information if debugging is enabled\n  def debug_msg(*msg)\n    $stderr.puts(*msg) if $DEBUG_RDOC\n  end\n\n  def render_file(template_path, output_path, context = nil)\n    debug_msg \"Rendering #{output_path}\"\n    return if @options.dry_run\n\n    result = SDoc::Renderer.new(context, @options).render(template_path)\n    result = SDoc::Postprocessor.process(result)\n\n    output_path = @output_dir.join(output_path)\n    output_path.dirname.mkpath\n    output_path.write(result)\n  end\n\n  def generate_index_file\n    render_file(\"index.rhtml\", \"index.html\", index)\n  end\n\n  def generate_class_files\n    @classes.each { |klass| render_file(\"class.rhtml\", klass.path, klass) }\n  end\n\n  def generate_file_files\n    @files.each { |file| render_file(\"file.rhtml\", file.path, file) }\n  end\n\n  def generate_search_index\n    debug_msg \"Generating search index\"\n    unless @options.dry_run\n      index = SDoc::SearchIndex.generate(@store.all_classes_and_modules)\n\n      @output_dir.join(\"js/search-index.js\").open(\"w\") do |file|\n        file.write(\"export default \")\n        JSON.dump(index, file)\n        file.write(\";\")\n      end\n    end\n  end\n\n  ### Copy all the resource files to output dir\n  def copy_resources\n    resources_path = @template_dir + RESOURCES_DIR\n    debug_msg \"Copying #{resources_path}/** to #{@output_dir}/**\"\n    FileUtils.cp_r resources_path.to_s, @output_dir.to_s unless @options.dry_run\n  end\nend\n"
  },
  {
    "path": "lib/sdoc/helpers/git.rb",
    "content": "module SDoc::Helpers::Git\n  def _git\n    @@_git ||= {}\n  end\n\n  def github_url(relative_path, line: nil)\n    return unless github?\n    line = \"#L#{line}\" if line\n    \"https://github.com/#{github_repository}/blob/#{git_head_sha1}/#{relative_path}#{line}\"\n  end\n\n  def github?\n    @options.github && git? && github_repository\n  end\n\n  def git?\n    _git[:repo_path] ||= Dir.chdir(@options.root) { `git rev-parse --show-toplevel 2> /dev/null`.chomp }\n    !_git[:repo_path].empty?\n  end\n\n  def git_command(command)\n    Dir.chdir(@options.root) { `git #{command}`.chomp } if git?\n  end\n\n  def git_head_branch\n    _git[:head_branch] ||= git_command(\"rev-parse --abbrev-ref HEAD\")\n  end\n\n  def git_head_sha1\n    _git[:head_sha1] ||= git_command(\"rev-parse HEAD\")\n  end\n\n  def git_head_timestamp\n    _git[:head_timestamp] ||= git_command(\"show -s --format=%cI HEAD\")\n  end\n\n  def git_origin_url\n    _git[:origin_url] ||= git_command(\"config --get remote.origin.url\")\n  end\n\n  def github_repository\n    _git.fetch(:github_repository) do\n      _git[:github_repository] = git_origin_url.chomp(\".git\")[%r\"github\\.com[/:](.+)\", 1]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/sdoc/helpers.rb",
    "content": "require \"erb\"\n\nmodule SDoc::Helpers\n  include ERB::Util\n\n  require_relative \"helpers/git\"\n  include SDoc::Helpers::Git\n\n  LEADING_PARAGRAPH_XPATH =\n    \"./*[not(self::h1 or self::h2 or self::h3 or self::h4 or self::h5 or self::h6)][1][self::p]\"\n\n  def link_to(text, url = nil, html_attributes = {})\n    url, html_attributes = nil, url if url.is_a?(Hash)\n    url ||= text\n\n    text = _link_body(text)\n\n    if url.is_a?(RDoc::CodeObject)\n      url = \"/#{url.path}\"\n      default_class = \"ref-link\" if text.start_with?(\"<code>\") && text.end_with?(\"</code>\")\n    end\n\n    html_attributes = html_attributes.transform_keys(&:to_s)\n    html_attributes = { \"href\" => url, \"class\" => default_class }.compact.merge(html_attributes)\n\n    attribute_string = html_attributes.map { |name, value| %( #{name}=\"#{h value}\") }.join\n    %(<a#{attribute_string}>#{text}</a>)\n  end\n\n  def _link_body(text)\n    text.is_a?(RDoc::CodeObject) ? full_name_for(text) : text\n  end\n\n  def link_to_if(condition, text, *args)\n    condition ? link_to(text, *args) : _link_body(text)\n  end\n\n  def link_to_external(text, url, html_attributes = {})\n    html_attributes = html_attributes.transform_keys(&:to_s)\n    html_attributes = { \"target\" => \"_blank\", \"class\" => nil }.merge(html_attributes)\n    html_attributes[\"class\"] = [*html_attributes[\"class\"], \"external-link\"].join(\" \")\n\n    link_to(text, url, html_attributes)\n  end\n\n  def button_to_search(query, display_name: full_name_for(query))\n    query = query.full_name if query.is_a?(RDoc::CodeObject)\n    %(<button class=\"query-button\" data-query=\"#{h query} \">Search #{display_name}</button>)\n  end\n\n  def full_name_for(named)\n    named = named.full_name if named.is_a?(RDoc::CodeObject)\n    \"<code>#{named.split(%r\"(?<=./|.::)\").map { |part| h part }.join(\"<wbr>\")}</code>\"\n  end\n\n  def short_name_for(named)\n    named = named.name if named.is_a?(RDoc::CodeObject)\n    \"<code>#{h named}</code>\"\n  end\n\n  def description_for(rdoc_object)\n    if rdoc_object.comment && !rdoc_object.comment.empty?\n      %(<div class=\"description\">#{rdoc_object.description}</div>)\n    end\n  end\n\n  def base_tag_for_context(context)\n    relative_root = \"../\" * context.path.count(\"/\")\n    %(<base href=\"./#{relative_root}\" data-current-path=\"#{context.path}\">)\n  end\n\n  def canonical_url(path = nil)\n    path = path.path if path.is_a?(RDoc::Context)\n    \"#{ENV[\"HORO_CANONICAL_URL\"]}/#{path&.delete_prefix(\"/\")}\" if ENV[\"HORO_CANONICAL_URL\"]\n  end\n\n  def project_name\n    h(ENV[\"HORO_PROJECT_NAME\"]) if ENV[\"HORO_PROJECT_NAME\"]\n  end\n\n  def project_version\n    version = ENV[\"HORO_BADGE_VERSION\"] || ENV[\"HORO_PROJECT_VERSION\"]\n    h version if version\n  end\n\n  def project_git_head\n    h \"#{git_head_branch}@#{git_head_sha1[0, 12]}\" if git?\n  end\n\n  def page_title(title = nil)\n    h [title, @options.title].compact.join(\" - \")\n  end\n\n  def og_title(title)\n    project = [project_name, project_version].join(\" \").strip\n    \"#{h title}#{\" (#{project})\" unless project.empty?}\"\n  end\n\n  def og_modified_time\n    git_head_timestamp\n  end\n\n  def page_description(leading_html, max_length: 160)\n    return if leading_html.nil? || !leading_html.include?(\"</p>\")\n\n    text = Nokogiri::HTML.fragment(leading_html).at(LEADING_PARAGRAPH_XPATH)&.inner_text\n    return unless text\n\n    if text.length > max_length\n      # `+ 1 - 3` because we remove at least one character and replace it with \"...\".\n      text = text[0, max_length + 1 - 3].sub(/(?:\\W+|\\W*\\w+)\\Z/, \"...\")\n    end\n\n    h text\n  end\n\n  def outline(context)\n    comment = context.respond_to?(:comment_location) ? context.comment_location : context.comment\n    return if comment.empty?\n\n    headings = context.parse(comment).table_of_contents\n    headings.shift if headings.one? { |heading| heading.level == 1 } && headings[0].level == 1\n\n    _outline_list(context, headings)\n  end\n\n  def _outline_list(context, headings, following: 0)\n    items = []\n    while headings[0] && headings[0].level > following\n      items << _outline_list_item(context, headings)\n    end\n    \"<ul>#{items.join}</ul>\" unless items.empty?\n  end\n\n  def _outline_list_item(context, headings)\n    heading = headings.shift\n    link = link_to(heading.plain_html, \"##{heading.label(context)}\")\n    sublist = _outline_list(context, headings, following: heading.level)\n    \"<li>#{link}#{sublist}</li>\"\n  end\n\n  def more_less_ul(items, limit)\n    soft_limit, hard_limit = (limit.is_a?(Range) ? limit : [limit]).minmax\n    items = items.map { |item| \"<li>#{item}</li>\" }\n\n    if items.length > hard_limit\n      <<~HTML\n        <ul>#{items[0...soft_limit].join}</ul>\n        <details class=\"more-less\">\n          <summary>\n            <span class=\"more-less__more\">#{items.length - soft_limit} More</span>\n            <span class=\"more-less__less\">Less</span>\n          </summary>\n          <ul>#{items[soft_limit..].join}</ul>\n        </details>\n      HTML\n    else\n      \"<ul>#{items.join}</ul>\"\n    end\n  end\n\n  def top_modules(rdoc_store)\n    _top_modules(rdoc_store).reject { |rdoc_module| _core_ext?(rdoc_module) }\n  end\n\n  def core_extensions(rdoc_store)\n    _top_modules(rdoc_store).select { |rdoc_module| _core_ext?(rdoc_module) }\n  end\n\n  def _top_modules(rdoc_store)\n    rdoc_store.all_classes_and_modules.select do |rdoc_module|\n      !rdoc_module.full_name.include?(\"::\")\n    end.sort\n  end\n\n  def _core_ext?(rdoc_module)\n    # HACK There is currently a bug in RDoc v6.5.0 that causes the value of\n    # RDoc::ClassModule#in_files for `Object` to become polluted. The cause is\n    # unclear, but it might be related to setting global constants (for example,\n    # setting `APP_PATH = \"...\"` outside of a class or module). To work around\n    # this bug, we always treat `Object` as a core extension.\n    rdoc_module.full_name == \"Object\" ||\n\n    rdoc_module.in_files.all? { |rdoc_file| @options.core_ext_pattern.match?(rdoc_file.full_name) }\n  end\n\n  def module_breadcrumbs(rdoc_module)\n    parent_names = rdoc_module.full_name.split(\"::\")[0...-1]\n\n    crumbs = parent_names.each_with_index.map do |name, i|\n      parent = rdoc_module.store.find_class_or_module(parent_names[0..i].join(\"::\"))\n      parent ? link_to(h(name), parent) : h(name)\n    end\n\n    \"<code>#{[*crumbs, h(rdoc_module.name)].join(\"::<wbr>\")}</code>\"\n  end\n\n  def module_ancestors(rdoc_module)\n    ancestors = rdoc_module.includes.map { |inc| [\"module\", inc.module] }\n\n    if !rdoc_module.module? && superclass = rdoc_module.superclass\n      superclass_name = superclass.is_a?(String) ? superclass : superclass.full_name\n      ancestors.unshift([\"class\", superclass]) unless superclass_name == \"Object\"\n    end\n\n    ancestors\n  end\n\n  def module_methods(rdoc_module)\n    rdoc_module.each_method.sort_by do |rdoc_method|\n      [rdoc_method.singleton ? 0 : 1, rdoc_method.name]\n    end\n  end\n\n  def method_signature(rdoc_method)\n    signature = if rdoc_method.call_seq\n      # Support specifying a call-seq like `to_s -> string`\n      rdoc_method.call_seq.gsub(/^\\s*([^(\\s]+)(.*?)(?: -> (.+))?$/) do\n        \"<b>#{h $1}</b>#{h $2}#{\" <span class=\\\"returns\\\">&rarr;</span> #{h $3}\" if $3}\"\n      end\n    else\n      \"<b>#{h rdoc_method.name}</b>#{h rdoc_method.params}\"\n    end\n\n    \"<code>#{signature}</code>\"\n  end\n\n  def method_source_code_and_url(rdoc_method)\n    source_code = rdoc_method.markup_code if rdoc_method.token_stream\n\n    if source_code&.match(/File\\s(\\S+), line (\\d+)/)\n      source_url = github_url($1, line: $2)\n    end\n\n    [(source_code unless rdoc_method.instance_of?(RDoc::GhostMethod)), source_url]\n  end\nend\n"
  },
  {
    "path": "lib/sdoc/name_list.rb",
    "content": "# frozen_string_literal: true\n\nclass RDoc::Generator::NameList\n\n  include RDoc::Text\n\n  attr_reader :index # :nodoc:\n\n  RDoc::RDoc.add_generator self\n\n  ##\n  # Creates a new generator.\n\n  def initialize store, options\n    @store            = store\n    @options          = options\n\n    @classes = nil\n    @files   = nil\n    @index   = nil\n  end\n\n  ##\n  # Builds the list of namespaces and methods as a Hash.\n\n  def build_index\n    reset @store.all_files.sort, @store.all_classes_and_modules.sort\n\n    index_classes\n    index_methods\n\n    { :index => @index }\n  end\n\n  ##\n  # Output progress information if debugging is enabled\n\n  def debug_msg *msg\n    return unless $DEBUG_RDOC\n    $stderr.puts(*msg)\n  end\n\n  ##\n  # Writes the name list to disk\n\n  def generate\n    debug_msg \"Generating Name List\"\n    data = build_index\n\n    return if @options.dry_run\n\n    out_dir = Pathname.new \".\"\n\n    FileUtils.mkdir_p out_dir, :verbose => $DEBUG_RDOC\n\n    generate_classes_index data[:index][:classes], out_dir\n    generate_methods_index data[:index][:methods], out_dir\n  end\n\n  def generate_classes_index data, out_dir\n    classes_file = out_dir + File.join(\"classes\")\n\n    debug_msg \"  writing classes index to %s\" % classes_file\n    classes_file.open 'w', 0644 do |io|\n      io.set_encoding Encoding::UTF_8\n\n      io.puts data\n    end\n  end\n\n  def generate_methods_index data, out_dir\n    methods_file = out_dir + File.join(\"methods\")\n\n    debug_msg \"  writing methods index to %s\" % methods_file\n    methods_file.open 'w', 0644 do |io|\n      io.set_encoding Encoding::UTF_8\n\n      io.puts data\n    end\n  end\n\n  ##\n  # Adds classes and modules to the index\n\n  def index_classes\n    debug_msg \"  generating class name index\"\n\n    documented = @classes.uniq.select do |klass|\n      klass.document_self_or_methods\n    end.flatten.sort_by(&:full_name)\n\n    documented.each do |klass|\n      debug_msg \"    #{klass.full_name}\"\n      @index[:classes] << \"#{klass.full_name}\"\n    end\n  end\n\n  ##\n  # Adds methods to the index\n\n  def index_methods\n    debug_msg \"  generating method name index\"\n\n    list = @classes.uniq.map do |klass|\n      klass.method_list\n    end.flatten.sort_by do |method|\n      [method.parent.full_name, method.type, method.name]\n    end\n\n    list.each do |method|\n      debug_msg \"    #{method.full_name}\"\n      @index[:methods] << \"#{method.full_name}\"\n    end\n  end\n\n  def class_dir # :nodoc:\n    nil\n  end\n\n  def file_dir # :nodoc:\n    nil\n  end\n\n  def reset files, classes # :nodoc:\n    @files   = files\n    @classes = classes\n\n    @index = {\n      :classes => [],\n      :methods => []\n    }\n  end\nend\n"
  },
  {
    "path": "lib/sdoc/postprocessor.rb",
    "content": "require \"nokogiri\"\nrequire \"pathname\"\nrequire \"rouge\"\nrequire \"uri\"\n\nmodule SDoc::Postprocessor\n  extend self\n\n  def process(rendered)\n    document = Nokogiri::HTML5.parse(rendered)\n\n    rebase_urls!(document)\n    version_rubyonrails_urls!(document)\n    add_ref_link_classes!(document)\n    unify_h1_headings!(document)\n    highlight_code_blocks!(document)\n\n    document.to_s\n  end\n\n  TAG_ATTRIBUTES_AFFECTED_BY_BASE_TAG = {\n    link: :href,\n    script: :src,\n    a: :href,\n    img: :src,\n  }\n\n  def rebase_urls!(document)\n    current_path = document.at_css(\"base\")&.attr(\"data-current-path\")\n    return unless current_path\n\n    TAG_ATTRIBUTES_AFFECTED_BY_BASE_TAG.each do |tag, attr|\n      document.css(\"#{tag}[#{attr}]\").each do |element|\n        element[attr] = rebase_url(element[attr], current_path)\n      end\n    end\n  end\n\n  def rebase_url(url, current_path)\n    case\n    when url.start_with?(\"//\", \"https:\", \"http:\", \"javascript:\", \"data:\")\n      url\n    when url.start_with?(\"/\")\n      url[1..]\n    when url.start_with?(\"#\")\n      current_path + url\n    else\n      Pathname(current_path).dirname.join(url).cleanpath.to_s\n    end\n  end\n\n  def version_rubyonrails_urls!(document)\n    if ENV[\"HORO_PROJECT_NAME\"] == \"Ruby on Rails\" && version = ENV[\"HORO_PROJECT_VERSION\"]\n      document.css(\n        \"a[href^='https://api.rubyonrails.org/']\",\n        \"a[href^='https://guides.rubyonrails.org/']\"\n      ).each do |element|\n        element[\"href\"] = version_url(element[\"href\"], version)\n      end\n    end\n  end\n\n  def version_url(url, version)\n    uri = URI(url)\n\n    unless uri.path.match?(%r\"\\A/v\\d\")\n      if version.match?(/\\Av?[.0-9]+\\z/)\n        uri.path = \"/#{version.sub(/\\Av?/, \"v\")}#{uri.path}\"\n      else\n        uri.host = \"edge#{uri.host}\"\n      end\n    end\n\n    uri.to_s\n  end\n\n  def add_ref_link_classes!(document)\n    document.css(\".description a code\").each do |element|\n      if element.parent.children.one?\n        element.parent.add_class(\"ref-link\")\n      end\n    end\n  end\n\n  def unify_h1_headings!(document)\n    if h1 = document.at_css(\"#context > .description h1:first-child\")\n      if hgroup = document.at_css(\"#content > hgroup\")\n        h1.remove\n        hgroup.add_child(%(<p>#{h1.inner_html}</p>))\n      end\n    end\n  end\n\n  def highlight_code_blocks!(document)\n    document.css(\".description pre > code, pre.source-code > code\").each do |element|\n      code = element.inner_text\n      language = element.classes.include?(\"ruby\") ? \"ruby\" : guess_code_language(code)\n      element.inner_html = highlight_code(code, language)\n      element.add_class(\"highlight\").add_class(language)\n    end\n  end\n\n  def highlight_code(code, language)\n    lexer = Rouge::Lexer.find_fancy(language)\n    Rouge::Formatters::HTML.format(lexer.lex(code))\n  end\n\n  def guess_code_language(code)\n    case code\n    when /^\\$ /\n      \"console\"\n    when /--[+|]--/ # ASCII-art table\n      \"plaintext\"\n    when /(?:GET|POST|PUT|PATCH|DELETE|HEAD) +\\// # routes listing or HTTP request\n      \"plaintext\"\n    when /\\A(?:SELECT|INSERT|UPDATE|DELETE|CREATE|ALTER|DROP) /\n      \"sql\"\n    when /^(?:To|Cc|Bcc): .+@/\n      \"email\"\n    when /^(?:- )?\\w+:(?:\\n| [#&|>])/ # YAML dictionary or list of dictionaries\n      if code.include?(\"<%\")\n        code.include?(\"<<:\") ? \"plaintext\" : \"erb\"\n      else\n        \"yaml\"\n      end\n    when /^ *<[%a-z]|%>$|<\\/\\w+>$/i\n      \"erb\" # also highlights HTML\n    else\n      \"ruby\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/sdoc/rdoc_monkey_patches.rb",
    "content": "require \"rdoc\"\n\nRDoc::TopLevel.prepend(Module.new do\n  attr_writer :path\n\n  def path\n    @path ||= super\n  end\nend)\n\n\nRDoc::Constant.prepend(Module.new do\n  def aref_prefix\n    \"constant\"\n  end\n\n  def aref\n    \"#{aref_prefix}-#{name}\"\n  end\n\n  def path\n    \"#{super.sub(/#.+/, \"\")}##{aref}\"\n  end\nend)\n\n\nRDoc::AnyMethod.prepend(Module.new do\n  def params\n    super&.sub(/\\A\\(\\s+/, \"(\")&.sub(/\\s+\\)\\z/, \")\")\n  end\nend)\n\n\nRDoc::Markup::ToHtmlCrossref.prepend(Module.new do\n  def cross_reference(name, text = nil, code = true, *, **)\n    if text\n      # Style ref links that look like code, such as `{Rails}[rdoc-ref:Rails]`.\n      code ||= !text.include?(\" \") || text.match?(/\\S\\(/)\n    elsif name.match?(/\\A[A-Z](?:[A-Z]+|[a-z]+)\\z/)\n      # Prevent unintentional ref links, such as `Rails` or `ERB`.\n      return name\n    end\n\n    super\n  end\nend)\n\n\nRDoc::Parser::Ruby.prepend(Module.new do\n  def get_class_or_module(container, ignore_constants = false)\n    @ignoring_constants ||= nil\n    original_ignoring_constants, @ignoring_constants = @ignoring_constants, ignore_constants\n    super\n  ensure\n    @ignoring_constants = original_ignoring_constants\n  end\n\n  def record_location(*)\n    @ignoring_constants ||= nil\n    super unless @ignoring_constants\n  end\nend)\n"
  },
  {
    "path": "lib/sdoc/renderer.rb",
    "content": "require \"erb\"\nrequire_relative \"helpers\"\n\nclass SDoc::Renderer\n  include SDoc::Helpers\n\n  def self.compile_erb(path)\n    @compiled_erb ||= {}\n    @compiled_erb[path] ||= begin\n      erb = ERB.new(File.read(path), trim_mode: \"<>\", eoutvar: \"self.buffer\")\n      erb.filename = path\n      erb\n    end\n  end\n\n  attr_reader :buffer\n  def buffer=(*); end # Ignore buffer reset from ERB itself.\n\n  def initialize(context, rdoc_options)\n    @context = context\n    @options = rdoc_options\n    @buffer = nil\n  end\n\n  def render(...)\n    original_buffer, @buffer = @buffer, +\"\"\n    inline(...)\n  ensure\n    @buffer = original_buffer\n  end\n\n  def inline(template_path, local_assigns = {})\n    template_path = File.expand_path(template_path, @options.template_dir)\n    _binding = binding\n    local_assigns.each { |name, value| _binding.local_variable_set(name, value) }\n    self.class.compile_erb(template_path).result(_binding)\n  end\nend\n"
  },
  {
    "path": "lib/sdoc/search_index.rb",
    "content": "require \"nokogiri\"\nrequire_relative \"helpers\"\n\nmodule SDoc::SearchIndex\n  extend self\n\n  class Uint8Array < Array\n    # This doesn't generate valid JSON, but it is suitable as an export from an\n    # ES6 module.\n    def to_json(*)\n      \"(new Uint8Array(#{super}))\"\n    end\n  end\n\n  def generate(rdoc_modules)\n    rdoc_objects = rdoc_modules +\n      rdoc_modules.flat_map(&:constants) +\n      rdoc_modules.flat_map(&:method_list) +\n      rdoc_modules.flat_map(&:attributes)\n\n    # RDoc duplicates member instances when modules are aliased by assigning to\n    # a constant. For example, `MyBar = Foo::Bar` will duplicate all of\n    # Foo::Bar's RDoc::Constant and RDoc::MethodAttr instances.\n    rdoc_objects.uniq!\n\n    ngram_sets = rdoc_objects.map { |rdoc_object| derive_ngrams(rdoc_object.full_name) }\n    ngram_bit_positions = compile_ngrams(ngram_sets)\n    bit_weights = compute_bit_weights(ngram_bit_positions)\n\n    entries = rdoc_objects.zip(ngram_sets).map do |rdoc_object, ngrams|\n      fingerprint = generate_fingerprint(ngrams, ngram_bit_positions)\n\n      case rdoc_object\n      when RDoc::ClassModule\n        build_entry(rdoc_object, fingerprint)\n      when RDoc::Constant\n        build_entry(rdoc_object, fingerprint, \"::#{rdoc_object.name}\")\n      when RDoc::MethodAttr\n        build_entry(rdoc_object, fingerprint, signature_for(rdoc_object))\n      end\n    end\n\n    { \"ngrams\" => ngram_bit_positions, \"weights\" => bit_weights, \"entries\" => entries }\n  end\n\n  def build_entry(rdoc_object, fingerprint, member_label = nil)\n    rdoc_module = member_label ? rdoc_object.parent : rdoc_object\n    description = rdoc_object.description\n\n    [\n      fingerprint,\n      compute_tiebreaker_bonus(rdoc_module.full_name, (rdoc_object.name if member_label), description),\n      rdoc_object.path,\n      rdoc_module.full_name,\n      member_label,\n      *truncate_description(description, 130),\n    ]\n  end\n\n  def derive_ngrams(name)\n    if name.match?(/:[^:A-Z]|#/)\n      # Example: \"ActiveModel::Name::new\" => [\"ActiveModel\", \"Name\", \":new\"]\n      # Example: \"ActiveModel::Name#<=>\" => [\"ActiveModel\", \"Name\", \"#<=>\"]\n      strings = name.split(/::(?=[A-Z])|:(?=:)|(?=#)/)\n\n      # Example: \":lookup_store\" => \".lookup_store(\"\n      strings.concat(strings.map { |string| string.sub(/^[:#](.+)/, '.\\1(') })\n    else\n      # Example: \"ActiveSupport::Cache::Store\" => [\":ActiveSupport\", \":Cache, \":Store\"]\n      strings = \":#{name}\".split(/:(?=:)/)\n    end\n\n    # Example: \":API\" => \":api\"\n    strings.concat(strings.map(&:downcase))\n    # Example: \":HashWithIndifferentAccess\" => \":HWIA\"\n    strings.concat(strings.map { |string| string.gsub(/([A-Z])[a-z]+/, '\\1') })\n    # Example: \"#find_by_sql\" => \"#findbysql\"\n    strings.concat(strings.map { |string| string.tr(\"_\", \"\") })\n    # Example: \"#action_name\" => \" action_name\"\n    strings.concat(strings.map { |string| string.tr(\":#\", \" \") })\n    # Example: \" action_name\" => \" a \"\n    strings.concat(strings.map { |string| string.sub(/^([:# ].).+/, '\\1 ') })\n\n    strings.flat_map { |string| string.each_char.each_cons(3).map(&:join) }.uniq\n  end\n\n  def compile_ngrams(ngram_sets)\n    # Assign each ngram a bit position based on its rarity. More common ngrams\n    # come first. This reduces the average number of bytes required to store a\n    # fingerprint.\n    ngram_sets.flatten.tally.sort_by(&:last).reverse.map(&:first).each_with_index.to_h\n  end\n\n  def generate_fingerprint(ngrams, ngram_bit_positions)\n    bit_positions = ngrams.map(&ngram_bit_positions)\n    byte_count = ((bit_positions.max + 1) / 8.0).ceil\n    bytes = [0] * byte_count\n\n    bit_positions.each do |position|\n      bytes[position / 8] |= 1 << (position % 8)\n    end\n\n    Uint8Array.new(bytes)\n  end\n\n  NGRAM_PATTERN_WEIGHTS = {\n    /[^a-z]/ => 2, # Bonus point for non-lowercase-alpha chars because they show intentionality.\n    /^ / => 3, # More points for matching generic start of token.\n    /^:/ => 4, # Even more points for explicit start of token.\n    /[#.(]/ => 50, # Strongly prefer methods when query includes \"#\", \".\", or \"(\".\n  }\n\n  def compute_bit_weights(ngram_bit_positions)\n    weights = ngram_bit_positions.uniq(&:last).sort_by(&:last).map do |ngram, _position|\n      NGRAM_PATTERN_WEIGHTS.map { |pattern, weight| ngram.match?(pattern) ? weight : 1 }.max\n    end\n\n    Uint8Array.new(weights)\n  end\n\n  def compute_tiebreaker_bonus(module_name, member_name, description)\n    # Give bonus in proportion to documentation length, but scale up extremely\n    # slowly. Bonus is per matching ngram so it must be small enough to not\n    # outweigh points from other matches.\n    bonus = (description.length + 1) ** 0.01 / 100\n    # Reduce bonus in proportion to name length. This favors short names over\n    # long names. Notably, this will often favor members over modules since\n    # member names are usually shorter than fully qualified module names.\n    bonus /= (member_name&.length || module_name.length) ** 0.1\n  end\n\n  def signature_for(rdoc_method)\n    sigil = rdoc_method.singleton ? \"::\" : \"#\"\n\n    params =\n      case rdoc_method.call_seq&.strip\n      when nil\n        rdoc_method.params\n      when /\\A[^ (]+(?: -> .+)?\\z/\n        \"()\"\n      when /\\A[^ (]+(\\(.*?\\))(?: -> .+)?\\z/\n        $1\n      else\n        \"(...)\"\n      end\n\n    \"#{sigil}#{rdoc_method.name}#{params}\"\n  end\n\n  def truncate_description(description, limit)\n    return if description.empty?\n    leading_paragraph = Nokogiri::HTML.fragment(description).at(SDoc::Helpers::LEADING_PARAGRAPH_XPATH)\n    return unless leading_paragraph\n\n    # Treat <code> elements as a whole when truncating\n    content = leading_paragraph.xpath(\".//text()\").map do |node|\n      node.parent.name == \"code\" ? \"_\" * node.content.length : node.content\n    end.join\n\n    if content.length > limit\n      # `+ 1 - 3` because we remove at least one character and replace it with \"...\".\n      remaining = content[0, limit + 1 - 3].sub(/(?:\\W+|\\W*\\w+)\\Z/, \"\").length\n\n      leading_paragraph.traverse do |node|\n        if remaining <= 0\n          node.remove if node.children.empty?\n        elsif node.text?\n          remaining -= node.content.length\n          node.content = node.content[0...remaining] if remaining < 0\n        end\n      end\n\n      leading_paragraph.add_child(\"...\")\n    elsif content.end_with?(\":\")\n      # Append ellipsis if paragraph refers to a subsequent block.\n      leading_paragraph.add_child(\"...\")\n    end\n\n    # Replace links with their inner HTML\n    leading_paragraph.css(\"a\").each { |a| a.replace(a.children) }\n\n    leading_paragraph.inner_html\n  end\nend\n"
  },
  {
    "path": "lib/sdoc/version.rb",
    "content": "module SDoc\n  VERSION = '3.0.0.alpha'\nend\n"
  },
  {
    "path": "lib/sdoc.rb",
    "content": "gem 'rdoc'\n\nmodule SDoc; end\n\nrequire 'sdoc/generator'\nrequire 'sdoc/name_list'"
  },
  {
    "path": "netlify.toml",
    "content": "[build]\n  command = \"bundle exec rake test:rails\"\n  publish = \"doc/public\"\n\n[[headers]]\n  for = \"*\"\n  [headers.values]\n    X-Frame-Options = \"DENY\"\n    X-XSS-Protection = \"1; mode=block\"\n    X-Content-Type-Options = \"nosniff\"\n    Content-Security-Policy = '''\n      object-src  'none';\n      worker-src  'none';\n      block-all-mixed-content;\n      upgrade-insecure-requests;'''\n    Strict-Transport-Security = \"max-age=15552000; includeSubDomains\"\n    Referrer-Policy = \"no-referrer-when-downgrade\"\n    Cache-Control = \"public, max-age=604800, s-max-age=604800\"\n\n[[headers]]\n  for = \"/\"\n\n[[headers]]\n  for = \"/*.(png|jpg|js|css|svg|woff|ttf|eot|ico|woff2)\"\n  [headers.values]\n    Cache-Control = \"public, max-age=31536000, s-max-age=31536000\"\n"
  },
  {
    "path": "sdoc.gemspec",
    "content": "# -*- encoding: utf-8 -*-\n$:.push File.expand_path(\"../lib\", __FILE__)\nrequire 'sdoc/version'\n\nGem::Specification.new do |s|\n  s.name = \"sdoc\"\n  s.version = SDoc::VERSION\n\n  s.authors = [\"Vladimir Kolesnikov\", \"Nathan Broadbent\", \"Jean Mertz\", \"Petrik de Heus\", \"zzak\"]\n  s.description = %q{rdoc generator html with javascript search index.}\n  s.summary = %q{rdoc html with javascript search index.}\n  s.homepage = %q{https://github.com/rails/sdoc}\n  s.email = %q{zzakscott@gmail.com petrik@deheus.net}\n  s.license = 'MIT'\n\n  s.require_path = 'lib'\n\n  s.required_ruby_version = \">= 2.7\"\n\n  s.rdoc_options = [\"--charset=UTF-8\"]\n  s.extra_rdoc_files = [\"README.md\"]\n\n  s.add_runtime_dependency \"rdoc\", \">= 5.0\"\n  s.add_runtime_dependency \"nokogiri\"\n  s.add_runtime_dependency \"rouge\"\n\n  s.files         = `git ls-files`.split(\"\\n\")\n  s.executables   = `git ls-files -- bin/*`.split(\"\\n\").map{ |f| File.basename(f) }\nend\n"
  },
  {
    "path": "spec/helpers_spec.rb",
    "content": "require \"spec_helper\"\n\ndescribe SDoc::Helpers do\n  before :each do\n    @helpers = Class.new do\n      include SDoc::Helpers\n\n      attr_accessor :options\n    end.new\n\n    @helpers._git.clear\n    @helpers.options = RDoc::Options.new\n  end\n\n  describe \"#git?\" do\n    it \"returns false when git is not installed\" do\n      with_env(\"PATH\" => \"\") do\n        _(@helpers.git?).must_equal false\n      end\n    end\n\n    it \"returns false when project is not a git repository\" do\n      Dir.mktmpdir do |dir|\n        @helpers.options.root = dir\n\n        _(@helpers.git?).must_equal false\n      end\n    end\n  end\n\n  describe \"#github_url\" do\n    before :each do\n      @helpers.options.github = true\n    end\n\n    it \"returns the URL for a given path in the project's GitHub repository at the current SHA1\" do\n      @helpers._git[:repo_path] = \"path/to/repo\"\n      @helpers._git[:origin_url] = \"git@github.com:user/repo.git\"\n      @helpers._git[:head_sha1] = \"1337c0d3\"\n\n      _(@helpers.github_url(\"foo/bar/qux.rb\")).\n        must_equal \"https://github.com/user/repo/blob/1337c0d3/foo/bar/qux.rb\"\n    end\n\n    it \"detects the GitHub repository name and current SHA1 (smoke test)\" do\n      _(@helpers.github_url(\"foo/bar/qux.rb\")).\n        must_match %r\"\\Ahttps://github.com/[^/]+/sdoc/blob/[0-9a-f]{40}/foo/bar/qux\\.rb\\z\"\n    end\n\n    it \"supports HTTPS remote URL\" do\n      @helpers._git[:origin_url] = \"https://github.com/user/repo.git\"\n\n      _(@helpers.github_url(\"foo/bar/qux.rb\")).\n        must_match %r\"\\Ahttps://github.com/user/repo/blob/[0-9a-f]{40}/foo/bar/qux\\.rb\\z\"\n    end\n\n    it \"supports HTTPS remote URL without .git extension\" do\n      @helpers._git[:origin_url] = \"https://github.com/user/repo\"\n\n      _(@helpers.github_url(\"foo/bar/qux.rb\")).\n        must_match %r\"\\Ahttps://github.com/user/repo/blob/[0-9a-f]{40}/foo/bar/qux\\.rb\\z\"\n    end\n\n    it \"returns nil when options.github is false\" do\n      @helpers.options.github = false\n\n      _(@helpers.github_url(\"foo/bar/qux.rb\")).must_be_nil\n    end\n\n    it \"returns nil when git is not installed or project is not a git repository\" do\n      @helpers._git[:repo_path] = \"\"\n\n      _(@helpers.github_url(\"foo/bar/qux.rb\")).must_be_nil\n    end\n\n    it \"returns nil when 'origin' remote is not present\" do\n      @helpers._git[:origin_url] = \"\"\n\n      _(@helpers.github_url(\"foo/bar/qux.rb\")).must_be_nil\n    end\n\n    it \"returns nil when 'origin' remote is not recognized\" do\n      @helpers._git[:origin_url] = \"git@gitlab.com:user/repo.git\"\n\n      _(@helpers.github_url(\"foo/bar/qux.rb\")).must_be_nil\n    end\n\n    it \"supports :line option\" do\n      _(@helpers.github_url(\"foo/bar/qux.rb\", line: 123)).\n        must_match %r\"\\Ahttps://github.com/.+/foo/bar/qux\\.rb#L123\\z\"\n    end\n  end\n\n  describe \"#link_to\" do\n    it \"returns a link tag\" do\n      _(@helpers.link_to(\"Foo::Bar::Qux\", \"foo/bar/qux.html\")).\n        must_equal %(<a href=\"foo/bar/qux.html\">Foo::Bar::Qux</a>)\n    end\n\n    it \"supports HTML attributes\" do\n      _(@helpers.link_to(\"foo\", \"bar\", class: \"qux\", \"data-hoge\": \"fuga\")).\n        must_equal %(<a href=\"bar\" class=\"qux\" data-hoge=\"fuga\">foo</a>)\n    end\n\n    it \"escapes the HTML attributes\" do\n      _(@helpers.link_to(\"Foo\", \"foo\", title: \"Foo < Object\")).\n        must_equal %(<a href=\"foo\" title=\"Foo &lt; Object\">Foo</a>)\n    end\n\n    it \"does not escape the link body\" do\n      _(@helpers.link_to(\"<code>Foo</code>\", \"foo\")).\n        must_equal %(<a href=\"foo\"><code>Foo</code></a>)\n    end\n\n    it \"uses the first argument as the URL when no URL is specified\" do\n      _(@helpers.link_to(\"foo/bar/qux.html\")).\n        must_equal %(<a href=\"foo/bar/qux.html\">foo/bar/qux.html</a>)\n\n      _(@helpers.link_to(\"foo/bar/qux.html\", \"data-hoge\": \"fuga\")).\n        must_equal %(<a href=\"foo/bar/qux.html\" data-hoge=\"fuga\">foo/bar/qux.html</a>)\n    end\n\n    it \"uses #full_name_for when the text argument is an RDoc::CodeObject\" do\n      top_level = rdoc_top_level_for <<~RUBY\n        module Foo; class Bar; def qux; end; end; end\n      RUBY\n\n      [\n        top_level,\n        top_level.find_module_named(\"Foo\"),\n        top_level.find_module_named(\"Foo::Bar\"),\n        top_level.find_module_named(\"Foo::Bar\").find_method(\"qux\", false),\n      ].each do |code_object|\n        _(@helpers.link_to(code_object, \"url\")).\n          must_equal %(<a href=\"url\">#{@helpers.full_name_for(code_object)}</a>)\n      end\n    end\n\n    it \"uses RDoc::CodeObject#path as the URL when URL argument is an RDoc::CodeObject\" do\n      top_level = rdoc_top_level_for <<~RUBY\n        module Foo; class Bar; def qux; end; end; end\n      RUBY\n\n      [\n        top_level,\n        top_level.find_module_named(\"Foo\"),\n        top_level.find_module_named(\"Foo::Bar\"),\n        top_level.find_module_named(\"Foo::Bar\").find_method(\"qux\", false),\n      ].each do |code_object|\n        _(@helpers.link_to(\"text\", code_object)).\n          must_equal %(<a href=\"/#{code_object.path}\">text</a>)\n      end\n    end\n\n    it \"uses .ref-link as the default class when creating a <code> link to an RDoc::CodeObject\" do\n      rdoc_module = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo::Bar\")\n        module Foo; module Bar; end\n      RUBY\n\n      _(@helpers.link_to(rdoc_module)).\n        must_equal %(<a href=\"/#{rdoc_module.path}\" class=\"ref-link\">#{@helpers.full_name_for(rdoc_module)}</a>)\n\n      _(@helpers.link_to(\"<code>Bar</code>\", rdoc_module)).\n        must_equal %(<a href=\"/#{rdoc_module.path}\" class=\"ref-link\"><code>Bar</code></a>)\n\n      _(@helpers.link_to(\"<code>Bar</code>\", rdoc_module, class: \"other\")).\n        must_equal %(<a href=\"/#{rdoc_module.path}\" class=\"other\"><code>Bar</code></a>)\n\n      _(@helpers.link_to(\"Jump to <code>Bar</code>\", rdoc_module)).\n        must_equal %(<a href=\"/#{rdoc_module.path}\">Jump to <code>Bar</code></a>)\n    end\n  end\n\n  describe \"#link_to_if\" do\n    it \"returns the link's HTML when the condition is true\" do\n      args = [\"<code>Foo</code>\", \"foo\", title: \"Foo < Object\"]\n      _(@helpers.link_to_if(true, *args)).must_equal @helpers.link_to(*args)\n    end\n\n    it \"returns the link's inner HTML when the condition is false\" do\n      _(@helpers.link_to_if(false, \"<code>Foo</code>\", \"url\")).must_equal \"<code>Foo</code>\"\n\n      rdoc_module = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo::Bar\")\n        module Foo; class Bar; end; end\n      RUBY\n\n      _(@helpers.link_to_if(false, rdoc_module, \"url\")).must_equal @helpers.full_name_for(rdoc_module)\n    end\n  end\n\n  describe \"#link_to_external\" do\n    it \"sets class='external-link' and target='_blank' by default\" do\n      _(@helpers.link_to_external(\"foo\", \"bar\")).\n        must_equal %(<a href=\"bar\" target=\"_blank\" class=\"external-link\">foo</a>)\n    end\n\n    it \"supports additional classes\" do\n      _(@helpers.link_to_external(\"foo\", \"bar\", class: \"qux\")).\n        must_equal %(<a href=\"bar\" target=\"_blank\" class=\"qux external-link\">foo</a>)\n    end\n\n    it \"supports overriding target\" do\n      _(@helpers.link_to_external(\"foo\", \"bar\", target: \"_self\")).\n        must_equal %(<a href=\"bar\" target=\"_self\" class=\"external-link\">foo</a>)\n    end\n\n    it \"supports additional attributes\" do\n      _(@helpers.link_to_external(\"foo\", \"bar\", \"data-hoge\": \"fuga\")).\n        must_equal %(<a href=\"bar\" target=\"_blank\" class=\"external-link\" data-hoge=\"fuga\">foo</a>)\n    end\n  end\n\n  describe \"#button_to_search\" do\n    it \"renders a button with the given query\" do\n      _(@helpers.button_to_search(\"Foo#<<\")).must_equal <<~HTML.chomp\n        <button class=\"query-button\" data-query=\"Foo#&lt;&lt; \">Search <code>Foo#&lt;&lt;</code></button>\n      HTML\n    end\n\n    it \"uses RDoc::CodeObject#full_name for the query when given an RDoc::CodeObject\" do\n      rdoc_method = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\").find_method(\"<<\", false)\n        module Foo; def <<(*); end; end\n      RUBY\n\n      _(@helpers.button_to_search(rdoc_method)).must_equal <<~HTML.chomp\n        <button class=\"query-button\" data-query=\"Foo#&lt;&lt; \">Search <code>Foo#&lt;&lt;</code></button>\n      HTML\n    end\n\n    it \"supports overriding the displayed name\" do\n      _(@helpers.button_to_search(\"Foo::Bar\", display_name: \"<i>Bar<i>\")).must_equal <<~HTML.chomp\n        <button class=\"query-button\" data-query=\"Foo::Bar \">Search <i>Bar<i></button>\n      HTML\n    end\n  end\n\n  describe \"#full_name_for\" do\n    it \"wraps name in <code>\" do\n      _(@helpers.full_name_for(\"Foo\")).must_equal \"<code>Foo</code>\"\n    end\n\n    it \"inserts word-break opportunities into module names\" do\n      _(@helpers.full_name_for(\"Foo::Bar::Qux\")).must_equal \"<code>Foo::<wbr>Bar::<wbr>Qux</code>\"\n      _(@helpers.full_name_for(\"::Foo::Bar::Qux\")).must_equal \"<code>::Foo::<wbr>Bar::<wbr>Qux</code>\"\n    end\n\n    it \"inserts word-break opportunities into file paths\" do\n      _(@helpers.full_name_for(\"path/to/file.rb\")).must_equal \"<code>path/<wbr>to/<wbr>file.rb</code>\"\n      _(@helpers.full_name_for(\"/path/to/file.rb\")).must_equal \"<code>/path/<wbr>to/<wbr>file.rb</code>\"\n    end\n\n    it \"escapes name parts\" do\n      _(@helpers.full_name_for(\"ruby&rails/file.rb\")).must_equal \"<code>ruby&amp;rails/<wbr>file.rb</code>\"\n    end\n\n    it \"uses RDoc::CodeObject#full_name when argument is an RDoc::CodeObject\" do\n      rdoc_module = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo::Bar::Qux\")\n        module Foo; module Bar; class Qux; end; end; end\n      RUBY\n\n      _(@helpers.full_name_for(rdoc_module)).must_equal \"<code>Foo::<wbr>Bar::<wbr>Qux</code>\"\n    end\n  end\n\n  describe \"#short_name_for\" do\n    it \"wraps name in <code>\" do\n      _(@helpers.short_name_for(\"foo\")).must_equal \"<code>foo</code>\"\n    end\n\n    it \"escapes the name\" do\n      _(@helpers.short_name_for(\"<=>\")).must_equal \"<code>&lt;=&gt;</code>\"\n    end\n\n    it \"uses RDoc::CodeObject#name when argument is an RDoc::CodeObject\" do\n      rdoc_method = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\").find_method(\"bar\", false)\n        module Foo; def bar; end; end\n      RUBY\n\n      _(@helpers.short_name_for(rdoc_method)).must_equal \"<code>bar</code>\"\n    end\n  end\n\n  describe \"#description_for\" do\n    it \"returns RDoc::CodeObject#description wrapped in div.description\" do\n      rdoc_module = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\")\n        # This is +Foo+.\n        module Foo; end\n      RUBY\n\n      _(@helpers.description_for(rdoc_module)).\n        must_equal %(<div class=\"description\">\\n<p>This is <code>Foo</code>.</p>\\n</div>)\n    end\n\n    it \"returns nil when RDoc::CodeObject#description is empty\" do\n      rdoc_module = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\")\n        module Foo; end\n      RUBY\n\n      _(@helpers.description_for(rdoc_module)).must_be_nil\n    end\n  end\n\n  describe \"#base_tag_for_context\" do\n    it \"returns a <base> tag with an appropriate path for the given RDoc::Context\" do\n      top_level = rdoc_top_level_for <<~RUBY\n        module Foo; module Bar; module Qux; end; end; end\n      RUBY\n\n      _(@helpers.base_tag_for_context(top_level.find_module_named(\"Foo\"))).\n        must_equal %(<base href=\"./../\" data-current-path=\"classes/Foo.html\">)\n\n      _(@helpers.base_tag_for_context(top_level.find_module_named(\"Foo::Bar\"))).\n        must_equal %(<base href=\"./../../\" data-current-path=\"classes/Foo/Bar.html\">)\n\n      _(@helpers.base_tag_for_context(top_level.find_module_named(\"Foo::Bar::Qux\"))).\n        must_equal %(<base href=\"./../../../\" data-current-path=\"classes/Foo/Bar/Qux.html\">)\n    end\n  end\n\n  describe \"#canonical_url\" do\n    it \"returns a URL based on ENV['HORO_CANONICAL_URL'] for an RDoc::Context\" do\n      context = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo::Bar::Qux\")\n        module Foo; module Bar; module Qux; end; end; end\n      RUBY\n\n      with_env(\"HORO_CANONICAL_URL\" => \"https://canonical\") do\n        _(@helpers.canonical_url(context)).must_equal \"https://canonical/classes/Foo/Bar/Qux.html\"\n      end\n    end\n\n    it \"returns a URL based on ENV['HORO_CANONICAL_URL'] for a path\" do\n      with_env(\"HORO_CANONICAL_URL\" => \"https://canonical\") do\n        _(@helpers.canonical_url(\"/path/to/foo\")).must_equal \"https://canonical/path/to/foo\"\n        _(@helpers.canonical_url(\"path/to/foo\")).must_equal \"https://canonical/path/to/foo\"\n      end\n    end\n\n    it \"returns a URL based on ENV['HORO_CANONICAL_URL'] for nil\" do\n      with_env(\"HORO_CANONICAL_URL\" => \"https://canonical\") do\n        _(@helpers.canonical_url(nil)).must_equal \"https://canonical/\"\n      end\n    end\n\n    it \"returns nil when ENV['HORO_CANONICAL_URL'] is not set\" do\n      with_env(\"HORO_CANONICAL_URL\" => nil) do\n        _(@helpers.canonical_url(nil)).must_be_nil\n      end\n    end\n  end\n\n  describe \"#project_name\" do\n    it \"returns escaped name from ENV['HORO_PROJECT_NAME']\" do\n      with_env(\"HORO_PROJECT_NAME\" => \"Ruby & Rails\") do\n        _(@helpers.project_name).must_equal \"Ruby &amp; Rails\"\n      end\n    end\n\n    it \"returns nil when ENV['HORO_PROJECT_NAME'] is not set\" do\n      with_env(\"HORO_PROJECT_NAME\" => nil) do\n        _(@helpers.project_name).must_be_nil\n      end\n    end\n  end\n\n  describe \"#project_version\" do\n    it \"returns escaped version from ENV['HORO_PROJECT_VERSION']\" do\n      with_env(\"HORO_PROJECT_VERSION\" => \"~> 1.0.0\") do\n        _(@helpers.project_version).must_equal \"~&gt; 1.0.0\"\n      end\n    end\n\n    it \"prioritizes ENV['HORO_BADGE_VERSION'] over ENV['HORO_PROJECT_VERSION']\" do\n      with_env(\"HORO_BADGE_VERSION\" => \"badge\", \"HORO_PROJECT_VERSION\" => \"project\") do\n        _(@helpers.project_version).must_equal \"badge\"\n      end\n    end\n\n    it \"returns nil when neither ENV['HORO_BADGE_VERSION'] nor ENV['HORO_PROJECT_VERSION'] are set\" do\n      with_env(\"HORO_BADGE_VERSION\" => nil, \"HORO_PROJECT_VERSION\" => nil) do\n        _(@helpers.project_version).must_be_nil\n      end\n    end\n  end\n\n  describe \"#project_git_head\" do\n    it \"returns the branch name and abbreviated SHA1 of the most recent commit in HEAD\" do\n      @helpers._git[:repo_path] = \"path/to/repo\"\n      @helpers._git[:head_branch] = \"1-0-stable\"\n      @helpers._git[:head_sha1] = \"1337c0d3d00d\" * 3\n\n      _(@helpers.project_git_head).must_equal \"1-0-stable@1337c0d3d00d\"\n    end\n\n    it \"returns the branch name and abbreviated SHA1 of the most recent commit in HEAD (smoke test)\" do\n      _(@helpers.project_git_head).must_match %r\"\\A.+@[[:xdigit:]]{12}\\z\"\n    end\n\n    it \"returns nil when git is not installed or project is not a git repository\" do\n      @helpers._git[:repo_path] = \"\"\n\n      _(@helpers.project_git_head).must_be_nil\n    end\n  end\n\n  describe \"#page_title\" do\n    it \"includes options.title\" do\n      @helpers.options.title = \"My Docs\"\n\n      _(@helpers.page_title).must_equal \"My Docs\"\n      _(@helpers.page_title(\"Foo\")).must_equal \"Foo - My Docs\"\n    end\n\n    it \"escapes the title\" do\n      @helpers.options.title = \"Docs & Stuff\"\n\n      _(@helpers.page_title(\"Foo<Bar>\")).must_equal \"Foo&lt;Bar&gt; - Docs &amp; Stuff\"\n    end\n  end\n\n  describe \"#og_title\" do\n    it \"includes ENV['HORO_PROJECT_NAME'] and ENV['HORO_PROJECT_VERSION']\" do\n      with_env(\"HORO_PROJECT_NAME\" => \"My Gem\", \"HORO_PROJECT_VERSION\" => \"v2.0\") do\n        _(@helpers.og_title(\"Foo\")).must_equal \"Foo (My Gem v2.0)\"\n      end\n\n      with_env(\"HORO_PROJECT_NAME\" => \"My Gem\", \"HORO_PROJECT_VERSION\" => nil) do\n        _(@helpers.og_title(\"Foo\")).must_equal \"Foo (My Gem)\"\n      end\n\n      with_env(\"HORO_PROJECT_NAME\" => nil, \"HORO_PROJECT_VERSION\" => \"v2.0\") do\n        _(@helpers.og_title(\"Foo\")).must_equal \"Foo (v2.0)\"\n      end\n\n      with_env(\"HORO_PROJECT_NAME\" => nil, \"HORO_PROJECT_VERSION\" => nil) do\n        _(@helpers.og_title(\"Foo\")).must_equal \"Foo\"\n      end\n    end\n\n    it \"escapes the title\" do\n      with_env(\"HORO_PROJECT_NAME\" => \"Ruby & Rails\", \"HORO_PROJECT_VERSION\" => \"~> 1.0.0\") do\n        _(@helpers.og_title(\"Foo<Bar>\")).must_equal \"Foo&lt;Bar&gt; (Ruby &amp; Rails ~&gt; 1.0.0)\"\n      end\n    end\n  end\n\n  describe \"#og_modified_time\" do\n    it \"returns the commit time of the most recent commit in HEAD\" do\n      @helpers._git[:repo_path] = \"path/to/repo\"\n      @helpers._git[:head_timestamp] = \"1999-12-31T12:34:56Z\"\n\n      _(@helpers.og_modified_time).must_equal \"1999-12-31T12:34:56Z\"\n    end\n\n    it \"returns the commit time of the most recent commit in HEAD (smoke test)\" do\n      _(@helpers.og_modified_time).\n        must_match %r\"\\A\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:Z|[-+]\\d{2}:\\d{2})\\z\"\n    end\n\n    it \"returns nil when git is not installed or project is not a git repository\" do\n      @helpers._git[:repo_path] = \"\"\n\n      _(@helpers.og_modified_time).must_be_nil\n    end\n  end\n\n  describe \"#page_description\" do\n    it \"extracts the description from the leading paragraph\" do\n      _(@helpers.page_description(<<~HTML)).must_equal \"leading\"\n        <p>leading</p>\n        <p>other</p>\n      HTML\n\n      _(@helpers.page_description(<<~HTML)).must_equal \"paragraph\"\n        <h1>headline</h1>\n        <p>paragraph</p>\n      HTML\n\n      _(@helpers.page_description(<<~HTML)).must_equal \"paragraph\"\n        <h1>1</h1><h2>2</h2><h3>3</h3><h4>4</h4><h5>5</h5><h6>6</h6>\n        <p>paragraph</p>\n      HTML\n    end\n\n    it \"returns nil when there is no leading paragraph\" do\n      _(@helpers.page_description(<<~HTML)).must_be_nil\n        <pre><code>code</code></pre>\n        <p>other</p>\n      HTML\n\n      _(@helpers.page_description(<<~HTML)).must_be_nil\n        <ul><li><p>item</p></li></ul>\n        <p>other</p>\n      HTML\n\n      _(@helpers.page_description(\"\")).must_be_nil\n      _(@helpers.page_description(nil)).must_be_nil\n    end\n\n    it \"strips HTML tags\" do\n      _(@helpers.page_description(<<~HTML)).must_equal \"emphatic text\"\n        <p><em>emphatic</em> text</p>\n      HTML\n    end\n\n    it \"escapes the text\" do\n      _(@helpers.page_description(<<~HTML)).must_equal \"x &lt; y\"\n        <p>x &lt; y</p>\n      HTML\n    end\n\n    it \"truncates at word boundaries\" do\n      leading_html = \"<p>12345 78. 12, 5 - 9.</p>\"\n\n      {\n         8..10 => \"12345...\",\n        11..14 => \"12345 78...\",\n        15..17 => \"12345 78. 12...\",\n        18..19 => \"12345 78. 12, 5...\",\n        20..25 => \"12345 78. 12, 5 - 9.\",\n      }.each do |range, expected|\n        range.each do |max_length|\n          _(@helpers.page_description(leading_html, max_length: max_length)).must_equal expected\n        end\n      end\n    end\n\n    it \"truncates to 160 characters by default\" do\n      _(@helpers.page_description(\"<p>#{\"x\" * 150}123 567 9.</p>\")).\n        must_equal \"#{\"x\" * 150}123 567 9.\"\n\n      _(@helpers.page_description(\"<p>#{\"x\" * 150}123 567 xxx.</p>\")).\n        must_equal \"#{\"x\" * 150}123 567...\"\n    end\n  end\n\n  describe \"#outline\" do\n    def expected(html, context:)\n      html.gsub(/\\s/, \"\").gsub(/<li>([^<]+)/, '<li><a href=\"#' + context.aref + '-label-\\1\">\\1</a>')\n    end\n\n    it \"renders a nested list of heading links\" do\n      context = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\")\n        # == L2-1\n        # == L2-2\n        # === L3-1\n        # ==== L4-1\n        # ===== L5-1\n        # ====== L6-1\n        # == L2-3\n        module Foo; end\n      RUBY\n\n      _(@helpers.outline(context)).must_equal expected(<<~HTML, context: context)\n        <ul>\n          <li>L2-1</li>\n          <li>L2-2 <ul>\n            <li>L3-1 <ul>\n              <li>L4-1 <ul>\n                <li>L5-1 <ul>\n                  <li>L6-1</li>\n                </ul></li>\n              </ul></li>\n            </ul></li>\n          </ul></li>\n          <li>L2-3</li>\n        </ul>\n      HTML\n    end\n\n    it \"handles skipped heading levels\" do\n      context = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\")\n        # === L3-1\n        # ===== L5-1\n        # == L2-1\n        # ==== L4-1\n        module Foo; end\n      RUBY\n\n      _(@helpers.outline(context)).must_equal expected(<<~HTML, context: context)\n        <ul>\n          <li>L3-1 <ul>\n            <li>L5-1</li>\n          </ul></li>\n          <li>L2-1 <ul>\n            <li>L4-1</li>\n          </ul></li>\n        </ul>\n      HTML\n    end\n\n    it \"omits the h1 heading when it is primary\" do\n      context = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\")\n        # = L1-1\n        # == L2-1\n        # === L3-1\n        # == L2-2\n        module Foo; end\n      RUBY\n\n      _(@helpers.outline(context)).must_equal expected(<<~HTML, context: context)\n        <ul>\n          <li>L2-1 <ul>\n            <li>L3-1</li>\n          </ul></li>\n          <li>L2-2</li>\n        </ul>\n      HTML\n    end\n\n    it \"preserves all h1 headings when any are non-primary\" do\n      context = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\")\n        # = L1-1\n        # == L2-1\n        # = L1-2\n        module Foo; end\n      RUBY\n\n      _(@helpers.outline(context)).must_equal expected(<<~HTML, context: context)\n        <ul>\n          <li>L1-1 <ul>\n            <li>L2-1</li>\n          </ul></li>\n          <li>L1-2</li>\n        </ul>\n      HTML\n    end\n  end\n\n  describe \"#more_less_ul\" do\n    def ul(items)\n      [\"<ul>\", *items.map { |item| \"<li>#{item}</li>\" }, \"</ul>\"].join\n    end\n\n    it \"returns a single list when the number of items is <= hard limit\" do\n      _(@helpers.more_less_ul(1..7, 7)).must_equal ul(1..7)\n      _(@helpers.more_less_ul(1..7, 8)).must_equal ul(1..7)\n\n      _(@helpers.more_less_ul(1..7, 6..7)).must_equal ul(1..7)\n      _(@helpers.more_less_ul(1..7, 6..8)).must_equal ul(1..7)\n\n      _(@helpers.more_less_ul(1..7, 7..9)).must_equal ul(1..7)\n      _(@helpers.more_less_ul(1..7, 8..9)).must_equal ul(1..7)\n    end\n\n    it \"returns split lists when the number of items is > hard limit\" do\n      _(@helpers.more_less_ul(1..7, 6)).must_match %r\"#{ul 1..6}.*<details.+#{ul 7..7}.*</details>\"m\n      _(@helpers.more_less_ul(1..7, 5)).must_match %r\"#{ul 1..5}.*<details.+#{ul 6..7}.*</details>\"m\n\n      _(@helpers.more_less_ul(1..7, 5..6)).must_match %r\"#{ul 1..5}.*<details.+#{ul 6..7}.*</details>\"m\n      _(@helpers.more_less_ul(1..7, 4..6)).must_match %r\"#{ul 1..4}.*<details.+#{ul 5..7}.*</details>\"m\n    end\n\n    it \"specifies the number of hidden items\" do\n      _(@helpers.more_less_ul(1..7, 4)).must_match %r\"\\b3 More\\b\"\n    end\n\n    it \"does not escape items\" do\n      _(@helpers.more_less_ul([\"<a>link</a>\"], 1)).must_include \"<a>link</a>\"\n    end\n  end\n\n  describe \"#top_modules\" do\n    it \"returns top-level classes and modules in sorted order\" do\n      top_level = rdoc_top_level_for <<~RUBY\n        class Foo; module Hoge; end; end\n        module Bar; class Fuga; end; end\n      RUBY\n\n      _(@helpers.top_modules(top_level.store)).\n        must_equal [top_level.find_module_named(\"Bar\"), top_level.find_module_named(\"Foo\")]\n    end\n\n    it \"handles flattened class and module declarations\" do\n      top_level = rdoc_top_level_for <<~RUBY\n        class Foo::Hoge; end\n        module Bar::Fuga; end\n      RUBY\n\n      _(@helpers.top_modules(top_level.store)).\n        must_equal [top_level.find_module_named(\"Bar\"), top_level.find_module_named(\"Foo\")]\n    end\n\n    it \"excludes core extensions (based on options.core_ext_pattern)\" do\n      top_level = rdoc_top_level_for <<~RUBY\n        module Foo; end\n      RUBY\n\n      @helpers.options.core_ext_pattern = /#{Regexp.escape top_level.name}/\n\n      _(@helpers.top_modules(top_level.store)).must_be_empty\n    end\n  end\n\n  describe \"#core_extensions\" do\n    it \"returns top-level core extensions in sorted order (based on options.core_ext_pattern)\" do\n      top_level = rdoc_top_level_for <<~RUBY\n        class Foo; module Hoge; end; end\n        module Bar; class Fuga; end; end\n      RUBY\n\n      _(@helpers.core_extensions(top_level.store)).must_be_empty\n\n      @helpers.options.core_ext_pattern = /#{Regexp.escape top_level.name}/\n\n      _(@helpers.core_extensions(top_level.store)).\n        must_equal [top_level.find_module_named(\"Bar\"), top_level.find_module_named(\"Foo\")]\n    end\n  end\n\n  describe \"#module_breadcrumbs\" do\n    it \"renders links for each of the module's parents\" do\n      top_level = rdoc_top_level_for <<~RUBY\n        module Foo; module Bar; module Qux; end; end; end\n      RUBY\n\n      foo = top_level.find_module_named(\"Foo\")\n      bar = top_level.find_module_named(\"Foo::Bar\")\n      qux = top_level.find_module_named(\"Foo::Bar::Qux\")\n\n      _(@helpers.module_breadcrumbs(foo)).\n        must_equal \"<code>Foo</code>\"\n\n      _(@helpers.module_breadcrumbs(bar)).\n        must_equal \"<code>#{@helpers.link_to \"Foo\", foo}::<wbr>Bar</code>\"\n\n      _(@helpers.module_breadcrumbs(qux)).\n        must_equal \"<code>#{@helpers.link_to \"Foo\", foo}::<wbr>#{@helpers.link_to \"Bar\", bar}::<wbr>Qux</code>\"\n    end\n\n    it \"handles flattened class declarations\" do\n      top_level = rdoc_top_level_for <<~RUBY\n        class Foo::Bar::Qux; end\n      RUBY\n\n      foo = top_level.find_module_named(\"Foo\")\n      bar = top_level.find_module_named(\"Foo::Bar\")\n      qux = top_level.find_module_named(\"Foo::Bar::Qux\")\n\n      _(@helpers.module_breadcrumbs(qux)).\n        must_equal \"<code>#{@helpers.link_to \"Foo\", foo}::<wbr>#{@helpers.link_to \"Bar\", bar}::<wbr>Qux</code>\"\n    end\n  end\n\n  describe \"#module_ancestors\" do\n    it \"returns a list with the base class (if applicable) and included modules\" do\n      # RDoc chokes on \";\" when parsing includes, so replace with \"\\n\".\n      top_level = rdoc_top_level_for <<~RUBY.gsub(\";\", \"\\n\")\n        module M1; end\n        module M2; end\n        class C1; end\n\n        module Foo; include M1; include M2; end\n        class Bar < C1; include M2; include M1; end\n        class Qux < Cx; include Foo; include Mx; end\n      RUBY\n\n      m1, m2, c1, foo, bar, qux = %w[M1 M2 C1 Foo Bar Qux].map { |name| top_level.find_module_named(name) }\n\n      _(@helpers.module_ancestors(foo)).must_equal [[\"module\", m1], [\"module\", m2]]\n      _(@helpers.module_ancestors(bar)).must_equal [[\"class\", c1], [\"module\", m2], [\"module\", m1]]\n      _(@helpers.module_ancestors(qux)).must_equal [[\"class\", \"Cx\"], [\"module\", foo], [\"module\", \"Mx\"]]\n    end\n\n    it \"excludes the default base class (Object) from the result\" do\n      # RDoc chokes on \";\" when parsing includes, so replace with \"\\n\".\n      top_level = rdoc_top_level_for <<~RUBY.gsub(\";\", \"\\n\")\n        class Object; end\n        class Foo; include M1; end\n      RUBY\n\n      _(@helpers.module_ancestors(top_level.find_module_named(\"Object\"))).must_equal [[\"class\", \"BasicObject\"]]\n      _(@helpers.module_ancestors(top_level.find_module_named(\"Foo\"))).must_equal [[\"module\", \"M1\"]]\n\n      top_level = rdoc_top_level_for <<~RUBY.gsub(\";\", \"\\n\")\n        class Foo; include M1; end\n      RUBY\n\n      _(@helpers.module_ancestors(top_level.find_module_named(\"Foo\"))).must_equal [[\"module\", \"M1\"]]\n    end\n  end\n\n  describe \"#module_methods\" do\n    it \"returns all methods of a given module, sorted by definition scope and name\" do\n      rdoc_module = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\")\n        module Foo\n          def foo; end\n          class << self\n            private def foo; end\n          end\n          private def bar; end\n          def self.bar; end\n        end\n      RUBY\n\n      _(@helpers.module_methods(rdoc_module)).must_equal [\n        rdoc_module.find_method(\"bar\", true),\n        rdoc_module.find_method(\"foo\", true),\n        rdoc_module.find_method(\"bar\", false),\n        rdoc_module.find_method(\"foo\", false),\n      ]\n    end\n  end\n\n  describe \"#method_signature\" do\n    it \"returns the method signature wrapped in <code>\" do\n      method = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\").find_method(\"bar\", false)\n        module Foo; def bar(qux); end; end\n      RUBY\n\n      _(@helpers.method_signature(method)).must_equal \"<code><b>bar</b>(qux)</code>\"\n    end\n\n    it \"escapes the method signature\" do\n      method = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\").find_method(\"bar\", false)\n        module Foo; def bar(op = :<, &block); end; end\n      RUBY\n\n      _(@helpers.method_signature(method)).must_equal \"<code><b>bar</b>(op = :&lt;, &amp;block)</code>\"\n    end\n\n    it \"handles :call-seq: documentation\" do\n      mod = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\")\n        module Foo\n          # :call-seq:\n          #   bar(op = :<)\n          #   bar(&block)\n          def bar(*args, &block); end\n\n          # :call-seq:\n          #   qux(&block) -> self\n          #   qux -> Enumerator\n          def qux(&block); end\n        end\n      RUBY\n\n      _(@helpers.method_signature(mod.find_method(\"bar\", false))).must_equal <<~HTML.chomp\n        <code><b>bar</b>(op = :&lt;)\n        <b>bar</b>(&amp;block)</code>\n      HTML\n\n      _(@helpers.method_signature(mod.find_method(\"qux\", false))).must_equal <<~HTML.chomp\n        <code><b>qux</b>(&amp;block) <span class=\"returns\">&rarr;</span> self\n        <b>qux</b> <span class=\"returns\">&rarr;</span> Enumerator</code>\n      HTML\n    end\n  end\n\n  describe \"#method_source_code_and_url\" do\n    before :each do\n      @helpers.options.github = true\n    end\n\n    it \"returns source code and GitHub URL for a given RDoc::AnyMethod\" do\n      method = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\").find_method(\"bar\", false)\n        module Foo # line 1\n          def bar # line 2\n            # line 3\n          end\n        end\n      RUBY\n\n      source_code, source_url = @helpers.method_source_code_and_url(method)\n\n      _(source_code).must_match %r\"# File .+\\.rb, line 2\\b\"\n      _(source_code).must_include \"line 3\"\n      _(source_url).must_match %r\"\\Ahttps://github.com/.+\\.rb#L2\\z\"\n    end\n\n    it \"returns nil source code when given method is an RDoc::GhostMethod\" do\n      method = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\").find_method(\"bar\", false)\n        module Foo # line 1\n          ##\n          # :method: bar\n        end\n      RUBY\n\n      source_code, source_url = @helpers.method_source_code_and_url(method)\n\n      _(source_code).must_be_nil\n      _(source_url).must_match %r\"\\Ahttps://github.com/.+\\.rb#L3\\z\"\n    end\n\n    it \"returns nil source code when given method is an alias\" do\n      method = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\").find_method(\"bar\", false)\n        module Foo # line 1\n          def qux; end\n          alias bar qux\n        end\n      RUBY\n\n      source_code, _source_url = @helpers.method_source_code_and_url(method)\n\n      _(source_code).must_be_nil\n      # Unfortunately, _source_url is also nil because RDoc does not provide the\n      # source code location in this case.\n    end\n\n    it \"returns nil GitHub URL when options.github is false\" do\n      @helpers.options.github = false\n\n      method = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\").find_method(\"bar\", false)\n        module Foo # line 1\n          def bar; end # line 2\n        end\n      RUBY\n\n      source_code, source_url = @helpers.method_source_code_and_url(method)\n\n      _(source_code).must_match %r\"# File .+\\.rb, line 2\\b\"\n      _(source_url).must_be_nil\n    end\n  end\nend\n"
  },
  {
    "path": "spec/postprocessor_spec.rb",
    "content": "require \"spec_helper\"\n\ndescribe SDoc::Postprocessor do\n  describe \"#process\" do\n    it \"rebases URLs\" do\n      rendered = <<~HTML\n        <base href=\"../../\" data-current-path=\"foo/bar/current.html\">\n\n        <link href=\"/stylesheet.css\" rel=\"stylesheet\">\n        <script src=\"/javascript.js\"></script>\n\n        <a href=\"#section\">Link</a>\n        <img src=\"image.png\">\n      HTML\n\n      expected_head = <<~HTML\n        <link href=\"stylesheet.css\" rel=\"stylesheet\">\n        <script src=\"javascript.js\"></script>\n      HTML\n\n      expected_body = <<~HTML\n        <a href=\"foo/bar/current.html#section\">Link</a>\n        <img src=\"foo/bar/image.png\">\n      HTML\n\n      postprocessed = SDoc::Postprocessor.process(rendered)\n\n      _(postprocessed).must_include expected_head\n      _(postprocessed).must_include expected_body\n    end\n\n    it \"versions Rails API Docs URLs based on ENV['HORO_PROJECT_VERSION']\" do\n      rendered = <<~HTML\n        <a href=\"https://api.rubyonrails.org/classes/ActiveRecord/Base.html\">Learn more</a>\n      HTML\n\n      {\n        \"3.2.1\" => %(<a href=\"https://api.rubyonrails.org/v3.2.1/classes/ActiveRecord/Base.html\">Learn more</a>),\n        \"v3.2.1\" => %(<a href=\"https://api.rubyonrails.org/v3.2.1/classes/ActiveRecord/Base.html\">Learn more</a>),\n        \"main@1337c0d3\" => %(<a href=\"https://edgeapi.rubyonrails.org/classes/ActiveRecord/Base.html\">Learn more</a>),\n        \"edge\" => %(<a href=\"https://edgeapi.rubyonrails.org/classes/ActiveRecord/Base.html\">Learn more</a>),\n        nil => %(<a href=\"https://api.rubyonrails.org/classes/ActiveRecord/Base.html\">Learn more</a>),\n      }.each do |version, expected|\n        with_env(\"HORO_PROJECT_VERSION\" => version, \"HORO_PROJECT_NAME\" => \"Ruby on Rails\") do\n          _(SDoc::Postprocessor.process(rendered)).must_include expected\n        end\n      end\n    end\n\n    it \"versions Rails guides URLs based on ENV['HORO_PROJECT_VERSION']\" do\n      rendered = <<~HTML\n        <a href=\"https://guides.rubyonrails.org/testing.html\">Testing</a>\n      HTML\n\n      {\n        \"3.2.1\" => %(<a href=\"https://guides.rubyonrails.org/v3.2.1/testing.html\">Testing</a>),\n        \"v3.2.1\" => %(<a href=\"https://guides.rubyonrails.org/v3.2.1/testing.html\">Testing</a>),\n        \"main@1337c0d3\" => %(<a href=\"https://edgeguides.rubyonrails.org/testing.html\">Testing</a>),\n        \"edge\" => %(<a href=\"https://edgeguides.rubyonrails.org/testing.html\">Testing</a>),\n        nil => %(<a href=\"https://guides.rubyonrails.org/testing.html\">Testing</a>),\n      }.each do |version, expected|\n        with_env(\"HORO_PROJECT_VERSION\" => version, \"HORO_PROJECT_NAME\" => \"Ruby on Rails\") do\n          _(SDoc::Postprocessor.process(rendered)).must_include expected\n        end\n      end\n    end\n\n    it \"does not version rubyonrails.org URLs when ENV['HORO_PROJECT_NAME'] is not Ruby on Rails\" do\n      rendered = <<~HTML\n        <a href=\"https://api.rubyonrails.org/classes/ActiveRecord/Base.html\">Learn more</a>\n        <a href=\"https://guides.rubyonrails.org/testing.html\">Testing</a>\n      HTML\n\n      with_env(\"HORO_PROJECT_VERSION\" => \"3.2.1\", \"HORO_PROJECT_NAME\" => \"My Rails Gem\") do\n        postprocessed = SDoc::Postprocessor.process(rendered)\n        _(postprocessed).must_include %(<a href=\"https://api.rubyonrails.org/classes/ActiveRecord/Base.html\">Learn more</a>)\n        _(postprocessed).must_include %(<a href=\"https://guides.rubyonrails.org/testing.html\">Testing</a>)\n      end\n    end\n\n    it \"adds .ref-link class to code ref links\" do\n      rendered = <<~HTML\n        <div class=\"description\">\n          <a href=\"classes/Foo.html\"><code>Foo</code></a>\n          <a href=\"classes/Foo.html\">not ref link</a>\n          <a href=\"classes/Foo.html\">not <code>ref</code> link</a>\n        </div>\n      HTML\n\n      expected = <<~HTML\n        <div class=\"description\">\n          <a href=\"classes/Foo.html\" class=\"ref-link\"><code>Foo</code></a>\n          <a href=\"classes/Foo.html\">not ref link</a>\n          <a href=\"classes/Foo.html\">not <code>ref</code> link</a>\n        </div>\n      HTML\n\n      _(SDoc::Postprocessor.process(rendered)).must_include expected\n    end\n\n    it \"unifies <h1> headings for a context\" do\n      rendered = <<~HTML\n        <div id=\"content\">\n          <hgroup><h1>module Foo</h1></hgroup>\n\n          <div id=\"context\">\n            <div class=\"description\"><h1>The Foo</h1><p>Lorem ipsum.</p></div>\n          </div>\n        </div>\n      HTML\n\n      expected = <<~HTML\n        <div id=\"content\">\n          <hgroup><h1>module Foo</h1><p>The Foo</p></hgroup>\n\n          <div id=\"context\">\n            <div class=\"description\"><p>Lorem ipsum.</p></div>\n          </div>\n        </div>\n      HTML\n\n      _(SDoc::Postprocessor.process(rendered)).must_include expected\n    end\n\n    it \"does not relocate non-leading <h1> headings\" do\n      rendered = <<~HTML\n        <div id=\"content\">\n          <hgroup><h1>module Foo</h1></hgroup>\n\n          <div id=\"context\">\n            <div class=\"description\"><p>Lorem ipsum.</p><h1>Red Herring</h1></div>\n            <div class=\"method\">\n              <div class=\"description\"><h1>Red Herring</h1></div>\n            </div>\n          </div>\n        </div>\n      HTML\n\n      _(SDoc::Postprocessor.process(rendered)).must_include rendered\n    end\n\n    it \"does not relocate <h1> headings when <hgroup> is not present\" do\n      rendered = <<~HTML\n        <div id=\"content\">\n          <div id=\"context\">\n            <div class=\"description\"><h1>Main Page</h1></div>\n          </div>\n        </div>\n      HTML\n\n      _(SDoc::Postprocessor.process(rendered)).must_include rendered\n    end\n\n    it \"highlights description code blocks\" do\n      rendered = <<~HTML\n        <div class=\"description\">\n          <p>Ruby:</p>\n          <pre><code>1 + 1</code></pre>\n          <p>ERB:</p>\n          <pre><code>&lt;%= 1 + 1 %&gt;</code></pre>\n        </div>\n      HTML\n\n      expected = <<~HTML\n        <div class=\"description\">\n          <p>Ruby:</p>\n          <pre><code class=\"highlight ruby\">#{SDoc::Postprocessor.highlight_code(\"1 + 1\", \"ruby\")}</code></pre>\n          <p>ERB:</p>\n          <pre><code class=\"highlight erb\">#{SDoc::Postprocessor.highlight_code(\"<%= 1 + 1 %>\", \"erb\")}</code></pre>\n        </div>\n      HTML\n\n      _(SDoc::Postprocessor.process(rendered)).must_include expected\n    end\n\n    it \"highlights source code blocks\" do\n      rendered = <<~HTML\n        <pre class=\"source-code\"><code class=\"ruby\"><span class=\"ruby-comment\"># highlighted by RDoc</span></code></pre>\n        <pre class=\"source-code\"><code class=\"ruby\">DELETE FROM 'tricky_ruby'</code></pre>\n      HTML\n\n      expected = <<~HTML\n        <pre class=\"source-code\"><code class=\"ruby highlight\">#{SDoc::Postprocessor.highlight_code(\"# highlighted by RDoc\", \"ruby\")}</code></pre>\n        <pre class=\"source-code\"><code class=\"ruby highlight\">#{SDoc::Postprocessor.highlight_code(\"DELETE FROM 'tricky_ruby'\", \"ruby\")}</code></pre>\n      HTML\n\n      _(SDoc::Postprocessor.process(rendered)).must_include expected\n    end\n  end\n\n  describe \"#rebase_url\" do\n    it \"does not change full URLs\" do\n      [\n        \"//example.com/hoge/fuga\",\n        \"https://example.com/hoge/fuga\",\n        \"http://example.com/hoge/fuga\",\n        \"javascript:alert('hoge')\",\n        \"data:,hoge\",\n      ].each do |url|\n        _(SDoc::Postprocessor.rebase_url(url, \"foo/bar/qux.html\")).must_equal url\n      end\n    end\n\n    it \"changes absolute paths to relative (to the expected <base> element)\" do\n      _(SDoc::Postprocessor.rebase_url(\"/hoge/fuga.html\", \"foo/bar/qux.html\")).\n        must_equal \"hoge/fuga.html\"\n    end\n\n    it \"expands relative paths\" do\n      _(SDoc::Postprocessor.rebase_url(\"hoge/fuga.html\", \"foo/bar/qux.html\")).\n        must_equal \"foo/bar/hoge/fuga.html\"\n\n      _(SDoc::Postprocessor.rebase_url(\"../hoge/fuga.html\", \"foo/bar/qux.html\")).\n        must_equal \"foo/hoge/fuga.html\"\n    end\n\n    it \"expands fragments\" do\n      _(SDoc::Postprocessor.rebase_url(\"#hoge\", \"foo/bar/qux.html\")).\n        must_equal \"foo/bar/qux.html#hoge\"\n    end\n  end\n\n  describe \"#version_url\" do\n    it \"prepends the version number to the URL's path\" do\n      _(SDoc::Postprocessor.version_url(\"https://example.com/foo/bar.html\", \"3.2.1\")).\n        must_equal \"https://example.com/v3.2.1/foo/bar.html\"\n    end\n\n    it \"does not prepend the version number when the URL is already versioned\" do\n      [\n        \"https://example.com/v1/foo/bar.html\",\n        \"https://example.com/v1.2/foo/bar.html\",\n        \"https://example.com/v1.2.3/foo/bar.html\",\n        \"https://example.com/v1.2.3.4/foo/bar.html\",\n      ].each do |url|\n        _(SDoc::Postprocessor.version_url(url, \"3.2.1\")).must_equal url\n      end\n    end\n\n    it \"prepends 'edge' to the URL's host when the version is not a number\" do\n      _(SDoc::Postprocessor.version_url(\"https://api.example.com/foo/bar.html\", \"main@1337c0d3\")).\n        must_equal \"https://edgeapi.example.com/foo/bar.html\"\n    end\n  end\n\n  describe \"#highlight_code\" do\n    it \"returns highlighted HTML\" do\n      _(SDoc::Postprocessor.highlight_code(\"1 + 1\", \"ruby\")).\n        must_equal %{<span class=\"mi\">1</span> <span class=\"o\">+</span> <span class=\"mi\">1</span>}\n\n      _(SDoc::Postprocessor.highlight_code(\"$ rails s\", \"console\")).\n        must_equal %{<span class=\"gp\">$</span><span class=\"w\"> </span>rails s}\n    end\n  end\n\n  describe \"#guess_code_language\" do\n    it \"guesses console for CLI session\" do\n      _(SDoc::Postprocessor.guess_code_language(<<~CLI)).must_equal \"console\"\n        $ rails server\n        Booting\n      CLI\n    end\n\n    it \"guesses plaintext for ASCII-art table\" do\n      _(SDoc::Postprocessor.guess_code_language(<<~TABLE)).must_equal \"plaintext\"\n        a | b\n        --+--\n        1 | 2\n      TABLE\n\n      _(SDoc::Postprocessor.guess_code_language(<<~TABLE)).must_equal \"plaintext\"\n        a | b\n        --|--\n        1 | 2\n      TABLE\n    end\n\n    it \"guesses plaintext for routes listing\" do\n      _(SDoc::Postprocessor.guess_code_language(<<~ROUTES)).must_equal \"plaintext\"\n        post GET    /posts/:id(.:format)      posts#show\n             DELETE /posts/:id(.:format)      posts#destroy\n      ROUTES\n\n      _(SDoc::Postprocessor.guess_code_language(<<~ROUTES)).must_equal \"plaintext\"\n        DELETE /posts/:id\n      ROUTES\n    end\n\n    it \"guesses sql for SQL query\" do\n      _(SDoc::Postprocessor.guess_code_language(<<~SQL)).must_equal \"sql\"\n        SELECT * FROM posts\n      SQL\n\n      _(SDoc::Postprocessor.guess_code_language(<<~SQL)).must_equal \"sql\"\n        DELETE FROM posts WHERE id = 1\n      SQL\n    end\n\n    it \"guesses email for email message\" do\n      _(SDoc::Postprocessor.guess_code_language(<<~EMAIL)).must_equal \"email\"\n        To: recipient@example.com\n      EMAIL\n\n      _(SDoc::Postprocessor.guess_code_language(<<~EMAIL)).must_equal \"email\"\n        Cc: recipient@example.com\n      EMAIL\n\n      _(SDoc::Postprocessor.guess_code_language(<<~EMAIL)).must_equal \"email\"\n        Bcc: recipient@example.com\n      EMAIL\n    end\n\n    it \"guesses yaml for YAML\" do\n      _(SDoc::Postprocessor.guess_code_language(<<~YAML)).must_equal \"yaml\"\n        foo:\n          bar: 1\n      YAML\n\n      _(SDoc::Postprocessor.guess_code_language(<<~YAML)).must_equal \"yaml\"\n        foo: # comment\n          bar: 1\n      YAML\n\n      _(SDoc::Postprocessor.guess_code_language(<<~YAML)).must_equal \"yaml\"\n        base: &base\n          baz: 1\n\n        foo:\n          <<: *base\n          bar: 2\n      YAML\n\n      _(SDoc::Postprocessor.guess_code_language(<<~YAML)).must_equal \"yaml\"\n        foo: |\n          bar\n      YAML\n\n      _(SDoc::Postprocessor.guess_code_language(<<~YAML)).must_equal \"yaml\"\n        foo: >\n          bar\n      YAML\n    end\n\n    it \"guesses erb for YAML that includes ERB\" do\n      _(SDoc::Postprocessor.guess_code_language(<<~ERB)).must_equal \"erb\"\n        foo:\n          bar: <%= 1 + 1 %>\n      ERB\n    end\n\n    it \"guesses plaintext for YAML that includes ERB and HTML-incompatible markup\" do\n      _(SDoc::Postprocessor.guess_code_language(<<~ERB)).must_equal \"plaintext\"\n        base: &base\n          baz: 1\n\n        foo:\n          <<: *base\n          bar: <%= 1 + 1 %>\n      ERB\n    end\n\n    it \"guesses erb for ERB\" do\n      _(SDoc::Postprocessor.guess_code_language(<<~ERB)).must_equal \"erb\"\n        <%= 1 + 1 %>\n      ERB\n\n      _(SDoc::Postprocessor.guess_code_language(<<~ERB)).must_equal \"erb\"\n        1 + 1 = <%= 1 + 1 %>\n      ERB\n\n      _(SDoc::Postprocessor.guess_code_language(<<~ERB)).must_equal \"erb\"\n        <% x = 1 + 1 %>\n      ERB\n\n      _(SDoc::Postprocessor.guess_code_language(<<~ERB)).must_equal \"erb\"\n        <%- x = 1 + 1 -%>\n      ERB\n    end\n\n    it \"guesses erb for HTML\" do\n      _(SDoc::Postprocessor.guess_code_language(<<~HTML)).must_equal \"erb\"\n        <p>1 + 1 = 2</p>\n      HTML\n\n      _(SDoc::Postprocessor.guess_code_language(<<~HTML)).must_equal \"erb\"\n        1 + 1 = <span>2</span>\n      HTML\n    end\n\n    it \"guesses erb for HTML that includes ERB\" do\n      _(SDoc::Postprocessor.guess_code_language(<<~ERB)).must_equal \"erb\"\n        <p>1 + 1 = <%= 1 + 1 %></p>\n      ERB\n    end\n\n    it \"guesses ruby for Ruby code\" do\n      _(SDoc::Postprocessor.guess_code_language(<<~RUBY)).must_equal \"ruby\"\n        1 + 1\n      RUBY\n    end\n\n    it \"guesses ruby for Ruby return value comment\" do\n      _(SDoc::Postprocessor.guess_code_language(<<~RUBY)).must_equal \"ruby\"\n        Object.new # => #<Object>\n      RUBY\n\n      _(SDoc::Postprocessor.guess_code_language(<<~RUBY)).must_equal \"ruby\"\n        Pathname(\"/span\")\n        # => #<Pathname:/span>\n      RUBY\n\n      _(SDoc::Postprocessor.guess_code_language(<<~RUBY)).must_equal \"ruby\"\n        image_tag(\"image.png\")\n        # => <img src=\"/assets/image.png\" />\n      RUBY\n    end\n\n    it \"guesses ruby for Ruby code that includes an ERB string\" do\n      _(SDoc::Postprocessor.guess_code_language(<<~RUBY)).must_equal \"ruby\"\n        ApplicationController.render inline: \"<%= 1 + 1 %>\"\n      RUBY\n    end\n\n    it \"guesses ruby by default\" do\n      _(SDoc::Postprocessor.guess_code_language(<<~RUBY)).must_equal \"ruby\"\n        f x\n      RUBY\n    end\n  end\nend\n"
  },
  {
    "path": "spec/rdoc_generator_spec.rb",
    "content": "require File.join(File.dirname(__FILE__), '/spec_helper')\n\ndescribe RDoc::Generator::SDoc do\n  def parse_options(*options)\n    rdoc_options = nil\n\n    _stdout, stderr = capture_io do\n      rdoc_options = RDoc::Options.new.parse([\"--format=sdoc\", *options.flatten])\n    end\n\n    assert_empty stderr\n\n    rdoc_options\n  end\n\n  it \"is registered in RDoc::RDoc::GENERATORS\" do\n    _(RDoc::RDoc::GENERATORS).must_include 'sdoc'\n  end\n\n  it \"is activated via --format=sdoc\" do\n    options = parse_options()\n    _(options.generator).must_equal RDoc::Generator::SDoc\n    _(options.generator_name).must_equal \"sdoc\"\n  end\n\n  it \"displays SDoc's version via --version\" do\n    _(`./bin/sdoc --version`.strip).must_equal SDoc::VERSION\n  end\n\n  it \"displays SDoc's version via -v\" do\n    _(`./bin/sdoc -v`.strip).must_equal SDoc::VERSION\n  end\n\n  it \"generates a search index\" do\n    Dir.mktmpdir do |dir|\n      Dir.chdir(dir) do\n        rdoc_run(\"--files\", \"#{__dir__}/../README.md\", \"#{__dir__}/../lib/sdoc/version.rb\")\n        index = File.read(\"doc/js/search-index.js\")\n        index.delete_prefix!(\"export default \").delete_suffix!(\";\")\n        index.gsub!(/\\(new Uint8Array\\((.+?)\\)\\)/, '\\1')\n        _(JSON.parse(index).keys.sort).must_equal [\"ngrams\", \"weights\", \"entries\"].sort\n      end\n    end\n  end\n\n  describe \"options.dry_run\" do\n    it \"prevents files from being rendered\" do\n      Dir.mktmpdir do |dir|\n        rdoc_dry_run(\n          \"--files\", \"#{__dir__}/../README.md\", \"#{__dir__}/../lib/sdoc/version.rb\",\n          \"--output\", dir\n        )\n\n        _(Dir.glob(\"**/*\", base: dir)).must_be_empty\n      end\n    end\n  end\n\n  describe \"options.core_ext_pattern\" do\n    it \"is /core_ext/ by default\" do\n      _(parse_options().core_ext_pattern).must_equal %r\"core_ext\"\n    end\n\n    it \"can be set via --core-ext\" do\n      _(parse_options(\"--core-ext\", \"foo.*bar\").core_ext_pattern).must_equal %r\"foo.*bar\"\n    end\n  end\n\n  describe \"options.github\" do\n    it \"is disabled by default\" do\n      _(parse_options().github).must_be_nil\n    end\n\n    it \"is enabled via --github\" do\n      _(parse_options(\"--github\").github).must_equal true\n    end\n\n    it \"is enabled via -g\" do\n      _(parse_options(\"-g\").github).must_equal true\n    end\n  end\n\n  describe \"options.title\" do\n    it \"includes ENV['HORO_PROJECT_NAME'] and ENV['HORO_PROJECT_VERSION'] by default\" do\n      with_env(\"HORO_PROJECT_NAME\" => \"My Gem\", \"HORO_PROJECT_VERSION\" => \"v2.0\") do\n        _(parse_options().title).must_equal \"My Gem v2.0 API documentation\"\n      end\n\n      with_env(\"HORO_PROJECT_NAME\" => \"My Gem\", \"HORO_PROJECT_VERSION\" => nil) do\n        _(parse_options().title).must_equal \"My Gem API documentation\"\n      end\n\n      with_env(\"HORO_PROJECT_NAME\" => nil, \"HORO_PROJECT_VERSION\" => \"v2.0\") do\n        _(parse_options().title).must_equal \"v2.0 API documentation\"\n      end\n\n      with_env(\"HORO_PROJECT_NAME\" => nil, \"HORO_PROJECT_VERSION\" => nil) do\n        _(parse_options().title).must_equal \"API documentation\"\n      end\n    end\n\n    it \"prioritizes ENV['HORO_BADGE_VERSION'] over ENV['HORO_PROJECT_VERSION']\" do\n      with_env(\"HORO_BADGE_VERSION\" => \"badge\", \"HORO_PROJECT_VERSION\" => \"project\") do\n        _(parse_options().title).must_equal \"badge API documentation\"\n      end\n    end\n\n    it \"can be overridden\" do\n      _(parse_options(\"--title\", \"Docs Docs Docs!\").title).must_equal \"Docs Docs Docs!\"\n    end\n  end\n\n  describe \"#index\" do\n    before do\n      @dir = File.expand_path(\"../lib\", __dir__)\n      @files = [\"sdoc.rb\", \"sdoc/version.rb\"].sort.reverse\n    end\n\n    it \"defaults to the first --files value\" do\n      Dir.chdir(@dir) do\n        sdoc = rdoc_dry_run(\"--files\", *@files).generator\n        _(sdoc.index.absolute_name).must_equal @files.first\n      end\n    end\n\n    it \"raises when the default value is not a file\" do\n      error = _{ rdoc_dry_run(\"--files\", @dir, \"--exclude=(js|css|svg)$\") }.must_raise\n      _(error.message).must_include @dir\n    end\n\n    it \"uses the value of --main\" do\n      Dir.chdir(@dir) do\n        sdoc = rdoc_dry_run(\"--main\", @files.first, \"--files\", *@files).generator\n        _(sdoc.index.absolute_name).must_equal @files.first\n      end\n    end\n\n    it \"raises when the main page is not among the rendered files\" do\n      Dir.chdir(@dir) do\n        error = _{ rdoc_dry_run(\"--main\", @files.first, \"--files\", @files.last) }.must_raise\n        _(error.message).must_include @files.first\n      end\n    end\n\n    it \"works when --root is specified\" do\n      Dir.chdir(File.dirname(@dir)) do\n        root = File.basename(@dir)\n        @files.map! { |file| File.join(root, file) }\n        sdoc = rdoc_dry_run(\"--root\", root, \"--main\", @files.first, \"--files\", *@files).generator\n        _(sdoc.index.absolute_name).must_equal @files.first\n      end\n    end\n\n    it \"works with unresolved paths\" do\n      Dir.chdir(@dir) do\n        @files.map! { |file| File.join(\"..\", File.basename(@dir), \".\", file) }\n        sdoc = rdoc_dry_run(\"--main\", @files.first, \"--files\", *@files).generator\n        _(sdoc.index.absolute_name).must_equal @files.first\n      end\n    end\n\n    it \"works with absolute paths\" do\n      @files.map! { |file| File.join(@dir, file) }\n      sdoc = rdoc_dry_run(\"--main\", @files.first, \"--files\", *@files).generator\n      _(sdoc.index.absolute_name).must_equal @files.first\n    end\n\n    it \"overrides RDoc::TopLevel#path\" do\n      Dir.chdir(@dir) do\n        sdoc = rdoc_dry_run(\"--files\", *@files).generator\n        _(sdoc.index.path).must_equal \"\"\n        sdoc.store.all_files.each { |file| _(file.path).wont_equal \"\" }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/rdoc_monkey_patches_spec.rb",
    "content": "require \"spec_helper\"\n\ndescribe \"RDoc monkey patches\" do\n  describe RDoc::TopLevel do\n    it \"supports setting #path\" do\n      top_level = rdoc_top_level_for(\"class Foo; end\")\n\n      _(top_level.path).wont_be_nil\n\n      top_level.path = \"some/path\"\n      _(top_level.path).must_equal \"some/path\"\n    end\n  end\n\n  describe RDoc::Constant do\n    it \"implements #aref\" do\n      rdoc_constant = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\").find_constant_named(\"BAR_QUX\")\n        module Foo\n          BAR_QUX = true\n        end\n      RUBY\n\n      _(rdoc_constant.aref).must_equal \"constant-BAR_QUX\"\n    end\n\n    it \"uses #aref in #path\" do\n      rdoc_constant = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\").find_constant_named(\"BAR_QUX\")\n        module Foo\n          BAR_QUX = true\n        end\n      RUBY\n\n      _(rdoc_constant.path).must_equal \"classes/Foo.html#constant-BAR_QUX\"\n    end\n  end\n\n  describe RDoc::AnyMethod do\n    it \"omits extra whitespace in #params\" do\n      rdoc_method = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\").find_method(\"bar\", false)\n        module Foo\n          def bar(\n            x,\n            y,\n            z\n          )\n          end\n        end\n      RUBY\n\n      _(rdoc_method.params).must_equal \"(x, y, z)\"\n    end\n  end\n\n  describe RDoc::Markup::ToHtmlCrossref do\n    it \"prevents unintentional ref links\" do\n      description = rdoc_top_level_for(<<~RUBY).find_module_named(\"CoolApp\").description\n        module ERB; end\n        module Rails; end\n\n        # CoolApp uses Rails and ERB. See ::Rails. See also ::ERB.\n        module CoolApp; end\n      RUBY\n\n      _(description).must_match %r\"<a href=.+?><code>CoolApp</code></a> uses Rails and ERB\"\n      _(description).must_match %r\"See <a href=.+?><code>::Rails</code></a>\"\n      _(description).must_match %r\"See also <a href=.+?><code>::ERB</code></a>\"\n    end\n\n    it \"styles ref links that look like code\" do\n      description = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\").description\n        # Some of {Foo}[rdoc-ref:Foo]'s methods can be called with multiple\n        # arguments, such as {bar(x, y)}[rdoc-ref:#bar].\n        #\n        # But {baz cannot}[rdoc-ref:#baz] and {qux (also) cannot}[rdoc-ref:#qux].\n        class Foo\n          def bar(x, y); end\n          def baz; end\n          def qux; end\n        end\n      RUBY\n\n      _(description).must_match %r\"Some of <a href=.+?><code>Foo</code></a>\"\n      _(description).must_match %r\"such as <a href=.+?><code>bar\\(x, y\\)</code></a>\"\n\n      _(description).must_match %r\"But <a href=.+?>baz cannot</a>\"\n      _(description).must_match %r\"and <a href=.+?>qux \\(also\\) cannot</a>\"\n    end\n  end\n\n  describe RDoc::Parser::Ruby do\n    it \"does not pollute RDoc::ClassModule#in_files when parsing constants\" do\n      Dir.mktmpdir do |dir|\n        Dir.chdir(dir) do\n          File.write(\"float_ext.rb\", <<~RUBY)\n            class Float\n              def ext; end\n            end\n          RUBY\n\n          File.write(\"foo.rb\", <<~RUBY)\n            class Foo\n              def foo; Float::INFINITY; end\n            end\n          RUBY\n\n          rdoc_store = rdoc_dry_run(\"--files\", \"float_ext.rb\", \"foo.rb\").store\n\n          _(rdoc_store.find_class_or_module(\"Float\").in_files).\n            must_equal [rdoc_store.find_file_named(\"float_ext.rb\")]\n\n          _(rdoc_store.find_file_named(\"foo.rb\").classes_and_modules).\n            must_equal [rdoc_store.find_class_or_module(\"Foo\")]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/renderer_spec.rb",
    "content": "require \"spec_helper\"\n\ndescribe SDoc::Renderer do\n  before do\n    @template_dir = Dir.mktmpdir\n    @rdoc_options = RDoc::Options.new.tap do |options|\n      options.template_dir = @template_dir\n    end\n  end\n\n  after do\n    FileUtils.remove_entry(@template_dir)\n  end\n\n  def create_template(name, erb)\n    File.write(File.join(@template_dir, name), erb)\n  end\n\n  describe \"#render\" do\n    it \"renders an ERB template\" do\n      create_template \"foo.erb\", %(<%= \"foo\".upcase %>)\n\n      _(SDoc::Renderer.new(nil, @rdoc_options).render(\"foo.erb\")).\n        must_equal \"FOO\"\n    end\n\n    it \"supports local variables\" do\n      create_template \"foo.erb\", %(<%= foo.upcase %>)\n\n      _(SDoc::Renderer.new(nil, @rdoc_options).render(\"foo.erb\", { foo: \"bar\" })).\n        must_equal \"BAR\"\n    end\n\n    it \"provides access to @context\" do\n      create_template \"foo.erb\", %(<%= @context[:foo] %>)\n\n      _(SDoc::Renderer.new({ foo: \"bar\" }, @rdoc_options).render(\"foo.erb\")).\n        must_equal \"bar\"\n    end\n\n    it \"provides access to @options\" do\n      create_template \"foo.erb\", %(<%= @options.object_id %>)\n\n      _(SDoc::Renderer.new(nil, @rdoc_options).render(\"foo.erb\")).\n        must_equal @rdoc_options.object_id.to_s\n    end\n\n    it \"provides access to helper methods\" do\n      create_template \"foo.erb\", %(<%= h \"foo & bar\" %>)\n\n      _(SDoc::Renderer.new(nil, @rdoc_options).render(\"foo.erb\")).\n        must_equal \"foo &amp; bar\"\n    end\n\n    it \"is reentrant\" do\n      create_template \"foo.erb\", %(1 + 1 = <%= render \"two.erb\" %>)\n      create_template \"two.erb\", %(<%= 1 + 1 %>)\n\n      _(SDoc::Renderer.new(nil, @rdoc_options).render(\"foo.erb\")).\n        must_equal \"1 + 1 = 2\"\n    end\n  end\n\n  describe \"#inline\" do\n    it \"supports isolated local variables\" do\n      create_template \"outer.erb\", %(<%= foo %> <% inline \"inner.erb\", bar: \"BAR\" %> <%= foo %>)\n      create_template \"inner.erb\", %(<%= foo = bar %>)\n\n      _(SDoc::Renderer.new(nil, @rdoc_options).render(\"outer.erb\", { foo: \"FOO\" })).\n        must_equal \"FOO BAR FOO\"\n    end\n\n    it \"provides access to the same non-local values as #render\" do\n      create_template \"outer.erb\", %(<% inline \"inner.erb\" %>)\n      create_template \"inner.erb\", %(<%= h @context[:foobar] %>)\n\n      _(SDoc::Renderer.new({ foobar: \"foo & bar\" }, @rdoc_options).render(\"outer.erb\")).\n        must_equal \"foo &amp; bar\"\n    end\n\n    it \"supports yield\" do\n      create_template \"outer.erb\", '1 <% inline(\"inner.erb\") { 3 } %> 5'\n      create_template \"inner.erb\", %(2 <%= yield %> 4)\n\n      _(SDoc::Renderer.new(nil, @rdoc_options).render(\"outer.erb\")).\n        must_equal \"1 2 3 4 5\"\n    end\n\n    it \"supports interleaved rendering\" do\n      create_template \"outer.erb\", '1 <% inline(\"inner.erb\") do %>3<% end %> 5'\n      create_template \"inner.erb\", %(2 <% yield %> 4)\n\n      _(SDoc::Renderer.new(nil, @rdoc_options).render(\"outer.erb\")).\n        must_equal \"1 2 3 4 5\"\n    end\n\n    it \"supports interleaved rendering with nested #inline calls\" do\n      create_template \"outer.erb\", '1 <% inline(\"middle.erb\") { inline(\"inner.erb\") } %> 5'\n      create_template \"middle.erb\", %(2 <% yield %> 4)\n      create_template \"inner.erb\", %(3)\n\n      _(SDoc::Renderer.new(nil, @rdoc_options).render(\"outer.erb\")).\n        must_equal \"1 2 3 4 5\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/search_index_spec.rb",
    "content": "require \"spec_helper\"\n\ndescribe SDoc::SearchIndex do\n  describe \"#generate\" do\n    it \"generates a search index for the given modules and their members\" do\n      top_level = rdoc_top_level_for <<~RUBY\n        # This is FooBar.\n        class FooBar\n          # This is #lorem_ipsum.\n          attr_reader :lorem_ipsum\n\n          # This is +BAZ_QUX+.\n          BAZ_QUX = true\n\n          # This is #hoge_fuga.\n          def hoge_fuga; end\n        end\n      RUBY\n\n      ngrams =\n        SDoc::SearchIndex.derive_ngrams(\"FooBar\") |\n        SDoc::SearchIndex.derive_ngrams(\"FooBar#lorem_ipsum\") |\n        SDoc::SearchIndex.derive_ngrams(\"FooBar::BAZ_QUX\") |\n        SDoc::SearchIndex.derive_ngrams(\"FooBar#hoge_fuga\")\n\n      search_index = SDoc::SearchIndex.generate(top_level.classes_and_modules)\n\n      _(search_index.keys.sort).must_equal [\"ngrams\", \"weights\", \"entries\"].sort\n\n      _(search_index[\"ngrams\"].keys.sort).must_equal ngrams.sort\n      _(search_index[\"ngrams\"].values.max).must_equal search_index[\"weights\"].length - 1\n\n      _(search_index[\"entries\"].length).must_equal 4\n      search_index[\"entries\"].each do |entry|\n        _(entry.length).must_be :<=, 6\n        _(entry[0]).must_be_kind_of Array # Fingerprint\n        _(entry[1]).must_be :<, 1.0 # Tiebreaker bonus\n        _(entry[3]).must_equal \"FooBar\" # Module name\n      end\n\n      module_entry, method_entry, attr_entry, constant_entry = search_index[\"entries\"].sort_by { |entry| entry[4].to_s }\n\n      # URL\n      _(module_entry[2]).must_equal \"classes/FooBar.html\"\n      _(constant_entry[2]).must_equal \"classes/FooBar.html#constant-BAZ_QUX\"\n      _(method_entry[2]).must_equal \"classes/FooBar.html#method-i-hoge_fuga\"\n      _(attr_entry[2]).must_equal \"classes/FooBar.html#attribute-i-lorem_ipsum\"\n\n      # Member label\n      _(module_entry[4]).must_be_nil\n      _(constant_entry[4]).must_equal \"::BAZ_QUX\"\n      _(method_entry[4]).must_equal \"#hoge_fuga()\"\n      _(attr_entry[4]).must_equal \"#lorem_ipsum\"\n\n      # Description\n      _(module_entry[5]).must_equal \"This is <code>FooBar</code>.\"\n      _(constant_entry[5]).must_equal \"This is <code>BAZ_QUX</code>.\"\n      _(method_entry[5]).must_equal \"This is <code>hoge_fuga</code>.\"\n      _(attr_entry[5]).must_equal \"This is <code>lorem_ipsum</code>.\"\n    end\n  end\n\n  describe \"#derive_ngrams\" do\n    it \"returns ngrams for a given string\" do\n      expected = %w[abc bcx cxy xyz]\n      _(SDoc::SearchIndex.derive_ngrams(\"abcxyz\") & expected).must_equal expected\n    end\n\n    it \"includes module-related ngrams\" do\n      ngrams = SDoc::SearchIndex.derive_ngrams(\"Abc::Def\")\n\n      _(ngrams.map(&:length).uniq.first).must_equal 3\n\n      _(ngrams).must_include \":Ab\"\n      _(ngrams).must_include \":A \"\n      _(ngrams).must_include \" Ab\"\n      _(ngrams).must_include \" A \"\n\n      _(ngrams).must_include \":De\"\n      _(ngrams).must_include \":D \"\n      _(ngrams).must_include \" De\"\n      _(ngrams).must_include \" D \"\n\n      _(ngrams.grep(/.:|[^: ]. |[.(]/)).must_be_empty\n    end\n\n    it \"includes method-related ngrams for instance methods\" do\n      ngrams = SDoc::SearchIndex.derive_ngrams(\"Abc::Def#uvw_xyz\")\n\n      _(ngrams.map(&:length).uniq.first).must_equal 3\n\n      _(ngrams).must_include \"#uv\"\n      _(ngrams).must_include \"#u \"\n      _(ngrams).must_include \" uv\"\n      _(ngrams).must_include \" u \"\n\n      _(ngrams).must_include \".uv\"\n      _(ngrams).must_include \"yz(\"\n\n      _(ngrams).must_include \"w_x\"\n      _(ngrams).must_include \"vwx\"\n      _(ngrams).must_include \"wxy\"\n\n      _(ngrams.grep(/.#|[^:# ]. /)).must_be_empty\n\n      ngrams_from_module = SDoc::SearchIndex.derive_ngrams(\"Abc::Def\")\n      _((ngrams & ngrams_from_module).sort).must_equal ngrams_from_module.grep_v(/[: ][A-F]/i).sort\n    end\n\n    it \"includes method-related ngrams for singleton methods\" do\n      ngrams = SDoc::SearchIndex.derive_ngrams(\"Abc::Def::uvw_xyz\")\n\n      instance_method_ngrams = SDoc::SearchIndex.derive_ngrams(\"Abc::Def#uvw_xyz\")\n      _(ngrams.sort).must_equal instance_method_ngrams.map { _1.tr(\"#\", \":\") }.sort\n    end\n\n    it \"includes acronym ngrams\" do\n      ngrams = SDoc::SearchIndex.derive_ngrams(\"AbcDef::StUvWxYz\")\n\n      _(ngrams).must_include \":AD\"\n      _(ngrams).must_include \" AD\"\n      _(ngrams).must_include \":SU\"\n      _(ngrams).must_include \" SU\"\n      _(ngrams).must_include \"SUW\"\n      _(ngrams).must_include \"UWY\"\n\n      _(ngrams.grep(/DS/)).must_be_empty\n    end\n\n    it \"includes downcased ngrams except for acronym ngrams\" do\n      ngrams = SDoc::SearchIndex.derive_ngrams(\"AbcDef::StUvWxYz\")\n\n      ngrams.grep(/[A-Z]/).grep_v(/[A-Z]{2}/).each do |uppercase|\n        _(ngrams).must_include uppercase.downcase\n      end\n    end\n  end\n\n  describe \"#compile_ngrams\" do\n    it \"assigns ngram bit positions based on ngram rarity\" do\n      base_ngrams = (\"aaa\"..\"zzz\").take(4)\n      ngram_sets = (0..3).map { |n| base_ngrams.drop(n) }\n\n      _(SDoc::SearchIndex.compile_ngrams(ngram_sets)).\n        must_equal base_ngrams.reverse.each_with_index.to_h\n    end\n  end\n\n  describe \"#generate_fingerprint\" do\n    it \"returns an array of bytes with bits set for the given ngrams\" do\n      ngrams = (\"aaa\"..\"zzz\").take(8)\n\n      packed_positions = ngrams.each_with_index.to_h\n      _(SDoc::SearchIndex.generate_fingerprint(ngrams, packed_positions)).must_equal [0b11111111]\n\n      sparse_positions = ngrams.each_with_index.to_h { |ngram, i| [ngram, i * 8] }\n      _(SDoc::SearchIndex.generate_fingerprint(ngrams, sparse_positions)).must_equal [1] * 8\n    end\n\n    it \"omits trailing zero bytes\" do\n      _(SDoc::SearchIndex.generate_fingerprint([\"xxx\"], { \"xxx\" => 0, \"yyy\" => 100 })).must_equal [1]\n    end\n  end\n\n  describe \"#compute_bit_weights\" do\n    it \"returns an array of weights\" do\n      _(SDoc::SearchIndex.compute_bit_weights({ \"xxx\" => 0, \"yyy\" => 1 })).must_equal [1, 1]\n    end\n\n    it \"computes weights based on ngram content\" do\n      ngram_bit_positions = { \"xxx\" => 0, \" xx\" => 1, \":Xx\" => 2, \"#xx\" => 3 }\n      bit_weights = SDoc::SearchIndex.compute_bit_weights(ngram_bit_positions)\n\n      _(bit_weights.length).must_equal ngram_bit_positions.length\n      _(bit_weights.uniq).must_equal bit_weights\n      _(bit_weights.sort).must_equal bit_weights\n    end\n\n    it \"orders weights by bit position\" do\n      ngram_bit_positions = { \"xxx\" => 0, \" xx\" => 1, \":Xx\" => 2, \"#xx\" => 3 }\n      bit_weights = SDoc::SearchIndex.compute_bit_weights(ngram_bit_positions)\n\n      reversed = ngram_bit_positions.reverse_each.to_h\n      _(SDoc::SearchIndex.compute_bit_weights(reversed)).must_equal bit_weights\n\n      inverted = ngram_bit_positions.transform_values { |pos| -pos + bit_weights.length }\n      _(SDoc::SearchIndex.compute_bit_weights(inverted)).must_equal bit_weights.reverse\n    end\n\n    it \"ignores alias ngrams\" do\n      _(SDoc::SearchIndex.compute_bit_weights({ \"#xx\" => 0, \".xx\" => 0}).length).must_equal 1\n    end\n  end\n\n  describe \"#compute_tiebreaker_bonus\" do\n    it \"returns a value much smaller than 1 (the value of a single matching ngram)\" do\n      _(SDoc::SearchIndex.compute_tiebreaker_bonus(\"X\", nil, \"\")).must_be :<=, 0.1\n    end\n\n    it \"favors short module names over long module names\" do\n      [\n        [\"X\", \"Xx\"],\n        [\"Time\", \"ActiveSupport::TimeZone\"],\n        [\"ActiveSupport::TimeZone\", \"ActiveSupport::TimeWithZone\"],\n      ].each do |short_name, long_name|\n        short_name_bonus = SDoc::SearchIndex.compute_tiebreaker_bonus(short_name, nil, \"\")\n        long_name_bonus = SDoc::SearchIndex.compute_tiebreaker_bonus(long_name, nil, \"\")\n\n        _(short_name_bonus).must_be :>, long_name_bonus, \"#{short_name} vs #{long_name}\"\n      end\n    end\n\n    it \"favors short method names over long method names\" do\n      [\n        [\"x\", \"xx\"],\n        [\"has_one\", \"has_many\"],\n        [\"has_many\", \"has_and_belongs_to_many\"],\n      ].each do |short_name, long_name|\n        short_name_bonus = SDoc::SearchIndex.compute_tiebreaker_bonus(\"X\", short_name, \"\")\n        long_name_bonus = SDoc::SearchIndex.compute_tiebreaker_bonus(\"X\", long_name, \"\")\n\n        _(short_name_bonus).must_be :>, long_name_bonus, \"X##{short_name} vs X##{long_name}\"\n      end\n    end\n\n    it \"favors methods with long documentation over methods with short documentation\" do\n      [\n        [ [\"X\", \"x\", 2],\n          [\"Y\", \"x\", 1] ],\n        [ [\"ActionView::Template\", \"render\", 300],\n          [\"ActionView::Renderer\", \"render\", 80] ],\n        [ [\"ActionController::Rendering\", \"render\", 3000],\n          [\"ActionController::Renderer\", \"render\", 80] ],\n      ].each do |(*names1, long_doc_length), (*names2, short_doc_length)|\n        long_doc_bonus = SDoc::SearchIndex.compute_tiebreaker_bonus(*names1, \"x\" * long_doc_length)\n        short_doc_bonus = SDoc::SearchIndex.compute_tiebreaker_bonus(*names2, \"x\" * short_doc_length)\n\n        _(long_doc_bonus).must_be :>, short_doc_bonus,\n          \"#{names1.join \"#\"} w/ #{long_doc_length} chars vs #{names2.join \"#\"} w/ #{short_doc_length} chars\"\n      end\n    end\n\n    it \"balances factors to produce desirable results\" do\n      [\n        [ [\"Pathname\", \"existence\", 200],\n          [\"ActiveSupport::Callbacks::CallTemplate::InstanceExec1\", \"expand\", 0] ],\n        [ [\"ActiveRecord::Associations::ClassMethods\", \"has_many\", 12000],\n          [\"ActiveStorage::Attached::Model\", \"has_many_attached\", 2000] ],\n        [ [\"ActiveRecord::FinderMethods\", \"find_by\", 200],\n          [\"ActiveRecord::Querying\", \"find_by_sql\", 2000] ],\n        [ [\"ActionController::Rendering\", \"render\", 3000],\n          [\"ActionController::Renderer\", \"render\", 100] ],\n        [ [\"ActionView::Helpers::RenderingHelper\", \"render\", 900],\n          [\"ActionView::Template\", \"render\", 300] ],\n      ].each do |(*names1, doc_length1), (*names2, doc_length2)|\n        bonus1 = SDoc::SearchIndex.compute_tiebreaker_bonus(*names1, \"x\" * doc_length1)\n        bonus2 = SDoc::SearchIndex.compute_tiebreaker_bonus(*names2, \"x\" * doc_length2)\n\n        _(bonus1).must_be :>, bonus2,\n          \"#{names1.join \"#\"} w/ #{doc_length1} chars vs #{names2.join \"#\"} w/ #{doc_length2} chars\"\n      end\n    end\n  end\n\n  describe \"#signature_for\" do\n    it \"returns a given method's signature\" do\n      rdoc_method = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\").find_method(\"bar\", false)\n        module Foo; def bar(x, y, z); end; end\n      RUBY\n\n      _(SDoc::SearchIndex.signature_for(rdoc_method)).must_equal \"#bar(x, y, z)\"\n    end\n\n    it \"prepends '::' for class methods\" do\n      rdoc_method = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\").find_method(\"bar\", true)\n        module Foo; def self.bar(x, y, z); end; end\n      RUBY\n\n      _(SDoc::SearchIndex.signature_for(rdoc_method)).must_equal \"::bar(x, y, z)\"\n    end\n\n    it \"extracts params for basic :call-seq: methods\" do\n      rdoc_module = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\")\n        module Foo\n          # :method: bar\n          # :call-seq:\n          #   bar(x, y, z)\n          #\n          # Returns result.\n\n          # :method: bar!\n          # :call-seq:\n          #   bar!(x, y, z) -> result\n\n          # :method: qux\n          # :call-seq:\n          #   qux\n\n          # :method: qux?\n          # :call-seq:\n          #   qux? -> result\n\n          # :method: fuga\n          # :call-seq:\n          #   fuga(x = ' -> ') -> result\n\n          # :method: hoge\n          # :call-seq:\n          #   hoge() -> (result)\n        end\n      RUBY\n\n      _(SDoc::SearchIndex.signature_for(rdoc_module.find_method(\"bar\", false))).must_equal \"#bar(x, y, z)\"\n      _(SDoc::SearchIndex.signature_for(rdoc_module.find_method(\"bar!\", false))).must_equal \"#bar!(x, y, z)\"\n      _(SDoc::SearchIndex.signature_for(rdoc_module.find_method(\"qux\", false))).must_equal \"#qux()\"\n      _(SDoc::SearchIndex.signature_for(rdoc_module.find_method(\"qux?\", false))).must_equal \"#qux?()\"\n      _(SDoc::SearchIndex.signature_for(rdoc_module.find_method(\"fuga\", false))).must_equal \"#fuga(x = ' -> ')\"\n      _(SDoc::SearchIndex.signature_for(rdoc_module.find_method(\"hoge\", false))).must_equal \"#hoge()\"\n    end\n\n    it \"uses '(...)' to represent params for overloaded :call-seq: methods\" do\n      rdoc_method = rdoc_top_level_for(<<~RUBY).find_module_named(\"Foo\").find_method(\"bar\", false)\n        module Foo\n          # :method: bar\n          # :call-seq:\n          #   bar(x, y, z) -> result\n          #   bar(&block) -> result\n        end\n      RUBY\n\n      _(SDoc::SearchIndex.signature_for(rdoc_method)).must_equal \"#bar(...)\"\n    end\n  end\n\n  describe \"#truncate_description\" do\n    it \"extracts text from the leading paragraph\" do\n      _(SDoc::SearchIndex.truncate_description(\"<p>leading</p><p>second</p>\", 100)).\n        must_equal \"leading\"\n\n      _(SDoc::SearchIndex.truncate_description(\"<h1>heading</h1><p>leading</p>\", 100)).\n        must_equal \"leading\"\n    end\n\n    it \"returns nil if there is no leading paragraph\" do\n      _(SDoc::SearchIndex.truncate_description(\"<pre>code</pre><p>explanation</p>\", 100)).\n        must_be_nil\n\n      _(SDoc::SearchIndex.truncate_description(\"\", 100)).\n        must_be_nil\n    end\n\n    it \"preserves HTML\" do\n      _(SDoc::SearchIndex.truncate_description(\"<p><em>emphatic</em> text</p>\", 100)).\n        must_equal \"<em>emphatic</em> text\"\n    end\n\n    it \"strips link HTML\" do\n      _(SDoc::SearchIndex.truncate_description(%(<p><a href=\"/\"><code>ref</code></a> link</p>), 100)).\n        must_equal \"<code>ref</code> link\"\n    end\n\n    it \"truncates inner text at word boundaries\" do\n      description = \"<p>12345 <i>78</i>. <b>12, <i>5 - 9</i>.</b></p>\"\n\n      {\n         8..10 => \"12345...\",\n        11..14 => \"12345 <i>78</i>...\",\n        15..17 => \"12345 <i>78</i>. <b>12</b>...\",\n        18..19 => \"12345 <i>78</i>. <b>12, <i>5</i></b>...\",\n        20..25 => \"12345 <i>78</i>. <b>12, <i>5 - 9</i>.</b>\",\n      }.each do |range, expected|\n        range.each do |limit|\n          _(SDoc::SearchIndex.truncate_description(description, limit)).must_equal expected\n        end\n      end\n    end\n\n    it \"treats <code> elements as a whole\" do\n      (5..8).each do |limit|\n        _(SDoc::SearchIndex.truncate_description(\"<p>1 <code>345 789</code></p>\", limit)).\n          must_equal \"1...\"\n      end\n\n      _(SDoc::SearchIndex.truncate_description(\"<p>1 <code>345 789</code></p>\", 9)).\n        must_equal \"1 <code>345 789</code>\"\n    end\n\n    it \"adds ellipsis after trailing colon\" do\n      _(SDoc::SearchIndex.truncate_description(\"<p>for example:</p>\", 100)).\n        must_equal \"for example:...\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/spec_helper.rb",
    "content": "require 'bundler/setup'\n\nrequire 'sdoc'\n\nrequire 'minitest/autorun'\n\ndef with_env(env, &block)\n  original_env = ENV.to_h\n  ENV.replace(env)\n  block.call\nensure\n  ENV.replace(original_env)\nend\n\ndef rdoc_run(*options)\n  RDoc::RDoc.new.tap do |rdoc|\n    rdoc.document(%W[--quiet --format=sdoc --template=rails] + options.flatten)\n  end\nend\n\ndef rdoc_dry_run(*options)\n  rdoc_run(\"--dry-run\", *options)\nend\n\n# Returns an RDoc::TopLevel instance for the given Ruby code.\ndef rdoc_top_level_for(ruby_code)\n  # RDoc has a lot of internal state that needs to be initialized. The most\n  # foolproof way to initialize it is by simply running it with a dummy file.\n  $rdoc_for_specs ||= rdoc_dry_run(\"--files\", __FILE__)\n\n  $rdoc_for_specs.store = RDoc::Store.new($rdoc_for_specs.options)\n\n  Dir.mktmpdir do |dir|\n    path = \"#{dir}/ruby_code.rb\"\n    File.write(path, ruby_code)\n    $rdoc_for_specs.parse_file(path)\n  end\nend\n"
  }
]