[
  {
    "path": ".github/dependabot.yaml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: github-actions\n    directory: \"/\"\n    schedule:\n      interval: weekly\n"
  },
  {
    "path": ".github/workflows/cla.yml",
    "content": "name: Contributor License Agreement (CLA)\n\non:\n  pull_request_target:\n    types: [opened, synchronize]\n  issue_comment:\n    types: [created]\n\njobs:\n  cla:\n    runs-on: ubuntu-latest\n    if: |\n      (github.event.issue.pull_request \n        && !github.event.issue.pull_request.merged_at\n        && contains(github.event.comment.body, 'signed')\n      ) \n      || (github.event.pull_request && !github.event.pull_request.merged)\n    steps:\n      - uses: Shopify/shopify-cla-action@v1\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          cla-token: ${{ secrets.CLA_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/liquid.yml",
    "content": "name: Liquid\non: [push]\n\nenv:\n  BUNDLE_JOBS: 4\n  BUNDLE_RETRY: 3\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        entry:\n          - { ruby: 3.3, allowed-failure: false } # minimum supported\n          - { ruby: 3.4, allowed-failure: false, rubyopt: \"--yjit\" }\n          - { ruby: 4.0, allowed-failure: false } # latest stable\n          - {\n              ruby: 4.0,\n              allowed-failure: false,\n              rubyopt: \"--enable-frozen-string-literal\",\n            }\n          - { ruby: 4.0, allowed-failure: false, rubyopt: \"--yjit\" }\n          - { ruby: 4.0, allowed-failure: false, rubyopt: \"--zjit\" }\n\n          # Head can have failures due to being in development\n          - { ruby: head, allowed-failure: true }\n          - {\n              ruby: head,\n              allowed-failure: true,\n              rubyopt: \"--enable-frozen-string-literal\",\n            }\n          - { ruby: head, allowed-failure: true, rubyopt: \"--yjit\" }\n          - { ruby: head, allowed-failure: true, rubyopt: \"--zjit\" }\n    name: Test Ruby ${{ matrix.entry.ruby }} ${{ matrix.entry.rubyopt }} --${{ matrix.entry.allowed-failure && 'allowed-failure' || 'strict' }}\n    steps:\n      - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0\n      - uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0\n        with:\n          ruby-version: ${{ matrix.entry.ruby }}\n          bundler-cache: true\n          bundler: latest\n      - run: bundle exec rake\n        continue-on-error: ${{ matrix.entry.allowed-failure }}\n        env:\n          RUBYOPT: ${{ matrix.entry.rubyopt }}\n\n  spec:\n    runs-on: ubuntu-latest\n    env:\n      BUNDLE_WITH: spec\n    steps:\n      - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0\n      - uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0\n        with:\n          bundler-cache: true\n          bundler: latest\n      - name: Run liquid-spec for all adapters\n        run: |\n          for adapter in spec/*.rb; do\n            echo \"=== Running $adapter ===\"\n            bundle exec liquid-spec run \"$adapter\" --no-max-failures\n          done\n\n  memory_profile:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0\n      - uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0\n        with:\n          bundler-cache: true\n      - run: bundle exec rake memory_profile:run\n"
  },
  {
    "path": ".gitignore",
    "content": "*~\n*.gem\n*.swp\npkg\n*.rbc\n.rvmrc\n.bundle\n.byebug_history\nGemfile.lock\n"
  },
  {
    "path": ".rubocop.yml",
    "content": "inherit_gem:\n  rubocop-shopify: rubocop.yml\n\ninherit_from:\n  - .rubocop_todo.yml\n\nrequire: rubocop-performance\n\nPerformance:\n  Enabled: true\n\nAllCops:\n  NewCops: disable\n  SuggestExtensions: false\n  Exclude:\n    - 'vendor/bundle/**/*'\n\nNaming/MethodName:\n  Exclude:\n    - 'example/server/liquid_servlet.rb'\n\nStyle/ClassMethodsDefinitions:\n  Enabled: false\n\n# liquid filter calls were being mistaken to be calls on arrays\nStyle/ConcatArrayLiterals:\n  Exclude:\n    - 'test/integration/standard_filter_test.rb'\n"
  },
  {
    "path": ".rubocop_todo.yml",
    "content": "# This configuration was generated by\n# `rubocop --auto-gen-config`\n# on 2022-05-18 19:25:47 UTC using RuboCop version 1.29.1.\n# The point is for the user to remove these configuration records\n# one by one as the offenses are removed from the code base.\n# Note that changes in the inspected code, or installation of new\n# versions of RuboCop, may require this file to be generated again.\n\n# Offense count: 1\n# This cop supports safe auto-correction (--auto-correct).\n# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include.\n# Include: **/*.gemspec\nGemspec/OrderedDependencies:\n  Exclude:\n    - 'liquid.gemspec'\n\n# Offense count: 6\n# This cop supports safe auto-correction (--auto-correct).\nLayout/ClosingHeredocIndentation:\n  Exclude:\n    - 'test/integration/tags/for_tag_test.rb'\n\n# Offense count: 34\n# This cop supports safe auto-correction (--auto-correct).\nLayout/EmptyLineAfterGuardClause:\n  Exclude:\n    - 'lib/liquid/block.rb'\n    - 'lib/liquid/block_body.rb'\n    - 'lib/liquid/context.rb'\n    - 'lib/liquid/drop.rb'\n    - 'lib/liquid/lexer.rb'\n    - 'lib/liquid/parser.rb'\n    - 'lib/liquid/profiler/hooks.rb'\n    - 'lib/liquid/standardfilters.rb'\n    - 'lib/liquid/tags/for.rb'\n    - 'lib/liquid/tags/if.rb'\n    - 'lib/liquid/utils.rb'\n    - 'lib/liquid/variable.rb'\n    - 'lib/liquid/variable_lookup.rb'\n    - 'performance/shopify/money_filter.rb'\n    - 'performance/shopify/paginate.rb'\n\n# Offense count: 8\n# This cop supports safe auto-correction (--auto-correct).\n# Configuration parameters: AllowAliasSyntax, AllowedMethods.\n# AllowedMethods: alias_method, public, protected, private\nLayout/EmptyLinesAroundAttributeAccessor:\n  Exclude:\n    - 'lib/liquid/template.rb'\n    - 'test/integration/filter_test.rb'\n    - 'test/integration/tags/include_tag_test.rb'\n    - 'test/unit/strainer_template_unit_test.rb'\n\n# Offense count: 17\n# This cop supports safe auto-correction (--auto-correct).\n# Configuration parameters: EnforcedStyle, IndentationWidth.\n# SupportedStyles: aligned, indented\nLayout/LineEndStringConcatenationIndentation:\n  Exclude:\n    - 'test/integration/tags/for_tag_test.rb'\n    - 'test/integration/tags/increment_tag_test.rb'\n\n# Offense count: 1\n# This cop supports safe auto-correction (--auto-correct).\n# Configuration parameters: EnforcedStyle, IndentationWidth.\n# SupportedStyles: aligned, indented\nLayout/MultilineOperationIndentation:\n  Exclude:\n    - 'lib/liquid/expression.rb'\n\n# Offense count: 9\nLint/MissingSuper:\n  Exclude:\n    - 'lib/liquid/forloop_drop.rb'\n    - 'lib/liquid/tablerowloop_drop.rb'\n    - 'test/integration/assign_test.rb'\n    - 'test/integration/context_test.rb'\n    - 'test/integration/filter_test.rb'\n    - 'test/integration/standard_filter_test.rb'\n    - 'test/integration/tags/for_tag_test.rb'\n    - 'test/integration/tags/table_row_test.rb'\n\n# Offense count: 44\nNaming/ConstantName:\n  Exclude:\n    - 'lib/liquid.rb'\n    - 'lib/liquid/block_body.rb'\n    - 'lib/liquid/tags/assign.rb'\n    - 'lib/liquid/tags/capture.rb'\n    - 'lib/liquid/tags/case.rb'\n    - 'lib/liquid/tags/cycle.rb'\n    - 'lib/liquid/tags/for.rb'\n    - 'lib/liquid/tags/if.rb'\n    - 'lib/liquid/tags/raw.rb'\n    - 'lib/liquid/tags/table_row.rb'\n    - 'lib/liquid/variable.rb'\n    - 'performance/shopify/comment_form.rb'\n    - 'performance/shopify/paginate.rb'\n    - 'test/integration/tags/include_tag_test.rb'\n\n# Offense count: 9\n# Configuration parameters: CheckIdentifiers, CheckConstants, CheckVariables, CheckStrings, CheckSymbols, CheckComments, CheckFilepaths, FlaggedTerms.\nNaming/InclusiveLanguage:\n  Exclude:\n    - 'lib/liquid/drop.rb'\n    - 'lib/liquid/parse_context.rb'\n    - 'test/integration/drop_test.rb'\n    - 'test/integration/tags/if_else_tag_test.rb'\n\n# Offense count: 2\nStyle/ClassVars:\n  Exclude:\n    - 'lib/liquid/condition.rb'\n\n# Offense count: 3\n# This cop supports safe auto-correction (--auto-correct).\nStyle/ExplicitBlockArgument:\n  Exclude:\n    - 'test/integration/context_test.rb'\n    - 'test/integration/tag/disableable_test.rb'\n    - 'test/integration/tags/for_tag_test.rb'\n\n# Offense count: 2982\n# This cop supports safe auto-correction (--auto-correct).\n# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.\n# SupportedStyles: single_quotes, double_quotes\nStyle/StringLiterals:\n  Enabled: false\n\n# Offense count: 20\n# This cop supports safe auto-correction (--auto-correct).\n# Configuration parameters: EnforcedStyle.\n# SupportedStyles: single_quotes, double_quotes\nStyle/StringLiteralsInInterpolation:\n  Exclude:\n    - 'lib/liquid/condition.rb'\n    - 'lib/liquid/strainer_template.rb'\n    - 'lib/liquid/tag/disableable.rb'\n    - 'performance/shopify/shop_filter.rb'\n    - 'performance/shopify/tag_filter.rb'\n\n# Offense count: 6\n# This cop supports safe auto-correction (--auto-correct).\n# Configuration parameters: EnforcedStyleForMultiline.\n# SupportedStylesForMultiline: comma, consistent_comma, no_comma\nStyle/TrailingCommaInArrayLiteral:\n  Exclude:\n    - 'example/server/example_servlet.rb'\n    - 'lib/liquid/condition.rb'\n    - 'test/integration/context_test.rb'\n    - 'test/integration/standard_filter_test.rb'\n    - 'test/unit/parse_tree_visitor_test.rb'\n\n# Offense count: 1\n# This cop supports safe auto-correction (--auto-correct).\n# Configuration parameters: EnforcedStyleForMultiline.\n# SupportedStylesForMultiline: comma, consistent_comma, no_comma\nStyle/TrailingCommaInHashLiteral:\n  Exclude:\n    - 'lib/liquid/expression.rb'\n\n# Offense count: 19\n# This cop supports safe auto-correction (--auto-correct).\n# Configuration parameters: EnforcedStyle, MinSize, WordRegex.\n# SupportedStyles: percent, brackets\nStyle/WordArray:\n  Exclude:\n    - 'lib/liquid/tags/if.rb'\n    - 'liquid.gemspec'\n    - 'test/integration/assign_test.rb'\n    - 'test/integration/context_test.rb'\n    - 'test/integration/drop_test.rb'\n    - 'test/integration/standard_filter_test.rb'\n\n# Offense count: 117\n# This cop supports safe auto-correction (--auto-correct).\n# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, AllowCopDirectives, AllowedPatterns.\n# URISchemes: http, https\nLayout/LineLength:\n  Max: 260\n\nNaming/PredicatePrefix:\n  Enabled: false\n\n# Offense count: 1\n# This is intentional - early return from begin/rescue in assignment context\nLint/NoReturnInBeginEndBlocks:\n  Exclude:\n    - 'lib/liquid/standardfilters.rb'\n"
  },
  {
    "path": ".ruby-version",
    "content": "3.4.1\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# How to contribute\n\n## Things we will merge\n\n* Bugfixes\n* Performance improvements\n* Features that are likely to be useful to the majority of Liquid users\n* Documentation updates that are concise and likely to be useful to the majority of Liquid users\n\n## Things we won't merge\n\n* Code that introduces considerable performance degrations\n* Code that touches performance-critical parts of Liquid and comes without benchmarks\n* Features that are not important for most people (we want to keep the core Liquid code small and tidy)\n* Features that can easily be implemented on top of Liquid (for example as a custom filter or custom filesystem)\n* Code that does not include tests\n* Code that breaks existing tests\n* Documentation changes that are verbose, incorrect or not important to most people (we want to keep it simple and easy to understand)\n\n## Workflow\n\n* [Sign the CLA](https://cla.shopify.com/) if you haven't already\n* Fork the Liquid repository\n* Create a new branch in your fork\n  * For updating [Liquid documentation](https://shopify.github.io/liquid/), create it from `gh-pages` branch. (You can skip tests.)\n* If it makes sense, add tests for your code and/or run a performance benchmark\n* Make sure all tests pass (`bundle exec rake`)\n* Create a pull request\n\n## Releasing\n\n* Bump the version in `lib/liquid/version.rb`\n* Update the `History.md` file\n* Open a PR like [this one](https://github.com/Shopify/liquid/pull/1894) and merge it to `main`\n* Create a new release using the [GitHub UI](https://github.com/Shopify/liquid/releases/new)\n\n"
  },
  {
    "path": "Gemfile",
    "content": "# frozen_string_literal: true\n\nsource 'https://rubygems.org'\ngit_source(:github) do |repo_name|\n  \"https://github.com/#{repo_name}.git\"\nend\n\ngemspec\n\ngem \"base64\"\n\ngroup :benchmark, :test do\n  gem 'benchmark-ips'\n  gem 'memory_profiler'\n  gem 'terminal-table'\n  gem \"lru_redux\"\n\n  install_if -> { RUBY_PLATFORM !~ /mingw|mswin|java/ && RUBY_ENGINE != 'truffleruby' } do\n    gem 'stackprof'\n  end\nend\n\ngroup :development do\n  gem \"webrick\"\nend\n\ngroup :test do\n  gem 'benchmark'\n  gem 'rubocop', '~> 1.82.0'\n  gem 'rubocop-shopify', '~> 2.18.0', require: false\n  gem 'rubocop-performance', require: false\nend\n\ngroup :spec do\n  gem 'liquid-spec', github: 'Shopify/liquid-spec', branch: 'main'\n  gem 'activesupport', require: false\nend\n"
  },
  {
    "path": "History.md",
    "content": "# Liquid Change Log\n\n## 5.11.0\n* Revert the Inline Snippets tag (#2001), treat its inclusion in the latest Liquid release as a bug, and allow for feedback on RFC#1916 to better support Liquid developers [Guilherme Carreiro]\n* Rename the `:rigid` error mode to `:strict2` and display a warning when users attempt to use the `:rigid` mode [Guilherme Carreiro]\n\n## 5.10.0\n* Introduce support for Inline Snippets [Julia Boutin]\n\n## 5.9.0\n* Introduce `:rigid` error mode for stricter, safer parsing of all tags [CP Clermont, Guilherme Carreiro]\n\n## 5.8.7\n* Expose body content in the `Doc` tag [James Meng]\n\n## 5.8.1\n\n* Fix `{% doc %}` tag to be visitable [Guilherme Carreiro]\n\n## 5.8.0\n\n* Introduce the new `{% doc %}` tag [Guilherme Carreiro]\n\n## 5.7.3\n\n* Raise Liquid::SyntaxError when parsing invalidly encoded strings [Chris AtLee]\n\n## 5.7.2 2025-01-31\n\n* Fix array filters to not support nested properties [Guilherme Carreiro]\n\n## 5.7.1 2025-01-24\n\n* Fix the `find` and `find_index`filters to return `nil` when filtering empty arrays [Guilherme Carreiro]\n* Fix the `has` filter to return `false` when filtering empty arrays [Guilherme Carreiro]\n\n## 5.7.0 2025-01-16\n\n### Features\n\n* Add `find`, `find_index`, `has`, and `reject` filters to arrays [Guilherme Carreiro]\n* Compatibility with Ruby 3.4 [Ian Ker-Seymer]\n\n## 5.6.4 2025-01-14\n\n### Fixes\n* Add a default `string_scanner` to avoid errors with `Liquid::VariableLookup.parse(\"foo.bar\")` [Ian Ker-Seymer]\n\n## 5.6.3 2025-01-13\n* Remove `lru_redux` dependency [Michael Go]\n\n## 5.6.2 2025-01-13\n\n### Fixes\n* Preserve the old behavior of requiring floats to start with a digit [Michael Go]\n\n## 5.6.1 2025-01-13\n\n### Performance improvements\n* Faster Expression parser / Tokenizer with StringScanner [Michael Go]\n\n## 5.6.0 2024-12-19\n\n### Architectural changes\n* Added new `Environment` class to manage configuration and state that was previously stored in `Template` [Ian Ker-Seymer]\n* Moved tag registration from `Template` to `Environment` [Ian Ker-Seymer]\n* Removed `StrainerFactory` in favor of `Environment`-based strainer creation [Ian Ker-Seymer]\n* Consolidated standard tags into a new `Tags` module with `STANDARD_TAGS` constant [Ian Ker-Seymer]\n\n### Performance improvements\n* Optimized `Lexer` with a new `Lexer2` implementation using jump tables for faster tokenization, requires Ruby 3.4 [Ian Ker-Seymer]\n* Improved variable rendering with specialized handling for different types [Michael Go]\n* Reduced array allocations by using frozen empty constants [Michael Go]\n\n### API changes\n* Deprecated several `Template` class methods in favor of `Environment` methods [Ian Ker-Seymer]\n* Added deprecation warnings system [Ian Ker-Seymer]\n* Changed how filters and tags are registered to use Environment [Ian Ker-Seymer]\n\n### Fixes\n* Fixed table row handling of break interrupts [Alex Coco]\n* Improved variable output handling for arrays [Ian Ker-Seymer]\n* Fix Tokenizer to handle null source value (#1873) [Bahar Pourazar]\n\n## 5.5.0 2024-03-21\n\nPlease reference the GitHub release for more information.\n\n## 5.4.0 2022-07-29\n\n### Breaking Changes\n* Drop support for end-of-life Ruby versions (2.5 and 2.6) (#1578) [Andy Waite]\n\n### Features\n* Allow `#` to be used as an inline comment tag (#1498) [CP Clermont]\n\n### Fixes\n* `PartialCache` now shares snippet cache with subcontexts by default (#1553) [Chris AtLee]\n* Hash registers no longer leak into subcontexts as static registers (#1564) [Chris AtLee]\n* Fix `ParseTreeVisitor` for `with` variable expressions in `Render` tag (#1596) [CP Clermont]\n\n### Changed\n* Liquid::Context#registers now always returns a Liquid::Registers object, though supports the most used Hash functions for compatibility (#1553)\n\n## 5.3.0 2022-03-22\n\n### Fixes\n* StandardFilter: Fix missing @context on iterations (#1525) [Thierry Joyal]\n* Fix warning about block and default value in `static_registers.rb` (#1531) [Peter Zhu]\n\n### Deprecation\n* Condition#evaluate to require mandatory context argument in Liquid 6.0.0 (#1527) [Thierry Joyal]\n\n## 5.2.0 2022-03-01\n\n### Features\n* Add `remove_last`, and `replace_last` filters (#1422) [Anders Hagbard]\n* Eagerly cache global filters (#1524) [Jean Boussier]\n\n### Fixes\n* Fix some internal errors in filters from invalid input (#1476) [Dylan Thacker-Smith]\n* Allow dash in filter kwarg name for consistency with Liquid::C (#1518) [CP Clermont]\n\n## 5.1.0 / 2021-09-09\n\n### Features\n* Add `base64_encode`, `base64_decode`, `base64_url_safe_encode`, and `base64_url_safe_decode` filters (#1450) [Daniel Insley]\n* Introduce `to_liquid_value` in `Liquid::Drop` (#1441) [Michael Go]\n\n### Fixes\n* Fix support for using a String subclass for the liquid source (#1421) [Dylan Thacker-Smith]\n* Add `ParseTreeVisitor` to `RangeLookup` (#1470) [CP Clermont]\n* Translate `RangeError` to `Liquid::Error` for `truncatewords` with large int (#1431) [Dylan Thacker-Smith]\n\n## 5.0.1 / 2021-03-24\n\n### Fixes\n* Add ParseTreeVisitor to Echo tag (#1414) [CP Clermont]\n* Test with ruby 3.0 as the latest ruby version (#1398) [Dylan Thacker-Smith]\n* Handle carriage return in newlines_to_br (#1391) [Unending]\n\n### Performance Improvements\n* Use split limit in truncatewords (#1361) [Dylan Thacker-Smith]\n\n## 5.0.0 / 2021-01-06\n\n### Features\n* Add new `{% render %}` tag (#1122) [Samuel Doiron]\n* Add support for `as` in `{% render %}` and `{% include %}` (#1181) [Mike Angell]\n* Add `{% liquid %}` and `{% echo %}` tags (#1086) [Justin Li]\n* Add [usage tracking](README.md#usage-tracking) [Mike Angell]\n* Add `Tag.disable_tags` for disabling tags that prepend `Tag::Disableable` at render time (#1162, #1274, #1275) [Mike Angell]\n* Support using a profiler for multiple renders (#1365, #1366) [Dylan Thacker-Smith]\n\n### Fixes\n* Fix catastrophic backtracking in `RANGES_REGEX` regular expression (#1357) [Dylan Thacker-Smith]\n* Make sure the for tag's limit and offset are integers (#1094) [David Cornu]\n* Invokable methods for enumerable reject include (#1151) [Thierry Joyal]\n* Allow `default` filter to handle `false` as value (#1144) [Mike Angell]\n* Fix render length resource limit so it doesn't multiply nested output (#1285) [Dylan Thacker-Smith]\n* Fix duplication of text in raw tags (#1304) [Peter Zhu]\n* Fix strict parsing of find variable with a name expression (#1317) [Dylan Thacker-Smith]\n* Use monotonic time to measure durations in Liquid::Profiler (#1362) [Dylan Thacker-Smith]\n\n### Breaking Changes\n* Require Ruby >= 2.5 (#1131, #1310) [Mike Angell, Dylan Thacker-Smith]\n* Remove support for taint checking (#1268) [Dylan Thacker-Smith]\n* Split Strainer class into StrainerFactory and StrainerTemplate (#1208) [Thierry Joyal]\n* Remove handling of a nil context in the Strainer class (#1218) [Thierry Joyal]\n* Handle `BlockBody#blank?` at parse time (#1287) [Dylan Thacker-Smith]\n* Pass the tag markup and tokenizer to `Document#unknown_tag` (#1290) [Dylan Thacker-Smith]\n* And several internal changes\n\n### Performance Improvements\n* Reduce allocations (#1073, #1091, #1115, #1099, #1117, #1141, #1322, #1341) [Richard Monette, Florian Weingarten, Ashwin Maroli]\n* Improve resources limits performance (#1093, #1323) [Florian Weingarten, Dylan Thacker-Smith]\n\n## 4.0.3 / 2019-03-12\n\n### Fixed\n* Fix break and continue tags inside included templates in loops (#1072) [Justin Li]\n\n## 4.0.2 / 2019-03-08\n\n### Changed\n* Add `where` filter (#1026) [Samuel Doiron]\n* Add `ParseTreeVisitor` to iterate the Liquid AST (#1025) [Stephen Paul Weber]\n* Improve `strip_html` performance (#1032) [printercu]\n\n### Fixed\n* Add error checking for invalid combinations of inputs to sort, sort_natural, where, uniq, map, compact filters (#1059) [Garland Zhang]\n* Validate the character encoding in url_decode (#1070) [Clayton Smith]\n\n## 4.0.1 / 2018-10-09\n\n### Changed\n* Add benchmark group in Gemfile (#855) [Jerry Liu]\n* Allow benchmarks to benchmark render by itself (#851) [Jerry Liu]\n* Avoid calling `line_number` on String node when rescuing a render error. (#860) [Dylan Thacker-Smith]\n* Avoid duck typing to detect whether to call render on a node. [Dylan Thacker-Smith]\n* Clarify spelling of `reversed` on `for` block tag (#843) [Mark Crossfield]\n* Replace recursion with loop to avoid potential stack overflow from malicious input (#891, #892) [Dylan Thacker-Smith]\n* Limit block tag nesting to 100 (#894) [Dylan Thacker-Smith]\n* Replace `assert_equal nil` with `assert_nil` (#895) [Dylan Thacker-Smith]\n* Remove Spy Gem (#896) [Dylan Thacker-Smith]\n* Add `collection_name` and `variable_name` reader to `For` block (#909)\n* Symbols render as strings (#920) [Justin Li]\n* Remove default value from Hash objects (#932) [Maxime Bedard]\n* Remove one level of nesting (#944) [Dylan Thacker-Smith]\n* Update Rubocop version (#952) [Justin Li]\n* Add `at_least` and `at_most` filters (#954, #958) [Nithin Bekal]\n* Add a regression test for a liquid-c trim mode bug (#972) [Dylan Thacker-Smith]\n* Use https rather than git protocol to fetch liquid-c [Dylan Thacker-Smith]\n* Add tests against Ruby 2.4 (#963) and 2.5 (#981)\n* Replace RegExp literals with constants (#988) [Ashwin Maroli]\n* Replace unnecessary `#each_with_index` with `#each` (#992) [Ashwin Maroli]\n* Improve the unexpected end delimiter message for block tags. (#1003) [Dylan Thacker-Smith]\n* Refactor and optimize rendering (#1005) [Christopher Aue]\n* Add installation instruction (#1006) [Ben Gift]\n* Remove Circle CI (#1010)\n* Rename deprecated `BigDecimal.new` to `BigDecimal` (#1024) [Koichi ITO]\n* Rename deprecated Rubocop name (#1027) [Justin Li]\n\n### Fixed\n* Handle `join` filter on non String joiners (#857) [Richard Monette]\n* Fix duplicate inclusion condition logic error of `Liquid::Strainer.add_filter` method (#861)\n* Fix `escape`, `url_encode`, `url_decode` not handling non-string values (#898) [Thierry Joyal]\n* Fix raise when variable is defined but nil when using `strict_variables` [Pascal Betz]\n* Fix `sort` and `sort_natural` to handle arrays with nils (#930) [Eric Chan]\n\n## 4.0.0 / 2016-12-14 / branch \"4-0-stable\"\n\n### Changed\n* Render an opaque internal error by default for non-Liquid::Error (#835) [Dylan Thacker-Smith]\n* Ruby 2.0 support dropped (#832) [Dylan Thacker-Smith]\n* Add to_number Drop method to allow custom drops to work with number filters (#731)\n* Add strict_variables and strict_filters options to detect undefined references (#691)\n* Improve loop performance (#681) [Florian Weingarten]\n* Rename Drop method `before_method` to `liquid_method_missing` (#661) [Thierry Joyal]\n* Add url_decode filter to invert url_encode (#645) [Larry Archer]\n* Add global_filter to apply a filter to all output (#610) [Loren Hale]\n* Add compact filter (#600) [Carson Reinke]\n* Rename deprecated \"has_key?\" and \"has_interrupt?\" methods (#593) [Florian Weingarten]\n* Include template name with line numbers in render errors (574) [Dylan Thacker-Smith]\n* Add sort_natural filter (#554) [Martin Hanzel]\n* Add forloop.parentloop as a reference to the parent loop (#520) [Justin Li]\n* Block parsing moved to BlockBody class (#458) [Dylan Thacker-Smith]\n* Add concat filter to concatenate arrays (#429) [Diogo Beato]\n* Ruby 1.9 support dropped (#491) [Justin Li]\n* Liquid::Template.file_system's read_template_file method is no longer passed the context. (#441) [James Reid-Smith]\n* Remove `liquid_methods` (See https://github.com/Shopify/liquid/pull/568 for replacement)\n* Liquid::Template.register_filter raises when the module overrides registered public methods as private or protected (#705) [Gaurav Chande]\n\n### Fixed\n\n* Fix variable names being detected as an operator when starting with contains (#788) [Michael Angell]\n* Fix include tag used with strict_variables (#828) [QuickPay]\n* Fix map filter when value is a Proc (#672) [Guillaume Malette]\n* Fix truncate filter when value is not a string (#672) [Guillaume Malette]\n* Fix behaviour of escape filter when input is nil (#665) [Tanel Jakobsoo]\n* Fix sort filter behaviour with empty array input (#652) [Marcel Cary]\n* Fix test failure under certain timezones (#631) [Dylan Thacker-Smith]\n* Fix bug in uniq filter (#595) [Florian Weingarten]\n* Fix bug when \"blank\" and \"empty\" are used as variable names (#592) [Florian Weingarten]\n* Fix condition parse order in strict mode (#569) [Justin Li]\n* Fix naming of the \"context variable\" when dynamically including a template (#559) [Justin Li]\n* Gracefully accept empty strings in the date filter (#555) [Loren Hale]\n* Fix capturing into variables with a hyphen in the name (#505) [Florian Weingarten]\n* Fix case sensitivity regression in date standard filter (#499) [Kelley Reynolds]\n* Disallow filters with no variable in strict mode (#475) [Justin Li]\n* Disallow variable names in the strict parser that are not valid in the lax parser (#463) [Justin Li]\n* Fix BlockBody#warnings taking exponential time to compute (#486) [Justin Li]\n\n## 3.0.5 / 2015-07-23 / branch \"3-0-stable\"\n\n* Fix test failure under certain timezones [Dylan Thacker-Smith]\n\n## 3.0.4 / 2015-07-17\n\n* Fix chained access to multi-dimensional hashes [Florian Weingarten]\n\n## 3.0.3 / 2015-05-28\n\n* Fix condition parse order in strict mode (#569) [Justin Li]\n\n## 3.0.2 / 2015-04-24\n\n* Expose VariableLookup private members (#551) [Justin Li]\n* Documentation fixes\n\n## 3.0.1 / 2015-01-23\n\n* Remove duplicate `index0` key in TableRow tag (#502) [Alfred Xing]\n\n## 3.0.0 / 2014-11-12\n\n* Removed Block#end_tag. Instead, override parse with `super` followed by your code. See #446 [Dylan Thacker-Smith]\n* Fixed condition with wrong data types (#423) [Bogdan Gusiev]\n* Add url_encode to standard filters (#421) [Derrick Reimer]\n* Add uniq to standard filters [Florian Weingarten]\n* Add exception_handler feature (#397) and #254 [Bogdan Gusiev, Florian Weingarten]\n* Optimize variable parsing to avoid repeated regex evaluation during template rendering #383 [Jason Hiltz-Laforge]\n* Optimize checking for block interrupts to reduce object allocation #380 [Jason Hiltz-Laforge]\n* Properly set context rethrow_errors on render! #349 [Thierry Joyal]\n* Fix broken rendering of variables which are equal to false (#345) [Florian Weingarten]\n* Remove ActionView template handler [Dylan Thacker-Smith]\n* Freeze lots of string literals for new Ruby 2.1 optimization (#297) [Florian Weingarten]\n* Allow newlines in tags and variables (#324) [Dylan Thacker-Smith]\n* Tag#parse is called after initialize, which now takes options instead of tokens as the 3rd argument. See #321 [Dylan Thacker-Smith]\n* Raise `Liquid::ArgumentError` instead of `::ArgumentError` when filter has wrong number of arguments #309 [Bogdan Gusiev]\n* Add a to_s default for liquid drops (#306) [Adam Doeler]\n* Add strip, lstrip, and rstrip to standard filters [Florian Weingarten]\n* Make if, for & case tags return complete and consistent nodelists (#250) [Nick Jones]\n* Prevent arbitrary method invocation on condition objects (#274) [Dylan Thacker-Smith]\n* Don't call to_sym when creating conditions for security reasons (#273) [Bouke van der Bijl]\n* Fix resource counting bug with respond_to?(:length) (#263) [Florian Weingarten]\n* Allow specifying custom patterns for template filenames (#284) [Andrei Gladkyi]\n* Allow drops to optimize loading a slice of elements (#282) [Tom Burns]\n* Support for passing variables to snippets in subdirs (#271) [Joost Hietbrink]\n* Add a class cache to avoid runtime extend calls (#249) [James Tucker]\n* Remove some legacy Ruby 1.8 compatibility code (#276) [Florian Weingarten]\n* Add default filter to standard filters (#267) [Derrick Reimer]\n* Add optional strict parsing and warn parsing (#235) [Tristan Hume]\n* Add I18n syntax error translation (#241) [Simon Hørup Eskildsen, Sirupsen]\n* Make sort filter work on enumerable drops (#239) [Florian Weingarten]\n* Fix clashing method names in enumerable drops (#238) [Florian Weingarten]\n* Make map filter work on enumerable drops (#233) [Florian Weingarten]\n* Improved whitespace stripping for blank blocks, related to #216 [Florian Weingarten]\n\n## 2.6.3 / 2015-07-23 / branch \"2-6-stable\"\n\n* Fix test failure under certain timezones [Dylan Thacker-Smith]\n\n## 2.6.2 / 2015-01-23\n\n* Remove duplicate hash key [Parker Moore]\n\n## 2.6.1 / 2014-01-10\n\nSecurity fix, cherry-picked from master (4e14a65):\n* Don't call to_sym when creating conditions for security reasons (#273) [Bouke van der Bijl]\n* Prevent arbitrary method invocation on condition objects (#274) [Dylan Thacker-Smith]\n\n## 2.6.0 / 2013-11-25\n\nIMPORTANT: Liquid 2.6 is going to be the last version of Liquid which maintains explicit Ruby 1.8 compatability.\nThe following releases will only be tested against Ruby 1.9 and Ruby 2.0 and are likely to break on Ruby 1.8.\n\n* Bugfix for #106: fix example servlet [gnowoel]\n* Bugfix for #97: strip_html filter supports multi-line tags [Jo Liss]\n* Bugfix for #114: strip_html filter supports style tags [James Allardice]\n* Bugfix for #117: 'now' support for date filter in Ruby 1.9 [Notre Dame Webgroup]\n* Bugfix for #166: truncate filter on UTF-8 strings with Ruby 1.8 [Florian Weingarten]\n* Bugfix for #204: 'raw' parsing bug [Florian Weingarten]\n* Bugfix for #150: 'for' parsing bug [Peter Schröder]\n* Bugfix for #126: Strip CRLF in strip_newline [Peter Schröder]\n* Bugfix for #174, \"can't convert Fixnum into String\" for \"replace\" [jsw0528]\n* Allow a Liquid::Drop to be passed into Template#render [Daniel Huckstep]\n* Resource limits [Florian Weingarten]\n* Add reverse filter [Jay Strybis]\n* Add utf-8 support\n* Use array instead of Hash to keep the registered filters [Tasos Stathopoulos]\n* Cache tokenized partial templates [Tom Burns]\n* Avoid warnings in Ruby 1.9.3 [Marcus Stollsteimer]\n* Better documentation for 'include' tag (closes #163) [Peter Schröder]\n* Use of BigDecimal on filters to have better precision (closes #155) [Arthur Nogueira Neves]\n\n## 2.5.5 / 2014-01-10 / branch \"2-5-stable\"\n\nSecurity fix, cherry-picked from master (4e14a65):\n* Don't call to_sym when creating conditions for security reasons (#273) [Bouke van der Bijl]\n* Prevent arbitrary method invocation on condition objects (#274) [Dylan Thacker-Smith]\n\n## 2.5.4 / 2013-11-11\n\n* Fix \"can't convert Fixnum into String\" for \"replace\" (#173), [jsw0528]\n\n## 2.5.3 / 2013-10-09\n\n* #232, #234, #237: Fix map filter bugs [Florian Weingarten]\n\n## 2.5.2 / 2013-09-03 / deleted\n\nYanked from rubygems, as it contained too many changes that broke compatibility. Those changes will be on following major releases.\n\n## 2.5.1 / 2013-07-24\n\n* #230: Fix security issue with map filter, Use invoke_drop in map filter [Florian Weingarten]\n\n## 2.5.0 / 2013-03-06\n\n* Prevent Object methods from being called on drops\n* Avoid symbol injection from liquid\n* Added break and continue statements\n* Fix filter parser for args without space separators\n* Add support for filter keyword arguments\n\n\n## 2.4.0 / 2012-08-03\n\n* Performance improvements\n* Allow filters in `assign`\n* Add `modulo` filter\n* Ruby 1.8, 1.9, and Rubinius compatibility fixes\n* Add support for `quoted['references']` in `tablerow`\n* Add support for Enumerable to `tablerow`\n* `strip_html` filter removes html comments\n\n\n## 2.3.0 / 2011-10-16\n\n* Several speed/memory improvements\n* Numerous bug fixes\n* Added support for MRI 1.9, Rubinius, and JRuby\n* Added support for integer drop parameters\n* Added epoch support to `date` filter\n* New `raw` tag that suppresses parsing\n* Added `else` option to `for` tag\n* New `increment` tag\n* New `split` filter\n\n\n## 2.2.1 / 2010-08-23\n\n* Added support for literal tags\n\n\n## 2.2.0 / 2010-08-22\n\n* Compatible with Ruby 1.8.7, 1.9.1 and 1.9.2-p0\n* Merged some changed made by the community\n\n\n## 1.9.0 / 2008-03-04\n\n* Fixed gem install rake task\n* Improve Error encapsulation in liquid by maintaining a own set of exceptions instead of relying on ruby build ins\n\n\n## Before 1.9.0\n\n* Added If with or / and expressions\n* Implemented .to_liquid for all objects which can be passed to liquid like Strings Arrays Hashes Numerics and Booleans. To export new objects to liquid just implement .to_liquid on them and return objects which themselves have .to_liquid methods.\n* Added more tags to standard library\n* Added include tag ( like partials in rails )\n* [...] Gazillion of detail improvements\n* Added strainers as filter hosts for better security [Tobias Luetke]\n* Fixed that rails integration would call filter with the wrong \"self\" [Michael Geary]\n* Fixed bad error reporting when a filter called a method which doesn't exist. Liquid told you that it couldn't find the filter which was obviously misleading [Tobias Luetke]\n* Removed count helper from standard lib. use size [Tobias Luetke]\n* Fixed bug with string filter parameters failing to tolerate commas in strings. [Paul Hammond]\n* Improved filter parameters. Filter parameters are now context sensitive; Types are resolved according to the rules of the context. Multiple parameters are now separated by the Liquid::ArgumentSeparator: , by default [Paul Hammond]\n    {{ 'Typo' | link_to: 'http://typo.leetsoft.com', 'Typo - a modern weblog engine' }}\n* Added Liquid::Drop. A base class which you can use for exporting proxy objects to liquid which can acquire more data when used in liquid. [Tobias Luetke]\n\n  class ProductDrop < Liquid::Drop\n    def top_sales\n       Shop.current.products.find(:all, :order => 'sales', :limit => 10 )\n    end\n  end\n  t = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {% endfor %} '  )\n  t.render('product' => ProductDrop.new )\n* Added filter parameters support. Example: {{ date | format_date: \"%Y\" }} [Paul Hammond]\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2005, 2006 Tobias Luetke\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "[![Build status](https://github.com/Shopify/liquid/actions/workflows/liquid.yml/badge.svg)](https://github.com/Shopify/liquid/actions/workflows/liquid.yml)\n[![Inline docs](http://inch-ci.org/github/Shopify/liquid.svg?branch=master)](http://inch-ci.org/github/Shopify/liquid)\n\n# Liquid template engine\n\n* [Contributing guidelines](CONTRIBUTING.md)\n* [Version history](History.md)\n* [Liquid documentation from Shopify](https://shopify.dev/docs/api/liquid)\n* [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki)\n* [Website](http://liquidmarkup.org/)\n\n## Introduction\n\nLiquid is a template engine which was written with very specific requirements:\n\n* It has to have beautiful and simple markup. Template engines which don't produce good looking markup are no fun to use.\n* It needs to be non evaling and secure. Liquid templates are made so that users can edit them. You don't want to run code on your server which your users wrote.\n* It has to be stateless. Compile and render steps have to be separate so that the expensive parsing and compiling can be done once and later on you can just render it passing in a hash with local variables and objects.\n\n## Why you should use Liquid\n\n* You want to allow your users to edit the appearance of your application but don't want them to run **insecure code on your server**.\n* You want to render templates directly from the database.\n* You like smarty (PHP) style template engines.\n* You need a template engine which does HTML just as well as emails.\n* You don't like the markup of your current templating engine.\n\n## What does it look like?\n\n```html\n<ul id=\"products\">\n  {% for product in products %}\n    <li>\n      <h2>{{ product.name }}</h2>\n      Only {{ product.price | price }}\n\n      {{ product.description | prettyprint | paragraph }}\n    </li>\n  {% endfor %}\n</ul>\n```\n\n## How to use Liquid\n\nInstall Liquid by adding `gem 'liquid'` to your gemfile.\n\nLiquid supports a very simple API based around the Liquid::Template class.\nFor standard use you can just pass it the content of a file and call render with a parameters hash.\n\n```ruby\n@template = Liquid::Template.parse(\"hi {{name}}\") # Parses and compiles the template\n@template.render('name' => 'tobi')                # => \"hi tobi\"\n```\n\n### Concept of Environments\n\nIn Liquid, a \"Environment\" is a scoped environment that encapsulates custom tags, filters, and other configurations. This allows you to define and isolate different sets of functionality for different contexts, avoiding global overrides that can lead to conflicts and unexpected behavior.\n\nBy using environments, you can:\n\n1. **Encapsulate Logic**: Keep the logic for different parts of your application separate.\n2. **Avoid Conflicts**: Prevent custom tags and filters from clashing with each other.\n3. **Improve Maintainability**: Make it easier to manage and understand the scope of customizations.\n4. **Enhance Security**: Limit the availability of certain tags and filters to specific contexts.\n\nWe encourage the use of Environments over globally overriding things because it promotes better software design principles such as modularity, encapsulation, and separation of concerns.\n\nHere's an example of how you can define and use Environments in Liquid:\n\n```ruby\nuser_environment = Liquid::Environment.build do |environment|\n  environment.register_tag(\"renderobj\", RenderObjTag)\nend\n\nLiquid::Template.parse(<<~LIQUID, environment: user_environment)\n  {% renderobj src: \"path/to/model.obj\" %}\nLIQUID\n```\n\nIn this example, `RenderObjTag` is a custom tag that is only available within the `user_environment`.\n\nSimilarly, you can define another environment for a different context, such as email templates:\n\n```ruby\nemail_environment = Liquid::Environment.build do |environment|\n  environment.register_tag(\"unsubscribe_footer\", UnsubscribeFooter)\nend\n\nLiquid::Template.parse(<<~LIQUID, environment: email_environment)\n  {% unsubscribe_footer %}\nLIQUID\n```\n\nBy using Environments, you ensure that custom tags and filters are only available in the contexts where they are needed, making your Liquid templates more robust and easier to manage. For smaller projects, a global environment is available via `Liquid::Environment.default`.\n\n### Error Modes\n\nSetting the error mode of Liquid lets you specify how strictly you want your templates to be interpreted.\nNormally the parser is very lax and will accept almost anything without error. Unfortunately this can make\nit very hard to debug and can lead to unexpected behaviour.\n\nLiquid also comes with different parsers that can be used when editing templates to give better error messages\nwhen templates are invalid. You can enable this new parser like this:\n\n```ruby\nLiquid::Environment.default.error_mode = :strict2 # Raises a SyntaxError when invalid syntax is used in all tags\nLiquid::Environment.default.error_mode = :strict  # Raises a SyntaxError when invalid syntax is used in some tags\nLiquid::Environment.default.error_mode = :warn    # Adds strict errors to template.errors but continues as normal\nLiquid::Environment.default.error_mode = :lax     # The default mode, accepts almost anything.\n```\n\nIf you want to set the error mode only on specific templates you can pass `:error_mode` as an option to `parse`:\n```ruby\nLiquid::Template.parse(source, error_mode: :strict)\n```\nThis is useful for doing things like enabling strict mode only in the theme editor.\n\nIt is recommended that you enable `:strict` or `:warn` mode on new apps to stop invalid templates from being created.\nIt is also recommended that you use it in the template editors of existing apps to give editors better error messages.\n\n### Undefined variables and filters\n\nBy default, the renderer doesn't raise or in any other way notify you if some variables or filters are missing, i.e. not passed to the `render` method.\nYou can improve this situation by passing `strict_variables: true` and/or `strict_filters: true` options to the `render` method.\nWhen one of these options is set to true, all errors about undefined variables and undefined filters will be stored in `errors` array of a `Liquid::Template` instance.\nHere are some examples:\n\n```ruby\ntemplate = Liquid::Template.parse(\"{{x}} {{y}} {{z.a}} {{z.b}}\")\ntemplate.render({ 'x' => 1, 'z' => { 'a' => 2 } }, { strict_variables: true })\n#=> '1  2 ' # when a variable is undefined, it's rendered as nil\ntemplate.errors\n#=> [#<Liquid::UndefinedVariable: Liquid error: undefined variable y>, #<Liquid::UndefinedVariable: Liquid error: undefined variable b>]\n```\n\n```ruby\ntemplate = Liquid::Template.parse(\"{{x | filter1 | upcase}}\")\ntemplate.render({ 'x' => 'foo' }, { strict_filters: true })\n#=> '' # when at least one filter in the filter chain is undefined, a whole expression is rendered as nil\ntemplate.errors\n#=> [#<Liquid::UndefinedFilter: Liquid error: undefined filter filter1>]\n```\n\nIf you want to raise on a first exception instead of pushing all of them in `errors`, you can use `render!` method:\n\n```ruby\ntemplate = Liquid::Template.parse(\"{{x}} {{y}}\")\ntemplate.render!({ 'x' => 1}, { strict_variables: true })\n#=> Liquid::UndefinedVariable: Liquid error: undefined variable y\n```\n\n### Usage tracking\n\nTo help track usages of a feature or code path in production, we have released opt-in usage tracking. To enable this, we provide an empty `Liquid:: Usage.increment` method which you can customize to your needs. The feature is well suited to https://github.com/Shopify/statsd-instrument. However, the choice of implementation is up to you.\n\nOnce you have enabled usage tracking, we recommend reporting any events through Github Issues that your system may be logging. It is highly likely this event has been added to consider deprecating or improving code specific to this event, so please raise any concerns.\n"
  },
  {
    "path": "Rakefile",
    "content": "# frozen_string_literal: true\n\nrequire 'rake'\nrequire 'rake/testtask'\n$LOAD_PATH.unshift(File.expand_path(\"../lib\", __FILE__))\nrequire \"liquid/version\"\n\ntask(default: [:test, :rubocop])\n\ndesc('run test suite with default parser')\nRake::TestTask.new(:base_test) do |t|\n  t.libs << 'lib' << 'test'\n  t.test_files = FileList['test/{integration,unit}/**/*_test.rb']\n  t.verbose    = false\nend\n\nRake::TestTask.new(:integration_test) do |t|\n  t.libs << 'lib' << 'test'\n  t.test_files = FileList['test/integration/**/*_test.rb']\n  t.verbose    = false\nend\n\ndesc('run test suite with warn error mode')\ntask :warn_test do\n  ENV['LIQUID_PARSER_MODE'] = 'warn'\n  Rake::Task['base_test'].invoke\nend\n\ntask :rubocop do\n  if RUBY_ENGINE == 'ruby'\n    require 'rubocop/rake_task'\n    RuboCop::RakeTask.new\n  end\nend\n\ndesc('runs test suite with lax, strict, and strict2 parsers')\ntask :test do\n  ENV['LIQUID_PARSER_MODE'] = 'lax'\n  Rake::Task['base_test'].invoke\n\n  ENV['LIQUID_PARSER_MODE'] = 'strict'\n  Rake::Task['base_test'].reenable\n  Rake::Task['base_test'].invoke\n\n  ENV['LIQUID_PARSER_MODE'] = 'strict2'\n  Rake::Task['base_test'].reenable\n  Rake::Task['base_test'].invoke\n\n  if RUBY_ENGINE == 'ruby' || RUBY_ENGINE == 'truffleruby'\n    ENV['LIQUID_PARSER_MODE'] = 'lax'\n    Rake::Task['integration_test'].reenable\n    Rake::Task['integration_test'].invoke\n\n    ENV['LIQUID_PARSER_MODE'] = 'strict'\n    Rake::Task['integration_test'].reenable\n    Rake::Task['integration_test'].invoke\n\n    ENV['LIQUID_PARSER_MODE'] = 'strict2'\n    Rake::Task['integration_test'].reenable\n    Rake::Task['integration_test'].invoke\n  end\nend\n\ntask(gem: :build)\ntask :build do\n  system \"gem build liquid.gemspec\"\nend\n\ntask install: :build do\n  system \"gem install liquid-#{Liquid::VERSION}.gem\"\nend\n\ntask release: :build do\n  system \"git tag -a v#{Liquid::VERSION} -m 'Tagging #{Liquid::VERSION}'\"\n  system \"git push --tags\"\n  system \"gem push liquid-#{Liquid::VERSION}.gem\"\n  system \"rm liquid-#{Liquid::VERSION}.gem\"\nend\n\nnamespace :benchmark do\n  desc \"Run the liquid benchmark with lax parsing\"\n  task :lax do\n    ruby \"./performance/benchmark.rb lax\"\n  end\n\n  desc \"Run the liquid benchmark with strict parsing\"\n  task :strict do\n    ruby \"./performance/benchmark.rb strict\"\n  end\n\n  desc \"Run the liquid benchmark with strict2 parsing\"\n  task :strict2 do\n    ruby \"./performance/benchmark.rb strict2\"\n  end\n\n  desc \"Run the liquid benchmark with lax, strict, and strict2 parsing\"\n  task run: [:lax, :strict, :strict2]\n\n  desc \"Run unit benchmarks\"\n  namespace :unit do\n    task :all do\n      Dir[\"./performance/unit/*_benchmark.rb\"].each do |file|\n        puts \"🧪 Running #{file}\"\n        ruby file\n      end\n    end\n\n    task :lexer do\n      Dir[\"./performance/unit/lexer_benchmark.rb\"].each do |file|\n        puts \"🧪 Running #{file}\"\n        ruby file\n      end\n    end\n\n    task :expression do\n      Dir[\"./performance/unit/expression_benchmark.rb\"].each do |file|\n        puts \"🧪 Running #{file}\"\n        ruby file\n      end\n    end\n  end\nend\n\nnamespace :profile do\n  desc \"Run the liquid profile/performance coverage\"\n  task :run do\n    ruby \"./performance/profile.rb\"\n  end\n\n  desc \"Run the liquid profile/performance coverage with strict parsing\"\n  task :strict do\n    ruby \"./performance/profile.rb strict\"\n  end\nend\n\nnamespace :memory_profile do\n  desc \"Run memory profiler\"\n  task :run do\n    ruby \"./performance/memory_profile.rb\"\n  end\nend\n\ndesc(\"Run example\")\ntask :example do\n  ruby \"-w -d -Ilib example/server/server.rb\"\nend\n\ntask :console do\n  exec 'irb -I lib -r liquid'\nend\n\ndesc('run liquid-spec suite across all adapters')\ntask :spec do\n  adapters = Dir['./spec/*.rb'].join(',')\n  sh \"bundle exec liquid-spec matrix --adapters=#{adapters} --reference=ruby_liquid\"\nend\n"
  },
  {
    "path": "bin/render",
    "content": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nrequire 'bundler/setup'\nrequire 'liquid'\n\nclass VirtualFileSystem\n  def initialize\n    snippet_1 = <<~LIQUID\n      <h1>\n        {{- greating | default: 'Hello' }}, {{ name | default: 'world' -}}!\n      </h1>\n    LIQUID\n    snippet_2 = <<~LIQUID\n      {%- for i in (1..5) -%}\n        > {{ i }}\n      {%- endfor -%}\n    LIQUID\n\n    @templates = {\n      'snippet-1' => snippet_1,\n      'snippet-2' => snippet_2,\n    }\n  end\n\n  def read_template_file(key)\n    @templates[key] || raise(Liquid::FileSystemError, \"No such template '#{key}'\")\n  end\nend\n\ndef source\n  File.read(ARGV[0])\nrescue StandardError\n  'Usage: bin/render example/server/templates/index.liquid'\nend\n\ndef assigns\n  {\n    'date' => Time.now,\n  }\nend\n\nputs Liquid::Template\n  .parse(source, error_mode: :strict2)\n  .tap { |t| t.registers[:file_system] = VirtualFileSystem.new }\n  .render(assigns)\n"
  },
  {
    "path": "example/server/example_servlet.rb",
    "content": "# frozen_string_literal: true\n\nmodule ProductsFilter\n  def price(integer)\n    format(\"$%.2d USD\", integer / 100.0)\n  end\n\n  def prettyprint(text)\n    text.gsub(/\\*(.*)\\*/, '<b>\\1</b>')\n  end\n\n  def count(array)\n    array.size\n  end\n\n  def paragraph(p)\n    \"<p>#{p}</p>\"\n  end\nend\n\nclass Servlet < LiquidServlet\n  def index\n    { 'date' => Time.now }\n  end\n\n  def products\n    { 'products' => products_list, 'more_products' => more_products_list, 'description' => description, 'section' => 'Snowboards', 'cool_products' => true }\n  end\n\n  private\n\n  def products_list\n    [\n      { 'name' => 'Arbor Draft', 'price' => 39900, 'description' => 'the *arbor draft* is a excellent product' },\n      { 'name' => 'Arbor Element', 'price' => 40000, 'description' => 'the *arbor element* rocks for freestyling' },\n      { 'name' => 'Arbor Diamond', 'price' => 59900, 'description' => 'the *arbor diamond* is a made up product because im obsessed with arbor and have no creativity' }\n    ]\n  end\n\n  def more_products_list\n    [\n      { 'name' => 'Arbor Catalyst', 'price' => 39900, 'description' => 'the *arbor catalyst* is an advanced drop-through for freestyle and flatground performance and versatility' },\n      { 'name' => 'Arbor Fish', 'price' => 40000, 'description' => 'the *arbor fish* is a compact pin that features an extended wheelbase and time-honored teardrop shape' }\n    ]\n  end\n\n  def description\n    \"List of Products ~ This is a list of products with price and description.\"\n  end\nend\n"
  },
  {
    "path": "example/server/liquid_servlet.rb",
    "content": "# frozen_string_literal: true\n\nclass LiquidServlet < WEBrick::HTTPServlet::AbstractServlet\n  def do_GET(req, res)\n    handle(:get, req, res)\n  end\n\n  def do_POST(req, res)\n    handle(:post, req, res)\n  end\n\n  private\n\n  def handle(_type, req, res)\n    @request  = req\n    @response = res\n\n    @request.path_info =~ /(\\w+)\\z/\n    @action  = Regexp.last_match(1) || 'index'\n    @assigns = send(@action) if respond_to?(@action)\n\n    @response['Content-Type'] = \"text/html\"\n    @response.status = 200\n    @response.body   = Liquid::Template.parse(read_template).render(@assigns, filters: [ProductsFilter])\n  end\n\n  def read_template(filename = @action)\n    File.read(\"#{__dir__}/templates/#{filename}.liquid\")\n  end\nend\n"
  },
  {
    "path": "example/server/server.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'webrick'\nrequire 'rexml/document'\n\nrequire_relative '../../lib/liquid'\nrequire_relative 'liquid_servlet'\nrequire_relative 'example_servlet'\n\n# Setup webrick\nserver = WEBrick::HTTPServer.new(Port: ARGV[1] || 3000)\nserver.mount('/', Servlet)\ntrap(\"INT\") { server.shutdown }\nserver.start\n"
  },
  {
    "path": "example/server/templates/index.liquid",
    "content": "<p>Hello world!</p>\n\n<p>It is {{date}}</p>\n\n\n<p>Check out the <a href=\"/products\">Products</a> screen </p>\n"
  },
  {
    "path": "example/server/templates/products.liquid",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n  \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n  <head>\n    <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\" />\n    <meta http-equiv=\"Content-Language\" content=\"en-us\" />\n\n    <title>products</title>\n\n    <meta name=\"ROBOTS\" content=\"ALL\" />\n    <meta http-equiv=\"imagetoolbar\" content=\"no\" />\n    <meta name=\"MSSmartTagsPreventParsing\" content=\"true\" />\n    <meta name=\"Copyright\" content=\"(c) 2005 Copyright content:  Copyright design: Tobias Luetke\" />\n    <!-- (c) Copyright 2005 by Tobias Luetke All Rights Reserved. -->\n  </head>\n\n  <body>\n    {% assign all_products = products | concat: more_products  %}\n    <h1>{{ description | split: '~' | first }}</h1>\n\n    <h2>{{ description | split: '~' | last }}</h2>\n\n    <h2>There are currently {{all_products | count}} products in the {{section}} catalog</h2>\n\n    {% if cool_products %}\n      Cool products :)\n    {% else %}\n      Uncool products :(\n    {% endif %}\n\n    <ul id=\"products\">\n\n      {% for product in all_products %}\n        <li>\n          <h2>{{product.name}}</h2>\n          Only {{product.price | price }}\n\n          {{product.description | prettyprint | paragraph }}\n\n          {{ 'it rocks!' | paragraph }}\n\n        </li>\n      {% endfor %}\n\n    </ul>\n\n  </body>\n</html>\n"
  },
  {
    "path": "lib/liquid/block.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  class Block < Tag\n    MAX_DEPTH = 100\n\n    def initialize(tag_name, markup, options)\n      super\n      @blank = true\n    end\n\n    def parse(tokens)\n      @body = new_body\n      while parse_body(@body, tokens)\n      end\n      @body.freeze\n    end\n\n    # For backwards compatibility\n    def render(context)\n      @body.render(context)\n    end\n\n    def blank?\n      @blank\n    end\n\n    def nodelist\n      @body.nodelist\n    end\n\n    def unknown_tag(tag_name, _markup, _tokenizer)\n      Block.raise_unknown_tag(tag_name, block_name, block_delimiter, parse_context)\n    end\n\n    # @api private\n    def self.raise_unknown_tag(tag, block_name, block_delimiter, parse_context)\n      if tag == 'else'\n        raise SyntaxError, parse_context.locale.t(\n          \"errors.syntax.unexpected_else\",\n          block_name: block_name,\n        )\n      elsif tag.start_with?('end')\n        raise SyntaxError, parse_context.locale.t(\n          \"errors.syntax.invalid_delimiter\",\n          tag: tag,\n          block_name: block_name,\n          block_delimiter: block_delimiter,\n        )\n      else\n        raise SyntaxError, parse_context.locale.t(\"errors.syntax.unknown_tag\", tag: tag)\n      end\n    end\n\n    def raise_tag_never_closed(block_name)\n      raise SyntaxError, parse_context.locale.t(\"errors.syntax.tag_never_closed\", block_name: block_name)\n    end\n\n    def block_name\n      @tag_name\n    end\n\n    def block_delimiter\n      @block_delimiter ||= \"end#{block_name}\"\n    end\n\n    private\n\n    # @api public\n    def new_body\n      parse_context.new_block_body\n    end\n\n    # @api public\n    def parse_body(body, tokens)\n      if parse_context.depth >= MAX_DEPTH\n        raise StackLevelError, \"Nesting too deep\"\n      end\n      parse_context.depth += 1\n      begin\n        body.parse(tokens, parse_context) do |end_tag_name, end_tag_params|\n          @blank &&= body.blank?\n\n          return false if end_tag_name == block_delimiter\n          raise_tag_never_closed(block_name) unless end_tag_name\n\n          # this tag is not registered with the system\n          # pass it to the current block for special handling or error reporting\n          unknown_tag(end_tag_name, end_tag_params, tokens)\n        end\n      ensure\n        parse_context.depth -= 1\n      end\n\n      true\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/block_body.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'English'\n\nmodule Liquid\n  class BlockBody\n    LiquidTagToken      = /\\A\\s*(#{TagName})\\s*(.*?)\\z/o\n    FullToken           = /\\A#{TagStart}#{WhitespaceControl}?(\\s*)(#{TagName})(\\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\\z/om\n    FullTokenPossiblyInvalid = /\\A(.*)#{TagStart}#{WhitespaceControl}?\\s*(\\w+)\\s*(.*)?#{WhitespaceControl}?#{TagEnd}\\z/om\n    ContentOfVariable   = /\\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\\z/om\n    WhitespaceOrNothing = /\\A\\s*\\z/\n    TAGSTART            = \"{%\"\n    VARSTART            = \"{{\"\n\n    attr_reader :nodelist\n\n    def initialize\n      @nodelist = []\n      @blank    = true\n    end\n\n    def parse(tokenizer, parse_context, &block)\n      raise FrozenError, \"can't modify frozen Liquid::BlockBody\" if frozen?\n\n      parse_context.line_number = tokenizer.line_number\n\n      if tokenizer.for_liquid_tag\n        parse_for_liquid_tag(tokenizer, parse_context, &block)\n      else\n        parse_for_document(tokenizer, parse_context, &block)\n      end\n    end\n\n    def freeze\n      @nodelist.freeze\n      super\n    end\n\n    private def parse_for_liquid_tag(tokenizer, parse_context)\n      while (token = tokenizer.shift)\n        unless token.empty? || token.match?(WhitespaceOrNothing)\n          unless token =~ LiquidTagToken\n            # line isn't empty but didn't match tag syntax, yield and let the\n            # caller raise a syntax error\n            return yield token, token\n          end\n          tag_name = Regexp.last_match(1)\n          markup   = Regexp.last_match(2)\n\n          if tag_name == 'liquid'\n            parse_context.line_number -= 1\n            next parse_liquid_tag(markup, parse_context)\n          end\n\n          unless (tag = parse_context.environment.tag_for_name(tag_name))\n            # end parsing if we reach an unknown tag and let the caller decide\n            # determine how to proceed\n            return yield tag_name, markup\n          end\n          new_tag = tag.parse(tag_name, markup, tokenizer, parse_context)\n          @blank &&= new_tag.blank?\n          @nodelist << new_tag\n        end\n        parse_context.line_number = tokenizer.line_number\n      end\n\n      yield nil, nil\n    end\n\n    # @api private\n    def self.unknown_tag_in_liquid_tag(tag, parse_context)\n      Block.raise_unknown_tag(tag, 'liquid', '%}', parse_context)\n    end\n\n    # @api private\n    def self.raise_missing_tag_terminator(token, parse_context)\n      raise SyntaxError, parse_context.locale.t(\"errors.syntax.tag_termination\", token: token, tag_end: TagEnd.inspect)\n    end\n\n    # @api private\n    def self.raise_missing_variable_terminator(token, parse_context)\n      raise SyntaxError, parse_context.locale.t(\"errors.syntax.variable_termination\", token: token, tag_end: VariableEnd.inspect)\n    end\n\n    # @api private\n    def self.render_node(context, output, node)\n      node.render_to_output_buffer(context, output)\n    rescue => exc\n      blank_tag = !node.instance_of?(Variable) && node.blank?\n      rescue_render_node(context, output, node.line_number, exc, blank_tag)\n    end\n\n    # @api private\n    def self.rescue_render_node(context, output, line_number, exc, blank_tag)\n      case exc\n      when MemoryError\n        raise\n      when UndefinedVariable, UndefinedDropMethod, UndefinedFilter\n        context.handle_error(exc, line_number)\n      else\n        error_message = context.handle_error(exc, line_number)\n        unless blank_tag # conditional for backwards compatibility\n          output << error_message\n        end\n      end\n    end\n\n    private def parse_liquid_tag(markup, parse_context)\n      liquid_tag_tokenizer = parse_context.new_tokenizer(\n        markup, start_line_number: parse_context.line_number, for_liquid_tag: true\n      )\n      parse_for_liquid_tag(liquid_tag_tokenizer, parse_context) do |end_tag_name, _end_tag_markup|\n        if end_tag_name\n          BlockBody.unknown_tag_in_liquid_tag(end_tag_name, parse_context)\n        end\n      end\n    end\n\n    private def handle_invalid_tag_token(token, parse_context)\n      if token.end_with?('%}')\n        yield token, token\n      else\n        BlockBody.raise_missing_tag_terminator(token, parse_context)\n      end\n    end\n\n    private def parse_for_document(tokenizer, parse_context, &block)\n      while (token = tokenizer.shift)\n        next if token.empty?\n        case\n        when token.start_with?(TAGSTART)\n          whitespace_handler(token, parse_context)\n          unless token =~ FullToken\n            return handle_invalid_tag_token(token, parse_context, &block)\n          end\n          tag_name = Regexp.last_match(2)\n          markup   = Regexp.last_match(4)\n\n          if parse_context.line_number\n            # newlines inside the tag should increase the line number,\n            # particularly important for multiline {% liquid %} tags\n            parse_context.line_number += Regexp.last_match(1).count(\"\\n\") + Regexp.last_match(3).count(\"\\n\")\n          end\n\n          if tag_name == 'liquid'\n            parse_liquid_tag(markup, parse_context)\n            next\n          end\n\n          unless (tag = parse_context.environment.tag_for_name(tag_name))\n            # end parsing if we reach an unknown tag and let the caller decide\n            # determine how to proceed\n            return yield tag_name, markup\n          end\n          new_tag = tag.parse(tag_name, markup, tokenizer, parse_context)\n          @blank &&= new_tag.blank?\n          @nodelist << new_tag\n        when token.start_with?(VARSTART)\n          whitespace_handler(token, parse_context)\n          @nodelist << create_variable(token, parse_context)\n          @blank = false\n        else\n          if parse_context.trim_whitespace\n            token.lstrip!\n          end\n          parse_context.trim_whitespace = false\n          @nodelist << token\n          @blank &&= token.match?(WhitespaceOrNothing)\n        end\n        parse_context.line_number = tokenizer.line_number\n      end\n\n      yield nil, nil\n    end\n\n    def whitespace_handler(token, parse_context)\n      if token[2] == WhitespaceControl\n        previous_token = @nodelist.last\n        if previous_token.is_a?(String)\n          first_byte = previous_token.getbyte(0)\n          previous_token.rstrip!\n          if previous_token.empty? && parse_context[:bug_compatible_whitespace_trimming] && first_byte\n            previous_token << first_byte\n          end\n        end\n      end\n      parse_context.trim_whitespace = (token[-3] == WhitespaceControl)\n    end\n\n    def blank?\n      @blank\n    end\n\n    # Remove blank strings in the block body for a control flow tag (e.g. `if`, `for`, `case`, `unless`)\n    # with a blank body.\n    #\n    # For example, in a conditional assignment like the following\n    #\n    # ```\n    # {% if size > max_size %}\n    #   {% assign size = max_size %}\n    # {% endif %}\n    # ```\n    #\n    # we assume the intention wasn't to output the blank spaces in the `if` tag's block body, so this method\n    # will remove them to reduce the render output size.\n    #\n    # Note that it is now preferred to use the `liquid` tag for this use case.\n    def remove_blank_strings\n      raise \"remove_blank_strings only support being called on a blank block body\" unless @blank\n      @nodelist.reject! { |node| node.instance_of?(String) }\n    end\n\n    def render(context)\n      render_to_output_buffer(context, +'')\n    end\n\n    def render_to_output_buffer(context, output)\n      freeze unless frozen?\n\n      context.resource_limits.increment_render_score(@nodelist.length)\n\n      idx = 0\n      while (node = @nodelist[idx])\n        if node.instance_of?(String)\n          output << node\n        else\n          render_node(context, output, node)\n          # If we get an Interrupt that means the block must stop processing. An\n          # Interrupt is any command that stops block execution such as {% break %}\n          # or {% continue %}. These tags may also occur through Block or Include tags.\n          break if context.interrupt? # might have happened in a for-block\n        end\n        idx += 1\n\n        context.resource_limits.increment_write_score(output)\n      end\n\n      output\n    end\n\n    private\n\n    def render_node(context, output, node)\n      BlockBody.render_node(context, output, node)\n    end\n\n    def create_variable(token, parse_context)\n      if token.end_with?(\"}}\")\n        i = 2\n        i = 3 if token[i] == \"-\"\n        parse_end = token.length - 3\n        parse_end -= 1 if token[parse_end] == \"-\"\n        markup_end = parse_end - i + 1\n        markup = markup_end <= 0 ? \"\" : token.slice(i, markup_end)\n\n        return Variable.new(markup, parse_context)\n      end\n\n      BlockBody.raise_missing_variable_terminator(token, parse_context)\n    end\n\n    # @deprecated Use {.raise_missing_tag_terminator} instead\n    def raise_missing_tag_terminator(token, parse_context)\n      BlockBody.raise_missing_tag_terminator(token, parse_context)\n    end\n\n    # @deprecated Use {.raise_missing_variable_terminator} instead\n    def raise_missing_variable_terminator(token, parse_context)\n      BlockBody.raise_missing_variable_terminator(token, parse_context)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/condition.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # Container for liquid nodes which conveniently wraps decision making logic\n  #\n  # Example:\n  #\n  #   c = Condition.new(1, '==', 1)\n  #   c.evaluate #=> true\n  #\n  class Condition # :nodoc:\n    @@operators = {\n      '==' => ->(cond, left, right) {  cond.send(:equal_variables, left, right) },\n      '!=' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },\n      '<>' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },\n      '<' => :<,\n      '>' => :>,\n      '>=' => :>=,\n      '<=' => :<=,\n      'contains' => lambda do |_cond, left, right|\n        if left && right && left.respond_to?(:include?)\n          right = right.to_s if left.is_a?(String)\n          left.include?(right)\n        else\n          false\n        end\n      rescue Encoding::CompatibilityError\n        # \"✅\".b.include?(\"✅\") raises Encoding::CompatibilityError despite being materially equal\n        left.b.include?(right.b)\n      end,\n    }\n\n    class MethodLiteral\n      attr_reader :method_name, :to_s\n\n      def initialize(method_name, to_s)\n        @method_name = method_name\n        @to_s = to_s\n      end\n    end\n\n    @@method_literals = {\n      'blank' => MethodLiteral.new(:blank?, '').freeze,\n      'empty' => MethodLiteral.new(:empty?, '').freeze,\n    }\n\n    def self.operators\n      @@operators\n    end\n\n    def self.parse_expression(parse_context, markup, safe: false)\n      @@method_literals[markup] || parse_context.parse_expression(markup, safe: safe)\n    end\n\n    attr_reader :attachment, :child_condition\n    attr_accessor :left, :operator, :right\n\n    def initialize(left = nil, operator = nil, right = nil)\n      @left     = left\n      @operator = operator\n      @right    = right\n\n      @child_relation  = nil\n      @child_condition = nil\n    end\n\n    def evaluate(context = deprecated_default_context)\n      condition = self\n      result = nil\n      loop do\n        result = interpret_condition(condition.left, condition.right, condition.operator, context)\n\n        case condition.child_relation\n        when :or\n          break if Liquid::Utils.to_liquid_value(result)\n        when :and\n          break unless Liquid::Utils.to_liquid_value(result)\n        else\n          break\n        end\n        condition = condition.child_condition\n      end\n      result\n    end\n\n    def or(condition)\n      @child_relation  = :or\n      @child_condition = condition\n    end\n\n    def and(condition)\n      @child_relation  = :and\n      @child_condition = condition\n    end\n\n    def attach(attachment)\n      @attachment = attachment\n    end\n\n    def else?\n      false\n    end\n\n    def inspect\n      \"#<Condition #{[@left, @operator, @right].compact.join(' ')}>\"\n    end\n\n    protected\n\n    attr_reader :child_relation\n\n    private\n\n    def equal_variables(left, right)\n      if left.is_a?(MethodLiteral)\n        return call_method_literal(left, right)\n      end\n\n      if right.is_a?(MethodLiteral)\n        return call_method_literal(right, left)\n      end\n\n      left == right\n    end\n\n    def call_method_literal(literal, value)\n      method_name = literal.method_name\n\n      # If the object responds to the method (e.g., ActiveSupport is loaded), use it\n      if value.respond_to?(method_name)\n        value.send(method_name)\n      else\n        # Emulate ActiveSupport's blank?/empty? to make Liquid invariant\n        # to whether ActiveSupport is loaded or not\n        case method_name\n        when :blank?\n          liquid_blank?(value)\n        when :empty?\n          liquid_empty?(value)\n        else\n          false\n        end\n      end\n    end\n\n    # Implement blank? semantics matching ActiveSupport\n    # blank? returns true for nil, false, empty strings, whitespace-only strings,\n    # empty arrays, and empty hashes\n    def liquid_blank?(value)\n      case value\n      when NilClass, FalseClass\n        true\n      when TrueClass, Numeric\n        false\n      when String\n        # Blank if empty or whitespace only (matches ActiveSupport)\n        value.empty? || value.match?(/\\A\\s*\\z/)\n      when Array, Hash\n        value.empty?\n      else\n        # Fall back to empty? if available, otherwise false\n        value.respond_to?(:empty?) ? value.empty? : false\n      end\n    end\n\n    # Implement empty? semantics\n    # Note: nil is NOT empty. empty? checks if a collection has zero elements.\n    def liquid_empty?(value)\n      case value\n      when String, Array, Hash\n        value.empty?\n      else\n        value.respond_to?(:empty?) ? value.empty? : false\n      end\n    end\n\n    def interpret_condition(left, right, op, context)\n      # If the operator is empty this means that the decision statement is just\n      # a single variable. We can just poll this variable from the context and\n      # return this as the result.\n      return context.evaluate(left) if op.nil?\n\n      left  = Liquid::Utils.to_liquid_value(context.evaluate(left))\n      right = Liquid::Utils.to_liquid_value(context.evaluate(right))\n\n      operation = self.class.operators[op] || raise(Liquid::ArgumentError, \"Unknown operator #{op}\")\n\n      if operation.respond_to?(:call)\n        operation.call(self, left, right)\n      elsif left.respond_to?(operation) && right.respond_to?(operation) && !left.is_a?(Hash) && !right.is_a?(Hash)\n        begin\n          left.send(operation, right)\n        rescue ::ArgumentError => e\n          raise Liquid::ArgumentError, e.message\n        end\n      end\n    end\n\n    def deprecated_default_context\n      warn(\"DEPRECATION WARNING: Condition#evaluate without a context argument is deprecated \" \\\n        \"and will be removed from Liquid 6.0.0.\")\n      Context.new\n    end\n\n    class ParseTreeVisitor < Liquid::ParseTreeVisitor\n      def children\n        [\n          @node.left,\n          @node.right,\n          @node.child_condition,\n          @node.attachment\n        ].compact\n      end\n    end\n  end\n\n  class ElseCondition < Condition\n    def else?\n      true\n    end\n\n    def evaluate(_context)\n      true\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/const.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  module Const\n    EMPTY_HASH = {}.freeze\n    EMPTY_ARRAY = [].freeze\n  end\nend\n"
  },
  {
    "path": "lib/liquid/context.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # Context keeps the variable stack and resolves variables, as well as keywords\n  #\n  #   context['variable'] = 'testing'\n  #   context['variable'] #=> 'testing'\n  #   context['true']     #=> true\n  #   context['10.2232']  #=> 10.2232\n  #\n  #   context.stack do\n  #      context['bob'] = 'bobsen'\n  #   end\n  #\n  #   context['bob']  #=> nil  class Context\n  class Context\n    attr_reader :scopes, :errors, :registers, :environments, :resource_limits, :static_registers, :static_environments\n    attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters, :environment\n\n    # rubocop:disable Metrics/ParameterLists\n    def self.build(environment: Environment.default, environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {}, &block)\n      new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_environments, environment, &block)\n    end\n\n    def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {}, environment = Environment.default)\n      @environment = environment\n      @environments = [environments]\n      @environments.flatten!\n\n      @static_environments = [static_environments].flatten(1).freeze\n      @scopes              = [outer_scope || {}]\n      @registers           = registers.is_a?(Registers) ? registers : Registers.new(registers)\n      @errors              = []\n      @partial             = false\n      @strict_variables    = false\n      @resource_limits     = resource_limits || ResourceLimits.new(environment.default_resource_limits)\n      @base_scope_depth    = 0\n      @interrupts          = []\n      @filters             = []\n      @global_filter       = nil\n      @disabled_tags       = {}\n\n      # Instead of constructing new StringScanner objects for each Expression parse,\n      # we recycle the same one.\n      @string_scanner = StringScanner.new(\"\")\n\n      @registers.static[:cached_partials] ||= {}\n      @registers.static[:file_system] ||= environment.file_system\n      @registers.static[:template_factory] ||= Liquid::TemplateFactory.new\n\n      self.exception_renderer = environment.exception_renderer\n      if rethrow_errors\n        self.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA\n      end\n\n      yield self if block_given?\n\n      # Do this last, since it could result in this object being passed to a Proc in the environment\n      squash_instance_assigns_with_environments\n    end\n    # rubocop:enable Metrics/ParameterLists\n\n    def warnings\n      @warnings ||= []\n    end\n\n    def strainer\n      @strainer ||= @environment.create_strainer(self, @filters)\n    end\n\n    # Adds filters to this context.\n    #\n    # Note that this does not register the filters with the main Template object. see <tt>Template.register_filter</tt>\n    # for that\n    def add_filters(filters)\n      filters = [filters].flatten.compact\n      @filters += filters\n      @strainer = nil\n    end\n\n    def apply_global_filter(obj)\n      global_filter.nil? ? obj : global_filter.call(obj)\n    end\n\n    # are there any not handled interrupts?\n    def interrupt?\n      !@interrupts.empty?\n    end\n\n    # push an interrupt to the stack. this interrupt is considered not handled.\n    def push_interrupt(e)\n      @interrupts.push(e)\n    end\n\n    # pop an interrupt from the stack\n    def pop_interrupt\n      @interrupts.pop\n    end\n\n    def handle_error(e, line_number = nil)\n      e = internal_error unless e.is_a?(Liquid::Error)\n      e.template_name ||= template_name\n      e.line_number   ||= line_number\n      errors.push(e)\n      exception_renderer.call(e).to_s\n    end\n\n    def invoke(method, *args)\n      strainer.invoke(method, *args).to_liquid\n    end\n\n    # Push new local scope on the stack. use <tt>Context#stack</tt> instead\n    def push(new_scope = {})\n      @scopes.unshift(new_scope)\n      check_overflow\n    end\n\n    # Merge a hash of variables in the current local scope\n    def merge(new_scopes)\n      @scopes[0].merge!(new_scopes)\n    end\n\n    # Pop from the stack. use <tt>Context#stack</tt> instead\n    def pop\n      raise ContextError if @scopes.size == 1\n      @scopes.shift\n    end\n\n    # Pushes a new local scope on the stack, pops it at the end of the block\n    #\n    # Example:\n    #   context.stack do\n    #      context['var'] = 'hi'\n    #   end\n    #\n    #   context['var']  #=> nil\n    def stack(new_scope = {})\n      push(new_scope)\n      yield\n    ensure\n      pop\n    end\n\n    # Creates a new context inheriting resource limits, filters, environment etc.,\n    # but with an isolated scope.\n    def new_isolated_subcontext\n      check_overflow\n\n      self.class.build(\n        environment: @environment,\n        resource_limits: resource_limits,\n        static_environments: static_environments,\n        registers: Registers.new(registers),\n      ).tap do |subcontext|\n        subcontext.base_scope_depth   = base_scope_depth + 1\n        subcontext.exception_renderer = exception_renderer\n        subcontext.filters  = @filters\n        subcontext.strainer = nil\n        subcontext.errors   = errors\n        subcontext.warnings = warnings\n        subcontext.disabled_tags = @disabled_tags\n      end\n    end\n\n    def clear_instance_assigns\n      @scopes[0] = {}\n    end\n\n    # Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>\n    def []=(key, value)\n      @scopes[0][key] = value\n    end\n\n    # Look up variable, either resolve directly after considering the name. We can directly handle\n    # Strings, digits, floats and booleans (true,false).\n    # If no match is made we lookup the variable in the current scope and\n    # later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.\n    # Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions\n    #\n    # Example:\n    #   products == empty #=> products.empty?\n    def [](expression)\n      evaluate(Expression.parse(expression, @string_scanner))\n    end\n\n    def key?(key)\n      find_variable(key, raise_on_not_found: false) != nil\n    end\n\n    def evaluate(object)\n      object.respond_to?(:evaluate) ? object.evaluate(self) : object\n    end\n\n    # Fetches an object starting at the local scope and then moving up the hierachy\n    def find_variable(key, raise_on_not_found: true)\n      # This was changed from find() to find_index() because this is a very hot\n      # path and find_index() is optimized in MRI to reduce object allocation\n      index = @scopes.find_index { |s| s.key?(key) }\n\n      variable = if index\n        lookup_and_evaluate(@scopes[index], key, raise_on_not_found: raise_on_not_found)\n      else\n        try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found)\n      end\n\n      # update variable's context before invoking #to_liquid\n      variable.context = self if variable.respond_to?(:context=)\n\n      liquid_variable = variable.to_liquid\n\n      liquid_variable.context = self if variable != liquid_variable && liquid_variable.respond_to?(:context=)\n\n      liquid_variable\n    end\n\n    def lookup_and_evaluate(obj, key, raise_on_not_found: true)\n      if @strict_variables && raise_on_not_found && obj.respond_to?(:key?) && !obj.key?(key)\n        raise Liquid::UndefinedVariable, \"undefined variable #{key}\"\n      end\n\n      value = obj[key]\n\n      if value.is_a?(Proc) && obj.respond_to?(:[]=)\n        obj[key] = value.arity == 0 ? value.call : value.call(self)\n      else\n        value\n      end\n    end\n\n    def with_disabled_tags(tag_names)\n      tag_names.each do |name|\n        @disabled_tags[name] = @disabled_tags.fetch(name, 0) + 1\n      end\n      yield\n    ensure\n      tag_names.each do |name|\n        @disabled_tags[name] -= 1\n      end\n    end\n\n    def tag_disabled?(tag_name)\n      @disabled_tags.fetch(tag_name, 0) > 0\n    end\n\n    protected\n\n    attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters, :disabled_tags\n\n    private\n\n    attr_reader :base_scope_depth\n\n    def try_variable_find_in_environments(key, raise_on_not_found:)\n      @environments.each do |environment|\n        found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)\n        if !found_variable.nil? || @strict_variables && raise_on_not_found\n          return found_variable\n        end\n      end\n      @static_environments.each do |environment|\n        found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)\n        if !found_variable.nil? || @strict_variables && raise_on_not_found\n          return found_variable\n        end\n      end\n      nil\n    end\n\n    def check_overflow\n      raise StackLevelError, \"Nesting too deep\" if overflow?\n    end\n\n    def overflow?\n      base_scope_depth + @scopes.length > Block::MAX_DEPTH\n    end\n\n    def internal_error\n      # raise and catch to set backtrace and cause on exception\n      raise Liquid::InternalError, 'internal'\n    rescue Liquid::InternalError => exc\n      exc\n    end\n\n    def squash_instance_assigns_with_environments\n      @scopes.last.each_key do |k|\n        @environments.each do |env|\n          if env.key?(k)\n            scopes.last[k] = lookup_and_evaluate(env, k)\n            break\n          end\n        end\n      end\n    end # squash_instance_assigns_with_environments\n  end # Context\nend # Liquid\n"
  },
  {
    "path": "lib/liquid/deprecations.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"set\"\n\nmodule Liquid\n  class Deprecations\n    class << self\n      attr_accessor :warned\n\n      Deprecations.warned = Set.new\n\n      def warn(name, alternative)\n        return if warned.include?(name)\n\n        warned << name\n\n        caller_location = caller_locations(2, 1).first\n        Warning.warn(\"[DEPRECATION] #{name} is deprecated. Use #{alternative} instead. Called from #{caller_location}\\n\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/document.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  class Document\n    def self.parse(tokens, parse_context)\n      doc = new(parse_context)\n      doc.parse(tokens, parse_context)\n      doc\n    end\n\n    attr_reader :parse_context, :body\n\n    def initialize(parse_context)\n      @parse_context = parse_context\n      @body = new_body\n    end\n\n    def nodelist\n      @body.nodelist\n    end\n\n    def parse(tokenizer, parse_context)\n      while parse_body(tokenizer)\n      end\n      @body.freeze\n    rescue SyntaxError => e\n      e.line_number ||= parse_context.line_number\n      raise\n    end\n\n    def unknown_tag(tag, _markup, _tokenizer)\n      case tag\n      when 'else', 'end'\n        raise SyntaxError, parse_context.locale.t(\"errors.syntax.unexpected_outer_tag\", tag: tag)\n      else\n        raise SyntaxError, parse_context.locale.t(\"errors.syntax.unknown_tag\", tag: tag)\n      end\n    end\n\n    def render_to_output_buffer(context, output)\n      @body.render_to_output_buffer(context, output)\n    end\n\n    def render(context)\n      render_to_output_buffer(context, +'')\n    end\n\n    private\n\n    def new_body\n      parse_context.new_block_body\n    end\n\n    def parse_body(tokenizer)\n      @body.parse(tokenizer, parse_context) do |unknown_tag_name, unknown_tag_markup|\n        if unknown_tag_name\n          unknown_tag(unknown_tag_name, unknown_tag_markup, tokenizer)\n          true\n        else\n          false\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/drop.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'set'\n\nmodule Liquid\n  # A drop in liquid is a class which allows you to export DOM like things to liquid.\n  # Methods of drops are callable.\n  # The main use for liquid drops is to implement lazy loaded objects.\n  # If you would like to make data available to the web designers which you don't want loaded unless needed then\n  # a drop is a great way to do that.\n  #\n  # Example:\n  #\n  #   class ProductDrop < Liquid::Drop\n  #     def top_sales\n  #       Shop.current.products.find(:all, :order => 'sales', :limit => 10 )\n  #     end\n  #   end\n  #\n  #   tmpl = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {%endfor%} '  )\n  #   tmpl.render('product' => ProductDrop.new ) # will invoke top_sales query.\n  #\n  # Your drop can either implement the methods sans any parameters\n  # or implement the liquid_method_missing(name) method which is a catch all.\n  class Drop\n    attr_writer :context\n\n    def initialize\n      @context = nil\n    end\n\n    # Catch all for the method\n    def liquid_method_missing(method)\n      return unless @context&.strict_variables\n      raise Liquid::UndefinedDropMethod, \"undefined method #{method}\"\n    end\n\n    # called by liquid to invoke a drop\n    def invoke_drop(method_or_key)\n      if self.class.invokable?(method_or_key)\n        send(method_or_key)\n      else\n        liquid_method_missing(method_or_key)\n      end\n    end\n\n    def key?(_name)\n      true\n    end\n\n    def inspect\n      self.class.to_s\n    end\n\n    def to_liquid\n      self\n    end\n\n    def to_s\n      self.class.name\n    end\n\n    alias_method :[], :invoke_drop\n\n    # Check for method existence without invoking respond_to?, which creates symbols\n    def self.invokable?(method_name)\n      invokable_methods.include?(method_name.to_s)\n    end\n\n    def self.invokable_methods\n      @invokable_methods ||= begin\n        blacklist = Liquid::Drop.public_instance_methods + [:each]\n\n        if include?(Enumerable)\n          blacklist += Enumerable.public_instance_methods\n          blacklist -= [:sort, :count, :first, :min, :max]\n        end\n\n        whitelist = [:to_liquid] + (public_instance_methods - blacklist)\n        Set.new(whitelist.map(&:to_s))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/environment.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # The Environment is the container for all configuration options of Liquid, such as\n  # the registered tags, filters, and the default error mode.\n  class Environment\n    # The default error mode for all templates. This can be overridden on a\n    # per-template basis.\n    attr_accessor :error_mode\n\n    # The tags that are available to use in the template.\n    attr_accessor :tags\n\n    # The strainer template which is used to store filters that are available to\n    # use in templates.\n    attr_accessor :strainer_template\n\n    # The exception renderer that is used to render exceptions that are raised\n    # when rendering a template\n    attr_accessor :exception_renderer\n\n    # The default file system that is used to load templates from.\n    attr_accessor :file_system\n\n    # The default resource limits that are used to limit the resources that a\n    # template can consume.\n    attr_accessor :default_resource_limits\n\n    class << self\n      # Creates a new environment instance.\n      #\n      # @param tags [Hash] The tags that are available to use in\n      #  the template.\n      # @param file_system The default file system that is used\n      #  to load templates from.\n      # @param error_mode [Symbol] The default error mode for all templates\n      #  (either :strict2, :strict, :warn, or :lax).\n      # @param exception_renderer [Proc] The exception renderer that is used to\n      #   render exceptions.\n      # @yieldparam environment [Environment] The environment instance that is being built.\n      # @return [Environment] The new environment instance.\n      def build(tags: nil, file_system: nil, error_mode: nil, exception_renderer: nil)\n        ret = new\n        ret.tags = tags if tags\n        ret.file_system = file_system if file_system\n        ret.error_mode = error_mode if error_mode\n        ret.exception_renderer = exception_renderer if exception_renderer\n        yield ret if block_given?\n        ret.freeze\n      end\n\n      # Returns the default environment instance.\n      #\n      # @return [Environment] The default environment instance.\n      def default\n        @default ||= new\n      end\n\n      # Sets the default environment instance for the duration of the block\n      #\n      # @param environment [Environment] The environment instance to use as the default for the\n      #   duration of the block.\n      # @yield\n      # @return [Object] The return value of the block.\n      def dangerously_override(environment)\n        original_default = @default\n        @default = environment\n        yield\n      ensure\n        @default = original_default\n      end\n    end\n\n    # Initializes a new environment instance.\n    # @api private\n    def initialize\n      @tags = Tags::STANDARD_TAGS.dup\n      @error_mode = :lax\n      @strainer_template = Class.new(StrainerTemplate).tap do |klass|\n        klass.add_filter(StandardFilters)\n      end\n      @exception_renderer = ->(exception) { exception }\n      @file_system = BlankFileSystem.new\n      @default_resource_limits = Const::EMPTY_HASH\n      @strainer_template_class_cache = {}\n    end\n\n    # Registers a new tag with the environment.\n    #\n    # @param name [String] The name of the tag.\n    # @param klass [Liquid::Tag] The class that implements the tag.\n    # @return [void]\n    def register_tag(name, klass)\n      @tags[name] = klass\n    end\n\n    # Registers a new filter with the environment.\n    #\n    # @param filter [Module] The module that contains the filter methods.\n    # @return [void]\n    def register_filter(filter)\n      @strainer_template_class_cache.clear\n      @strainer_template.add_filter(filter)\n    end\n\n    # Registers multiple filters with this environment.\n    #\n    # @param filters [Array<Module>] The modules that contain the filter methods.\n    # @return [self]\n    def register_filters(filters)\n      @strainer_template_class_cache.clear\n      filters.each { |f| @strainer_template.add_filter(f) }\n      self\n    end\n\n    # Creates a new strainer instance with the given filters, caching the result\n    # for faster lookup.\n    #\n    # @param context [Liquid::Context] The context that the strainer will be\n    #   used in.\n    # @param filters [Array<Module>] The filters that the strainer will have\n    #   access to.\n    # @return [Liquid::Strainer] The new strainer instance.\n    def create_strainer(context, filters = Const::EMPTY_ARRAY)\n      return @strainer_template.new(context) if filters.empty?\n\n      strainer_template = @strainer_template_class_cache[filters] ||= begin\n        klass = Class.new(@strainer_template)\n        filters.each { |f| klass.add_filter(f) }\n        klass\n      end\n\n      strainer_template.new(context)\n    end\n\n    # Returns the names of all the filter methods that are available to use in\n    # the strainer template.\n    #\n    # @return [Array<String>] The names of all the filter methods.\n    def filter_method_names\n      @strainer_template.filter_method_names\n    end\n\n    # Returns the tag class for the given tag name.\n    #\n    # @param name [String] The name of the tag.\n    # @return [Liquid::Tag] The tag class.\n    def tag_for_name(name)\n      @tags[name]\n    end\n\n    def freeze\n      @tags.freeze\n      # TODO: freeze the tags, currently this is not possible because of liquid-c\n      # @strainer_template.freeze\n      super\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/errors.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  class Error < ::StandardError\n    attr_accessor :line_number\n    attr_accessor :template_name\n    attr_accessor :markup_context\n\n    def to_s(with_prefix = true)\n      str = +\"\"\n      str << message_prefix if with_prefix\n      str << super()\n\n      if markup_context\n        str << \" \"\n        str << markup_context\n      end\n\n      str\n    end\n\n    private\n\n    def message_prefix\n      str = +\"\"\n      str << if is_a?(SyntaxError)\n        \"Liquid syntax error\"\n      else\n        \"Liquid error\"\n      end\n\n      if line_number\n        str << \" (\"\n        str << template_name << \" \" if template_name\n        str << \"line \" << line_number.to_s << \")\"\n      end\n\n      str << \": \"\n      str\n    end\n  end\n\n  ArgumentError         = Class.new(Error)\n  ContextError          = Class.new(Error)\n  FileSystemError       = Class.new(Error)\n  StandardError         = Class.new(Error)\n  SyntaxError           = Class.new(Error)\n  StackLevelError       = Class.new(Error)\n  MemoryError           = Class.new(Error)\n  ZeroDivisionError     = Class.new(Error)\n  FloatDomainError      = Class.new(Error)\n  UndefinedVariable     = Class.new(Error)\n  UndefinedDropMethod   = Class.new(Error)\n  UndefinedFilter       = Class.new(Error)\n  MethodOverrideError   = Class.new(Error)\n  DisabledError         = Class.new(Error)\n  InternalError         = Class.new(Error)\n  TemplateEncodingError = Class.new(Error)\nend\n"
  },
  {
    "path": "lib/liquid/expression.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  class Expression\n    LITERALS = {\n      nil => nil,\n      'nil' => nil,\n      'null' => nil,\n      '' => nil,\n      'true' => true,\n      'false' => false,\n      'blank' => '',\n      'empty' => '',\n      # in lax mode, minus sign can be a VariableLookup\n      # For simplicity and performace, we treat it like a literal\n      '-' => VariableLookup.parse(\"-\", nil).freeze,\n    }.freeze\n\n    DOT = \".\".ord\n    ZERO = \"0\".ord\n    NINE = \"9\".ord\n    DASH = \"-\".ord\n\n    # Use an atomic group (?>...) to avoid pathological backtracing from\n    # malicious input as described in https://github.com/Shopify/liquid/issues/1357\n    RANGES_REGEX = /\\A\\(\\s*(?>(\\S+)\\s*\\.\\.)\\s*(\\S+)\\s*\\)\\z/\n    INTEGER_REGEX = /\\A(-?\\d+)\\z/\n    FLOAT_REGEX = /\\A(-?\\d+)\\.\\d+\\z/\n\n    class << self\n      def safe_parse(parser, ss = StringScanner.new(\"\"), cache = nil)\n        parse(parser.expression, ss, cache)\n      end\n\n      def parse(markup, ss = StringScanner.new(\"\"), cache = nil)\n        return unless markup\n\n        markup = markup.strip # markup can be a frozen string\n\n        if (markup.start_with?('\"') && markup.end_with?('\"')) ||\n          (markup.start_with?(\"'\") && markup.end_with?(\"'\"))\n          return markup[1..-2]\n        elsif LITERALS.key?(markup)\n          return LITERALS[markup]\n        end\n\n        # Cache only exists during parsing\n        if cache\n          return cache[markup] if cache.key?(markup)\n\n          cache[markup] = inner_parse(markup, ss, cache).freeze\n        else\n          inner_parse(markup, ss, nil).freeze\n        end\n      end\n\n      def inner_parse(markup, ss, cache)\n        if markup.start_with?(\"(\") && markup.end_with?(\")\") && markup =~ RANGES_REGEX\n          return RangeLookup.parse(\n            Regexp.last_match(1),\n            Regexp.last_match(2),\n            ss,\n            cache,\n          )\n        end\n\n        if (num = parse_number(markup, ss))\n          num\n        else\n          VariableLookup.parse(markup, ss, cache)\n        end\n      end\n\n      def parse_number(markup, ss)\n        # check if the markup is simple integer or float\n        case markup\n        when INTEGER_REGEX\n          return Integer(markup, 10)\n        when FLOAT_REGEX\n          return markup.to_f\n        end\n\n        ss.string = markup\n        # the first byte must be a digit or  a dash\n        byte = ss.scan_byte\n\n        return false if byte != DASH && (byte < ZERO || byte > NINE)\n\n        if byte == DASH\n          peek_byte = ss.peek_byte\n\n          # if it starts with a dash, the next byte must be a digit\n          return false if peek_byte.nil? || !(peek_byte >= ZERO && peek_byte <= NINE)\n        end\n\n        # The markup could be a float with multiple dots\n        first_dot_pos = nil\n        num_end_pos = nil\n\n        while (byte = ss.scan_byte)\n          return false if byte != DOT && (byte < ZERO || byte > NINE)\n\n          # we found our number and now we are just scanning the rest of the string\n          next if num_end_pos\n\n          if byte == DOT\n            if first_dot_pos.nil?\n              first_dot_pos = ss.pos\n            else\n              # we found another dot, so we know that the number ends here\n              num_end_pos = ss.pos - 1\n            end\n          end\n        end\n\n        num_end_pos = markup.length if ss.eos?\n\n        if num_end_pos\n          # number ends with a number \"123.123\"\n          markup.byteslice(0, num_end_pos).to_f\n        else\n          # number ends with a dot \"123.\"\n          markup.byteslice(0, first_dot_pos).to_f\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/extensions.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'time'\nrequire 'date'\n\nclass String # :nodoc:\n  def to_liquid\n    self\n  end\nend\n\nclass Symbol # :nodoc:\n  def to_liquid\n    to_s\n  end\nend\n\nclass Array # :nodoc:\n  def to_liquid\n    self\n  end\nend\n\nclass Hash # :nodoc:\n  def to_liquid\n    self\n  end\nend\n\nclass Numeric # :nodoc:\n  def to_liquid\n    self\n  end\nend\n\nclass Range # :nodoc:\n  def to_liquid\n    self\n  end\nend\n\nclass Time # :nodoc:\n  def to_liquid\n    self\n  end\nend\n\nclass DateTime < Date # :nodoc:\n  def to_liquid\n    self\n  end\nend\n\nclass Date # :nodoc:\n  def to_liquid\n    self\n  end\nend\n\nclass TrueClass\n  def to_liquid # :nodoc:\n    self\n  end\nend\n\nclass FalseClass\n  def to_liquid # :nodoc:\n    self\n  end\nend\n\nclass NilClass\n  def to_liquid # :nodoc:\n    self\n  end\nend\n"
  },
  {
    "path": "lib/liquid/file_system.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # A Liquid file system is a way to let your templates retrieve other templates for use with the include tag.\n  #\n  # You can implement subclasses that retrieve templates from the database, from the file system using a different\n  # path structure, you can provide them as hard-coded inline strings, or any manner that you see fit.\n  #\n  # You can add additional instance variables, arguments, or methods as needed.\n  #\n  # Example:\n  #\n  #   Liquid::Template.file_system = Liquid::LocalFileSystem.new(template_path)\n  #   liquid = Liquid::Template.parse(template)\n  #\n  # This will parse the template with a LocalFileSystem implementation rooted at 'template_path'.\n  class BlankFileSystem\n    # Called by Liquid to retrieve a template file\n    def read_template_file(_template_path)\n      raise FileSystemError, \"This liquid context does not allow includes.\"\n    end\n  end\n\n  # This implements an abstract file system which retrieves template files named in a manner similar to Rails partials,\n  # ie. with the template name prefixed with an underscore. The extension \".liquid\" is also added.\n  #\n  # For security reasons, template paths are only allowed to contain letters, numbers, and underscore.\n  #\n  # Example:\n  #\n  #   file_system = Liquid::LocalFileSystem.new(\"/some/path\")\n  #\n  #   file_system.full_path(\"mypartial\")       # => \"/some/path/_mypartial.liquid\"\n  #   file_system.full_path(\"dir/mypartial\")   # => \"/some/path/dir/_mypartial.liquid\"\n  #\n  # Optionally in the second argument you can specify a custom pattern for template filenames.\n  # The Kernel::sprintf format specification is used.\n  # Default pattern is \"_%s.liquid\".\n  #\n  # Example:\n  #\n  #   file_system = Liquid::LocalFileSystem.new(\"/some/path\", \"%s.html\")\n  #\n  #   file_system.full_path(\"index\") # => \"/some/path/index.html\"\n  #\n  class LocalFileSystem\n    attr_accessor :root\n\n    def initialize(root, pattern = \"_%s.liquid\")\n      @root    = root\n      @pattern = pattern\n    end\n\n    def read_template_file(template_path)\n      full_path = full_path(template_path)\n      raise FileSystemError, \"No such template '#{template_path}'\" unless File.exist?(full_path)\n\n      File.read(full_path)\n    end\n\n    def full_path(template_path)\n      raise FileSystemError, \"Illegal template name '#{template_path}'\" unless %r{\\A[^./][a-zA-Z0-9_/]+\\z}.match?(template_path)\n\n      full_path = if template_path.include?('/')\n        File.join(root, File.dirname(template_path), @pattern % File.basename(template_path))\n      else\n        File.join(root, @pattern % template_path)\n      end\n\n      raise FileSystemError, \"Illegal template path '#{File.expand_path(full_path)}'\" unless File.expand_path(full_path).start_with?(File.expand_path(root))\n\n      full_path\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/forloop_drop.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # @liquid_public_docs\n  # @liquid_type object\n  # @liquid_name forloop\n  # @liquid_summary\n  #   Information about a parent [`for` loop](/docs/api/liquid/tags/for).\n  class ForloopDrop < Drop\n    def initialize(name, length, parentloop)\n      @name       = name\n      @length     = length\n      @parentloop = parentloop\n      @index      = 0\n    end\n\n    # @liquid_public_docs\n    # @liquid_name length\n    # @liquid_summary\n    #   The total number of iterations in the loop.\n    # @liquid_return [number]\n    attr_reader :length\n\n    # @liquid_public_docs\n    # @liquid_name parentloop\n    # @liquid_summary\n    #   The parent `forloop` object.\n    # @liquid_description\n    #   If the current `for` loop isn't nested inside another `for` loop, then `nil` is returned.\n    # @liquid_return [forloop]\n    attr_reader :parentloop\n\n    attr_reader :name\n\n    # @liquid_public_docs\n    # @liquid_summary\n    #   The 1-based index of the current iteration.\n    # @liquid_return [number]\n    def index\n      @index + 1\n    end\n\n    # @liquid_public_docs\n    # @liquid_summary\n    #   The 0-based index of the current iteration.\n    # @liquid_return [number]\n    def index0\n      @index\n    end\n\n    # @liquid_public_docs\n    # @liquid_summary\n    #   The 1-based index of the current iteration, in reverse order.\n    # @liquid_return [number]\n    def rindex\n      @length - @index\n    end\n\n    # @liquid_public_docs\n    # @liquid_summary\n    #   The 0-based index of the current iteration, in reverse order.\n    # @liquid_return [number]\n    def rindex0\n      @length - @index - 1\n    end\n\n    # @liquid_public_docs\n    # @liquid_summary\n    #   Returns `true` if the current iteration is the first. Returns `false` if not.\n    # @liquid_return [boolean]\n    def first\n      @index == 0\n    end\n\n    # @liquid_public_docs\n    # @liquid_summary\n    #   Returns `true` if the current iteration is the last. Returns `false` if not.\n    # @liquid_return [boolean]\n    def last\n      @index == @length - 1\n    end\n\n    protected\n\n    def increment!\n      @index += 1\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/i18n.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'yaml'\n\nmodule Liquid\n  class I18n\n    DEFAULT_LOCALE = File.join(File.expand_path(__dir__), \"locales\", \"en.yml\")\n\n    TranslationError = Class.new(StandardError)\n\n    attr_reader :path\n\n    def initialize(path = DEFAULT_LOCALE)\n      @path = path\n    end\n\n    def translate(name, vars = {})\n      interpolate(deep_fetch_translation(name), vars)\n    end\n    alias_method :t, :translate\n\n    def locale\n      @locale ||= YAML.load_file(@path)\n    end\n\n    private\n\n    def interpolate(name, vars)\n      name.gsub(/%\\{(\\w+)\\}/) do\n        # raise TranslationError, \"Undefined key #{$1} for interpolation in translation #{name}\"  unless vars[$1.to_sym]\n        vars[Regexp.last_match(1).to_sym].to_s\n      end\n    end\n\n    def deep_fetch_translation(name)\n      name.split('.').reduce(locale) do |level, cur|\n        level[cur] || raise(TranslationError, \"Translation for #{name} does not exist in locale #{path}\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/interrupts.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # An interrupt is any command that breaks processing of a block (ex: a for loop).\n  class Interrupt\n    attr_reader :message\n\n    def initialize(message = nil)\n      @message = message || \"interrupt\"\n    end\n  end\n\n  # Interrupt that is thrown whenever a {% break %} is called.\n  class BreakInterrupt < Interrupt; end\n\n  # Interrupt that is thrown whenever a {% continue %} is called.\n  class ContinueInterrupt < Interrupt; end\nend\n"
  },
  {
    "path": "lib/liquid/lexer.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  class Lexer\n    CLOSE_ROUND = [:close_round, \")\"].freeze\n    CLOSE_SQUARE = [:close_square, \"]\"].freeze\n    COLON = [:colon, \":\"].freeze\n    COMMA = [:comma, \",\"].freeze\n    COMPARISION_NOT_EQUAL = [:comparison, \"!=\"].freeze\n    COMPARISON_CONTAINS = [:comparison, \"contains\"].freeze\n    COMPARISON_EQUAL = [:comparison, \"==\"].freeze\n    COMPARISON_GREATER_THAN = [:comparison, \">\"].freeze\n    COMPARISON_GREATER_THAN_OR_EQUAL = [:comparison, \">=\"].freeze\n    COMPARISON_LESS_THAN = [:comparison, \"<\"].freeze\n    COMPARISON_LESS_THAN_OR_EQUAL = [:comparison, \"<=\"].freeze\n    COMPARISON_NOT_EQUAL_ALT = [:comparison, \"<>\"].freeze\n    DASH = [:dash, \"-\"].freeze\n    DOT = [:dot, \".\"].freeze\n    DOTDOT = [:dotdot, \"..\"].freeze\n    DOT_ORD = \".\".ord\n    DOUBLE_STRING_LITERAL = /\"[^\\\"]*\"/\n    EOS = [:end_of_string].freeze\n    IDENTIFIER            = /[a-zA-Z_][\\w-]*\\??/\n    NUMBER_LITERAL        = /-?\\d+(\\.\\d+)?/\n    OPEN_ROUND = [:open_round, \"(\"].freeze\n    OPEN_SQUARE = [:open_square, \"[\"].freeze\n    PIPE = [:pipe, \"|\"].freeze\n    QUESTION = [:question, \"?\"].freeze\n    RUBY_WHITESPACE = [\" \", \"\\t\", \"\\r\", \"\\n\", \"\\f\"].freeze\n    SINGLE_STRING_LITERAL = /'[^\\']*'/\n    WHITESPACE_OR_NOTHING = /\\s*/\n\n    SINGLE_COMPARISON_TOKENS = [].tap do |table|\n      table[\"<\".ord] = COMPARISON_LESS_THAN\n      table[\">\".ord] = COMPARISON_GREATER_THAN\n      table.freeze\n    end\n\n    TWO_CHARS_COMPARISON_JUMP_TABLE = [].tap do |table|\n      table[\"=\".ord] = [].tap do |sub_table|\n        sub_table[\"=\".ord] = COMPARISON_EQUAL\n        sub_table.freeze\n      end\n      table[\"!\".ord] = [].tap do |sub_table|\n        sub_table[\"=\".ord] = COMPARISION_NOT_EQUAL\n        sub_table.freeze\n      end\n      table.freeze\n    end\n\n    COMPARISON_JUMP_TABLE = [].tap do |table|\n      table[\"<\".ord] = [].tap do |sub_table|\n        sub_table[\"=\".ord] = COMPARISON_LESS_THAN_OR_EQUAL\n        sub_table[\">\".ord] = COMPARISON_NOT_EQUAL_ALT\n        sub_table.freeze\n      end\n      table[\">\".ord] = [].tap do |sub_table|\n        sub_table[\"=\".ord] = COMPARISON_GREATER_THAN_OR_EQUAL\n        sub_table.freeze\n      end\n      table.freeze\n    end\n\n    NEXT_MATCHER_JUMP_TABLE = [].tap do |table|\n      \"a\".upto(\"z\") do |c|\n        table[c.ord] = [:id, IDENTIFIER].freeze\n        table[c.upcase.ord] = [:id, IDENTIFIER].freeze\n      end\n      table[\"_\".ord] = [:id, IDENTIFIER].freeze\n\n      \"0\".upto(\"9\") do |c|\n        table[c.ord] = [:number, NUMBER_LITERAL].freeze\n      end\n      table[\"-\".ord] = [:number, NUMBER_LITERAL].freeze\n\n      table[\"'\".ord] = [:string, SINGLE_STRING_LITERAL].freeze\n      table[\"\\\"\".ord] = [:string, DOUBLE_STRING_LITERAL].freeze\n      table.freeze\n    end\n\n    SPECIAL_TABLE = [].tap do |table|\n      table[\"|\".ord] = PIPE\n      table[\".\".ord] = DOT\n      table[\":\".ord] = COLON\n      table[\",\".ord] = COMMA\n      table[\"[\".ord] = OPEN_SQUARE\n      table[\"]\".ord] = CLOSE_SQUARE\n      table[\"(\".ord] = OPEN_ROUND\n      table[\")\".ord] = CLOSE_ROUND\n      table[\"?\".ord] = QUESTION\n      table[\"-\".ord] = DASH\n    end\n\n    NUMBER_TABLE = [].tap do |table|\n      \"0\".upto(\"9\") do |c|\n        table[c.ord] = true\n      end\n      table.freeze\n    end\n\n    # rubocop:disable Metrics/BlockNesting\n    class << self\n      def tokenize(ss)\n        output = []\n\n        until ss.eos?\n          ss.skip(WHITESPACE_OR_NOTHING)\n\n          break if ss.eos?\n\n          start_pos = ss.pos\n          peeked = ss.peek_byte\n\n          if (special = SPECIAL_TABLE[peeked])\n            ss.scan_byte\n            # Special case for \"..\"\n            if special == DOT && ss.peek_byte == DOT_ORD\n              ss.scan_byte\n              output << DOTDOT\n            elsif special == DASH\n              # Special case for negative numbers\n              if (peeked_byte = ss.peek_byte) && NUMBER_TABLE[peeked_byte]\n                ss.pos -= 1\n                output << [:number, ss.scan(NUMBER_LITERAL)]\n              else\n                output << special\n              end\n            else\n              output << special\n            end\n          elsif (sub_table = TWO_CHARS_COMPARISON_JUMP_TABLE[peeked])\n            ss.scan_byte\n            if (peeked_byte = ss.peek_byte) && (found = sub_table[peeked_byte])\n              output << found\n              ss.scan_byte\n            else\n              raise_syntax_error(start_pos, ss)\n            end\n          elsif (sub_table = COMPARISON_JUMP_TABLE[peeked])\n            ss.scan_byte\n            if (peeked_byte = ss.peek_byte) && (found = sub_table[peeked_byte])\n              output << found\n              ss.scan_byte\n            else\n              output << SINGLE_COMPARISON_TOKENS[peeked]\n            end\n          else\n            type, pattern = NEXT_MATCHER_JUMP_TABLE[peeked]\n\n            if type && (t = ss.scan(pattern))\n              # Special case for \"contains\"\n              output << if type == :id && t == \"contains\" && output.last&.first != :dot\n                COMPARISON_CONTAINS\n              else\n                [type, t]\n              end\n            else\n              raise_syntax_error(start_pos, ss)\n            end\n          end\n        end\n        # rubocop:enable Metrics/BlockNesting\n        output << EOS\n      rescue ::ArgumentError => e\n        if e.message == \"invalid byte sequence in #{ss.string.encoding}\"\n          raise SyntaxError, \"Invalid byte sequence in #{ss.string.encoding}\"\n        else\n          raise\n        end\n      end\n\n      def raise_syntax_error(start_pos, ss)\n        ss.pos = start_pos\n        # the character could be a UTF-8 character, use getch to get all the bytes\n        raise SyntaxError, \"Unexpected character #{ss.getch}\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/locales/en.yml",
    "content": "---\n  errors:\n    syntax:\n      tag_unexpected_args: \"Syntax Error in '%{tag}' - Valid syntax: %{tag}\"\n      block_tag_unexpected_args: \"Syntax Error in '%{tag}' - Valid syntax: {% %{tag} %}{% end%{tag} %}\"\n      assign: \"Syntax Error in 'assign' - Valid syntax: assign [var] = [source]\"\n      capture: \"Syntax Error in 'capture' - Valid syntax: capture [var]\"\n      case: \"Syntax Error in 'case' - Valid syntax: case [condition]\"\n      case_invalid_when: \"Syntax Error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %}\"\n      case_invalid_else: \"Syntax Error in tag 'case' - Valid else condition: {% else %} (no parameters) \"\n      cycle: \"Syntax Error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]\"\n      doc_invalid_nested: \"Syntax Error in 'doc' - Nested doc tags are not allowed\"\n      for: \"Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]\"\n      for_invalid_in: \"For loops require an 'in' clause\"\n      for_invalid_attribute: \"Invalid attribute in for loop. Valid attributes are limit and offset\"\n      if: \"Syntax Error in tag 'if' - Valid syntax: if [expression]\"\n      include: \"Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]\"\n      inline_comment_invalid: \"Syntax error in tag '#' - Each line of comments must be prefixed by the '#' character\"\n      invalid_delimiter: \"'%{tag}' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}\"\n      invalid_template_encoding: \"Invalid template encoding\"\n      render: \"Syntax error in tag 'render' - Template name must be a quoted string\"\n      table_row: \"Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3\"\n      table_row_invalid_attribute: \"Invalid attribute '%{attribute}' in tablerow loop. Valid attributes are cols, limit, offset, and range\"\n      tag_never_closed: \"'%{block_name}' tag was never closed\"\n      tag_termination: \"Tag '%{token}' was not properly terminated with regexp: %{tag_end}\"\n      unexpected_else: \"%{block_name} tag does not expect 'else' tag\"\n      unexpected_outer_tag: \"Unexpected outer '%{tag}' tag\"\n      unknown_tag: \"Unknown tag '%{tag}'\"\n      variable_termination: \"Variable '%{token}' was not properly terminated with regexp: %{tag_end}\"\n    argument:\n      include: \"Argument error in tag 'include' - Illegal template name\"\n    disabled:\n      tag: \"usage is not allowed in this context\"\n"
  },
  {
    "path": "lib/liquid/parse_context.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  class ParseContext\n    attr_accessor :locale, :line_number, :trim_whitespace, :depth\n    attr_reader :partial, :warnings, :error_mode, :environment\n\n    def initialize(options = Const::EMPTY_HASH)\n      @environment = options.fetch(:environment, Environment.default)\n      @template_options = options ? options.dup : {}\n\n      @locale   = @template_options[:locale] ||= I18n.new\n      @warnings = []\n\n      # constructing new StringScanner in Lexer, Tokenizer, etc is expensive\n      # This StringScanner will be shared by all of them\n      @string_scanner = StringScanner.new(\"\")\n\n      @expression_cache = if options[:expression_cache].nil?\n        {}\n      elsif options[:expression_cache].respond_to?(:[]) && options[:expression_cache].respond_to?(:[]=)\n        options[:expression_cache]\n      elsif options[:expression_cache]\n        {}\n      end\n\n      self.depth   = 0\n      self.partial = false\n    end\n\n    def [](option_key)\n      @options[option_key]\n    end\n\n    def new_block_body\n      Liquid::BlockBody.new\n    end\n\n    def new_parser(input)\n      @string_scanner.string = input\n      Parser.new(@string_scanner)\n    end\n\n    def new_tokenizer(source, start_line_number: nil, for_liquid_tag: false)\n      Tokenizer.new(\n        source: source,\n        string_scanner: @string_scanner,\n        line_number: start_line_number,\n        for_liquid_tag: for_liquid_tag,\n      )\n    end\n\n    def safe_parse_expression(parser)\n      Expression.safe_parse(parser, @string_scanner, @expression_cache)\n    end\n\n    def parse_expression(markup, safe: false)\n      if !safe && @error_mode == :strict2\n        # parse_expression is a widely used API. To maintain backward\n        # compatibility while raising awareness about strict2 parser standards,\n        # the safe flag supports API users make a deliberate decision.\n        #\n        # In strict2 mode, markup MUST come from a string returned by the parser\n        # (e.g., parser.expression). We're not calling the parser here to\n        # prevent redundant parser overhead.\n        raise Liquid::InternalError, \"unsafe parse_expression cannot be used in strict2 mode\"\n      end\n\n      Expression.parse(markup, @string_scanner, @expression_cache)\n    end\n\n    def partial=(value)\n      @partial = value\n      @options = value ? partial_options : @template_options\n\n      @error_mode = @options[:error_mode] || @environment.error_mode\n    end\n\n    def partial_options\n      @partial_options ||= begin\n        dont_pass = @template_options[:include_options_blacklist]\n        if dont_pass == true\n          { locale: locale }\n        elsif dont_pass.is_a?(Array)\n          @template_options.reject { |k, _v| dont_pass.include?(k) }\n        else\n          @template_options\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/parse_tree_visitor.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  class ParseTreeVisitor\n    def self.for(node, callbacks = Hash.new(proc {}))\n      if defined?(node.class::ParseTreeVisitor)\n        node.class::ParseTreeVisitor\n      else\n        self\n      end.new(node, callbacks)\n    end\n\n    def initialize(node, callbacks)\n      @node      = node\n      @callbacks = callbacks\n    end\n\n    def add_callback_for(*classes, &block)\n      callback = block\n      callback = ->(node, _) { yield node } if block.arity.abs == 1\n      callback = ->(_, _) { yield } if block.arity.zero?\n      classes.each { |klass| @callbacks[klass] = callback }\n      self\n    end\n\n    def visit(context = nil)\n      children.map do |node|\n        item, new_context = @callbacks[node.class].call(node, context)\n        [\n          item,\n          ParseTreeVisitor.for(node, @callbacks).visit(new_context || context),\n        ]\n      end\n    end\n\n    protected\n\n    def children\n      @node.respond_to?(:nodelist) ? Array(@node.nodelist) : Const::EMPTY_ARRAY\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/parser.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  class Parser\n    def initialize(input)\n      ss = input.is_a?(StringScanner) ? input : StringScanner.new(input)\n      @tokens = Lexer.tokenize(ss)\n      @p      = 0 # pointer to current location\n    end\n\n    def jump(point)\n      @p = point\n    end\n\n    def consume(type = nil)\n      token = @tokens[@p]\n      if type && token[0] != type\n        raise SyntaxError, \"Expected #{type} but found #{@tokens[@p].first}\"\n      end\n      @p += 1\n      token[1]\n    end\n\n    # Only consumes the token if it matches the type\n    # Returns the token's contents if it was consumed\n    # or false otherwise.\n    def consume?(type)\n      token = @tokens[@p]\n      return false unless token && token[0] == type\n      @p += 1\n      token[1]\n    end\n\n    # Like consume? Except for an :id token of a certain name\n    def id?(str)\n      token = @tokens[@p]\n      return false unless token && token[0] == :id\n      return false unless token[1] == str\n      @p += 1\n      token[1]\n    end\n\n    def look(type, ahead = 0)\n      tok = @tokens[@p + ahead]\n      return false unless tok\n      tok[0] == type\n    end\n\n    def expression\n      token = @tokens[@p]\n      case token[0]\n      when :id\n        str = consume\n        str << variable_lookups\n      when :open_square\n        str = consume.dup\n        str << expression\n        str << consume(:close_square)\n        str << variable_lookups\n      when :string, :number\n        consume\n      when :open_round\n        consume\n        first = expression\n        consume(:dotdot)\n        last = expression\n        consume(:close_round)\n        \"(#{first}..#{last})\"\n      else\n        raise SyntaxError, \"#{token} is not a valid expression\"\n      end\n    end\n\n    def argument\n      str = +\"\"\n      # might be a keyword argument (identifier: expression)\n      if look(:id) && look(:colon, 1)\n        str << consume << consume << ' '\n      end\n\n      str << expression\n      str\n    end\n\n    def variable_lookups\n      str = +\"\"\n      loop do\n        if look(:open_square)\n          str << consume\n          str << expression\n          str << consume(:close_square)\n        elsif look(:dot)\n          str << consume\n          str << consume(:id)\n        else\n          break\n        end\n      end\n      str\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/parser_switching.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  module ParserSwitching\n    # Do not use this.\n    #\n    # It's basically doing the same thing the {#parse_with_selected_parser},\n    # except this will try the strict parser regardless of the error mode,\n    # and fall back to the lax parser if the error mode is lax or warn,\n    # except when in strict2 mode where it uses the strict2 parser.\n    #\n    # @deprecated Use {#parse_with_selected_parser} instead.\n    def strict_parse_with_error_mode_fallback(markup)\n      return strict2_parse_with_error_context(markup) if strict2_mode?\n\n      strict_parse_with_error_context(markup)\n    rescue SyntaxError => e\n      case parse_context.error_mode\n      when :rigid\n        rigid_warn\n        raise\n      when :strict2\n        raise\n      when :strict\n        raise\n      when :warn\n        parse_context.warnings << e\n      end\n      lax_parse(markup)\n    end\n\n    def parse_with_selected_parser(markup)\n      case parse_context.error_mode\n      when :rigid   then rigid_warn && strict2_parse_with_error_context(markup)\n      when :strict2 then strict2_parse_with_error_context(markup)\n      when :strict  then strict_parse_with_error_context(markup)\n      when :lax     then lax_parse(markup)\n      when :warn\n        begin\n          strict2_parse_with_error_context(markup)\n        rescue SyntaxError => e\n          parse_context.warnings << e\n          lax_parse(markup)\n        end\n      end\n    end\n\n    def strict2_mode?\n      parse_context.error_mode == :strict2 || parse_context.error_mode == :rigid\n    end\n\n    private\n\n    def rigid_warn\n      Deprecations.warn(':rigid', ':strict2')\n    end\n\n    def strict2_parse_with_error_context(markup)\n      strict2_parse(markup)\n    rescue SyntaxError => e\n      e.line_number    = line_number\n      e.markup_context = markup_context(markup)\n      raise e\n    end\n\n    def strict_parse_with_error_context(markup)\n      strict_parse(markup)\n    rescue SyntaxError => e\n      e.line_number    = line_number\n      e.markup_context = markup_context(markup)\n      raise e\n    end\n\n    def markup_context(markup)\n      \"in \\\"#{markup.strip}\\\"\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/partial_cache.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  class PartialCache\n    def self.load(template_name, context:, parse_context:)\n      cached_partials = context.registers[:cached_partials]\n      cache_key = \"#{template_name}:#{parse_context.error_mode}\"\n      cached = cached_partials[cache_key]\n      return cached if cached\n\n      file_system = context.registers[:file_system]\n      source      = file_system.read_template_file(template_name)\n\n      parse_context.partial = true\n\n      template_factory = context.registers[:template_factory]\n      template = template_factory.for(template_name)\n\n      begin\n        partial = template.parse(source, parse_context)\n      rescue Liquid::Error => e\n        e.template_name = template&.name || template_name\n        raise e\n      end\n\n      partial.name ||= template_name\n\n      cached_partials[cache_key] = partial\n    ensure\n      parse_context.partial = false\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/profiler/hooks.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  module BlockBodyProfilingHook\n    def render_node(context, output, node)\n      if (profiler = context.profiler)\n        profiler.profile_node(context.template_name, code: node.raw, line_number: node.line_number) do\n          super\n        end\n      else\n        super\n      end\n    end\n  end\n  BlockBody.prepend(BlockBodyProfilingHook)\n\n  module DocumentProfilingHook\n    def render_to_output_buffer(context, output)\n      return super unless context.profiler\n      context.profiler.profile(context.template_name) { super }\n    end\n  end\n  Document.prepend(DocumentProfilingHook)\n\n  module ContextProfilingHook\n    attr_accessor :profiler\n\n    def new_isolated_subcontext\n      new_context = super\n      new_context.profiler = profiler\n      new_context\n    end\n  end\n  Context.prepend(ContextProfilingHook)\nend\n"
  },
  {
    "path": "lib/liquid/profiler.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'liquid/profiler/hooks'\n\nmodule Liquid\n  # Profiler enables support for profiling template rendering to help track down performance issues.\n  #\n  # To enable profiling, first require 'liquid/profiler'.\n  # Then, to profile a parse/render cycle, pass the <tt>profile: true</tt> option to <tt>Liquid::Template.parse</tt>.\n  # After <tt>Liquid::Template#render</tt> is called, the template object makes available an instance of this\n  # class via the <tt>Liquid::Template#profiler</tt> method.\n  #\n  #   template = Liquid::Template.parse(template_content, profile: true)\n  #   output  = template.render\n  #   profile = template.profiler\n  #\n  # This object contains all profiling information, containing information on what tags were rendered,\n  # where in the templates these tags live, and how long each tag took to render.\n  #\n  # This is a tree structure that is Enumerable all the way down, and keeps track of tags and rendering times\n  # inside of <tt>{% include %}</tt> tags.\n  #\n  #   profile.each do |node|\n  #     # Access to the node itself\n  #     node.code\n  #\n  #     # Which template and line number of this node.\n  #     # The top-level template name is `nil` by default, but can be set in the Liquid::Context before rendering.\n  #     node.partial\n  #     node.line_number\n  #\n  #     # Render time in seconds of this node\n  #     node.render_time\n  #\n  #     # If the template used {% include %}, this node will also have children.\n  #     node.children.each do |child2|\n  #       # ...\n  #     end\n  #   end\n  #\n  # Profiler also exposes the total time of the template's render in <tt>Liquid::Profiler#total_render_time</tt>.\n  #\n  # All render times are in seconds. There is a small performance hit when profiling is enabled.\n  #\n  class Profiler\n    include Enumerable\n\n    class Timing\n      attr_reader :code, :template_name, :line_number, :children\n      attr_accessor :total_time\n      alias_method :render_time, :total_time\n      alias_method :partial, :template_name\n\n      def initialize(code: nil, template_name: nil, line_number: nil)\n        @code = code\n        @template_name = template_name\n        @line_number = line_number\n        @children = []\n      end\n\n      def self_time\n        @self_time ||= begin\n          total_children_time = 0.0\n          @children.each do |child|\n            total_children_time += child.total_time\n          end\n          @total_time - total_children_time\n        end\n      end\n    end\n\n    attr_reader :total_time\n    alias_method :total_render_time, :total_time\n\n    def initialize\n      @root_children = []\n      @current_children = nil\n      @total_time = 0.0\n    end\n\n    def profile(template_name, &block)\n      # nested renders are done from a tag that already has a timing node\n      return yield if @current_children\n\n      root_children = @root_children\n      render_idx = root_children.length\n      begin\n        @current_children = root_children\n        profile_node(template_name, &block)\n      ensure\n        @current_children = nil\n        if (timing = root_children[render_idx])\n          @total_time += timing.total_time\n        end\n      end\n    end\n\n    def children\n      children = @root_children\n      if children.length == 1\n        children.first.children\n      else\n        children\n      end\n    end\n\n    def each(&block)\n      children.each(&block)\n    end\n\n    def [](idx)\n      children[idx]\n    end\n\n    def length\n      children.length\n    end\n\n    def profile_node(template_name, code: nil, line_number: nil)\n      timing = Timing.new(code: code, template_name: template_name, line_number: line_number)\n      parent_children = @current_children\n      start_time = monotonic_time\n      begin\n        @current_children = timing.children\n        yield\n      ensure\n        @current_children = parent_children\n        timing.total_time = monotonic_time - start_time\n        parent_children << timing\n      end\n    end\n\n    private\n\n    def monotonic_time\n      Process.clock_gettime(Process::CLOCK_MONOTONIC)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/range_lookup.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  class RangeLookup\n    def self.parse(start_markup, end_markup, string_scanner, cache = nil)\n      start_obj = Expression.parse(start_markup, string_scanner, cache)\n      end_obj   = Expression.parse(end_markup, string_scanner, cache)\n      if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate)\n        new(start_obj, end_obj)\n      else\n        begin\n          start_obj.to_i..end_obj.to_i\n        rescue NoMethodError\n          invalid_expr = start_markup unless start_obj.respond_to?(:to_i)\n          invalid_expr ||= end_markup unless end_obj.respond_to?(:to_i)\n          if invalid_expr\n            raise Liquid::SyntaxError, \"Invalid expression type '#{invalid_expr}' in range expression\"\n          end\n\n          raise\n        end\n      end\n    end\n\n    attr_reader :start_obj, :end_obj\n\n    def initialize(start_obj, end_obj)\n      @start_obj = start_obj\n      @end_obj   = end_obj\n    end\n\n    def evaluate(context)\n      start_int = to_integer(context.evaluate(@start_obj))\n      end_int   = to_integer(context.evaluate(@end_obj))\n      start_int..end_int\n    end\n\n    private\n\n    def to_integer(input)\n      case input\n      when Integer\n        input\n      when NilClass, String\n        input.to_i\n      else\n        Utils.to_integer(input)\n      end\n    end\n\n    class ParseTreeVisitor < Liquid::ParseTreeVisitor\n      def children\n        [@node.start_obj, @node.end_obj]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/registers.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  class Registers\n    attr_reader :static\n\n    def initialize(registers = {})\n      @static = registers.is_a?(Registers) ? registers.static : registers\n      @changes = {}\n    end\n\n    def []=(key, value)\n      @changes[key] = value\n    end\n\n    def [](key)\n      if @changes.key?(key)\n        @changes[key]\n      else\n        @static[key]\n      end\n    end\n\n    def delete(key)\n      @changes.delete(key)\n    end\n\n    UNDEFINED = Object.new\n\n    def fetch(key, default = UNDEFINED, &block)\n      if @changes.key?(key)\n        @changes.fetch(key)\n      elsif default != UNDEFINED\n        if block_given?\n          @static.fetch(key, &block)\n        else\n          @static.fetch(key, default)\n        end\n      else\n        @static.fetch(key, &block)\n      end\n    end\n\n    def key?(key)\n      @changes.key?(key) || @static.key?(key)\n    end\n  end\n\n  # Alias for backwards compatibility\n  StaticRegisters = Registers\nend\n"
  },
  {
    "path": "lib/liquid/resource_limits.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  class ResourceLimits\n    attr_accessor :render_length_limit,\n      :render_score_limit,\n      :assign_score_limit,\n      :cumulative_render_score_limit,\n      :cumulative_assign_score_limit\n    attr_reader :render_score,\n      :assign_score,\n      :cumulative_render_score,\n      :cumulative_assign_score\n\n    def initialize(limits)\n      @render_length_limit           = limits[:render_length_limit]\n      @render_score_limit            = limits[:render_score_limit]\n      @assign_score_limit            = limits[:assign_score_limit]\n      @cumulative_render_score_limit = limits[:cumulative_render_score_limit]\n      @cumulative_assign_score_limit = limits[:cumulative_assign_score_limit]\n      @cumulative_render_score = 0\n      @cumulative_assign_score = 0\n      reset\n    end\n\n    def increment_render_score(amount)\n      @render_score += amount\n      @cumulative_render_score += amount\n      raise_limits_reached if @render_score_limit && @render_score > @render_score_limit\n      raise_limits_reached if @cumulative_render_score_limit && @cumulative_render_score > @cumulative_render_score_limit\n    end\n\n    def increment_assign_score(amount)\n      @assign_score += amount\n      @cumulative_assign_score += amount\n      raise_limits_reached if @assign_score_limit && @assign_score > @assign_score_limit\n      raise_limits_reached if @cumulative_assign_score_limit && @cumulative_assign_score > @cumulative_assign_score_limit\n    end\n\n    # update either render_length or assign_score based on whether or not the writes are captured\n    def increment_write_score(output)\n      if (last_captured = @last_capture_length)\n        captured = output.bytesize\n        increment = captured - last_captured\n        @last_capture_length = captured\n        increment_assign_score(increment)\n      elsif @render_length_limit && output.bytesize > @render_length_limit\n        raise_limits_reached\n      end\n    end\n\n    def raise_limits_reached\n      @reached_limit = true\n      raise MemoryError, \"Memory limits exceeded\"\n    end\n\n    def reached?\n      @reached_limit\n    end\n\n    def reset\n      @reached_limit = false\n      @last_capture_length = nil\n      @render_score = @assign_score = 0\n      raise_limits_reached if @cumulative_render_score_limit && @cumulative_render_score > @cumulative_render_score_limit\n      raise_limits_reached if @cumulative_assign_score_limit && @cumulative_assign_score > @cumulative_assign_score_limit\n    end\n\n    def with_capture\n      old_capture_length = @last_capture_length\n      begin\n        @last_capture_length = 0\n        yield\n      ensure\n        @last_capture_length = old_capture_length\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/standardfilters.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'cgi'\nrequire 'base64'\nrequire 'bigdecimal'\nmodule Liquid\n  module StandardFilters\n    MAX_I32 = (1 << 31) - 1\n    private_constant :MAX_I32\n\n    MIN_I64 = -(1 << 63)\n    MAX_I64 = (1 << 63) - 1\n    I64_RANGE = MIN_I64..MAX_I64\n    private_constant :MIN_I64, :MAX_I64, :I64_RANGE\n\n    HTML_ESCAPE = {\n      '&' => '&amp;',\n      '>' => '&gt;',\n      '<' => '&lt;',\n      '\"' => '&quot;',\n      \"'\" => '&#39;',\n    }.freeze\n    HTML_ESCAPE_ONCE_REGEXP = /[\"><']|&(?!([a-zA-Z]+|(#\\d+));)/\n    STRIP_HTML_BLOCKS       = Regexp.union(\n      %r{<script.*?</script>}m,\n      /<!--.*?-->/m,\n      %r{<style.*?</style>}m,\n    )\n    STRIP_HTML_TAGS = /<.*?>/m\n\n    class << self\n      def try_coerce_encoding(input, encoding:)\n        original_encoding = input.encoding\n        if input.encoding != encoding\n          input.force_encoding(encoding)\n          unless input.valid_encoding?\n            input.force_encoding(original_encoding)\n          end\n        end\n        input\n      end\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category array\n    # @liquid_summary\n    #   Returns the size of a string or array.\n    # @liquid_description\n    #   The size of a string is the number of characters that the string includes. The size of an array is the number of items\n    #   in the array.\n    # @liquid_syntax variable | size\n    # @liquid_return [number]\n    def size(input)\n      input.respond_to?(:size) ? input.size : 0\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Converts a string to all lowercase characters.\n    # @liquid_syntax string | downcase\n    # @liquid_return [string]\n    def downcase(input)\n      Utils.to_s(input).downcase\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Converts a string to all uppercase characters.\n    # @liquid_syntax string | upcase\n    # @liquid_return [string]\n    def upcase(input)\n      Utils.to_s(input).upcase\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Capitalizes the first word in a string and downcases the remaining characters.\n    # @liquid_syntax string | capitalize\n    # @liquid_return [string]\n    def capitalize(input)\n      Utils.to_s(input).capitalize\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Escapes special characters in HTML, such as `<>`, `'`, and `&`, and converts characters into escape sequences. The filter doesn't effect characters within the string that don’t have a corresponding escape sequence.\".\n    # @liquid_syntax string | escape\n    # @liquid_return [string]\n    def escape(input)\n      CGI.escapeHTML(Utils.to_s(input)) unless input.nil?\n    end\n    alias_method :h, :escape\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Escapes a string without changing characters that have already been escaped.\n    # @liquid_syntax string | escape_once\n    # @liquid_return [string]\n    def escape_once(input)\n      Utils.to_s(input).gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Converts any URL-unsafe characters in a string to the\n    #   [percent-encoded](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding) equivalent.\n    # @liquid_description\n    #   > Note:\n    #   > Spaces are converted to a `+` character, instead of a percent-encoded character.\n    # @liquid_syntax string | url_encode\n    # @liquid_return [string]\n    def url_encode(input)\n      CGI.escape(Utils.to_s(input)) unless input.nil?\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Decodes any [percent-encoded](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding) characters\n    #   in a string.\n    # @liquid_syntax string | url_decode\n    # @liquid_return [string]\n    def url_decode(input)\n      return if input.nil?\n\n      result = CGI.unescape(Utils.to_s(input))\n      raise Liquid::ArgumentError, \"invalid byte sequence in #{result.encoding}\" unless result.valid_encoding?\n\n      result\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Encodes a string to [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).\n    # @liquid_syntax string | base64_encode\n    # @liquid_return [string]\n    def base64_encode(input)\n      Base64.strict_encode64(Utils.to_s(input))\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Decodes a string in [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).\n    # @liquid_syntax string | base64_decode\n    # @liquid_return [string]\n    def base64_decode(input)\n      input = Utils.to_s(input)\n      StandardFilters.try_coerce_encoding(Base64.strict_decode64(input), encoding: input.encoding)\n    rescue ::ArgumentError\n      raise Liquid::ArgumentError, \"invalid base64 provided to base64_decode\"\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Encodes a string to URL-safe [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).\n    # @liquid_syntax string | base64_url_safe_encode\n    # @liquid_return [string]\n    def base64_url_safe_encode(input)\n      Base64.urlsafe_encode64(Utils.to_s(input))\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Decodes a string in URL-safe [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).\n    # @liquid_syntax string | base64_url_safe_decode\n    # @liquid_return [string]\n    def base64_url_safe_decode(input)\n      input = Utils.to_s(input)\n      StandardFilters.try_coerce_encoding(Base64.urlsafe_decode64(input), encoding: input.encoding)\n    rescue ::ArgumentError\n      raise Liquid::ArgumentError, \"invalid base64 provided to base64_url_safe_decode\"\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Returns a substring or series of array items, starting at a given 0-based index.\n    # @liquid_description\n    #   By default, the substring has a length of one character, and the array series has one array item. However, you can\n    #   provide a second parameter to specify the number of characters or array items.\n    # @liquid_syntax string | slice\n    # @liquid_return [string]\n    def slice(input, offset, length = nil)\n      offset = Utils.to_integer(offset)\n      length = length ? Utils.to_integer(length) : 1\n\n      begin\n        if input.is_a?(Array)\n          input.slice(offset, length) || []\n        else\n          Utils.to_s(input).slice(offset, length) || ''\n        end\n      rescue RangeError\n        if I64_RANGE.cover?(length) && I64_RANGE.cover?(offset)\n          raise # unexpected error\n        end\n        offset = offset.clamp(I64_RANGE)\n        length = length.clamp(I64_RANGE)\n        retry\n      end\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Truncates a string down to a given number of characters.\n    # @liquid_description\n    #   If the specified number of characters is less than the length of the string, then an ellipsis (`...`) is appended to\n    #   the truncated string. The ellipsis is included in the character count of the truncated string.\n    # @liquid_syntax string | truncate: number\n    # @liquid_return [string]\n    def truncate(input, length = 50, truncate_string = \"...\")\n      return if input.nil?\n      input_str = Utils.to_s(input)\n      length    = Utils.to_integer(length)\n\n      truncate_string_str = Utils.to_s(truncate_string)\n\n      l = length - truncate_string_str.length\n      l = 0 if l < 0\n\n      input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Truncates a string down to a given number of words.\n    # @liquid_description\n    #   If the specified number of words is less than the number of words in the string, then an ellipsis (`...`) is appended to\n    #   the truncated string.\n    #\n    #   > Caution:\n    #   > HTML tags are treated as words, so you should strip any HTML from truncated content. If you don't strip HTML, then\n    #   > closing HTML tags can be removed, which can result in unexpected behavior.\n    # @liquid_syntax string | truncatewords: number\n    # @liquid_return [string]\n    def truncatewords(input, words = 15, truncate_string = \"...\")\n      return if input.nil?\n      input = Utils.to_s(input)\n      words = Utils.to_integer(words)\n      words = 1 if words <= 0\n\n      wordlist = begin\n        input.split(\" \", words + 1)\n      rescue RangeError\n        # integer too big for String#split, but we can semantically assume no truncation is needed\n        return input if words + 1 > MAX_I32\n        raise # unexpected error\n      end\n      return input if wordlist.length <= words\n\n      wordlist.pop\n      truncate_string = Utils.to_s(truncate_string)\n      wordlist.join(\" \").concat(truncate_string)\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Splits a string into an array of substrings based on a given separator.\n    # @liquid_syntax string | split: string\n    # @liquid_return [array[string]]\n    def split(input, pattern)\n      pattern = Utils.to_s(pattern)\n      input = Utils.to_s(input)\n      input.split(pattern)\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Removes leading and trailing whitespace and collapses consecutive whitespace to a single space.\n    # @liquid_syntax string | squish\n    # @liquid_return [string]\n    def squish(input)\n      return if input.nil?\n\n      Utils.to_s(input).strip.gsub(/\\s+/, ' ')\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Strips all whitespace from the left and right of a string.\n    # @liquid_syntax string | strip\n    # @liquid_return [string]\n    def strip(input)\n      input = Utils.to_s(input)\n      input.strip\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Strips all whitespace from the left of a string.\n    # @liquid_syntax string | lstrip\n    # @liquid_return [string]\n    def lstrip(input)\n      input = Utils.to_s(input)\n      input.lstrip\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Strips all whitespace from the right of a string.\n    # @liquid_syntax string | rstrip\n    # @liquid_return [string]\n    def rstrip(input)\n      input = Utils.to_s(input)\n      input.rstrip\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Strips all HTML tags from a string.\n    # @liquid_syntax string | strip_html\n    # @liquid_return [string]\n    def strip_html(input)\n      input = Utils.to_s(input)\n      empty  = ''\n      result = input.gsub(STRIP_HTML_BLOCKS, empty)\n      result.gsub!(STRIP_HTML_TAGS, empty)\n      result\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Strips all newline characters (line breaks) from a string.\n    # @liquid_syntax string | strip_newlines\n    # @liquid_return [string]\n    def strip_newlines(input)\n      input = Utils.to_s(input)\n      input.gsub(/\\r?\\n/, '')\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category array\n    # @liquid_summary\n    #   Combines all of the items in an array into a single string, separated by a space.\n    # @liquid_syntax array | join\n    # @liquid_return [string]\n    def join(input, glue = ' ')\n      glue = Utils.to_s(glue)\n      InputIterator.new(input, context).join(glue)\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category array\n    # @liquid_summary\n    #   Sorts the items in an array in case-sensitive alphabetical, or numerical, order.\n    # @liquid_syntax array | sort\n    # @liquid_return [array[untyped]]\n    def sort(input, property = nil)\n      ary = InputIterator.new(input, context)\n\n      return [] if ary.empty?\n\n      if property.nil?\n        ary.sort do |a, b|\n          nil_safe_compare(a, b)\n        end\n      elsif ary.all? { |el| el.respond_to?(:[]) }\n        begin\n          ary.sort { |a, b| nil_safe_compare(a[property], b[property]) }\n        rescue TypeError\n          raise_property_error(property)\n        end\n      end\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category array\n    # @liquid_summary\n    #   Sorts the items in an array in case-insensitive alphabetical order.\n    # @liquid_description\n    #   > Caution:\n    #   > You shouldn't use the `sort_natural` filter to sort numerical values. When comparing items an array, each item is converted to a\n    #   > string, so sorting on numerical values can lead to unexpected results.\n    # @liquid_syntax array | sort_natural\n    # @liquid_return [array[untyped]]\n    def sort_natural(input, property = nil)\n      ary = InputIterator.new(input, context)\n\n      return [] if ary.empty?\n\n      if property.nil?\n        ary.sort do |a, b|\n          nil_safe_casecmp(a, b)\n        end\n      elsif ary.all? { |el| el.respond_to?(:[]) }\n        begin\n          ary.sort { |a, b| nil_safe_casecmp(a[property], b[property]) }\n        rescue TypeError\n          raise_property_error(property)\n        end\n      end\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category array\n    # @liquid_summary\n    #   Filters an array to include only items with a specific property value.\n    # @liquid_description\n    #   This requires you to provide both the property name and the associated value.\n    # @liquid_syntax array | where: string, string\n    # @liquid_return [array[untyped]]\n    def where(input, property, target_value = nil)\n      filter_array(input, property, target_value) { |ary, &block| ary.select(&block) }\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category array\n    # @liquid_summary\n    #   Filters an array to exclude items with a specific property value.\n    # @liquid_description\n    #   This requires you to provide both the property name and the associated value.\n    # @liquid_syntax array | reject: string, string\n    # @liquid_return [array[untyped]]\n    def reject(input, property, target_value = nil)\n      filter_array(input, property, target_value) { |ary, &block| ary.reject(&block) }\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category array\n    # @liquid_summary\n    #   Tests if any item in an array has a specific property value.\n    # @liquid_description\n    #   This requires you to provide both the property name and the associated value.\n    # @liquid_syntax array | has: string, string\n    # @liquid_return [boolean]\n    def has(input, property, target_value = nil)\n      filter_array(input, property, target_value, false) { |ary, &block| ary.any?(&block) }\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category array\n    # @liquid_summary\n    #   Returns the first item in an array with a specific property value.\n    # @liquid_description\n    #   This requires you to provide both the property name and the associated value.\n    # @liquid_syntax array | find: string, string\n    # @liquid_return [untyped]\n    def find(input, property, target_value = nil)\n      filter_array(input, property, target_value, nil) { |ary, &block| ary.find(&block) }\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category array\n    # @liquid_summary\n    #   Returns the index of the first item in an array with a specific property value.\n    # @liquid_description\n    #   This requires you to provide both the property name and the associated value.\n    # @liquid_syntax array | find_index: string, string\n    # @liquid_return [number]\n    def find_index(input, property, target_value = nil)\n      filter_array(input, property, target_value, nil) { |ary, &block| ary.find_index(&block) }\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category array\n    # @liquid_summary\n    #   Removes any duplicate items in an array.\n    # @liquid_syntax array | uniq\n    # @liquid_return [array[untyped]]\n    def uniq(input, property = nil)\n      ary = InputIterator.new(input, context)\n\n      if property.nil?\n        ary.uniq\n      elsif ary.empty? # The next two cases assume a non-empty array.\n        []\n      else\n        ary.uniq do |item|\n          item[property]\n        rescue TypeError\n          raise_property_error(property)\n        rescue NoMethodError\n          return nil unless item.respond_to?(:[])\n          raise\n        end\n      end\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category array\n    # @liquid_summary\n    #   Reverses the order of the items in an array.\n    # @liquid_syntax array | reverse\n    # @liquid_return [array[untyped]]\n    def reverse(input)\n      ary = InputIterator.new(input, context)\n      ary.reverse\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category array\n    # @liquid_summary\n    #   Creates an array of values from a specific property of the items in an array.\n    # @liquid_syntax array | map: string\n    # @liquid_return [array[untyped]]\n    def map(input, property)\n      InputIterator.new(input, context).map do |e|\n        e = e.call if e.is_a?(Proc)\n\n        if property == \"to_liquid\"\n          e\n        elsif e.respond_to?(:[])\n          r = e[property]\n          r.is_a?(Proc) ? r.call : r\n        end\n      end\n    rescue TypeError\n      raise_property_error(property)\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category array\n    # @liquid_summary\n    #   Removes any `nil` items from an array.\n    # @liquid_syntax array | compact\n    # @liquid_return [array[untyped]]\n    def compact(input, property = nil)\n      ary = InputIterator.new(input, context)\n\n      if property.nil?\n        ary.compact\n      elsif ary.empty? # The next two cases assume a non-empty array.\n        []\n      else\n        ary.reject do |item|\n          item[property].nil?\n        rescue TypeError\n          raise_property_error(property)\n        rescue NoMethodError\n          return nil unless item.respond_to?(:[])\n          raise\n        end\n      end\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Replaces any instance of a substring inside a string with a given string.\n    # @liquid_syntax string | replace: string, string\n    # @liquid_return [string]\n    def replace(input, string, replacement = '')\n      string = Utils.to_s(string)\n      replacement = Utils.to_s(replacement)\n      input = Utils.to_s(input)\n      input.gsub(string, replacement)\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Replaces the first instance of a substring inside a string with a given string.\n    # @liquid_syntax string | replace_first: string, string\n    # @liquid_return [string]\n    def replace_first(input, string, replacement = '')\n      string = Utils.to_s(string)\n      replacement = Utils.to_s(replacement)\n      input = Utils.to_s(input)\n      input.sub(string, replacement)\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Replaces the last instance of a substring inside a string with a given string.\n    # @liquid_syntax string | replace_last: string, string\n    # @liquid_return [string]\n    def replace_last(input, string, replacement)\n      input = Utils.to_s(input)\n      string = Utils.to_s(string)\n      replacement = Utils.to_s(replacement)\n\n      start_index = input.rindex(string)\n\n      return input unless start_index\n\n      output = input.dup\n      output[start_index, string.length] = replacement\n      output\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Removes any instance of a substring inside a string.\n    # @liquid_syntax string | remove: string\n    # @liquid_return [string]\n    def remove(input, string)\n      replace(input, string, '')\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Removes the first instance of a substring inside a string.\n    # @liquid_syntax string | remove_first: string\n    # @liquid_return [string]\n    def remove_first(input, string)\n      replace_first(input, string, '')\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Removes the last instance of a substring inside a string.\n    # @liquid_syntax string | remove_last: string\n    # @liquid_return [string]\n    def remove_last(input, string)\n      replace_last(input, string, '')\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Adds a given string to the end of a string.\n    # @liquid_syntax string | append: string\n    # @liquid_return [string]\n    def append(input, string)\n      input = Utils.to_s(input)\n      string = Utils.to_s(string)\n      input + string\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category array\n    # @liquid_summary\n    #   Concatenates (combines) two arrays.\n    # @liquid_description\n    #   > Note:\n    #   > The `concat` filter won't filter out duplicates. If you want to remove duplicates, then you need to use the\n    #   > [`uniq` filter](/docs/api/liquid/filters/uniq).\n    # @liquid_syntax array | concat: array\n    # @liquid_return [array[untyped]]\n    def concat(input, array)\n      unless array.respond_to?(:to_ary)\n        raise ArgumentError, \"concat filter requires an array argument\"\n      end\n      InputIterator.new(input, context).concat(array)\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Adds a given string to the beginning of a string.\n    # @liquid_syntax string | prepend: string\n    # @liquid_return [string]\n    def prepend(input, string)\n      input = Utils.to_s(input)\n      string = Utils.to_s(string)\n      string + input\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category string\n    # @liquid_summary\n    #   Converts newlines (`\\n`) in a string to HTML line breaks (`<br>`).\n    # @liquid_syntax string | newline_to_br\n    # @liquid_return [string]\n    def newline_to_br(input)\n      input = Utils.to_s(input)\n      input.gsub(/\\r?\\n/, \"<br />\\n\")\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category date\n    # @liquid_summary\n    #   Formats a date according to a specified format string.\n    # @liquid_description\n    #   This filter formats a date using various format specifiers. If the format string is empty,\n    #   the original input is returned. If the input cannot be converted to a date, the original input is returned.\n    #\n    #   The following format specifiers can be used:\n    #\n    #   %a - The abbreviated weekday name (``Sun'')\n    #   %A - The  full  weekday  name (``Sunday'')\n    #   %b - The abbreviated month name (``Jan'')\n    #   %B - The  full  month  name (``January'')\n    #   %c - The preferred local date and time representation\n    #   %d - Day of the month (01..31)\n    #   %H - Hour of the day, 24-hour clock (00..23)\n    #   %I - Hour of the day, 12-hour clock (01..12)\n    #   %j - Day of the year (001..366)\n    #   %m - Month of the year (01..12)\n    #   %M - Minute of the hour (00..59)\n    #   %p - Meridian indicator (``AM''  or  ``PM'')\n    #   %s - Number of seconds since 1970-01-01 00:00:00 UTC.\n    #   %S - Second of the minute (00..60)\n    #   %U - Week  number  of the current year,\n    #           starting with the first Sunday as the first\n    #           day of the first week (00..53)\n    #   %W - Week  number  of the current year,\n    #           starting with the first Monday as the first\n    #           day of the first week (00..53)\n    #   %w - Day of the week (Sunday is 0, 0..6)\n    #   %x - Preferred representation for the date alone, no time\n    #   %X - Preferred representation for the time alone, no date\n    #   %y - Year without a century (00..99)\n    #   %Y - Year with century\n    #   %Z - Time zone name\n    #   %% - Literal ``%'' character\n    # @liquid_syntax date | date: string\n    # @liquid_return [string]\n    def date(input, format)\n      str_format = Utils.to_s(format)\n      return input if str_format.empty?\n\n      return input unless (date = Utils.to_date(input))\n\n      date.strftime(str_format)\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category array\n    # @liquid_summary\n    #   Returns the first item in an array.\n    # @liquid_syntax array | first\n    # @liquid_return [untyped]\n    def first(array)\n      # ActiveSupport returns \"\" for empty strings, not nil\n      return array[0] || \"\" if array.is_a?(String)\n      array.first if array.respond_to?(:first)\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category array\n    # @liquid_summary\n    #   Returns the last item in an array.\n    # @liquid_syntax array | last\n    # @liquid_return [untyped]\n    def last(array)\n      # ActiveSupport returns \"\" for empty strings, not nil\n      return array[-1] || \"\" if array.is_a?(String)\n      array.last if array.respond_to?(:last)\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category math\n    # @liquid_summary\n    #   Returns the absolute value of a number.\n    # @liquid_syntax number | abs\n    # @liquid_return [number]\n    def abs(input)\n      result = Utils.to_number(input).abs\n      result.is_a?(BigDecimal) ? result.to_f : result\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category math\n    # @liquid_summary\n    #   Adds two numbers.\n    # @liquid_syntax number | plus: number\n    # @liquid_return [number]\n    def plus(input, operand)\n      apply_operation(input, operand, :+)\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category math\n    # @liquid_summary\n    #   Subtracts a given number from another number.\n    # @liquid_syntax number | minus: number\n    # @liquid_return [number]\n    def minus(input, operand)\n      apply_operation(input, operand, :-)\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category math\n    # @liquid_summary\n    #   Multiplies a number by a given number.\n    # @liquid_syntax number | times: number\n    # @liquid_return [number]\n    def times(input, operand)\n      apply_operation(input, operand, :*)\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category math\n    # @liquid_summary\n    #   Divides a number by a given number. The `divided_by` filter produces a result of the same type as the divisor. This means if you divide by an integer, the result will be an integer, and if you divide by a float, the result will be a float.\n    # @liquid_syntax number | divided_by: number\n    # @liquid_return [number]\n    def divided_by(input, operand)\n      apply_operation(input, operand, :/)\n    rescue ::ZeroDivisionError => e\n      raise Liquid::ZeroDivisionError, e.message\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category math\n    # @liquid_summary\n    #   Returns the remainder of dividing a number by a given number.\n    # @liquid_syntax number | modulo: number\n    # @liquid_return [number]\n    def modulo(input, operand)\n      apply_operation(input, operand, :%)\n    rescue ::ZeroDivisionError => e\n      raise Liquid::ZeroDivisionError, e.message\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category math\n    # @liquid_summary\n    #   Rounds a number to the nearest integer.\n    # @liquid_syntax number | round\n    # @liquid_return [number]\n    def round(input, n = 0)\n      result = Utils.to_number(input).round(Utils.to_number(n))\n      result = result.to_f if result.is_a?(BigDecimal)\n      result = result.to_i if n == 0\n      result\n    rescue ::FloatDomainError => e\n      raise Liquid::FloatDomainError, e.message\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category math\n    # @liquid_summary\n    #   Rounds a number up to the nearest integer.\n    # @liquid_syntax number | ceil\n    # @liquid_return [number]\n    def ceil(input)\n      Utils.to_number(input).ceil.to_i\n    rescue ::FloatDomainError => e\n      raise Liquid::FloatDomainError, e.message\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category math\n    # @liquid_summary\n    #   Rounds a number down to the nearest integer.\n    # @liquid_syntax number | floor\n    # @liquid_return [number]\n    def floor(input)\n      Utils.to_number(input).floor.to_i\n    rescue ::FloatDomainError => e\n      raise Liquid::FloatDomainError, e.message\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category math\n    # @liquid_summary\n    #   Limits a number to a minimum value.\n    # @liquid_syntax number | at_least\n    # @liquid_return [number]\n    def at_least(input, n)\n      min_value = Utils.to_number(n)\n\n      result = Utils.to_number(input)\n      result = min_value if min_value > result\n      result.is_a?(BigDecimal) ? result.to_f : result\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category math\n    # @liquid_summary\n    #   Limits a number to a maximum value.\n    # @liquid_syntax number | at_most\n    # @liquid_return [number]\n    def at_most(input, n)\n      max_value = Utils.to_number(n)\n\n      result = Utils.to_number(input)\n      result = max_value if max_value < result\n      result.is_a?(BigDecimal) ? result.to_f : result\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category default\n    # @liquid_summary\n    #   Sets a default value for any variable whose value is one of the following:\n    #\n    #   - [`empty`](/docs/api/liquid/basics#empty)\n    #   - [`false`](/docs/api/liquid/basics#truthy-and-falsy)\n    #   - [`nil`](/docs/api/liquid/basics#nil)\n    # @liquid_syntax variable | default: variable\n    # @liquid_return [untyped]\n    # @liquid_optional_param allow_false: [boolean] Whether to use false values instead of the default.\n    def default(input, default_value = '', options = {})\n      options = {} unless options.is_a?(Hash)\n      false_check = options['allow_false'] ? input.nil? : !Liquid::Utils.to_liquid_value(input)\n      false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input\n    end\n\n    # @liquid_public_docs\n    # @liquid_type filter\n    # @liquid_category array\n    # @liquid_summary\n    #   Returns the sum of all elements in an array.\n    # @liquid_syntax array | sum\n    # @liquid_return [number]\n    def sum(input, property = nil)\n      ary = InputIterator.new(input, context)\n      return 0 if ary.empty?\n\n      values_for_sum = ary.map do |item|\n        if property.nil?\n          item\n        elsif item.respond_to?(:[])\n          item[property]\n        else\n          0\n        end\n      rescue TypeError\n        raise_property_error(property)\n      end\n\n      result = InputIterator.new(values_for_sum, context).sum do |item|\n        Utils.to_number(item)\n      end\n\n      result.is_a?(BigDecimal) ? result.to_f : result\n    end\n\n    private\n\n    attr_reader :context\n\n    def filter_array(input, property, target_value, default_value = [], &block)\n      ary = InputIterator.new(input, context)\n\n      return default_value if ary.empty?\n\n      block.call(ary) do |item|\n        if target_value.nil?\n          item[property]\n        else\n          item[property] == target_value\n        end\n      rescue TypeError\n        raise_property_error(property)\n      rescue NoMethodError\n        return nil unless item.respond_to?(:[])\n        raise\n      end\n    end\n\n    def raise_property_error(property)\n      raise Liquid::ArgumentError, \"cannot select the property '#{Utils.to_s(property)}'\"\n    end\n\n    def apply_operation(input, operand, operation)\n      result = Utils.to_number(input).send(operation, Utils.to_number(operand))\n      result.is_a?(BigDecimal) ? result.to_f : result\n    end\n\n    def nil_safe_compare(a, b)\n      result = a <=> b\n\n      if result\n        result\n      elsif a.nil?\n        1\n      elsif b.nil?\n        -1\n      else\n        raise Liquid::ArgumentError, \"cannot sort values of incompatible types\"\n      end\n    end\n\n    def nil_safe_casecmp(a, b)\n      if !a.nil? && !b.nil?\n        a.to_s.casecmp(b.to_s)\n      elsif a.nil? && b.nil?\n        0\n      else\n        a.nil? ? 1 : -1\n      end\n    end\n\n    class InputIterator\n      include Enumerable\n\n      def initialize(input, context)\n        @context = context\n        @input   = if input.is_a?(Array)\n          input.flatten\n        elsif input.is_a?(Hash)\n          [input]\n        elsif input.is_a?(Enumerable)\n          input\n        else\n          Array(input)\n        end\n      end\n\n      def join(glue)\n        first = true\n        output = +\"\"\n        each do |item|\n          if first\n            first = false\n          else\n            output << glue\n          end\n\n          output << Liquid::Utils.to_s(item)\n        end\n        output\n      end\n\n      def concat(args)\n        to_a.concat(args)\n      end\n\n      def reverse\n        reverse_each.to_a\n      end\n\n      def uniq(&block)\n        to_a.uniq do |item|\n          item = Utils.to_liquid_value(item)\n          block ? yield(item) : item\n        end\n      end\n\n      def compact\n        to_a.compact\n      end\n\n      def empty?\n        @input.each { return false }\n        true\n      end\n\n      def each\n        @input.each do |e|\n          e = e.respond_to?(:to_liquid) ? e.to_liquid : e\n          e.context = @context if e.respond_to?(:context=)\n          yield(e)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/strainer_template.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'set'\n\nmodule Liquid\n  # StrainerTemplate is the computed class for the filters system.\n  # New filters are mixed into the strainer class which is then instantiated for each liquid template render run.\n  #\n  # The Strainer only allows method calls defined in filters given to it via StrainerFactory.add_global_filter,\n  # Context#add_filters or Template.register_filter\n  class StrainerTemplate\n    def initialize(context)\n      @context = context\n    end\n\n    class << self\n      def add_filter(filter)\n        return if include?(filter)\n\n        invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) }\n        if invokable_non_public_methods.any?\n          raise MethodOverrideError, \"Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}\"\n        end\n\n        include(filter)\n\n        filter_methods.merge(filter.public_instance_methods.map(&:to_s))\n      end\n\n      def invokable?(method)\n        filter_methods.include?(method.to_s)\n      end\n\n      def inherited(subclass)\n        super\n        subclass.instance_variable_set(:@filter_methods, @filter_methods.dup)\n      end\n\n      def filter_method_names\n        filter_methods.map(&:to_s).to_a\n      end\n\n      private\n\n      def filter_methods\n        @filter_methods ||= Set.new\n      end\n    end\n\n    def invoke(method, *args)\n      if self.class.invokable?(method)\n        send(method, *args)\n      elsif @context.strict_filters\n        raise Liquid::UndefinedFilter, \"undefined filter #{method}\"\n      else\n        args.first\n      end\n    rescue ::ArgumentError => e\n      raise Liquid::ArgumentError, e.message, e.backtrace\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tablerowloop_drop.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # @liquid_public_docs\n  # @liquid_type object\n  # @liquid_name tablerowloop\n  # @liquid_summary\n  #   Information about a parent [`tablerow` loop](/docs/api/liquid/tags/tablerow).\n  class TablerowloopDrop < Drop\n    def initialize(length, cols)\n      @length = length\n      @row    = 1\n      @col    = 1\n      @cols   = cols\n      @index  = 0\n    end\n\n    # @liquid_public_docs\n    # @liquid_summary\n    #   The total number of iterations in the loop.\n    # @liquid_return [number]\n    attr_reader :length\n\n    # @liquid_public_docs\n    # @liquid_summary\n    #   The 1-based index of the current column.\n    # @liquid_return [number]\n    attr_reader :col\n\n    # @liquid_public_docs\n    # @liquid_summary\n    #   The 1-based index of current row.\n    # @liquid_return [number]\n    attr_reader :row\n\n    # @liquid_public_docs\n    # @liquid_summary\n    #   The 1-based index of the current iteration.\n    # @liquid_return [number]\n    def index\n      @index + 1\n    end\n\n    # @liquid_public_docs\n    # @liquid_summary\n    #   The 0-based index of the current iteration.\n    # @liquid_return [number]\n    def index0\n      @index\n    end\n\n    # @liquid_public_docs\n    # @liquid_summary\n    #   The 0-based index of the current column.\n    # @liquid_return [number]\n    def col0\n      @col - 1\n    end\n\n    # @liquid_public_docs\n    # @liquid_summary\n    #   The 1-based index of the current iteration, in reverse order.\n    # @liquid_return [number]\n    def rindex\n      @length - @index\n    end\n\n    # @liquid_public_docs\n    # @liquid_summary\n    #   The 0-based index of the current iteration, in reverse order.\n    # @liquid_return [number]\n    def rindex0\n      @length - @index - 1\n    end\n\n    # @liquid_public_docs\n    # @liquid_summary\n    #   Returns `true` if the current iteration is the first. Returns `false` if not.\n    # @liquid_return [boolean]\n    def first\n      @index == 0\n    end\n\n    # @liquid_public_docs\n    # @liquid_summary\n    #   Returns `true` if the current iteration is the last. Returns `false` if not.\n    # @liquid_return [boolean]\n    def last\n      @index == @length - 1\n    end\n\n    # @liquid_public_docs\n    # @liquid_summary\n    #   Returns `true` if the current column is the first in the row. Returns `false` if not.\n    # @liquid_return [boolean]\n    def col_first\n      @col == 1\n    end\n\n    # @liquid_public_docs\n    # @liquid_summary\n    #   Returns `true` if the current column is the last in the row. Returns `false` if not.\n    # @liquid_return [boolean]\n    def col_last\n      @col == @cols\n    end\n\n    protected\n\n    def increment!\n      @index += 1\n\n      if @col == @cols\n        @col = 1\n        @row += 1\n      else\n        @col += 1\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tag/disableable.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  class Tag\n    module Disableable\n      def render_to_output_buffer(context, output)\n        if context.tag_disabled?(tag_name)\n          output << disabled_error(context)\n          return\n        end\n        super\n      end\n\n      def disabled_error(context)\n        # raise then rescue the exception so that the Context#exception_renderer can re-raise it\n        raise DisabledError, \"#{tag_name} #{parse_context[:locale].t('errors.disabled.tag')}\"\n      rescue DisabledError => exc\n        context.handle_error(exc, line_number)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tag/disabler.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  class Tag\n    module Disabler\n      def render_to_output_buffer(context, output)\n        context.with_disabled_tags(self.class.disabled_tags) do\n          super\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tag.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'liquid/tag/disabler'\nrequire 'liquid/tag/disableable'\n\nmodule Liquid\n  class Tag\n    attr_reader :nodelist, :tag_name, :line_number, :parse_context\n    alias_method :options, :parse_context\n    include ParserSwitching\n\n    class << self\n      def parse(tag_name, markup, tokenizer, parse_context)\n        tag = new(tag_name, markup, parse_context)\n        tag.parse(tokenizer)\n        tag\n      end\n\n      def disable_tags(*tag_names)\n        tag_names += disabled_tags\n        define_singleton_method(:disabled_tags) { tag_names }\n        prepend(Disabler)\n      end\n\n      private :new\n\n      protected\n\n      def disabled_tags\n        []\n      end\n    end\n\n    def initialize(tag_name, markup, parse_context)\n      @tag_name      = tag_name\n      @markup        = markup\n      @parse_context = parse_context\n      @line_number   = parse_context.line_number\n    end\n\n    def parse(_tokens)\n    end\n\n    def raw\n      \"#{@tag_name} #{@markup}\"\n    end\n\n    def name\n      self.class.name.downcase\n    end\n\n    def render(_context)\n      ''\n    end\n\n    # For backwards compatibility with custom tags. In a future release, the semantics\n    # of the `render_to_output_buffer` method will become the default and the `render`\n    # method will be removed.\n    def render_to_output_buffer(context, output)\n      render_result = render(context)\n      output << render_result if render_result\n      output\n    end\n\n    def blank?\n      false\n    end\n\n    private\n\n    def safe_parse_expression(parser)\n      parse_context.safe_parse_expression(parser)\n    end\n\n    def parse_expression(markup, safe: false)\n      parse_context.parse_expression(markup, safe: safe)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tags/assign.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # @liquid_public_docs\n  # @liquid_type tag\n  # @liquid_category variable\n  # @liquid_name assign\n  # @liquid_summary\n  #   Creates a new variable.\n  # @liquid_description\n  #   You can create variables of any [basic type](/docs/api/liquid/basics#types), [object](/docs/api/liquid/objects), or object property.\n  #\n  #   > Caution:\n  #   > Predefined Liquid objects can be overridden by variables with the same name.\n  #   > To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.\n  # @liquid_syntax\n  #   {% assign variable_name = value %}\n  # @liquid_syntax_keyword variable_name The name of the variable being created.\n  # @liquid_syntax_keyword value The value you want to assign to the variable.\n  class Assign < Tag\n    Syntax = /(#{VariableSignature}+)\\s*=\\s*(.*)\\s*/om\n\n    # @api private\n    def self.raise_syntax_error(parse_context)\n      raise Liquid::SyntaxError, parse_context.locale.t('errors.syntax.assign')\n    end\n\n    attr_reader :to, :from\n\n    def initialize(tag_name, markup, parse_context)\n      super\n      if markup =~ Syntax\n        @to   = Regexp.last_match(1)\n        @from = Variable.new(Regexp.last_match(2), parse_context)\n      else\n        self.class.raise_syntax_error(parse_context)\n      end\n    end\n\n    def render_to_output_buffer(context, output)\n      val = @from.render(context)\n      context.scopes.last[@to] = val\n      context.resource_limits.increment_assign_score(assign_score_of(val))\n      output\n    end\n\n    def blank?\n      true\n    end\n\n    private\n\n    def assign_score_of(val)\n      if val.instance_of?(String)\n        val.bytesize\n      elsif val.instance_of?(Array)\n        sum = 1\n        # Uses #each to avoid extra allocations.\n        val.each { |child| sum += assign_score_of(child) }\n        sum\n      elsif val.instance_of?(Hash)\n        sum = 1\n        val.each do |key, entry_value|\n          sum += assign_score_of(key)\n          sum += assign_score_of(entry_value)\n        end\n        sum\n      else\n        1\n      end\n    end\n\n    class ParseTreeVisitor < Liquid::ParseTreeVisitor\n      def children\n        [@node.from]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tags/break.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # Break tag to be used to break out of a for loop.\n  #\n  # == Basic Usage:\n  #    {% for item in collection %}\n  #      {% if item.condition %}\n  #        {% break %}\n  #      {% endif %}\n  #    {% endfor %}\n  #\n  # @liquid_public_docs\n  # @liquid_type tag\n  # @liquid_category iteration\n  # @liquid_name break\n  # @liquid_summary\n  #   Stops a [`for` loop](/docs/api/liquid/tags/for) from iterating.\n  # @liquid_syntax\n  #   {% break %}\n  class Break < Tag\n    INTERRUPT = BreakInterrupt.new.freeze\n\n    def render_to_output_buffer(context, output)\n      context.push_interrupt(INTERRUPT)\n      output\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tags/capture.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # @liquid_public_docs\n  # @liquid_type tag\n  # @liquid_category variable\n  # @liquid_name capture\n  # @liquid_summary\n  #   Creates a new variable with a string value.\n  # @liquid_description\n  #   You can create complex strings with Liquid logic and variables.\n  #\n  #   > Caution:\n  #   > Predefined Liquid objects can be overridden by variables with the same name.\n  #   > To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.\n  # @liquid_syntax\n  #   {% capture variable %}\n  #     value\n  #   {% endcapture %}\n  # @liquid_syntax_keyword variable The name of the variable being created.\n  # @liquid_syntax_keyword value The value you want to assign to the variable.\n  class Capture < Block\n    Syntax = /(#{VariableSignature}+)/o\n\n    def initialize(tag_name, markup, options)\n      super\n      if markup =~ Syntax\n        @to = Regexp.last_match(1)\n      else\n        raise SyntaxError, options[:locale].t(\"errors.syntax.capture\")\n      end\n    end\n\n    def render_to_output_buffer(context, output)\n      context.resource_limits.with_capture do\n        capture_output = render(context)\n        context.scopes.last[@to] = capture_output\n      end\n      output\n    end\n\n    def blank?\n      true\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tags/case.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # @liquid_public_docs\n  # @liquid_type tag\n  # @liquid_category conditional\n  # @liquid_name case\n  # @liquid_summary\n  #   Renders a specific expression depending on the value of a specific variable.\n  # @liquid_syntax\n  #   {% case variable %}\n  #     {% when first_value %}\n  #       first_expression\n  #     {% when second_value %}\n  #       second_expression\n  #     {% else %}\n  #       third_expression\n  #   {% endcase %}\n  # @liquid_syntax_keyword variable The name of the variable you want to base your case statement on.\n  # @liquid_syntax_keyword first_value A specific value to check for.\n  # @liquid_syntax_keyword second_value A specific value to check for.\n  # @liquid_syntax_keyword first_expression An expression to be rendered when the variable's value matches `first_value`.\n  # @liquid_syntax_keyword second_expression An expression to be rendered when the variable's value matches `second_value`.\n  # @liquid_syntax_keyword third_expression An expression to be rendered when the variable's value has no match.\n  class Case < Block\n    Syntax     = /(#{QuotedFragment})/o\n    WhenSyntax = /(#{QuotedFragment})(?:(?:\\s+or\\s+|\\s*\\,\\s*)(#{QuotedFragment}.*))?/om\n\n    attr_reader :blocks, :left\n\n    def initialize(tag_name, markup, options)\n      super\n      @blocks = []\n      parse_with_selected_parser(markup)\n    end\n\n    def parse(tokens)\n      body = case_body = new_body\n      body = @blocks.last.attachment while parse_body(body, tokens)\n      @blocks.reverse_each do |condition|\n        body = condition.attachment\n        unless body.frozen?\n          body.remove_blank_strings if blank?\n          body.freeze\n        end\n      end\n      case_body.freeze\n    end\n\n    def nodelist\n      @blocks.map(&:attachment)\n    end\n\n    def unknown_tag(tag, markup, tokens)\n      case tag\n      when 'when'\n        record_when_condition(markup)\n      when 'else'\n        record_else_condition(markup)\n      else\n        super\n      end\n    end\n\n    def render_to_output_buffer(context, output)\n      execute_else_block = true\n\n      @blocks.each do |block|\n        if block.else?\n          block.attachment.render_to_output_buffer(context, output) if execute_else_block\n          next\n        end\n\n        result = Liquid::Utils.to_liquid_value(\n          block.evaluate(context),\n        )\n\n        if result\n          execute_else_block = false\n          block.attachment.render_to_output_buffer(context, output)\n        end\n      end\n\n      output\n    end\n\n    private\n\n    def strict2_parse(markup)\n      parser = @parse_context.new_parser(markup)\n      @left = safe_parse_expression(parser)\n      parser.consume(:end_of_string)\n    end\n\n    def strict_parse(markup)\n      lax_parse(markup)\n    end\n\n    def lax_parse(markup)\n      if markup =~ Syntax\n        @left = parse_expression(Regexp.last_match(1))\n      else\n        raise SyntaxError, options[:locale].t(\"errors.syntax.case\")\n      end\n    end\n\n    def record_when_condition(markup)\n      body = new_body\n\n      if strict2_mode?\n        parse_strict2_when(markup, body)\n      else\n        parse_lax_when(markup, body)\n      end\n    end\n\n    def parse_strict2_when(markup, body)\n      parser = @parse_context.new_parser(markup)\n\n      loop do\n        expr = Condition.parse_expression(parse_context, parser.expression, safe: true)\n        block = Condition.new(@left, '==', expr)\n        block.attach(body)\n        @blocks << block\n\n        break unless parser.id?('or') || parser.consume?(:comma)\n      end\n\n      parser.consume(:end_of_string)\n    end\n\n    def parse_lax_when(markup, body)\n      while markup\n        unless markup =~ WhenSyntax\n          raise SyntaxError, options[:locale].t(\"errors.syntax.case_invalid_when\")\n        end\n\n        markup = Regexp.last_match(2)\n\n        block = Condition.new(@left, '==', Condition.parse_expression(parse_context, Regexp.last_match(1)))\n        block.attach(body)\n        @blocks << block\n      end\n    end\n\n    def record_else_condition(markup)\n      unless markup.strip.empty?\n        raise SyntaxError, options[:locale].t(\"errors.syntax.case_invalid_else\")\n      end\n\n      block = ElseCondition.new\n      block.attach(new_body)\n      @blocks << block\n    end\n\n    class ParseTreeVisitor < Liquid::ParseTreeVisitor\n      def children\n        [@node.left] + @node.blocks\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tags/comment.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # @liquid_public_docs\n  # @liquid_type tag\n  # @liquid_category syntax\n  # @liquid_name comment\n  # @liquid_summary\n  #   Prevents an expression from being rendered or output.\n  # @liquid_description\n  #   Any text inside `comment` tags won't be output, and any Liquid code will be parsed, but not executed.\n  # @liquid_syntax\n  #   {% comment %}\n  #     content\n  #   {% endcomment %}\n  # @liquid_syntax_keyword content The content of the comment.\n  class Comment < Block\n    def render_to_output_buffer(_context, output)\n      output\n    end\n\n    def unknown_tag(_tag, _markup, _tokens)\n    end\n\n    def blank?\n      true\n    end\n\n    private\n\n    def parse_body(body, tokenizer)\n      if parse_context.depth >= MAX_DEPTH\n        raise StackLevelError, \"Nesting too deep\"\n      end\n\n      parse_context.depth += 1\n      comment_tag_depth = 1\n\n      begin\n        # Consume tokens without creating child nodes.\n        # The children tag doesn't require to be a valid Liquid except the comment and raw tag.\n        # The child comment and raw tag must be closed.\n        while (token = tokenizer.send(:shift))\n          tag_name = if tokenizer.for_liquid_tag\n            next if token.empty? || token.match?(BlockBody::WhitespaceOrNothing)\n\n            tag_name_match = BlockBody::LiquidTagToken.match(token)\n\n            next if tag_name_match.nil?\n\n            tag_name_match[1]\n          else\n            token =~ BlockBody::FullToken\n            Regexp.last_match(2)\n          end\n\n          case tag_name\n          when \"raw\"\n            parse_raw_tag_body(tokenizer)\n          when \"comment\"\n            comment_tag_depth += 1\n          when \"endcomment\"\n            comment_tag_depth -= 1\n          end\n\n          if comment_tag_depth.zero?\n            parse_context.trim_whitespace = (token[-3] == WhitespaceControl) unless tokenizer.for_liquid_tag\n            return false\n          end\n        end\n\n        raise_tag_never_closed(block_name)\n      ensure\n        parse_context.depth -= 1\n      end\n\n      false\n    end\n\n    def parse_raw_tag_body(tokenizer)\n      while (token = tokenizer.send(:shift))\n        return if token =~ BlockBody::FullTokenPossiblyInvalid && \"endraw\" == Regexp.last_match(2)\n      end\n\n      raise_tag_never_closed(\"raw\")\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tags/continue.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # @liquid_public_docs\n  # @liquid_type tag\n  # @liquid_category iteration\n  # @liquid_name continue\n  # @liquid_summary\n  #   Causes a [`for` loop](/docs/api/liquid/tags/for) to skip to the next iteration.\n  # @liquid_syntax\n  #   {% continue %}\n  class Continue < Tag\n    INTERRUPT = ContinueInterrupt.new.freeze\n\n    def render_to_output_buffer(context, output)\n      context.push_interrupt(INTERRUPT)\n      output\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tags/cycle.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # @liquid_public_docs\n  # @liquid_type tag\n  # @liquid_category iteration\n  # @liquid_name cycle\n  # @liquid_summary\n  #   Loops through a group of strings and outputs them one at a time for each iteration of a [`for` loop](/docs/api/liquid/tags/for).\n  # @liquid_description\n  #   The `cycle` tag must be used inside a `for` loop.\n  #\n  #   > Tip:\n  #   > Use the `cycle` tag to output text in a predictable pattern. For example, to apply odd/even classes to rows in a table.\n  # @liquid_syntax\n  #   {% cycle string, string, ... %}\n  class Cycle < Tag\n    SimpleSyntax = /\\A#{QuotedFragment}+/o\n    NamedSyntax  = /\\A(#{QuotedFragment})\\s*\\:\\s*(.*)/om\n    UNNAMED_CYCLE_PATTERN = /\\w+:0x\\h{8}/\n\n    attr_reader :variables\n\n    def initialize(tag_name, markup, options)\n      super\n      parse_with_selected_parser(markup)\n    end\n\n    def named?\n      @is_named\n    end\n\n    def render_to_output_buffer(context, output)\n      context.registers[:cycle] ||= {}\n\n      key       = context.evaluate(@name)\n      iteration = context.registers[:cycle][key].to_i\n\n      val = context.evaluate(@variables[iteration])\n\n      if val.is_a?(Array)\n        val = val.join\n      elsif !val.is_a?(String)\n        val = val.to_s\n      end\n\n      output << val\n\n      iteration += 1\n      iteration = 0 if iteration >= @variables.size\n\n      context.registers[:cycle][key] = iteration\n      output\n    end\n\n    private\n\n    # cycle [name:] expression(, expression)*\n    def strict2_parse(markup)\n      p = @parse_context.new_parser(markup)\n\n      @variables = []\n\n      raise SyntaxError, options[:locale].t(\"errors.syntax.cycle\") if p.look(:end_of_string)\n\n      first_expression = safe_parse_expression(p)\n      if p.look(:colon)\n        # cycle name: expr1, expr2, ...\n        @name = first_expression\n        @is_named = true\n        p.consume(:colon)\n        # After the colon, parse the first variable (required for named cycles)\n        @variables << maybe_dup_lookup(safe_parse_expression(p))\n      else\n        # cycle expr1, expr2, ...\n        @variables << maybe_dup_lookup(first_expression)\n      end\n\n      # Parse remaining comma-separated expressions\n      while p.consume?(:comma)\n        break if p.look(:end_of_string)\n\n        @variables << maybe_dup_lookup(safe_parse_expression(p))\n      end\n\n      p.consume(:end_of_string)\n\n      unless @is_named\n        @name = @variables.to_s\n        @is_named = !@name.match?(UNNAMED_CYCLE_PATTERN)\n      end\n    end\n\n    def strict_parse(markup)\n      lax_parse(markup)\n    end\n\n    def lax_parse(markup)\n      case markup\n      when NamedSyntax\n        @variables = variables_from_string(Regexp.last_match(2))\n        @name      = parse_expression(Regexp.last_match(1))\n        @is_named = true\n      when SimpleSyntax\n        @variables = variables_from_string(markup)\n        @name      = @variables.to_s\n        @is_named = !@name.match?(UNNAMED_CYCLE_PATTERN)\n      else\n        raise SyntaxError, options[:locale].t(\"errors.syntax.cycle\")\n      end\n    end\n\n    def variables_from_string(markup)\n      markup.split(',').collect do |var|\n        var =~ /\\s*(#{QuotedFragment})\\s*/o\n        next unless Regexp.last_match(1)\n\n        var = parse_expression(Regexp.last_match(1))\n        maybe_dup_lookup(var)\n      end.compact\n    end\n\n    # For backwards compatibility, whenever a lookup is used in an unnamed cycle,\n    # we make it so that the @variables.to_s produces different strings for cycles\n    # called with the same arguments (since @variables.to_s is used as the cycle counter key)\n    # This makes it so {% cycle a, b %} and {% cycle a, b %} have independent counters even if a and b share value.\n    # This is not true for literal values, {% cycle \"a\", \"b\" %} and {% cycle \"a\", \"b\" %} share the same counter.\n    # I was really scratching my head about this one, but migrating away from this would be more headache\n    # than it's worth. So we're keeping this quirk for now.\n    def maybe_dup_lookup(var)\n      var.is_a?(VariableLookup) ? var.dup : var\n    end\n\n    class ParseTreeVisitor < Liquid::ParseTreeVisitor\n      def children\n        Array(@node.variables)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tags/decrement.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # @liquid_public_docs\n  # @liquid_type tag\n  # @liquid_category variable\n  # @liquid_name decrement\n  # @liquid_summary\n  #   Creates a new variable, with a default value of -1, that's decreased by 1 with each subsequent call.\n  #\n  #   > Caution:\n  #   > Predefined Liquid objects can be overridden by variables with the same name.\n  #   > To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.\n  # @liquid_description\n  #   Variables that are declared with `decrement` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates),\n  #   or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across\n  #   [snippets](/themes/architecture/snippets) included in the file.\n  #\n  #   Similarly, variables that are created with `decrement` are independent from those created with [`assign`](/docs/api/liquid/tags/assign)\n  #   and [`capture`](/docs/api/liquid/tags/capture). However, `decrement` and [`increment`](/docs/api/liquid/tags/increment) share\n  #   variables.\n  # @liquid_syntax\n  #   {% decrement variable_name %}\n  # @liquid_syntax_keyword variable_name The name of the variable being decremented.\n  class Decrement < Tag\n    attr_reader :variable_name\n\n    def initialize(tag_name, markup, options)\n      super\n      @variable_name = markup.strip\n    end\n\n    def render_to_output_buffer(context, output)\n      counter_environment = context.environments.first\n      value = counter_environment[@variable_name] || 0\n      value -= 1\n      counter_environment[@variable_name] = value\n      output << value.to_s\n      output\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tags/doc.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # @liquid_public_docs\n  # @liquid_type tag\n  # @liquid_category syntax\n  # @liquid_name doc\n  # @liquid_summary\n  #   Documents template elements with annotations.\n  # @liquid_description\n  #   The `doc` tag allows developers to include documentation within Liquid\n  #   templates. Any content inside `doc` tags is not rendered or outputted.\n  #   Liquid code inside will be parsed but not executed. This facilitates\n  #   tooling support for features like code completion, linting, and inline\n  #   documentation.\n  #\n  #   For detailed documentation syntax and examples, see the\n  #   [`LiquidDoc` reference](/docs/storefronts/themes/tools/liquid-doc).\n  #\n  # @liquid_syntax\n  #   {% doc %}\n  #     Renders a message.\n  #\n  #     @param {string} foo - A string value.\n  #     @param {string} [bar] - An optional string value.\n  #\n  #     @example\n  #     {% render 'message', foo: 'Hello', bar: 'World' %}\n  #   {% enddoc %}\n  class Doc < Block\n    NO_UNEXPECTED_ARGS = /\\A\\s*\\z/\n\n    def initialize(tag_name, markup, parse_context)\n      super\n      ensure_valid_markup(tag_name, markup, parse_context)\n    end\n\n    def parse(tokens)\n      @body = +\"\"\n\n      while (token = tokens.shift)\n        tag_name = token =~ BlockBody::FullTokenPossiblyInvalid && Regexp.last_match(2)\n\n        raise_nested_doc_error if tag_name == @tag_name\n\n        if tag_name == block_delimiter\n          parse_context.trim_whitespace = (token[-3] == WhitespaceControl)\n          @body << Regexp.last_match(1) if Regexp.last_match(1) != \"\"\n          return\n        end\n        @body << token unless token.empty?\n      end\n\n      raise_tag_never_closed(block_name)\n    end\n\n    def render_to_output_buffer(_context, output)\n      output\n    end\n\n    def blank?\n      @body.empty?\n    end\n\n    def nodelist\n      [@body]\n    end\n\n    private\n\n    def ensure_valid_markup(tag_name, markup, parse_context)\n      unless NO_UNEXPECTED_ARGS.match?(markup)\n        raise SyntaxError, parse_context.locale.t(\"errors.syntax.block_tag_unexpected_args\", tag: tag_name)\n      end\n    end\n\n    def raise_nested_doc_error\n      raise SyntaxError, parse_context.locale.t(\"errors.syntax.doc_invalid_nested\")\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tags/echo.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # @liquid_public_docs\n  # @liquid_type tag\n  # @liquid_category syntax\n  # @liquid_name echo\n  # @liquid_summary\n  #   Outputs an expression.\n  # @liquid_description\n  #   Using the `echo` tag is the same as wrapping an expression in curly brackets (`{{` and `}}`). However, unlike the curly\n  #   bracket method, you can use the `echo` tag inside [`liquid` tags](/docs/api/liquid/tags/liquid).\n  #\n  #   > Tip:\n  #   > You can use [filters](/docs/api/liquid/filters) on expressions inside `echo` tags.\n  # @liquid_syntax\n  #   {% liquid\n  #     echo expression\n  #   %}\n  # @liquid_syntax_keyword expression The expression to be output.\n  class Echo < Tag\n    attr_reader :variable\n\n    def initialize(tag_name, markup, parse_context)\n      super\n      @variable = Variable.new(markup, parse_context)\n    end\n\n    def render(context)\n      @variable.render_to_output_buffer(context, +'')\n    end\n\n    class ParseTreeVisitor < Liquid::ParseTreeVisitor\n      def children\n        [@node.variable]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tags/for.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # @liquid_public_docs\n  # @liquid_type tag\n  # @liquid_category iteration\n  # @liquid_name for\n  # @liquid_summary\n  #   Renders an expression for every item in an array.\n  # @liquid_description\n  #   You can do a maximum of 50 iterations with a `for` loop. If you need to iterate over more than 50 items, then use the\n  #   [`paginate` tag](/docs/api/liquid/tags/paginate) to split the items over multiple pages.\n  #\n  #   > Tip:\n  #   > Every `for` loop has an associated [`forloop` object](/docs/api/liquid/objects/forloop) with information about the loop.\n  # @liquid_syntax\n  #   {% for variable in array %}\n  #     expression\n  #   {% endfor %}\n  # @liquid_syntax_keyword variable The current item in the array.\n  # @liquid_syntax_keyword array The array to iterate over.\n  # @liquid_syntax_keyword expression The expression to render for each iteration.\n  # @liquid_optional_param limit: [number] The number of iterations to perform.\n  # @liquid_optional_param offset: [number] The 1-based index to start iterating at.\n  # @liquid_optional_param range [untyped] A custom numeric range to iterate over.\n  # @liquid_optional_param reversed [untyped] Iterate in reverse order.\n  class For < Block\n    Syntax = /\\A(#{VariableSegment}+)\\s+in\\s+(#{QuotedFragment}+)\\s*(reversed)?/o\n\n    attr_reader :collection_name, :variable_name, :limit, :from\n\n    def initialize(tag_name, markup, options)\n      super\n      @from = @limit = nil\n      parse_with_selected_parser(markup)\n      @for_block = new_body\n      @else_block = nil\n    end\n\n    def parse(tokens)\n      if parse_body(@for_block, tokens)\n        parse_body(@else_block, tokens)\n      end\n      if blank?\n        @else_block&.remove_blank_strings\n        @for_block.remove_blank_strings\n      end\n      @else_block&.freeze\n      @for_block.freeze\n    end\n\n    def nodelist\n      @else_block ? [@for_block, @else_block] : [@for_block]\n    end\n\n    def unknown_tag(tag, markup, tokens)\n      return super unless tag == 'else'\n      @else_block = new_body\n    end\n\n    def render_to_output_buffer(context, output)\n      segment = collection_segment(context)\n\n      if segment.empty?\n        render_else(context, output)\n      else\n        render_segment(context, output, segment)\n      end\n\n      output\n    end\n\n    protected\n\n    def lax_parse(markup)\n      if markup =~ Syntax\n        @variable_name   = Regexp.last_match(1)\n        collection_name  = Regexp.last_match(2)\n        @reversed        = !!Regexp.last_match(3)\n        @name            = \"#{@variable_name}-#{collection_name}\"\n        @collection_name = parse_expression(collection_name)\n        markup.scan(TagAttributes) do |key, value|\n          set_attribute(key, value)\n        end\n      else\n        raise SyntaxError, options[:locale].t(\"errors.syntax.for\")\n      end\n    end\n\n    def strict_parse(markup)\n      p = @parse_context.new_parser(markup)\n      @variable_name = p.consume(:id)\n      raise SyntaxError, options[:locale].t(\"errors.syntax.for_invalid_in\") unless p.id?('in')\n\n      collection_name  = p.expression\n      @collection_name = parse_expression(collection_name, safe: true)\n\n      @name     = \"#{@variable_name}-#{collection_name}\"\n      @reversed = p.id?('reversed')\n\n      while p.look(:comma) || p.look(:id)\n        p.consume?(:comma)\n        unless (attribute = p.id?('limit') || p.id?('offset'))\n          raise SyntaxError, options[:locale].t(\"errors.syntax.for_invalid_attribute\")\n        end\n        p.consume(:colon)\n        set_attribute(attribute, p.expression, safe: true)\n      end\n      p.consume(:end_of_string)\n    end\n\n    private\n\n    def strict2_parse(markup)\n      strict_parse(markup)\n    end\n\n    def collection_segment(context)\n      offsets = context.registers[:for] ||= {}\n\n      from = if @from == :continue\n        offsets[@name].to_i\n      else\n        from_value = context.evaluate(@from)\n        if from_value.nil?\n          0\n        else\n          Utils.to_integer(from_value)\n        end\n      end\n\n      collection = context.evaluate(@collection_name)\n      collection = collection.to_a if collection.is_a?(Range)\n\n      limit_value = context.evaluate(@limit)\n      to = if limit_value.nil?\n        nil\n      else\n        Utils.to_integer(limit_value) + from\n      end\n\n      segment = Utils.slice_collection(collection, from, to)\n      segment.reverse! if @reversed\n\n      offsets[@name] = from + segment.length\n\n      segment\n    end\n\n    def render_segment(context, output, segment)\n      for_stack = context.registers[:for_stack] ||= []\n      length    = segment.length\n\n      context.stack do\n        loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1])\n\n        for_stack.push(loop_vars)\n\n        begin\n          context['forloop'] = loop_vars\n\n          segment.each do |item|\n            context[@variable_name] = item\n            @for_block.render_to_output_buffer(context, output)\n            loop_vars.send(:increment!)\n\n            # Handle any interrupts if they exist.\n            next unless context.interrupt?\n            interrupt = context.pop_interrupt\n            break if interrupt.is_a?(BreakInterrupt)\n            next if interrupt.is_a?(ContinueInterrupt)\n          end\n        ensure\n          for_stack.pop\n        end\n      end\n\n      output\n    end\n\n    def set_attribute(key, expr, safe: false)\n      case key\n      when 'offset'\n        @from = if expr == 'continue'\n          :continue\n        else\n          parse_expression(expr, safe: safe)\n        end\n      when 'limit'\n        @limit = parse_expression(expr, safe: safe)\n      end\n    end\n\n    def render_else(context, output)\n      if @else_block\n        @else_block.render_to_output_buffer(context, output)\n      else\n        output\n      end\n    end\n\n    class ParseTreeVisitor < Liquid::ParseTreeVisitor\n      def children\n        (super + [@node.limit, @node.from, @node.collection_name]).compact\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tags/if.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # @liquid_public_docs\n  # @liquid_type tag\n  # @liquid_category conditional\n  # @liquid_name if\n  # @liquid_summary\n  #   Renders an expression if a specific condition is `true`.\n  # @liquid_syntax\n  #   {% if condition %}\n  #     expression\n  #   {% endif %}\n  # @liquid_syntax_keyword condition The condition to evaluate.\n  # @liquid_syntax_keyword expression The expression to render if the condition is met.\n  class If < Block\n    Syntax                  = /(#{QuotedFragment})\\s*([=!<>a-z_]+)?\\s*(#{QuotedFragment})?/o\n    ExpressionsAndOperators = /(?:\\b(?:\\s?and\\s?|\\s?or\\s?)\\b|(?:\\s*(?!\\b(?:\\s?and\\s?|\\s?or\\s?)\\b)(?:#{QuotedFragment}|\\S+)\\s*)+)/o\n    BOOLEAN_OPERATORS       = %w(and or).freeze\n\n    attr_reader :blocks\n\n    def initialize(tag_name, markup, options)\n      super\n      @blocks = []\n      push_block('if', markup)\n    end\n\n    def nodelist\n      @blocks.map(&:attachment)\n    end\n\n    def parse(tokens)\n      while parse_body(@blocks.last.attachment, tokens)\n      end\n      @blocks.reverse_each do |block|\n        block.attachment.remove_blank_strings if blank?\n        block.attachment.freeze\n      end\n    end\n\n    ELSE_TAG_NAMES = ['elsif', 'else'].freeze\n    private_constant :ELSE_TAG_NAMES\n\n    def unknown_tag(tag, markup, tokens)\n      if ELSE_TAG_NAMES.include?(tag)\n        push_block(tag, markup)\n      else\n        super\n      end\n    end\n\n    def render_to_output_buffer(context, output)\n      @blocks.each do |block|\n        result = Liquid::Utils.to_liquid_value(\n          block.evaluate(context),\n        )\n\n        if result\n          return block.attachment.render_to_output_buffer(context, output)\n        end\n      end\n\n      output\n    end\n\n    private\n\n    def strict2_parse(markup)\n      strict_parse(markup)\n    end\n\n    def push_block(tag, markup)\n      block = if tag == 'else'\n        ElseCondition.new\n      else\n        parse_with_selected_parser(markup)\n      end\n\n      @blocks.push(block)\n      block.attach(new_body)\n    end\n\n    def parse_expression(markup, safe: false)\n      Condition.parse_expression(parse_context, markup, safe: safe)\n    end\n\n    def lax_parse(markup)\n      expressions = markup.scan(ExpressionsAndOperators)\n      raise SyntaxError, options[:locale].t(\"errors.syntax.if\") unless expressions.pop =~ Syntax\n\n      condition = Condition.new(parse_expression(Regexp.last_match(1)), Regexp.last_match(2), parse_expression(Regexp.last_match(3)))\n\n      until expressions.empty?\n        operator = expressions.pop.to_s.strip\n\n        raise SyntaxError, options[:locale].t(\"errors.syntax.if\") unless expressions.pop.to_s =~ Syntax\n\n        new_condition = Condition.new(parse_expression(Regexp.last_match(1)), Regexp.last_match(2), parse_expression(Regexp.last_match(3)))\n        raise SyntaxError, options[:locale].t(\"errors.syntax.if\") unless BOOLEAN_OPERATORS.include?(operator)\n        new_condition.send(operator, condition)\n        condition = new_condition\n      end\n\n      condition\n    end\n\n    def strict_parse(markup)\n      p = @parse_context.new_parser(markup)\n      condition = parse_binary_comparisons(p)\n      p.consume(:end_of_string)\n      condition\n    end\n\n    def parse_binary_comparisons(p)\n      condition = parse_comparison(p)\n      first_condition = condition\n      while (op = p.id?('and') || p.id?('or'))\n        child_condition = parse_comparison(p)\n        condition.send(op, child_condition)\n        condition = child_condition\n      end\n      first_condition\n    end\n\n    def parse_comparison(p)\n      a = parse_expression(p.expression, safe: true)\n      if (op = p.consume?(:comparison))\n        b = parse_expression(p.expression, safe: true)\n        Condition.new(a, op, b)\n      else\n        Condition.new(a)\n      end\n    end\n\n    class ParseTreeVisitor < Liquid::ParseTreeVisitor\n      def children\n        @node.blocks\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tags/ifchanged.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  class Ifchanged < Block\n    def render_to_output_buffer(context, output)\n      block_output = +''\n      super(context, block_output)\n\n      if block_output != context.registers[:ifchanged]\n        context.registers[:ifchanged] = block_output\n        output << block_output\n      end\n\n      output\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tags/include.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # @liquid_public_docs\n  # @liquid_type tag\n  # @liquid_category theme\n  # @liquid_name include\n  # @liquid_summary\n  #   Renders a [snippet](/themes/architecture/snippets).\n  # @liquid_description\n  #   Inside the snippet, you can access and alter variables that are [created](/docs/api/liquid/tags/variable-tags) outside of the\n  #   snippet.\n  # @liquid_syntax\n  #   {% include 'filename' %}\n  # @liquid_syntax_keyword filename The name of the snippet to render, without the `.liquid` extension.\n  # @liquid_deprecated\n  #   Deprecated because the way that variables are handled reduces performance and makes code harder to both read and maintain.\n  #\n  #   The `include` tag has been replaced by [`render`](/docs/api/liquid/tags/render).\n  class Include < Tag\n    prepend Tag::Disableable\n\n    SYNTAX = /(#{QuotedFragment}+)(\\s+(?:with|for)\\s+(#{QuotedFragment}+))?(\\s+(?:as)\\s+(#{VariableSegment}+))?/o\n    Syntax = SYNTAX\n\n    attr_reader :template_name_expr, :variable_name_expr, :attributes\n\n    def initialize(tag_name, markup, options)\n      super\n      parse_with_selected_parser(markup)\n    end\n\n    def parse(_tokens)\n    end\n\n    def render_to_output_buffer(context, output)\n      template_name = context.evaluate(@template_name_expr)\n      raise ArgumentError, options[:locale].t(\"errors.argument.include\") unless template_name.is_a?(String)\n\n      partial = PartialCache.load(\n        template_name,\n        context: context,\n        parse_context: parse_context,\n      )\n\n      context_variable_name = @alias_name || template_name.split('/').last\n\n      variable = if @variable_name_expr\n        context.evaluate(@variable_name_expr)\n      else\n        context.find_variable(template_name, raise_on_not_found: false)\n      end\n\n      old_template_name = context.template_name\n      old_partial       = context.partial\n\n      begin\n        context.template_name = partial.name\n        context.partial = true\n\n        context.stack do\n          @attributes.each do |key, value|\n            context[key] = context.evaluate(value)\n          end\n\n          if variable.is_a?(Array)\n            variable.each do |var|\n              context[context_variable_name] = var\n              partial.render_to_output_buffer(context, output)\n            end\n          else\n            context[context_variable_name] = variable\n            partial.render_to_output_buffer(context, output)\n          end\n        end\n      ensure\n        context.template_name = old_template_name\n        context.partial       = old_partial\n      end\n\n      output\n    end\n\n    alias_method :parse_context, :options\n    private :parse_context\n\n    def strict2_parse(markup)\n      p = @parse_context.new_parser(markup)\n\n      @template_name_expr = safe_parse_expression(p)\n      @variable_name_expr = safe_parse_expression(p) if p.id?(\"for\") || p.id?(\"with\")\n      @alias_name         = p.consume(:id) if p.id?(\"as\")\n\n      p.consume?(:comma)\n\n      @attributes = {}\n      while p.look(:id)\n        key = p.consume\n        p.consume(:colon)\n        @attributes[key] = safe_parse_expression(p)\n        p.consume?(:comma)\n      end\n\n      p.consume(:end_of_string)\n    end\n\n    def strict_parse(markup)\n      lax_parse(markup)\n    end\n\n    def lax_parse(markup)\n      if markup =~ SYNTAX\n        template_name = Regexp.last_match(1)\n        variable_name = Regexp.last_match(3)\n\n        @alias_name         = Regexp.last_match(5)\n        @variable_name_expr = variable_name ? parse_expression(variable_name) : nil\n        @template_name_expr = parse_expression(template_name)\n        @attributes         = {}\n\n        markup.scan(TagAttributes) do |key, value|\n          @attributes[key] = parse_expression(value)\n        end\n\n      else\n        raise SyntaxError, options[:locale].t(\"errors.syntax.include\")\n      end\n    end\n\n    class ParseTreeVisitor < Liquid::ParseTreeVisitor\n      def children\n        [\n          @node.template_name_expr,\n          @node.variable_name_expr,\n        ] + @node.attributes.values\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tags/increment.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # @liquid_public_docs\n  # @liquid_type tag\n  # @liquid_category variable\n  # @liquid_name increment\n  # @liquid_summary\n  #   Creates a new variable, with a default value of 0, that's increased by 1 with each subsequent call.\n  #\n  #   > Caution:\n  #   > Predefined Liquid objects can be overridden by variables with the same name.\n  #   > To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.\n  # @liquid_description\n  #   Variables that are declared with `increment` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates),\n  #   or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across\n  #   [snippets](/themes/architecture/snippets) included in the file.\n  #\n  #   Similarly, variables that are created with `increment` are independent from those created with [`assign`](/docs/api/liquid/tags/assign)\n  #   and [`capture`](/docs/api/liquid/tags/capture). However, `increment` and [`decrement`](/docs/api/liquid/tags/decrement) share\n  #   variables.\n  # @liquid_syntax\n  #   {% increment variable_name %}\n  # @liquid_syntax_keyword variable_name The name of the variable being incremented.\n  class Increment < Tag\n    attr_reader :variable_name\n\n    def initialize(tag_name, markup, options)\n      super\n      @variable_name = markup.strip\n    end\n\n    def render_to_output_buffer(context, output)\n      counter_environment = context.environments.first\n      value = counter_environment[@variable_name] || 0\n      counter_environment[@variable_name] = value + 1\n\n      output << value.to_s\n      output\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tags/inline_comment.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  class InlineComment < Tag\n    def initialize(tag_name, markup, options)\n      super\n\n      # Semantically, a comment should only ignore everything after it on the line.\n      # Currently, this implementation doesn't support mixing a comment with another tag\n      # but we need to reserve future support for this and prevent the introduction\n      # of inline comments from being backward incompatible change.\n      #\n      # As such, we're forcing users to put a # symbol on every line otherwise this\n      # tag will throw an error.\n      if markup.match?(/\\n\\s*[^#\\s]/)\n        raise SyntaxError, options[:locale].t(\"errors.syntax.inline_comment_invalid\")\n      end\n    end\n\n    def render_to_output_buffer(_context, output)\n      output\n    end\n\n    def blank?\n      true\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tags/raw.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # @liquid_public_docs\n  # @liquid_type tag\n  # @liquid_category syntax\n  # @liquid_name raw\n  # @liquid_summary\n  #   Outputs any Liquid code as text instead of rendering it.\n  # @liquid_syntax\n  #   {% raw %}\n  #     expression\n  #   {% endraw %}\n  # @liquid_syntax_keyword expression The expression to be output without being rendered.\n  class Raw < Block\n    Syntax = /\\A\\s*\\z/\n\n    def initialize(tag_name, markup, parse_context)\n      super\n\n      ensure_valid_markup(tag_name, markup, parse_context)\n    end\n\n    def parse(tokens)\n      @body = +''\n      while (token = tokens.shift)\n        if token =~ BlockBody::FullTokenPossiblyInvalid && block_delimiter == Regexp.last_match(2)\n          parse_context.trim_whitespace = (token[-3] == WhitespaceControl)\n          @body << Regexp.last_match(1) if Regexp.last_match(1) != \"\"\n          return\n        end\n        @body << token unless token.empty?\n      end\n\n      raise_tag_never_closed(block_name)\n    end\n\n    def render_to_output_buffer(_context, output)\n      output << @body\n      output\n    end\n\n    def nodelist\n      [@body]\n    end\n\n    def blank?\n      @body.empty?\n    end\n\n    protected\n\n    def ensure_valid_markup(tag_name, markup, parse_context)\n      unless Syntax.match?(markup)\n        raise SyntaxError, parse_context.locale.t(\"errors.syntax.tag_unexpected_args\", tag: tag_name)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tags/render.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # @liquid_public_docs\n  # @liquid_type tag\n  # @liquid_category theme\n  # @liquid_name render\n  # @liquid_summary\n  #   Renders a [snippet](/themes/architecture/snippets) or [app block](/themes/architecture/sections/section-schema#render-app-blocks).\n  # @liquid_description\n  #   Inside snippets and app blocks, you can't directly access variables that are [created](/docs/api/liquid/tags/variable-tags) outside\n  #   of the snippet or app block. However, you can [specify variables as parameters](/docs/api/liquid/tags/render#render-passing-variables-to-a-snippet)\n  #   to pass outside variables to snippets.\n  #\n  #   While you can't directly access created variables, you can access global objects, as well as any objects that are\n  #   directly accessible outside the snippet or app block. For example, a snippet or app block inside the [product template](/themes/architecture/templates/product)\n  #   can access the [`product` object](/docs/api/liquid/objects/product), and a snippet or app block inside a [section](/themes/architecture/sections)\n  #   can access the [`section` object](/docs/api/liquid/objects/section).\n  #\n  #   Outside a snippet or app block, you can't access variables created inside the snippet or app block.\n  #\n  #   > Note:\n  #   > When you render a snippet using the `render` tag, you can't use the [`include` tag](/docs/api/liquid/tags/include)\n  #   > inside the snippet.\n  # @liquid_syntax\n  #   {% render 'filename' %}\n  # @liquid_syntax_keyword filename The name of the snippet to render, without the `.liquid` extension.\n  class Render < Tag\n    FOR = 'for'\n    SYNTAX = /(#{QuotedString}+)(\\s+(with|#{FOR})\\s+(#{QuotedFragment}+))?(\\s+(?:as)\\s+(#{VariableSegment}+))?/o\n\n    disable_tags \"include\"\n\n    attr_reader :template_name_expr, :variable_name_expr, :attributes, :alias_name\n\n    def initialize(tag_name, markup, options)\n      super\n      parse_with_selected_parser(markup)\n    end\n\n    def for_loop?\n      @is_for_loop\n    end\n\n    def render_to_output_buffer(context, output)\n      render_tag(context, output)\n    end\n\n    def render_tag(context, output)\n      # The expression should be a String literal, which parses to a String object\n      template_name = @template_name_expr\n      raise ::ArgumentError unless template_name.is_a?(String)\n\n      partial = PartialCache.load(\n        template_name,\n        context: context,\n        parse_context: parse_context,\n      )\n\n      context_variable_name = @alias_name || template_name.split('/').last\n\n      render_partial_func = ->(var, forloop) {\n        inner_context               = context.new_isolated_subcontext\n        inner_context.template_name = partial.name\n        inner_context.partial       = true\n        inner_context['forloop']    = forloop if forloop\n\n        @attributes.each do |key, value|\n          inner_context[key] = context.evaluate(value)\n        end\n        inner_context[context_variable_name] = var unless var.nil?\n        partial.render_to_output_buffer(inner_context, output)\n        forloop&.send(:increment!)\n      }\n\n      variable = @variable_name_expr ? context.evaluate(@variable_name_expr) : nil\n      if @is_for_loop && variable.respond_to?(:each) && variable.respond_to?(:count)\n        forloop = Liquid::ForloopDrop.new(template_name, variable.count, nil)\n        variable.each { |var| render_partial_func.call(var, forloop) }\n      else\n        render_partial_func.call(variable, nil)\n      end\n\n      output\n    end\n\n    # render (string) (with|for expression)? (as id)? (key: value)*\n    def strict2_parse(markup)\n      p = @parse_context.new_parser(markup)\n\n      @template_name_expr = parse_expression(strict2_template_name(p), safe: true)\n      with_or_for         = p.id?(\"for\") || p.id?(\"with\")\n      @variable_name_expr = safe_parse_expression(p) if with_or_for\n      @alias_name         = p.consume(:id) if p.id?(\"as\")\n      @is_for_loop        = (with_or_for == FOR)\n\n      p.consume?(:comma)\n\n      @attributes = {}\n      while p.look(:id)\n        key = p.consume\n        p.consume(:colon)\n        @attributes[key] = safe_parse_expression(p)\n        p.consume?(:comma)\n      end\n\n      p.consume(:end_of_string)\n    end\n\n    def strict2_template_name(p)\n      p.consume(:string)\n    end\n\n    def strict_parse(markup)\n      lax_parse(markup)\n    end\n\n    def lax_parse(markup)\n      raise SyntaxError, options[:locale].t(\"errors.syntax.render\") unless markup =~ SYNTAX\n\n      template_name = Regexp.last_match(1)\n      with_or_for = Regexp.last_match(3)\n      variable_name = Regexp.last_match(4)\n\n      @alias_name = Regexp.last_match(6)\n      @variable_name_expr = variable_name ? parse_expression(variable_name) : nil\n      @template_name_expr = parse_expression(template_name)\n      @is_for_loop = (with_or_for == FOR)\n\n      @attributes = {}\n      markup.scan(TagAttributes) do |key, value|\n        @attributes[key] = parse_expression(value)\n      end\n    end\n\n    class ParseTreeVisitor < Liquid::ParseTreeVisitor\n      def children\n        [\n          @node.template_name_expr,\n          @node.variable_name_expr,\n        ] + @node.attributes.values\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tags/table_row.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # @liquid_public_docs\n  # @liquid_type tag\n  # @liquid_category iteration\n  # @liquid_name tablerow\n  # @liquid_summary\n  #   Generates HTML table rows for every item in an array.\n  # @liquid_description\n  #   The `tablerow` tag must be wrapped in HTML `<table>` and `</table>` tags.\n  #\n  #   > Tip:\n  #   > Every `tablerow` loop has an associated [`tablerowloop` object](/docs/api/liquid/objects/tablerowloop) with information about the loop.\n  # @liquid_syntax\n  #   {% tablerow variable in array %}\n  #     expression\n  #   {% endtablerow %}\n  # @liquid_syntax_keyword variable The current item in the array.\n  # @liquid_syntax_keyword array The array to iterate over.\n  # @liquid_syntax_keyword expression The expression to render.\n  # @liquid_optional_param cols: [number] The number of columns that the table should have.\n  # @liquid_optional_param limit: [number] The number of iterations to perform.\n  # @liquid_optional_param offset: [number] The 1-based index to start iterating at.\n  # @liquid_optional_param range [untyped] A custom numeric range to iterate over.\n  class TableRow < Block\n    Syntax = /(\\w+)\\s+in\\s+(#{QuotedFragment}+)/o\n    ALLOWED_ATTRIBUTES = ['cols', 'limit', 'offset', 'range'].freeze\n\n    attr_reader :variable_name, :collection_name, :attributes\n\n    def initialize(tag_name, markup, options)\n      super\n      parse_with_selected_parser(markup)\n    end\n\n    def strict2_parse(markup)\n      p = @parse_context.new_parser(markup)\n\n      @variable_name = p.consume(:id)\n\n      unless p.id?(\"in\")\n        raise SyntaxError, options[:locale].t(\"errors.syntax.for_invalid_in\")\n      end\n\n      @collection_name = safe_parse_expression(p)\n\n      p.consume?(:comma)\n\n      @attributes = {}\n      while p.look(:id)\n        key = p.consume\n        unless ALLOWED_ATTRIBUTES.include?(key)\n          raise SyntaxError, options[:locale].t(\"errors.syntax.table_row_invalid_attribute\", attribute: key)\n        end\n\n        p.consume(:colon)\n        @attributes[key] = safe_parse_expression(p)\n        p.consume?(:comma)\n      end\n\n      p.consume(:end_of_string)\n    end\n\n    def strict_parse(markup)\n      lax_parse(markup)\n    end\n\n    def lax_parse(markup)\n      if markup =~ Syntax\n        @variable_name   = Regexp.last_match(1)\n        @collection_name = parse_expression(Regexp.last_match(2))\n        @attributes      = {}\n        markup.scan(TagAttributes) do |key, value|\n          @attributes[key] = parse_expression(value)\n        end\n      else\n        raise SyntaxError, options[:locale].t(\"errors.syntax.table_row\")\n      end\n    end\n\n    def render_to_output_buffer(context, output)\n      (collection = context.evaluate(@collection_name)) || (return '')\n\n      from = @attributes.key?('offset') ? to_integer(context.evaluate(@attributes['offset'])) : 0\n      to = @attributes.key?('limit') ? from + to_integer(context.evaluate(@attributes['limit'])) : nil\n\n      collection = Utils.slice_collection(collection, from, to)\n      length     = collection.length\n\n      cols = @attributes.key?('cols') ? to_integer(context.evaluate(@attributes['cols'])) : length\n\n      output << \"<tr class=\\\"row1\\\">\\n\"\n      context.stack do\n        tablerowloop = Liquid::TablerowloopDrop.new(length, cols)\n        context['tablerowloop'] = tablerowloop\n\n        collection.each do |item|\n          context[@variable_name] = item\n\n          output << \"<td class=\\\"col#{tablerowloop.col}\\\">\"\n          super\n          output << '</td>'\n\n          # Handle any interrupts if they exist.\n          if context.interrupt?\n            interrupt = context.pop_interrupt\n            break if interrupt.is_a?(BreakInterrupt)\n          end\n\n          if tablerowloop.col_last && !tablerowloop.last\n            output << \"</tr>\\n<tr class=\\\"row#{tablerowloop.row + 1}\\\">\"\n          end\n\n          tablerowloop.send(:increment!)\n        end\n      end\n\n      output << \"</tr>\\n\"\n      output\n    end\n\n    class ParseTreeVisitor < Liquid::ParseTreeVisitor\n      def children\n        super + @node.attributes.values + [@node.collection_name]\n      end\n    end\n\n    private\n\n    def to_integer(value)\n      value.to_i\n    rescue NoMethodError\n      raise Liquid::ArgumentError, \"invalid integer\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tags/unless.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative 'if'\n\nmodule Liquid\n  # @liquid_public_docs\n  # @liquid_type tag\n  # @liquid_category conditional\n  # @liquid_name unless\n  # @liquid_summary\n  #   Renders an expression unless a specific condition is `true`.\n  # @liquid_description\n  #   > Tip:\n  #   > Similar to the [`if` tag](/docs/api/liquid/tags/if), you can use `elsif` to add more conditions to an `unless` tag.\n  # @liquid_syntax\n  #   {% unless condition %}\n  #     expression\n  #   {% endunless %}\n  # @liquid_syntax_keyword condition The condition to evaluate.\n  # @liquid_syntax_keyword expression The expression to render unless the condition is met.\n  class Unless < If\n    def render_to_output_buffer(context, output)\n      # First condition is interpreted backwards ( if not )\n      first_block = @blocks.first\n      result = Liquid::Utils.to_liquid_value(\n        first_block.evaluate(context),\n      )\n\n      unless result\n        return first_block.attachment.render_to_output_buffer(context, output)\n      end\n\n      # After the first condition unless works just like if\n      @blocks[1..-1].each do |block|\n        result = Liquid::Utils.to_liquid_value(\n          block.evaluate(context),\n        )\n\n        if result\n          return block.attachment.render_to_output_buffer(context, output)\n        end\n      end\n\n      output\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tags.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative \"tags/table_row\"\nrequire_relative \"tags/echo\"\nrequire_relative \"tags/if\"\nrequire_relative \"tags/break\"\nrequire_relative \"tags/inline_comment\"\nrequire_relative \"tags/for\"\nrequire_relative \"tags/assign\"\nrequire_relative \"tags/ifchanged\"\nrequire_relative \"tags/case\"\nrequire_relative \"tags/include\"\nrequire_relative \"tags/continue\"\nrequire_relative \"tags/capture\"\nrequire_relative \"tags/decrement\"\nrequire_relative \"tags/unless\"\nrequire_relative \"tags/increment\"\nrequire_relative \"tags/comment\"\nrequire_relative \"tags/raw\"\nrequire_relative \"tags/render\"\nrequire_relative \"tags/cycle\"\nrequire_relative \"tags/doc\"\n\nmodule Liquid\n  module Tags\n    STANDARD_TAGS = {\n      'cycle' => Cycle,\n      'render' => Render,\n      'raw' => Raw,\n      'comment' => Comment,\n      'increment' => Increment,\n      'unless' => Unless,\n      'decrement' => Decrement,\n      'capture' => Capture,\n      'continue' => Continue,\n      'include' => Include,\n      'case' => Case,\n      'ifchanged' => Ifchanged,\n      'assign' => Assign,\n      'for' => For,\n      '#' => InlineComment,\n      'break' => Break,\n      'if' => If,\n      'echo' => Echo,\n      'tablerow' => TableRow,\n      'doc' => Doc,\n    }.freeze\n  end\nend\n"
  },
  {
    "path": "lib/liquid/template.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # Templates are central to liquid.\n  # Interpreting templates is a two step process. First you compile the\n  # source code you got. During compile time some extensive error checking is performed.\n  # your code should expect to get some SyntaxErrors.\n  #\n  # After you have a compiled template you can then <tt>render</tt> it.\n  # You can use a compiled template over and over again and keep it cached.\n  #\n  # Example:\n  #\n  #   template = Liquid::Template.parse(source)\n  #   template.render('user_name' => 'bob')\n  #\n  class Template\n    attr_accessor :root, :name\n    attr_reader :resource_limits, :warnings\n\n    attr_reader :profiler\n\n    class << self\n      # Sets how strict the parser should be.\n      # :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.\n      # :warn is the default and will give deprecation warnings when invalid syntax is used.\n      # :strict enforces correct syntax for most tags\n      # :strict2 enforces correct syntax for all tags\n      def error_mode=(mode)\n        Deprecations.warn(\"Template.error_mode=\", \"Environment#error_mode=\")\n        Environment.default.error_mode = mode\n      end\n\n      def error_mode\n        Environment.default.error_mode\n      end\n\n      def default_exception_renderer=(renderer)\n        Deprecations.warn(\"Template.default_exception_renderer=\", \"Environment#exception_renderer=\")\n        Environment.default.exception_renderer = renderer\n      end\n\n      def default_exception_renderer\n        Environment.default.exception_renderer\n      end\n\n      def file_system=(file_system)\n        Deprecations.warn(\"Template.file_system=\", \"Environment#file_system=\")\n        Environment.default.file_system = file_system\n      end\n\n      def file_system\n        Environment.default.file_system\n      end\n\n      def tags\n        Environment.default.tags\n      end\n\n      def register_tag(name, klass)\n        Deprecations.warn(\"Template.register_tag\", \"Environment#register_tag\")\n        Environment.default.register_tag(name, klass)\n      end\n\n      # Pass a module with filter methods which should be available\n      # to all liquid views. Good for registering the standard library\n      def register_filter(mod)\n        Deprecations.warn(\"Template.register_filter\", \"Environment#register_filter\")\n        Environment.default.register_filter(mod)\n      end\n\n      private def default_resource_limits=(limits)\n        Deprecations.warn(\"Template.default_resource_limits=\", \"Environment#default_resource_limits=\")\n        Environment.default.default_resource_limits = limits\n      end\n\n      def default_resource_limits\n        Environment.default.default_resource_limits\n      end\n\n      # creates a new <tt>Template</tt> object from liquid source code\n      # To enable profiling, pass in <tt>profile: true</tt> as an option.\n      # See Liquid::Profiler for more information\n      def parse(source, options = {})\n        environment = options[:environment] || Environment.default\n        new(environment: environment).parse(source, options)\n      end\n    end\n\n    def initialize(environment: Environment.default)\n      @environment = environment\n      @rethrow_errors  = false\n      @resource_limits = ResourceLimits.new(environment.default_resource_limits)\n    end\n\n    # Parse source code.\n    # Returns self for easy chaining\n    def parse(source, options = {})\n      parse_context = configure_options(options)\n      source = source.to_s.to_str\n\n      unless source.valid_encoding?\n        raise TemplateEncodingError, parse_context.locale.t(\"errors.syntax.invalid_template_encoding\")\n      end\n\n      tokenizer     = parse_context.new_tokenizer(source, start_line_number: @line_numbers && 1)\n      @root         = Document.parse(tokenizer, parse_context)\n      self\n    end\n\n    def registers\n      @registers ||= {}\n    end\n\n    def assigns\n      @assigns ||= {}\n    end\n\n    def instance_assigns\n      @instance_assigns ||= {}\n    end\n\n    def errors\n      @errors ||= []\n    end\n\n    # Render takes a hash with local variables.\n    #\n    # if you use the same filters over and over again consider registering them globally\n    # with <tt>Template.register_filter</tt>\n    #\n    # if profiling was enabled in <tt>Template#parse</tt> then the resulting profiling information\n    # will be available via <tt>Template#profiler</tt>\n    #\n    # Following options can be passed:\n    #\n    #  * <tt>filters</tt> : array with local filters\n    #  * <tt>registers</tt> : hash with register variables. Those can be accessed from\n    #    filters and tags and might be useful to integrate liquid more with its host application\n    #\n    def render(*args)\n      return '' if @root.nil?\n\n      context = case args.first\n      when Liquid::Context\n        c = args.shift\n\n        if @rethrow_errors\n          c.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA\n        end\n\n        c\n      when Liquid::Drop\n        drop         = args.shift\n        drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)\n      when Hash\n        Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)\n      when nil\n        Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)\n      else\n        raise ArgumentError, \"Expected Hash or Liquid::Context as parameter\"\n      end\n\n      output = nil\n\n      case args.last\n      when Hash\n        options = args.pop\n        output  = options[:output] if options[:output]\n        static_registers = context.registers.static\n\n        options[:registers]&.each do |key, register|\n          static_registers[key] = register\n        end\n\n        apply_options_to_context(context, options)\n      when Module, Array\n        context.add_filters(args.pop)\n      end\n\n      # Retrying a render resets resource usage\n      context.resource_limits.reset\n\n      if @profiling && context.profiler.nil?\n        @profiler = context.profiler = Liquid::Profiler.new\n      end\n\n      context.template_name ||= name\n\n      begin\n        # render the nodelist.\n        @root.render_to_output_buffer(context, output || +'')\n      rescue Liquid::MemoryError => e\n        context.handle_error(e)\n      ensure\n        @errors = context.errors\n      end\n    end\n\n    def render!(*args)\n      @rethrow_errors = true\n      render(*args)\n    end\n\n    def render_to_output_buffer(context, output)\n      render(context, output: output)\n    end\n\n    private\n\n    def configure_options(options)\n      if (profiling = options[:profile])\n        raise \"Profiler not loaded, require 'liquid/profiler' first\" unless defined?(Liquid::Profiler)\n      end\n\n      @options      = options\n      @profiling    = profiling\n      @line_numbers = options[:line_numbers] || @profiling\n      parse_context = if options.is_a?(ParseContext)\n        options\n      else\n        opts = options.key?(:environment) ? options : options.merge(environment: @environment)\n        ParseContext.new(opts)\n      end\n\n      @warnings = parse_context.warnings\n      parse_context\n    end\n\n    def apply_options_to_context(context, options)\n      context.add_filters(options[:filters]) if options[:filters]\n      context.global_filter      = options[:global_filter] if options[:global_filter]\n      context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]\n      context.strict_variables   = options[:strict_variables] if options[:strict_variables]\n      context.strict_filters     = options[:strict_filters] if options[:strict_filters]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/template_factory.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  class TemplateFactory\n    def for(_template_name)\n      Liquid::Template.new\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/tokenizer.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"strscan\"\n\nmodule Liquid\n  class Tokenizer\n    attr_reader :line_number, :for_liquid_tag\n\n    TAG_END = /%\\}/\n    TAG_OR_VARIABLE_START = /\\{[\\{\\%]/\n    NEWLINE = /\\n/\n\n    OPEN_CURLEY = \"{\".ord\n    CLOSE_CURLEY = \"}\".ord\n    PERCENTAGE = \"%\".ord\n\n    def initialize(\n      source:,\n      string_scanner:,\n      line_numbers: false,\n      line_number: nil,\n      for_liquid_tag: false\n    )\n      @line_number = line_number || (line_numbers ? 1 : nil)\n      @for_liquid_tag = for_liquid_tag\n      @source = source.to_s.to_str\n      @offset = 0\n      @tokens = []\n\n      if @source\n        @ss = string_scanner\n        @ss.string = @source\n        tokenize\n      end\n    end\n\n    def shift\n      token = @tokens[@offset]\n\n      return unless token\n\n      @offset += 1\n\n      if @line_number\n        @line_number += @for_liquid_tag ? 1 : token.count(\"\\n\")\n      end\n\n      token\n    end\n\n    private\n\n    def tokenize\n      if @for_liquid_tag\n        @tokens = @source.split(\"\\n\")\n      else\n        @tokens << shift_normal until @ss.eos?\n      end\n\n      @source = nil\n      @ss = nil\n    end\n\n    def shift_normal\n      token = next_token\n\n      return unless token\n\n      token\n    end\n\n    def next_token\n      # possible states: :text, :tag, :variable\n      byte_a = @ss.peek_byte\n\n      if byte_a == OPEN_CURLEY\n        @ss.scan_byte\n\n        byte_b = @ss.peek_byte\n\n        if byte_b == PERCENTAGE\n          @ss.scan_byte\n          return next_tag_token\n        elsif byte_b == OPEN_CURLEY\n          @ss.scan_byte\n          return next_variable_token\n        end\n\n        @ss.pos -= 1\n      end\n\n      next_text_token\n    end\n\n    def next_text_token\n      start = @ss.pos\n\n      unless @ss.skip_until(TAG_OR_VARIABLE_START)\n        token = @ss.rest\n        @ss.terminate\n        return token\n      end\n\n      pos = @ss.pos -= 2\n      @source.byteslice(start, pos - start)\n    rescue ::ArgumentError => e\n      if e.message == \"invalid byte sequence in #{@ss.string.encoding}\"\n        raise SyntaxError, \"Invalid byte sequence in #{@ss.string.encoding}\"\n      else\n        raise\n      end\n    end\n\n    def next_variable_token\n      start = @ss.pos - 2\n\n      byte_a = byte_b = @ss.scan_byte\n\n      while byte_b\n        byte_a = @ss.scan_byte while byte_a && byte_a != CLOSE_CURLEY && byte_a != OPEN_CURLEY\n\n        break unless byte_a\n\n        if @ss.eos?\n          return byte_a == CLOSE_CURLEY ? @source.byteslice(start, @ss.pos - start) : \"{{\"\n        end\n\n        byte_b = @ss.scan_byte\n\n        if byte_a == CLOSE_CURLEY\n          if byte_b == CLOSE_CURLEY\n            return @source.byteslice(start, @ss.pos - start)\n          elsif byte_b != CLOSE_CURLEY\n            @ss.pos -= 1\n            return @source.byteslice(start, @ss.pos - start)\n          end\n        elsif byte_a == OPEN_CURLEY && byte_b == PERCENTAGE\n          return next_tag_token_with_start(start)\n        end\n\n        byte_a = byte_b\n      end\n\n      \"{{\"\n    end\n\n    def next_tag_token\n      start = @ss.pos - 2\n      if (len = @ss.skip_until(TAG_END))\n        @source.byteslice(start, len + 2)\n      else\n        \"{%\"\n      end\n    end\n\n    def next_tag_token_with_start(start)\n      @ss.skip_until(TAG_END)\n      @source.byteslice(start, @ss.pos - start)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/usage.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  module Usage\n    def self.increment(name)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/utils.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  module Utils\n    DECIMAL_REGEX = /\\A-?\\d+\\.\\d+\\z/\n    UNIX_TIMESTAMP_REGEX = /\\A\\d+\\z/\n\n    def self.slice_collection(collection, from, to)\n      if (from != 0 || !to.nil?) && collection.respond_to?(:load_slice)\n        collection.load_slice(from, to)\n      else\n        slice_collection_using_each(collection, from, to)\n      end\n    end\n\n    def self.slice_collection_using_each(collection, from, to)\n      segments = []\n      index    = 0\n\n      # Maintains Ruby 1.8.7 String#each behaviour on 1.9\n      if collection.is_a?(String)\n        return collection.empty? ? [] : [collection]\n      end\n      return [] unless collection.respond_to?(:each)\n\n      collection.each do |item|\n        if to && to <= index\n          break\n        end\n\n        if from <= index\n          segments << item\n        end\n\n        index += 1\n      end\n\n      segments\n    end\n\n    def self.to_integer(num)\n      return num if num.is_a?(Integer)\n      num = num.to_s\n      begin\n        Integer(num)\n      rescue ::ArgumentError\n        raise Liquid::ArgumentError, \"invalid integer\"\n      end\n    end\n\n    def self.to_number(obj)\n      case obj\n      when Float\n        BigDecimal(obj.to_s)\n      when Numeric\n        obj\n      when String\n        DECIMAL_REGEX.match?(obj.strip) ? BigDecimal(obj) : obj.to_i\n      else\n        if obj.respond_to?(:to_number)\n          obj.to_number\n        else\n          0\n        end\n      end\n    end\n\n    def self.to_date(obj)\n      return obj if obj.respond_to?(:strftime)\n\n      if obj.is_a?(String)\n        return if obj.empty?\n        obj = obj.downcase\n      end\n\n      case obj\n      when 'now', 'today'\n        Time.now\n      when UNIX_TIMESTAMP_REGEX, Integer\n        Time.at(obj.to_i)\n      when String\n        Time.parse(obj)\n      end\n    rescue ::ArgumentError\n      nil\n    end\n\n    def self.to_liquid_value(obj)\n      # Enable \"obj\" to represent itself as a primitive value like integer, string, or boolean\n      return obj.to_liquid_value if obj.respond_to?(:to_liquid_value)\n\n      # Otherwise return the object itself\n      obj\n    end\n\n    def self.to_s(obj, seen = {})\n      case obj\n      when BigDecimal\n        obj.to_s(\"F\")\n      when Hash\n        # If the custom hash implementation overrides `#to_s`, use their\n        # custom implementation. Otherwise we use Liquid's default\n        # implementation.\n        if obj.class.instance_method(:to_s) == HASH_TO_S_METHOD\n          hash_inspect(obj, seen)\n        else\n          obj.to_s\n        end\n      when Array\n        array_inspect(obj, seen)\n      else\n        obj.to_s\n      end\n    end\n\n    def self.inspect(obj, seen = {})\n      case obj\n      when Hash\n        # If the custom hash implementation overrides `#inspect`, use their\n        # custom implementation. Otherwise we use Liquid's default\n        # implementation.\n        if obj.class.instance_method(:inspect) == HASH_INSPECT_METHOD\n          hash_inspect(obj, seen)\n        else\n          obj.inspect\n        end\n      when Array\n        array_inspect(obj, seen)\n      else\n        obj.inspect\n      end\n    end\n\n    def self.array_inspect(arr, seen = {})\n      if seen[arr.object_id]\n        return \"[...]\"\n      end\n\n      seen[arr.object_id] = true\n      str = +\"[\"\n      cursor = 0\n      len = arr.length\n\n      while cursor < len\n        if cursor > 0\n          str << \", \"\n        end\n\n        item_str = inspect(arr[cursor], seen)\n        str << item_str\n        cursor += 1\n      end\n\n      str << \"]\"\n      str\n    ensure\n      seen.delete(arr.object_id)\n    end\n\n    def self.hash_inspect(hash, seen = {})\n      if seen[hash.object_id]\n        return \"{...}\"\n      end\n      seen[hash.object_id] = true\n\n      str = +\"{\"\n      first = true\n      hash.each do |key, value|\n        if first\n          first = false\n        else\n          str << \", \"\n        end\n\n        key_str = inspect(key, seen)\n        str << key_str\n        str << \"=>\"\n\n        value_str = inspect(value, seen)\n        str << value_str\n      end\n      str << \"}\"\n      str\n    ensure\n      seen.delete(hash.object_id)\n    end\n\n    HASH_TO_S_METHOD = Hash.instance_method(:to_s)\n    private_constant :HASH_TO_S_METHOD\n\n    HASH_INSPECT_METHOD = Hash.instance_method(:inspect)\n    private_constant :HASH_INSPECT_METHOD\n  end\nend\n"
  },
  {
    "path": "lib/liquid/variable.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  # Holds variables. Variables are only loaded \"just in time\"\n  # and are not evaluated as part of the render stage\n  #\n  #   {{ monkey }}\n  #   {{ user.name }}\n  #\n  # Variables can be combined with filters:\n  #\n  #   {{ user | link }}\n  #\n  class Variable\n    FilterMarkupRegex        = /#{FilterSeparator}\\s*(.*)/om\n    FilterParser             = /(?:\\s+|#{QuotedFragment}|#{ArgumentSeparator})+/o\n    FilterArgsRegex          = /(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\\s*((?:\\w+\\s*\\:\\s*)?#{QuotedFragment})/o\n    JustTagAttributes        = /\\A#{TagAttributes}\\z/o\n    MarkupWithQuotedFragment = /(#{QuotedFragment})(.*)/om\n\n    attr_accessor :filters, :name, :line_number\n    attr_reader :parse_context\n    alias_method :options, :parse_context\n\n    include ParserSwitching\n\n    def initialize(markup, parse_context)\n      @markup        = markup\n      @name          = nil\n      @parse_context = parse_context\n      @line_number   = parse_context.line_number\n\n      strict_parse_with_error_mode_fallback(markup)\n    end\n\n    def raw\n      @markup\n    end\n\n    def markup_context(markup)\n      \"in \\\"{{#{markup}}}\\\"\"\n    end\n\n    def lax_parse(markup)\n      @filters = []\n      return unless markup =~ MarkupWithQuotedFragment\n\n      name_markup   = Regexp.last_match(1)\n      filter_markup = Regexp.last_match(2)\n      @name         = parse_context.parse_expression(name_markup)\n      if filter_markup =~ FilterMarkupRegex\n        filters = Regexp.last_match(1).scan(FilterParser)\n        filters.each do |f|\n          next unless f =~ /\\w+/\n          filtername = Regexp.last_match(0)\n          filterargs = f.scan(FilterArgsRegex).flatten\n          @filters << lax_parse_filter_expressions(filtername, filterargs)\n        end\n      end\n    end\n\n    def strict_parse(markup)\n      @filters = []\n      p = @parse_context.new_parser(markup)\n\n      return if p.look(:end_of_string)\n\n      @name = parse_context.safe_parse_expression(p)\n      while p.consume?(:pipe)\n        filtername = p.consume(:id)\n        filterargs = p.consume?(:colon) ? parse_filterargs(p) : Const::EMPTY_ARRAY\n        @filters << lax_parse_filter_expressions(filtername, filterargs)\n      end\n      p.consume(:end_of_string)\n    end\n\n    def strict2_parse(markup)\n      @filters = []\n      p = @parse_context.new_parser(markup)\n\n      return if p.look(:end_of_string)\n\n      @name = parse_context.safe_parse_expression(p)\n      @filters << strict2_parse_filter_expressions(p) while p.consume?(:pipe)\n      p.consume(:end_of_string)\n    end\n\n    def parse_filterargs(p)\n      # first argument\n      filterargs = [p.argument]\n      # followed by comma separated others\n      filterargs << p.argument while p.consume?(:comma)\n      filterargs\n    end\n\n    def render(context)\n      obj = context.evaluate(@name)\n\n      @filters.each do |filter_name, filter_args, filter_kwargs|\n        filter_args = evaluate_filter_expressions(context, filter_args, filter_kwargs)\n        obj = context.invoke(filter_name, obj, *filter_args)\n      end\n\n      context.apply_global_filter(obj)\n    end\n\n    def render_to_output_buffer(context, output)\n      obj = render(context)\n      render_obj_to_output(obj, output)\n      output\n    end\n\n    def render_obj_to_output(obj, output)\n      case obj\n      when NilClass\n        # Do nothing\n      when Array\n        obj.each do |o|\n          render_obj_to_output(o, output)\n        end\n      else\n        output << Liquid::Utils.to_s(obj)\n      end\n    end\n\n    def disabled?(_context)\n      false\n    end\n\n    def disabled_tags\n      []\n    end\n\n    private\n\n    def lax_parse_filter_expressions(filter_name, unparsed_args)\n      filter_args  = []\n      keyword_args = nil\n      unparsed_args.each do |a|\n        if (matches = a.match(JustTagAttributes))\n          keyword_args           ||= {}\n          keyword_args[matches[1]] = parse_context.parse_expression(matches[2])\n        else\n          filter_args << parse_context.parse_expression(a)\n        end\n      end\n      result = [filter_name, filter_args]\n      result << keyword_args if keyword_args\n      result\n    end\n\n    # Surprisingly, positional and keyword arguments can be mixed.\n    #\n    # filter = filtername [\":\" filterargs?]\n    # filterargs = argument (\",\" argument)*\n    # argument = (positional_argument | keyword_argument)\n    # positional_argument = expression\n    # keyword_argument = id \":\" expression\n    def strict2_parse_filter_expressions(p)\n      filtername = p.consume(:id)\n      filter_args = []\n      keyword_args = {}\n\n      if p.consume?(:colon)\n        # Parse first argument (no leading comma)\n        argument(p, filter_args, keyword_args) unless end_of_arguments?(p)\n\n        # Parse remaining arguments (with leading commas) and optional trailing comma\n        argument(p, filter_args, keyword_args) while p.consume?(:comma) && !end_of_arguments?(p)\n      end\n\n      result = [filtername, filter_args]\n      result << keyword_args unless keyword_args.empty?\n      result\n    end\n\n    def argument(p, positional_arguments, keyword_arguments)\n      if p.look(:id) && p.look(:colon, 1)\n        key = p.consume(:id)\n        p.consume(:colon)\n        value = parse_context.safe_parse_expression(p)\n        keyword_arguments[key] = value\n      else\n        positional_arguments << parse_context.safe_parse_expression(p)\n      end\n    end\n\n    def end_of_arguments?(p)\n      p.look(:pipe) || p.look(:end_of_string)\n    end\n\n    def evaluate_filter_expressions(context, filter_args, filter_kwargs)\n      parsed_args = filter_args.map { |expr| context.evaluate(expr) }\n      if filter_kwargs\n        parsed_kwargs = {}\n        filter_kwargs.each do |key, expr|\n          parsed_kwargs[key] = context.evaluate(expr)\n        end\n        parsed_args << parsed_kwargs\n      end\n      parsed_args\n    end\n\n    class ParseTreeVisitor < Liquid::ParseTreeVisitor\n      def children\n        [@node.name] + @node.filters.flatten\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/variable_lookup.rb",
    "content": "# frozen_string_literal: true\n\nmodule Liquid\n  class VariableLookup\n    COMMAND_METHODS = ['size', 'first', 'last'].freeze\n\n    attr_reader :name, :lookups\n\n    def self.parse(markup, string_scanner = StringScanner.new(\"\"), cache = nil)\n      new(markup, string_scanner, cache)\n    end\n\n    def initialize(markup, string_scanner = StringScanner.new(\"\"), cache = nil)\n      lookups = markup.scan(VariableParser)\n\n      name = lookups.shift\n      if name&.start_with?('[') && name&.end_with?(']')\n        name = Expression.parse(\n          name[1..-2],\n          string_scanner,\n          cache,\n        )\n      end\n      @name = name\n\n      @lookups       = lookups\n      @command_flags = 0\n\n      @lookups.each_index do |i|\n        lookup = lookups[i]\n        if lookup&.start_with?('[') && lookup&.end_with?(']')\n          lookups[i] = Expression.parse(\n            lookup[1..-2],\n            string_scanner,\n            cache,\n          )\n        elsif COMMAND_METHODS.include?(lookup)\n          @command_flags |= 1 << i\n        end\n      end\n    end\n\n    def lookup_command?(lookup_index)\n      @command_flags & (1 << lookup_index) != 0\n    end\n\n    def evaluate(context)\n      name   = context.evaluate(@name)\n      object = context.find_variable(name)\n\n      @lookups.each_index do |i|\n        key = context.evaluate(@lookups[i])\n\n        # Cast \"key\" to its liquid value to enable it to act as a primitive value\n        key = Liquid::Utils.to_liquid_value(key)\n\n        # If object is a hash- or array-like object we look for the\n        # presence of the key and if its available we return it\n        if object.respond_to?(:[]) &&\n            ((object.respond_to?(:key?) && object.key?(key)) ||\n             (object.respond_to?(:fetch) && key.is_a?(Integer)))\n\n          # if its a proc we will replace the entry with the proc\n          res    = context.lookup_and_evaluate(object, key)\n          object = res.to_liquid\n\n          # Some special cases. If the part wasn't in square brackets and\n          # no key with the same name was found we interpret following calls\n          # as commands and call them on the current object\n        elsif lookup_command?(i) && object.respond_to?(key)\n          object = object.send(key).to_liquid\n\n        # Handle string first/last like ActiveSupport does (returns first/last character)\n        # ActiveSupport returns \"\" for empty strings, not nil\n        elsif lookup_command?(i) && object.is_a?(String) && (key == \"first\" || key == \"last\")\n          object = key == \"first\" ? (object[0] || \"\") : (object[-1] || \"\")\n\n          # No key was present with the desired value and it wasn't one of the directly supported\n          # keywords either. The only thing we got left is to return nil or\n          # raise an exception if `strict_variables` option is set to true\n        else\n          return nil unless context.strict_variables\n          raise Liquid::UndefinedVariable, \"undefined variable #{key}\"\n        end\n\n        # If we are dealing with a drop here we have to\n        object.context = context if object.respond_to?(:context=)\n      end\n\n      object\n    end\n\n    def ==(other)\n      self.class == other.class && state == other.state\n    end\n\n    protected\n\n    def state\n      [@name, @lookups, @command_flags]\n    end\n\n    class ParseTreeVisitor < Liquid::ParseTreeVisitor\n      def children\n        @node.lookups\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/liquid/version.rb",
    "content": "# encoding: utf-8\n# frozen_string_literal: true\n\nmodule Liquid\n  VERSION = \"5.12.0\"\nend\n"
  },
  {
    "path": "lib/liquid.rb",
    "content": "# frozen_string_literal: true\n\n# Copyright (c) 2005 Tobias Luetke\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of this software and associated documentation files (the\n# \"Software\"), to deal in the Software without restriction, including\n# without limitation the rights to use, copy, modify, merge, publish,\n# distribute, sublicense, and/or sell copies of the Software, and to\n# permit persons to whom the Software is furnished to do so, subject to\n# the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nrequire \"strscan\"\n\nmodule Liquid\n  FilterSeparator             = /\\|/\n  ArgumentSeparator           = ','\n  FilterArgumentSeparator     = ':'\n  VariableAttributeSeparator  = '.'\n  WhitespaceControl           = '-'\n  TagStart                    = /\\{\\%/\n  TagEnd                      = /\\%\\}/\n  TagName                     = /#|\\w+/\n  VariableSignature           = /\\(?[\\w\\-\\.\\[\\]]\\)?/\n  VariableSegment             = /[\\w\\-]/\n  VariableStart               = /\\{\\{/\n  VariableEnd                 = /\\}\\}/\n  VariableIncompleteEnd       = /\\}\\}?/\n  QuotedString                = /\"[^\"]*\"|'[^']*'/\n  QuotedFragment              = /#{QuotedString}|(?:[^\\s,\\|'\"]|#{QuotedString})+/o\n  TagAttributes               = /(\\w[\\w-]*)\\s*\\:\\s*(#{QuotedFragment})/o\n  AnyStartingTag              = /#{TagStart}|#{VariableStart}/o\n  PartialTemplateParser       = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om\n  TemplateParser              = /(#{PartialTemplateParser}|#{AnyStartingTag})/om\n  VariableParser              = /\\[(?>[^\\[\\]]+|\\g<0>)*\\]|#{VariableSegment}+\\??/o\n\n  RAISE_EXCEPTION_LAMBDA = ->(_e) { raise }\n  HAS_STRING_SCANNER_SCAN_BYTE = StringScanner.instance_methods.include?(:scan_byte)\nend\n\nrequire \"liquid/version\"\nrequire \"liquid/deprecations\"\nrequire \"liquid/const\"\nrequire 'liquid/standardfilters'\nrequire 'liquid/file_system'\nrequire 'liquid/parser_switching'\nrequire 'liquid/tag'\nrequire 'liquid/block'\nrequire 'liquid/parse_tree_visitor'\nrequire 'liquid/interrupts'\nrequire 'liquid/tags'\nrequire \"liquid/environment\"\nrequire 'liquid/lexer'\nrequire 'liquid/parser'\nrequire 'liquid/i18n'\nrequire 'liquid/drop'\nrequire 'liquid/tablerowloop_drop'\nrequire 'liquid/forloop_drop'\nrequire 'liquid/extensions'\nrequire 'liquid/errors'\nrequire 'liquid/interrupts'\nrequire 'liquid/strainer_template'\nrequire 'liquid/context'\nrequire 'liquid/tag'\nrequire 'liquid/block_body'\nrequire 'liquid/document'\nrequire 'liquid/variable'\nrequire 'liquid/variable_lookup'\nrequire 'liquid/range_lookup'\nrequire 'liquid/resource_limits'\nrequire 'liquid/expression'\nrequire 'liquid/template'\nrequire 'liquid/condition'\nrequire 'liquid/utils'\nrequire 'liquid/tokenizer'\nrequire 'liquid/parse_context'\nrequire 'liquid/partial_cache'\nrequire 'liquid/usage'\nrequire 'liquid/registers'\nrequire 'liquid/template_factory'\n"
  },
  {
    "path": "liquid.gemspec",
    "content": "# encoding: utf-8\n# frozen_string_literal: true\n\nlib = File.expand_path('../lib/', __FILE__)\n$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)\n\nrequire \"liquid/version\"\n\nGem::Specification.new do |s|\n  s.name        = \"liquid\"\n  s.version     = Liquid::VERSION\n  s.platform    = Gem::Platform::RUBY\n  s.summary     = \"A secure, non-evaling end user template engine with aesthetic markup.\"\n  s.authors     = [\"Tobias Lütke\"]\n  s.email       = [\"tobi@leetsoft.com\"]\n  s.homepage    = \"https://shopify.github.io/liquid/\"\n  s.license     = \"MIT\"\n  # s.description = \"A secure, non-evaling end user template engine with aesthetic markup.\"\n\n  s.required_ruby_version     = \">= 3.0.0\"\n  s.required_rubygems_version = \">= 1.3.7\"\n\n  s.metadata['allowed_push_host'] = 'https://rubygems.org'\n\n  s.files = Dir.glob(\"{lib}/**/*\") + %w(LICENSE README.md)\n\n  s.extra_rdoc_files = [\"History.md\", \"README.md\"]\n\n  s.require_path = \"lib\"\n\n  s.add_dependency(\"strscan\", \">= 3.1.1\")\n  s.add_dependency(\"bigdecimal\")\n\n  s.add_development_dependency('rake', '~> 13.0')\n  s.add_development_dependency('minitest')\nend\n"
  },
  {
    "path": "performance/benchmark.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'benchmark/ips'\nrequire_relative 'theme_runner'\n\nRubyVM::YJIT.enable if defined?(RubyVM::YJIT)\nLiquid::Environment.default.error_mode = ARGV.first.to_sym if ARGV.first\n\nprofiler = ThemeRunner.new\n\nBenchmark.ips do |x|\n  x.time   = 20\n  x.warmup = 10\n\n  puts\n  puts \"Running benchmark for #{x.time} seconds (with #{x.warmup} seconds warmup).\"\n  puts\n\n  phase = ENV[\"PHASE\"] || \"all\"\n\n  x.report(\"tokenize:\") { profiler.tokenize } if phase == \"all\" || phase == \"tokenize\"\n  x.report(\"parse:\") { profiler.compile } if phase == \"all\" || phase == \"parse\"\n  x.report(\"render:\") { profiler.render } if phase == \"all\" || phase == \"render\"\n  x.report(\"parse & render:\") { profiler.run } if phase == \"all\" || phase == \"run\"\nend\n"
  },
  {
    "path": "performance/memory_profile.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'benchmark/ips'\nrequire 'memory_profiler'\nrequire 'terminal-table'\nrequire_relative 'theme_runner'\n\nclass Profiler\n  LOG_LABEL   = \"Profiling: \".rjust(14).freeze\n  REPORTS_DIR = File.expand_path('.memprof', __dir__).freeze\n\n  def self.run\n    puts\n    yield new\n  end\n\n  def initialize\n    @allocated = []\n    @retained  = []\n    @headings  = []\n  end\n\n  def profile(phase, &block)\n    print(LOG_LABEL)\n    print(\"#{phase}.. \".ljust(10))\n    report = MemoryProfiler.report(&block)\n    puts 'Done.'\n    @headings  << phase.capitalize\n    @allocated << \"#{report.scale_bytes(report.total_allocated_memsize)} (#{report.total_allocated} objects)\"\n    @retained  << \"#{report.scale_bytes(report.total_retained_memsize)} (#{report.total_retained} objects)\"\n\n    return if ENV['CI']\n\n    require 'fileutils'\n    report_file = File.join(REPORTS_DIR, \"#{sanitize(phase)}.txt\")\n    FileUtils.mkdir_p(REPORTS_DIR)\n    report.pretty_print(to_file: report_file, scale_bytes: true)\n  end\n\n  def tabulate\n    table = Terminal::Table.new(headings: @headings.unshift('Phase')) do |t|\n      t << @allocated.unshift('Total allocated')\n      t << @retained.unshift('Total retained')\n    end\n\n    puts\n    puts table\n    puts \"\\nDetailed report(s) saved to #{REPORTS_DIR}/\" unless ENV['CI']\n  end\n\n  def sanitize(string)\n    string.downcase.gsub(/[\\W]/, '-').squeeze('-')\n  end\nend\n\nLiquid::Template.error_mode = ARGV.first.to_sym if ARGV.first\n\nrunner = ThemeRunner.new\nProfiler.run do |x|\n  x.profile('parse') { runner.compile }\n  x.profile('render') { runner.render }\n  x.tabulate\nend\n"
  },
  {
    "path": "performance/profile.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'stackprof'\nrequire_relative 'theme_runner'\n\nLiquid::Template.error_mode = ARGV.first.to_sym if ARGV.first\nprofiler = ThemeRunner.new\nprofiler.run\n\n[:cpu, :object].each do |profile_type|\n  puts \"Profiling in #{profile_type} mode...\"\n  results = StackProf.run(mode: profile_type) do\n    200.times do\n      profiler.run\n    end\n  end\n\n  if profile_type == :cpu && (graph_filename = ENV['GRAPH_FILENAME'])\n    File.open(graph_filename, 'w') do |f|\n      StackProf::Report.new(results).print_graphviz(nil, f)\n    end\n  end\n\n  StackProf::Report.new(results).print_text(false, 20)\n  File.write(ENV['FILENAME'] + \".\" + profile_type.to_s, Marshal.dump(results)) if ENV['FILENAME']\nend\n"
  },
  {
    "path": "performance/shopify/comment_form.rb",
    "content": "# frozen_string_literal: true\n\nclass CommentForm < Liquid::Block\n  Syntax = /(#{Liquid::VariableSignature}+)/\n\n  def initialize(tag_name, markup, options)\n    super\n\n    if markup =~ Syntax\n      @variable_name = Regexp.last_match(1)\n      @attributes    = {}\n    else\n      raise SyntaxError, \"Syntax Error in 'comment_form' - Valid syntax: comment_form [article]\"\n    end\n  end\n\n  def render_to_output_buffer(context, output)\n    article = context[@variable_name]\n\n    context.stack do\n      context['form'] = {\n        'posted_successfully?' => context.registers[:posted_successfully],\n        'errors' => context['comment.errors'],\n        'author' => context['comment.author'],\n        'email' => context['comment.email'],\n        'body' => context['comment.body'],\n      }\n\n      output << wrap_in_form(article, render_all(@nodelist, context, output))\n      output\n    end\n  end\n\n  def wrap_in_form(article, input)\n    %(<form id=\"article-#{article.id}-comment-form\" class=\"comment-form\" method=\"post\" action=\"\">\\n#{input}\\n</form>)\n  end\nend\n"
  },
  {
    "path": "performance/shopify/database.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'yaml'\n\nmodule Database\n  DATABASE_FILE_PATH = \"#{__dir__}/vision.database.yml\"\n\n  # Load the standard vision toolkit database and re-arrage it to be simply exportable\n  # to liquid as assigns. All this is based on Shopify\n  def self.tables\n    @tables ||= begin\n      db =\n        if YAML.respond_to?(:unsafe_load_file) # Only Psych 4+ can use unsafe_load_file\n          # unsafe_load_file is needed for YAML references\n          YAML.unsafe_load_file(DATABASE_FILE_PATH)\n        else\n          YAML.load_file(DATABASE_FILE_PATH)\n        end\n\n      # From vision source\n      db['products'].each do |product|\n        collections = db['collections'].find_all do |collection|\n          collection['products'].any? { |p| p['id'].to_i == product['id'].to_i }\n        end\n        product['collections'] = collections\n      end\n\n      # key the tables by handles, as this is how liquid expects it.\n      db = db.each_with_object({}) do |(key, values), assigns|\n        assigns[key] = values.each_with_object({}) do |v, h|\n          h[v['handle']] = v\n        end\n      end\n\n      # Some standard direct accessors so that the specialized templates\n      # render correctly\n      db['collection'] = db['collections'].values.first\n      db['product']    = db['products'].values.first\n      db['blog']       = db['blogs'].values.first\n      db['article']    = db['blog']['articles'].first\n\n      db['cart']       = {\n        'total_price' => db['line_items'].values.inject(0) { |sum, item| sum + item['line_price'] * item['quantity'] },\n        'item_count' => db['line_items'].values.inject(0) { |sum, item| sum + item['quantity'] },\n        'items' => db['line_items'].values,\n      }\n\n      db\n    end\n  end\nend\n"
  },
  {
    "path": "performance/shopify/json_filter.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'json'\n\nmodule JsonFilter\n  def json(object)\n    JSON.dump(object.reject { |k, _v| k == \"collections\" })\n  end\nend\n"
  },
  {
    "path": "performance/shopify/liquid.rb",
    "content": "# frozen_string_literal: true\n\n$LOAD_PATH.unshift(__dir__ + '/../../lib')\nrequire_relative '../../lib/liquid'\n\nrequire_relative 'comment_form'\nrequire_relative 'paginate'\nrequire_relative 'json_filter'\nrequire_relative 'money_filter'\nrequire_relative 'shop_filter'\nrequire_relative 'tag_filter'\nrequire_relative 'weight_filter'\n\ndefault_environment = Liquid::Environment.default\ndefault_environment.register_tag('paginate', Paginate)\ndefault_environment.register_tag('form', CommentForm)\n\ndefault_environment.register_filter(JsonFilter)\ndefault_environment.register_filter(MoneyFilter)\ndefault_environment.register_filter(WeightFilter)\ndefault_environment.register_filter(ShopFilter)\ndefault_environment.register_filter(TagFilter)\n"
  },
  {
    "path": "performance/shopify/money_filter.rb",
    "content": "# frozen_string_literal: true\n\nmodule MoneyFilter\n  def money_with_currency(money)\n    return '' if money.nil?\n    format(\"$ %.2f USD\", money / 100.0)\n  end\n\n  def money(money)\n    return '' if money.nil?\n    format(\"$ %.2f\", money / 100.0)\n  end\n\n  private\n\n  def currency\n    ShopDrop.new.currency\n  end\nend\n"
  },
  {
    "path": "performance/shopify/paginate.rb",
    "content": "# frozen_string_literal: true\n\nclass Paginate < Liquid::Block\n  Syntax = /(#{Liquid::QuotedFragment})\\s*(by\\s*(\\d+))?/\n\n  def initialize(tag_name, markup, options)\n    super\n\n    if markup =~ Syntax\n      @collection_name = Regexp.last_match(1)\n      @page_size       = if Regexp.last_match(2)\n        Regexp.last_match(3).to_i\n      else\n        20\n      end\n\n      @attributes = { 'window_size' => 3 }\n      markup.scan(Liquid::TagAttributes) do |key, value|\n        @attributes[key] = value\n      end\n    else\n      raise SyntaxError, \"Syntax Error in tag 'paginate' - Valid syntax: paginate [collection] by number\"\n    end\n  end\n\n  def render_to_output_buffer(context, output)\n    @context = context\n\n    context.stack do\n      current_page = context['current_page'].to_i\n\n      pagination = {\n        'page_size' => @page_size,\n        'current_page' => 5,\n        'current_offset' => @page_size * 5,\n      }\n\n      context['paginate'] = pagination\n\n      collection_size = context[@collection_name].size\n\n      raise ArgumentError, \"Cannot paginate array '#{@collection_name}'. Not found.\" if collection_size.nil?\n\n      page_count = (collection_size.to_f / @page_size.to_f).to_f.ceil + 1\n\n      pagination['items']      = collection_size\n      pagination['pages']      = page_count - 1\n      pagination['previous']   = link('&laquo; Previous', current_page - 1)  if 1 < current_page\n      pagination['next']       = link('Next &raquo;', current_page + 1)      if page_count > current_page + 1\n      pagination['parts']      = []\n\n      hellip_break = false\n\n      if page_count > 2\n        1.upto(page_count - 1) do |page|\n          if current_page == page\n            pagination['parts'] << no_link(page)\n          elsif page == 1\n            pagination['parts'] << link(page, page)\n          elsif page == page_count - 1\n            pagination['parts'] << link(page, page)\n          elsif page <= current_page - @attributes['window_size'] || page >= current_page + @attributes['window_size']\n            next if hellip_break\n            pagination['parts'] << no_link('&hellip;')\n            hellip_break = true\n            next\n          else\n            pagination['parts'] << link(page, page)\n          end\n\n          hellip_break = false\n        end\n      end\n\n      super\n    end\n  end\n\n  private\n\n  def no_link(title)\n    { 'title' => title, 'is_link' => false }\n  end\n\n  def link(title, page)\n    { 'title' => title, 'url' => current_url + \"?page=#{page}\", 'is_link' => true }\n  end\n\n  def current_url\n    \"/collections/frontpage\"\n  end\nend\n"
  },
  {
    "path": "performance/shopify/shop_filter.rb",
    "content": "# frozen_string_literal: true\n\nmodule ShopFilter\n  def asset_url(input)\n    \"/files/1/[shop_id]/[shop_id]/assets/#{input}\"\n  end\n\n  def global_asset_url(input)\n    \"/global/#{input}\"\n  end\n\n  def shopify_asset_url(input)\n    \"/shopify/#{input}\"\n  end\n\n  def script_tag(url)\n    %(<script src=\"#{url}\" type=\"text/javascript\"></script>)\n  end\n\n  def stylesheet_tag(url, media = \"all\")\n    %(<link href=\"#{url}\" rel=\"stylesheet\" type=\"text/css\"  media=\"#{media}\"  />)\n  end\n\n  def link_to(link, url, title = \"\")\n    %(<a href=\"#{url}\" title=\"#{title}\">#{link}</a>)\n  end\n\n  def img_tag(url, alt = \"\")\n    %(<img src=\"#{url}\" alt=\"#{alt}\" />)\n  end\n\n  def link_to_vendor(vendor)\n    if vendor\n      link_to(vendor, url_for_vendor(vendor), vendor)\n    else\n      'Unknown Vendor'\n    end\n  end\n\n  def link_to_type(type)\n    if type\n      link_to(type, url_for_type(type), type)\n    else\n      'Unknown Vendor'\n    end\n  end\n\n  def url_for_vendor(vendor_title)\n    \"/collections/#{to_handle(vendor_title)}\"\n  end\n\n  def url_for_type(type_title)\n    \"/collections/#{to_handle(type_title)}\"\n  end\n\n  def product_img_url(url, style = 'small')\n    unless url =~ %r{\\Aproducts/([\\w\\-\\_]+)\\.(\\w{2,4})}\n      raise ArgumentError, 'filter \"size\" can only be called on product images'\n    end\n\n    case style\n    when 'original'\n      '/files/shops/random_number/' + url\n    when 'grande', 'large', 'medium', 'compact', 'small', 'thumb', 'icon'\n      \"/files/shops/random_number/products/#{Regexp.last_match(1)}_#{style}.#{Regexp.last_match(2)}\"\n    else\n      raise ArgumentError, 'valid parameters for filter \"size\" are: original, grande, large, medium, compact, small, thumb and icon '\n    end\n  end\n\n  def default_pagination(paginate)\n    html = []\n    html << %(<span class=\"prev\">#{link_to(paginate['previous']['title'], paginate['previous']['url'])}</span>) if paginate['previous']\n\n    paginate['parts'].each do |part|\n      html << if part['is_link']\n        %(<span class=\"page\">#{link_to(part['title'], part['url'])}</span>)\n      elsif part['title'].to_i == paginate['current_page'].to_i\n        %(<span class=\"page current\">#{part['title']}</span>)\n      else\n        %(<span class=\"deco\">#{part['title']}</span>)\n      end\n    end\n\n    html << %(<span class=\"next\">#{link_to(paginate['next']['title'], paginate['next']['url'])}</span>) if paginate['next']\n    html.join(' ')\n  end\n\n  # Accepts a number, and two words - one for singular, one for plural\n  # Returns the singular word if input equals 1, otherwise plural\n  def pluralize(input, singular, plural)\n    input == 1 ? singular : plural\n  end\n\n  private\n\n  def to_handle(str)\n    result = str.dup\n    result.downcase!\n    result.delete!(\"'\\\"()[]\")\n    result.gsub!(/\\W+/, '-')\n    result.gsub!(/-+\\z/, '') if result[-1] == '-'\n    result.gsub!(/\\A-+/, '') if result[0] == '-'\n    result\n  end\nend\n"
  },
  {
    "path": "performance/shopify/tag_filter.rb",
    "content": "# frozen_string_literal: true\n\nmodule TagFilter\n  def link_to_tag(label, tag)\n    \"<a title=\\\"Show tag #{tag}\\\" href=\\\"/collections/#{@context['handle']}/#{tag}\\\">#{label}</a>\"\n  end\n\n  def highlight_active_tag(tag, css_class = 'active')\n    if @context['current_tags'].include?(tag)\n      \"<span class=\\\"#{css_class}\\\">#{tag}</span>\"\n    else\n      tag\n    end\n  end\n\n  def link_to_add_tag(label, tag)\n    tags = (@context['current_tags'] + [tag]).uniq\n    \"<a title=\\\"Show tag #{tag}\\\" href=\\\"/collections/#{@context['handle']}/#{tags.join('+')}\\\">#{label}</a>\"\n  end\n\n  def link_to_remove_tag(label, tag)\n    tags = (@context['current_tags'] - [tag]).uniq\n    \"<a title=\\\"Show tag #{tag}\\\" href=\\\"/collections/#{@context['handle']}/#{tags.join('+')}\\\">#{label}</a>\"\n  end\nend\n"
  },
  {
    "path": "performance/shopify/vision.database.yml",
    "content": "# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n#   Variants\n# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n\nproduct_variants:\n  - &product-1-var-1\n    id: 1\n    title: 151cm / Normal\n    price: 19900\n    weight: 1000\n    compare_at_price: 49900\n    available: true\n    inventory_quantity: 5\n    option1: 151cm\n    option2: Normal\n    option3:\n  - &product-1-var-2\n    id: 2\n    title: 155cm / Normal\n    price: 31900\n    weight: 1000\n    compare_at_price: 50900\n    available: true\n    inventory_quantity: 2\n    option1: 155cm\n    option2: Normal\n    option3:\n  - &product-2-var-1\n    id: 3\n    title: 162cm\n    price: 29900\n    weight: 1000\n    compare_at_price: 52900\n    available: true\n    inventory_quantity: 3\n    option1: 162cm\n    option2:\n    option3:\n  - &product-3-var-1\n    id: 4\n    title: 159cm\n    price: 19900\n    weight: 1000\n    compare_at_price:\n    available: true\n    inventory_quantity: 4\n    option1: 159cm\n    option2:\n    option3:\n  - &product-4-var-1\n    id: 5\n    title: 159cm\n    price: 19900\n    weight: 1000\n    compare_at_price: 32900\n    available: true\n    inventory_quantity: 6\n    option1: 159cm\n    option2:\n    option3:\n  - &product-1-var-3\n    id: 6\n    title: 158cm / Wide\n    price: 23900\n    weight: 1000\n    compare_at_price: 99900\n    available: false\n    inventory_quantity: 0\n    option1: 158cm\n    option2: Wide\n    option3:\n  - &product-3-var-2\n    id: 7\n    title: 162cm\n    price: 19900\n    weight: 1000\n    compare_at_price:\n    available: false\n    inventory_quantity: 0\n    option1: 162cm\n    option2:\n    option3:\n  - &product-3-var-3\n    id: 8\n    title: 165cm\n    price: 22900\n    weight: 1000\n    compare_at_price:\n    available: true\n    inventory_quantity: 4\n    option1: 165cm\n    option2:\n    option3:\n  - &product-5-var-1\n    id: 9\n    title: black / 42\n    price: 11900\n    weight: 500\n    compare_at_price: 22900\n    available: true\n    inventory_quantity: 1\n    option1: black\n    option2: 42\n    option3:\n  - &product-5-var-2\n    id: 10\n    title: beige / 42\n    price: 11900\n    weight: 500\n    compare_at_price: 22900\n    available: true\n    inventory_quantity: 3\n    option1: beige\n    option2: 42\n    option3:\n  - &product-5-var-3\n    id: 11\n    title: white / 42\n    price: 13900\n    weight: 500\n    compare_at_price: 24900\n    available: true\n    inventory_quantity: 1\n    option1: white\n    option2: 42\n    option3:\n  - &product-5-var-4\n    id: 12\n    title: black / 44\n    price: 11900\n    weight: 500\n    compare_at_price: 22900\n    available: true\n    inventory_quantity: 2\n    option1: black\n    option2: 44\n    option3:\n  - &product-5-var-5\n    id: 13\n    title: beige / 44\n    price: 11900\n    weight: 500\n    compare_at_price: 22900\n    available: false\n    inventory_quantity: 0\n    option1: beige\n    option2: 44\n    option3:\n  - &product-5-var-6\n    id: 14\n    title: white / 44\n    price: 13900\n    weight: 500\n    compare_at_price: 24900\n    available: false\n    inventory_quantity: 0\n    option1: white\n    option2: 44\n    option3:\n  - &product-6-var-1\n    id: 15\n    title: red\n    price: 2179500\n    weight: 200000\n    compare_at_price:\n    available: true\n    inventory_quantity: 0\n    option1: red\n    option2:\n    option3:\n  - &product-7-var-1\n    id: 16\n    title: black / small\n    price: 1900\n    weight: 200\n    compare_at_price:\n    available: true\n    inventory_quantity: 20\n    option1: black\n    option2: small\n    option3:\n  - &product-7-var-2\n    id: 17\n    title: black / medium\n    price: 1900\n    weight: 200\n    compare_at_price:\n    available: false\n    inventory_quantity: 0\n    option1: black\n    option2: medium\n    option3:\n  - &product-7-var-3\n    id: 18\n    title: black / large\n    price: 1900\n    weight: 200\n    compare_at_price:\n    available: true\n    inventory_quantity: 10\n    option1: black\n    option2: large\n    option3:\n  - &product-7-var-4\n    id: 19\n    title: black / extra large\n    price: 1900\n    weight: 200\n    compare_at_price:\n    available: false\n    inventory_quantity: 0\n    option1: black\n    option2: extra large\n    option3:\n  - &product-8-var-1\n    id: 20\n    title: brown / small\n    price: 5900\n    weight: 400\n    compare_at_price: 6900\n    available: true\n    inventory_quantity: 5\n    option1: brown\n    option2: small\n    option3:\n  - &product-8-var-2\n    id: 21\n    title: brown / medium\n    price: 5900\n    weight: 400\n    compare_at_price: 6900\n    available: false\n    inventory_quantity: 0\n    option1: brown\n    option2: medium\n    option3:\n  - &product-8-var-3\n    id: 22\n    title: brown / large\n    price: 5900\n    weight: 400\n    compare_at_price: 6900\n    available: true\n    inventory_quantity: 10\n    option1: brown\n    option2: large\n    option3:\n  - &product-8-var-4\n    id: 23\n    title: black / small\n    price: 5900\n    weight: 400\n    compare_at_price: 6900\n    available: true\n    inventory_quantity: 10\n    option1: black\n    option2: small\n    option3:\n  - &product-8-var-5\n    id: 24\n    title: black / medium\n    price: 5900\n    weight: 400\n    compare_at_price: 6900\n    available: true\n    inventory_quantity: 10\n    option1: black\n    option2: medium\n    option3:\n  - &product-8-var-6\n    id: 25\n    title: black / large\n    price: 5900\n    weight: 400\n    compare_at_price: 6900\n    available: false\n    inventory_quantity: 0\n    option1: black\n    option2: large\n    option3:\n  - &product-9-var-1\n    id: 26\n    title: Body Only\n    price: 499995\n    weight: 2000\n    compare_at_price:\n    available: true\n    inventory_quantity: 3\n    option1: Body Only\n    option2:\n    option3:\n  - &product-9-var-2\n    id: 27\n    title: Kit with 18-55mm VR lens\n    price: 523995\n    weight: 2000\n    compare_at_price:\n    available: true\n    inventory_quantity: 2\n    option1: Kit with 18-55mm VR lens\n    option2:\n    option3:\n  - &product-9-var-3\n    id: 28\n    title: Kit with 18-200 VR lens\n    price: 552500\n    weight: 2000\n    compare_at_price:\n    available: true\n    inventory_quantity: 3\n    option1: Kit with 18-200 VR lens\n    option2:\n    option3:\n\n# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n#   Products\n# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n\nproducts:\n  - &product-1\n    id: 1\n    title: Arbor Draft\n    handle: arbor-draft\n    type: Snowboards\n    vendor: Arbor\n    price: 23900\n    price_max: 31900\n    price_min: 23900\n    price_varies: true\n    available: true\n    tags:\n      - season2005\n      - pro\n      - intermediate\n      - wooden\n      - freestyle\n    options:\n      - Length\n      - Style\n    compare_at_price: 49900\n    compare_at_price_max: 50900\n    compare_at_price_min: 49900\n    compare_at_price_varies: true\n    url: /products/arbor-draft\n    featured_image: products/arbor_draft.jpg\n    images:\n      - products/arbor_draft.jpg\n    description:\n      The Arbor Draft snowboard wouldn't exist if Polynesians hadn't figured out how to surf hundreds of years ago. But the Draft does exist, and it's here to bring your urban and park riding to a new level. The board's freaky Tiki design pays homage to culture that inspired snowboarding. It's designed to spin with ease, land smoothly, lock hook-free onto rails, and take the abuse of a pavement pounding or twelve. The Draft will pop off kickers with authority and carve solidly across the pipe. The Draft features targeted Koa wood die cuts inlayed into the deck that enhance the flex pattern. Now bow down to riding's ancestors.\n    variants:\n      - *product-1-var-1\n      - *product-1-var-2\n      - *product-1-var-3\n  - &product-2\n    id: 2\n    title: Arbor Element\n    handle: arbor-element\n    type: Snowboards\n    vendor: Arbor\n    price: 29900\n    price_max: 29900\n    price_min: 29900\n    price_varies: false\n    available: true\n    tags:\n      - season2005\n      - pro\n      - wooden\n      - freestyle\n    options:\n      - Length\n    compare_at_price: 52900\n    compare_at_price_max: 52900\n    compare_at_price_min: 52900\n    compare_at_price_varies: false\n    url: /products/arbor-element\n    featured_image: products/element58.jpg\n    images:\n      - products/element58.jpg\n    description:\n      The Element is a technically advanced all-mountain board for riders who readily transition from one terrain, snow condition, or riding style to another. Its balanced design provides the versatility needed for the true ride-it-all experience. The Element is exceedingly lively, freely initiates, and holds a tight edge at speed. Its structural real-wood topsheet is made with book-matched Koa.\n    variants:\n      - *product-2-var-1\n\n  - &product-3\n    id: 3\n    title: Comic ~ Pastel\n    handle: comic-pastel\n    type: Snowboards\n    vendor: Technine\n    price: 19900\n    price_max: 22900\n    price_min: 19900\n    tags:\n      - season2006\n      - beginner\n      - intermediate\n      - freestyle\n      - purple\n    options:\n      - Length\n    price_varies: true\n    available: true\n    compare_at_price:\n    compare_at_price_max: 0\n    compare_at_price_min: 0\n    compare_at_price_varies: false\n    url: /products/comic-pastel\n    featured_image: products/technine1.jpg\n    images:\n      - products/technine1.jpg\n      - products/technine2.jpg\n      - products/technine_detail.jpg\n    description:\n      2005 Technine Comic Series Description The Comic series was developed to be the ultimate progressive freestyle board in the Technine line. Dependable edge control and a perfect flex pattern for jumping in the park or out of bounds. Landins and progression will come easy with this board and it will help your riding progress to the next level. Street rails, park jibs, backcountry booters and park jumps, this board will do it all.\n    variants:\n      - *product-3-var-1\n      - *product-3-var-2\n      - *product-3-var-3\n\n  - &product-4\n    id: 4\n    title: Comic ~ Orange\n    handle: comic-orange\n    type: Snowboards\n    vendor: Technine\n    price: 19900\n    price_max: 19900\n    price_min: 19900\n    price_varies: false\n    available: true\n    tags:\n      - season2006\n      - beginner\n      - intermediate\n      - freestyle\n      - orange\n    options:\n      - Length\n    compare_at_price: 32900\n    compare_at_price_max: 32900\n    compare_at_price_min: 32900\n    compare_at_price_varies: false\n    url: /products/comic-orange\n    featured_image: products/technine3.jpg\n    images:\n      - products/technine3.jpg\n      - products/technine4.jpg\n    description:\n      2005 Technine Comic Series Description The Comic series was developed to be the ultimate progressive freestyle board in the Technine line. Dependable edge control and a perfect flex pattern for jumping in the park or out of bounds. Landins and progression will come easy with this board and it will help your riding progress to the next level. Street rails, park jibs, backcountry booters and park jumps, this board will do it all.\n    variants:\n      - *product-4-var-1\n\n  - &product-5\n    id: 5\n    title: Burton Boots\n    handle: burton-boots\n    type: Boots\n    vendor: Burton\n    price: 11900\n    price_max: 11900\n    price_min: 11900\n    price_varies: false\n    available: true\n    tags:\n      - season2006\n      - beginner\n      - intermediate\n      - boots\n    options:\n      - Color\n      - Shoe Size\n    compare_at_price: 22900\n    compare_at_price_max: 22900\n    compare_at_price_min: 22900\n    compare_at_price_varies: false\n    url: /products/burton-boots\n    featured_image: products/burton.jpg\n    images:\n      - products/burton.jpg\n    description:\n      The Burton boots are particularly well on snowboards. The very best thing about them is that the according picture is cubic. This makes testing in a Vision testing environment very easy.\n    variants:\n      - *product-5-var-1\n      - *product-5-var-2\n      - *product-5-var-3\n      - *product-5-var-4\n      - *product-5-var-5\n      - *product-5-var-6\n\n  - &product-6\n    id: 6\n    title: Superbike 1198 S\n    handle: superbike\n    type: Superbike\n    vendor: Ducati\n    price: 2179500\n    price_max: 2179500\n    price_min: 2179500\n    price_varies: false\n    available: true\n    tags:\n      - ducati\n      - superbike\n      - bike\n      - street\n      - racing\n      - performance\n    options:\n      - Color\n    compare_at_price:\n    compare_at_price_max: 0\n    compare_at_price_min: 0\n    compare_at_price_varies: false\n    url: /products/superbike\n    featured_image: products/ducati.jpg\n    images:\n      - products/ducati.jpg\n    description:\n      <h3>‘S’ PERFORMANCE</h3>\n      <p>Producing 170hp (125kW) and with a dry weight of just 169kg (372.6lb), the new 1198 S now incorporates more World Superbike technology than ever before by taking the 1198 motor and adding top-of-the-range suspension, lightweight chassis components and a true racing-style traction control system designed for road use.</p>\n      <p>The high performance, fully adjustable 43mm Öhlins forks, which sport low friction titanium nitride-treated fork sliders, respond effortlessly to every imperfection in the tarmac. Beyond their advanced engineering solutions, one of the most important characteristics of Öhlins forks is their ability to communicate the condition and quality of the tyre-to-road contact patch, a feature that puts every rider in superior control. The suspension set-up at the rear is complemented with a fully adjustable Öhlins rear shock equipped with a ride enhancing top-out spring and mounted to a single-sided swingarm for outstanding drive and traction. The front-to-rear Öhlins package is completed with a control-enhancing adjustable steering damper.</p>\n    variants:\n      - *product-6-var-1\n\n  - &product-7\n    id: 7\n    title: Shopify Shirt\n    handle: shopify-shirt\n    type: Shirt\n    vendor: Shopify\n    price: 1900\n    price_max: 1900\n    price_min: 1900\n    price_varies: false\n    available: true\n    tags:\n      - shopify\n      - shirt\n      - apparel\n      - tshirt\n      - clothing\n    options:\n      - Color\n      - Size\n    compare_at_price:\n    compare_at_price_max: 0\n    compare_at_price_min: 0\n    compare_at_price_varies: false\n    url: /products/shopify-shirt\n    featured_image: products/shopify_shirt.png\n    images:\n      - products/shopify_shirt.png\n    description:\n      <p>High Quality Shopify Shirt. Wear your e-commerce solution with pride and attract attention anywhere you go.</p>\n      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>\n    variants:\n      - *product-7-var-1\n      - *product-7-var-2\n      - *product-7-var-3\n      - *product-7-var-4\n\n  - &product-8\n    id: 8\n    title: Hooded Sweater\n    handle: hooded-sweater\n    type: Sweater\n    vendor: Stormtech\n    price: 5900\n    price_max: 5900\n    price_min: 5900\n    price_varies: false\n    available: true\n    tags:\n      - sweater\n      - hooded\n      - apparel\n      - clothing\n    options:\n      - Color\n      - Size\n    compare_at_price: 6900\n    compare_at_price_max: 6900\n    compare_at_price_min: 6900\n    compare_at_price_varies: false\n    url: /products/hooded-sweater\n    featured_image: products/hooded-sweater.jpg\n    images:\n      - products/hooded-sweater.jpg\n      - products/hooded-sweater-b.jpg\n    description:\n      <p>Extra comfortable zip up sweater. Durable quality, ideal for any outdoor activities.</p>\n      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>\n    variants:\n      - *product-8-var-1\n      - *product-8-var-2\n      - *product-8-var-3\n      - *product-8-var-4\n      - *product-8-var-5\n      - *product-8-var-6\n\n  - &product-9\n    id: 9\n    title: D3 Digital SLR Camera\n    handle: d3\n    type: SLR\n    vendor: Nikon\n    price: 499995\n    price_max: 552500\n    price_min: 499995\n    price_varies: true\n    available: true\n    tags:\n      - camera\n      - slr\n      - nikon\n      - professional\n    options:\n      - Bundle\n    compare_at_price:\n    compare_at_price_max: 0\n    compare_at_price_min: 0\n    compare_at_price_varies: false\n    url: /products/d3\n    featured_image: products/d3.jpg\n    images:\n      - products/d3.jpg\n      - products/d3_2.jpg\n      - products/d3_3.jpg\n    description:\n      <p>Flagship pro D-SLR with a 12.1-MP FX-format CMOS sensor, blazing 9 fps shooting at full FX resolution and low-noise performance up to 6400 ISO.</p>\n      <p><strong>Nikon's original 12.1-megapixel FX-format (23.9 x 36mm) CMOS sensor:</strong> Couple Nikon's exclusive digital image processing system with the 12.1-megapixel FX-format and you'll get breathtakingly rich images while also reducing noise to unprecedented levels with even higher ISOs.</p>\n      <p><strong>Continuous shooting at up to 9 frames per second:</strong> At full FX resolution and up to 11fps in the DX crop mode, the D3 offers uncompromised shooting speeds for fast-action and sports photography.</p>\n    variants:\n      - *product-9-var-1\n      - *product-9-var-2\n      - *product-9-var-3\n\n\n# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n#   Line Items\n# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n\nline_items:\n  - &line_item-1\n    id: 1\n    title: 'Arbor Draft'\n    subtitle: '151cm'\n    price: 29900\n    line_price: 29900\n    quantity: 1\n    variant: *product-1-var-1\n    product: *product-1\n\n  - &line_item-2\n    id: 2\n    title: 'Comic ~ Orange'\n    subtitle: '159cm'\n    price: 19900\n    line_price: 39800\n    quantity: 2\n    variant: *product-4-var-1\n    product: *product-4\n\n# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n#   Link Lists\n# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\nlinks:\n  - &link-1\n    id: 1\n    title: Our Sale\n    url: /collections/sale\n  - &link-2\n    id: 2\n    title: Arbor Stuff\n    url: /collections/arbor\n  - &link-3\n    id: 3\n    title: All our Snowboards\n    url: /collections/snowboards\n  - &link-4\n    id: 4\n    title: Powered by Shopify\n    url: 'http://shopify.com'\n  - &link-5\n    id: 5\n    title: About Us\n    url: /pages/about-us\n  - &link-6\n    id: 6\n    title: Policies\n    url: /pages/shipping\n  - &link-7\n    id: 7\n    title: Contact Us\n    url: /pages/contact\n  - &link-8\n    id: 8\n    title: Our blog\n    url: /blogs/bigcheese-blog\n  - &link-9\n    id: 9\n    title: New Boots\n    url: /products/burton-boots\n  - &link-10\n    id: 10\n    title: Paginated Sale\n    url: /collections/paginated-sale\n  - &link-11\n    id: 11\n    title: Our Paginated blog\n    url: /blogs/paginated-blog\n  - &link-12\n    id: 12\n    title: Catalog\n    url: /collections/all\n\n\n\n# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n#   Link Lists\n# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n\nlink_lists:\n  - &link-list-1\n    id: 1\n    title: 'Main Menu'\n    handle: 'main-menu'\n    links:\n      - *link-12\n      - *link-5\n      - *link-7\n      - *link-8\n  - &link-list-2\n    id: 1\n    title: 'Footer Menu'\n    handle: 'footer'\n    links:\n      - *link-5\n      - *link-6\n\n# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n#   Collections\n# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n\ncollections:\n  - &collection-1\n    id: 1\n    title: Frontpage\n    handle: frontpage\n    url: /collections/frontpage\n    products:\n      - *product-7\n      - *product-8\n      - *product-9\n\n  - &collection-2\n    id: 2\n    title: Arbor\n    handle: arbor\n    url: /collections/arbor\n    products:\n      - *product-1\n      - *product-2\n\n  - &collection-3\n    id: 3\n    title: Snowboards\n    handle: snowboards\n    url: /collections/snowboards\n    description:\n      <p>This is a description for my <strong>Snowboards</strong> collection.</p>\n    products:\n      - *product-1\n      - *product-2\n      - *product-3\n      - *product-4\n\n  - &collection-4\n    id: 4\n    title: Items On Sale\n    handle: sale\n    url: /collections/sale\n    products:\n      - *product-1\n\n  - &collection-5\n    id: 5\n    title: Paginated Sale\n    handle: 'paginated-sale'\n    url: '/collections/paginated-sale'\n    products:\n      - *product-1\n      - *product-2\n      - *product-3\n      - *product-4\n    products_count: 210\n\n  - &collection-6\n    id: 6\n    title: All products\n    handle: 'all'\n    url: '/collections/all'\n    products:\n      - *product-7\n      - *product-8\n      - *product-9\n      - *product-6\n      - *product-1\n      - *product-2\n      - *product-3\n      - *product-4\n      - *product-5\n\n\n# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n#   Pages and Blogs\n# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\npages:\n  - &page-2\n    id: 1\n    title: Contact Us\n    handle: contact\n    url: /pages/contact\n    author: Tobi\n    content:\n      \"<p>You can contact us via phone under (555) 567-2222.</p>\n      <p>Our retail store is located at <em>Rue d'Avignon 32, Avignon (Provence)</em>.</p>\n      <p><strong>Opening Hours:</strong><br />Monday through Friday: 9am - 6pm<br />Saturday: 10am - 3pm<br />Sunday: closed</p>\"\n    created_at: 2005-04-04 12:00\n\n  - &page-3\n    id: 2\n    title: About Us\n    handle: about-us\n    url: /pages/about-us\n    author: Tobi\n    content:\n      \"<p>Our company was founded in 1894 and we are since operating out of Avignon from the beautiful Provence.</p>\n      <p>We offer the highest quality products and are proud to serve our customers to their heart's content.</p>\"\n    created_at: 2005-04-04 12:00\n\n  - &page-4\n    id: 3\n    title: Shopping Cart\n    handle: shopping-cart\n    url: /pages/shopping-cart\n    author: Tobi\n    content: \"<ul><li>Your order is safe with us. Our checkout uses industry standard security to protect your information.</li><li>Your order will be billed immediately upon checkout.</li><li><b>ALL SALES ARE FINAL:</b> Defective or damaged product will be exchanged</li><li>All orders are processed expediently: usually in under 24 hours.</li></ul>\"\n    created_at: 2005-04-04 12:00\n\n  - &page-5\n    id: 4\n    title: Shipping and Handling\n    handle: shipping\n    url: /pages/shipping\n    author: Tobi\n    content: <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>\n    created_at: 2005-04-04 12:00\n\n  - &page-6\n    id: 5\n    title: Frontpage\n    handle: frontpage\n    url: /pages/frontpage\n    author: Tobi\n    content: <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>\n    created_at: 2005-04-04 12:00\n\nblogs:\n  - id: 1\n    handle: news\n    title: News\n    url: /blogs/news\n    articles:\n      - id: 3\n        title: 'Welcome to the new Foo Shop'\n        author: Daniel\n        content: <p><strong>Welcome to your Shopify store! The jaded Pixel crew is really glad you decided to take Shopify for a spin.</strong></p><p>To help you get you started with Shopify, here are a couple of tips regarding what you see on this page.</p><p>The text you see here is an article. To edit this article, create new articles or create new pages you can go to the <a href=\"/admin/pages\">Blogs &amp; Pages</a> tab of the administration menu.</p><p>The Shopify t-shirt above is a product and selling products is what Shopify is all about. To edit this product, or create new products you can go to the <a href=\"/admin/products\">Products Tab</a> in of the administration menu.</p><p>While you're looking around be sure to check out the <a href=\"/admin/collections\">Collections</a> and <a href=\"/admin/links\">Navigations</a> tabs and soon you will be well on your way to populating your site.</p><p>And of course don't forget to browse the <a href=\"admin/design/appearance/themes\">theme gallery</a> to pick a new look for your shop!</p><p><strong>Shopify is in beta</strong><br />If you would like to make comments or suggestions please visit us in the <a href=\"http://forums.shopify.com/community\">Shopify Forums</a> or drop us an <a href=\"mailto:feedback@shopify.com\">email</a>.</p>\n        created_at: 2005-04-04 16:00\n      - id: 4\n        title: 'Breaking News: Restock on all sales products'\n        author: Tobi\n        content: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n        created_at: 2005-04-04 12:00\n    articles_count: 2\n\n  - id: 2\n    handle: bigcheese-blog\n    title: Bigcheese blog\n    url: /blogs/bigcheese-blog\n    articles:\n      - id: 1\n        title: 'One thing you probably did not know yet...'\n        author: Justin\n        content: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n        created_at: 2005-04-04 16:00\n        comments:\n          -\n            id: 1\n            author: John Smith\n            email: john@smith.com\n            content: Wow...great article man.\n            status: published\n            created_at: 2009-01-01 12:00\n            updated_at: 2009-02-01 12:00\n            url: \"\"\n          -\n            id: 2\n            author: John Jones\n            email: john@jones.com\n            content: I really enjoyed this article. And I love your shop! It's awesome. Shopify rocks!\n            status: published\n            created_at: 2009-03-01 12:00\n            updated_at: 2009-02-01 12:00\n            url: \"http://somesite.com/\"\n      - id: 2\n        title: Fascinating\n        author: Tobi\n        content: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n        created_at: 2005-04-06 12:00\n        comments:\n    articles_count: 2\n    comments_enabled?: true\n    comment_post_url: \"\"\n    comments_count: 2\n    moderated?: true\n\n  - id: 3\n    handle: paginated-blog\n    title: Paginated blog\n    url: /blogs/paginated-blog\n    articles:\n      - id: 6\n        title: 'One thing you probably did not know yet...'\n        author: Justin\n        content: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n        created_at: 2005-04-04 16:00\n\n      - id: 7\n        title: Fascinating\n        author: Tobi\n        content: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n        created_at: 2005-04-06 12:00\n    articles_count: 200\n"
  },
  {
    "path": "performance/shopify/weight_filter.rb",
    "content": "# frozen_string_literal: true\n\nmodule WeightFilter\n  def weight(grams)\n    format(\"%.2f\", grams / 1000)\n  end\n\n  def weight_with_unit(grams)\n    \"#{weight(grams)} kg\"\n  end\nend\n"
  },
  {
    "path": "performance/tests/dropify/article.liquid",
    "content": "<div class=\"article\">\n  <h2 class=\"article-title\">{{ article.title }}</h2>\n  <p class=\"article-details\">posted <span class=\"article-time\">{{ article.created_at | date: \"%Y %h\" }}</span> by <span class=\"article-author\">{{ article.author }}</span></p>\n\n  <div class=\"article-body textile\">\n    {{ article.content }}\n  </div>\n\n</div>\n\n<!-- Comments -->\n{% if blog.comments_enabled? %}\n<div id=\"comments\">\n  <h3>Comments</h3>\n\n  <!-- List all comments -->\n  <ul id=\"comment-list\">\n  {% for comment in article.comments %}\n    <li>\n      <div class=\"comment-details\">\n        <span class=\"comment-author\">{{ comment.author }}</span> said on <span class=\"comment-date\">{{ comment.created_at | date: \"%B %d, %Y\" }}</span>:\n      </div>\n\n      <div class=\"comment\">\n        {{ comment.content }}\n      </div>\n    </li>\n  {% endfor %}\n  </ul>\n\n  <!-- Comment Form -->\n  <div id=\"comment-form\">\n  {% form article %}\n    <h3>Leave a comment</h3>\n\n    <!-- Check if a comment has been submitted in the last request, and if yes display an appropriate message -->\n    {% if form.posted_successfully? %}\n      {% if blog.moderated? %}\n        <div class=\"notice\">\n          Successfully posted your comment.<br />\n          It will have to be approved by the blog owner first before showing up.\n        </div>\n      {% else %}\n        <div class=\"notice\">Successfully posted your comment.</div>\n      {% endif %}\n    {% endif %}\n\n    {% if form.errors %}\n      <div class=\"notice error\">Not all the fields have been filled out correctly!</div>\n    {% endif %}\n\n    <dl>\n      <dt class=\"{% if form.errors contains 'author' %}error{% endif %}\"><label for=\"comment_author\">Your name</label></dt>\n      <dd><input type=\"text\" id=\"comment_author\" name=\"comment[author]\" size=\"40\" value=\"{{form.author}}\" class=\"{% if form.errors contains 'author' %}input-error{% endif %}\" /></dd>\n\n      <dt class=\"{% if form.errors contains 'email' %}error{% endif %}\"><label for=\"comment_email\">Your email</label></dt>\n      <dd><input type=\"text\" id=\"comment_email\" name=\"comment[email]\" size=\"40\" value=\"{{form.email}}\" class=\"{% if form.errors contains 'email' %}input-error{% endif %}\" /></dd>\n\n      <dt class=\"{% if form.errors contains 'body' %}error{% endif %}\"><label for=\"comment_body\">Your comment</label></dt>\n      <dd><textarea id=\"comment_body\" name=\"comment[body]\" cols=\"40\" rows=\"5\" class=\"{% if form.errors contains 'body' %}input-error{% endif %}\">{{form.body}}</textarea></dd>\n    </dl>\n\n    {% if blog.moderated? %}\n      <p class=\"hint\">comments have to be approved before showing up</p>\n    {% endif %}\n\n    <input type=\"submit\" value=\"Post comment\" id=\"comment-submit\" />\n  {% endform %}\n  </div>\n  <!-- END Comment Form -->\n\n</div>\n{% endif %}\n<!-- END Comments -->\n"
  },
  {
    "path": "performance/tests/dropify/blog.liquid",
    "content": "<div id=\"page\">\n  <h2>{{page.title}}</h2>\n\n  {% paginate blog.articles by 20 %}\n\n    {% for article in blog.articles  %}\n\n    <div class=\"article\">\n      <div class=\"headline\">\n      <h3 class=\"title\">\n        <a href=\"{{article.url}}\">{{ article.title }}</a>\n      </h3>\n      <h4 class=\"date\">Posted on {{ article.created_at | date: \"%B %d, '%y\" }} by {{ article.author }}.</h4>\n      </div>\n\n      <div class=\"article-body textile\">\n        {{ article.content | strip_html | truncate: 250 }}\n      </div>\n\n      {% if blog.comments_enabled? %}\n        <p style=\"text-align: right\"><a href=\"{{article.url}}#comments\">{{ article.comments_count }} comments</a></p>\n      {% endif %}\n    </div>\n\n    {% endfor %}\n\n    <div id=\"pagination\">\n      {{ paginate | default_pagination }}\n    </div>\n\n  {% endpaginate %}\n\n</div>\n"
  },
  {
    "path": "performance/tests/dropify/cart.liquid",
    "content": "<script type=\"text/javascript\">\n  function remove_item(id) {\n      document.getElementById('updates_'+id).value = 0;\n      document.getElementById('cartform').submit();\n  }\n</script>\n\n<div>\n\n  {% if cart.item_count == 0 %}\n    <h4>Your shopping cart is looking rather empty...</h4>\n  {% else %}\n  <form action=\"/cart\" method=\"post\" id=\"cartform\">\n\n  <div id=\"cart\">\n\n  <h3>You have {{ cart.item_count }} {{ cart.item_count | pluralize: 'product', 'products' }} in here!</h3>\n\n    <ul id=\"line-items\">\n      {% for item in cart.items %}\n      <li id=\"item-{{item.id}}\" class=\"clearfix\">\n        <div class=\"thumb\">\n          <div class=\"prodimage\">\n          <a href=\"{{item.product.url}}\" title=\"View {{item.title}} Page\"><img src=\"{{item.product.featured_image | product_img_url: 'thumb' }}\" alt=\"{{item.title | escape }}\" /></a>\n        </div></div>\n        <h3 style=\"padding-right: 150px\">\n      <a href=\"{{item.product.url}}\" title=\"View {{item.title | escape }} Page\">\n        {{ item.title }}\n        {% if item.variant.available == true %}\n           ({{item.variant.title}})\n        {% endif %}\n      </a>\n    </h3>\n        <small class=\"itemcost\">Costs {{ item.price | money }} each, <span class=\"money\">{{item.line_price | money }}</span> total.</small>\n        <p class=\"right\">\n          <label for=\"updates\">How many? </label>\n          <input type=\"text\" size=\"4\" name=\"updates[{{item.variant.id}}]\" id=\"updates_{{item.variant.id}}\" value=\"{{item.quantity}}\" onfocus=\"this.select();\"/><br />\n          <a href=\"#\" onclick=\"remove_item({{item.variant.id}}); return false;\" class=\"remove\"><img style=\"padding:15px 0 0 0;margin:0;\" src=\"{{ 'delete.gif' | asset_url }}\" /></a>\n        </p>\n      </li>\n      {% endfor %}\n      <li id=\"total\">\n        <input type=\"image\" id=\"update-cart\" name=\"update\" value=\"Update My Cart\" src=\"{{ 'update.gif' | asset_url }}\" />\n        Subtotal:\n        <span class=\"money\">{{ cart.total_price | money_with_currency }}</span>\n      </li>\n    </ul>\n\n  </div>\n\n    <div class=\"info\">\n    <input type=\"image\" value=\"Checkout!\" name=\"checkout\" src=\"{{ 'checkout.gif' | asset_url }}\" />\n    </div>\n\n    {% if additional_checkout_buttons %}\n    <div class=\"additional-checkout-buttons\">\n      <p>- or -</p>\n      {{ content_for_additional_checkout_buttons }}\n    </div>\n    {% endif %}\n\n  </form>\n\n  {% endif %}\n\n</div>\n"
  },
  {
    "path": "performance/tests/dropify/collection.liquid",
    "content": "{% paginate collection.products by 20 %}\n\n<ul id=\"product-collection\">\n    {% for product in collection.products %}\n    <li class=\"singleproduct clearfix\">\n      <div class=\"small\">\n        <div class=\"prodimage\"><a href=\"{{product.url}}\"><img src=\"{{ product.featured_image | product_img_url: 'small' }}\" /></a></div>\n      </div>\n      <div class=\"description\">\n        <h3><a href=\"{{product.url}}\">{{product.title}}</a></h3>\n        <p>{{ product.description | strip_html | truncatewords: 35 }}</p>\n      <p class=\"money\">{{ product.price_min | money }}{% if product.price_varies %} - {{ product.price_max | money }}{% endif %}</p>\n     </div>\n    </li>\n    {% endfor %}\n</ul>\n\n<div id=\"pagination\">\n  {{ paginate | default_pagination }}\n</div>\n\n{% endpaginate %}\n"
  },
  {
    "path": "performance/tests/dropify/index.liquid",
    "content": "<div id=\"frontproducts\"><div id=\"frontproducts-top\"><div id=\"frontproducts-bottom\">\n<h2 style=\"display: none;\">Featured Items</h2>\n{% for product in collections.frontpage.products limit:1 offset:0 %}\n  <div class=\"productmain\">\n   <a href=\"{{ product.url }}\"><img src=\"{{ product.featured_image | product_img_url: 'small' }}\" alt=\"{{ product.title | escape }}\" /></a>\n   <h3><a href=\"{{ product.url }}\">{{ product.title }}</a></h3>\n   <div class=\"description\">{{ product.description | strip_html | truncatewords: 18 }}</div>\n  <p class=\"money\">{{ product.price_min | money }}</p>\n  </div>\n{% endfor %}\n{% for product in collections.frontpage.products offset:1 %}\n  <div class=\"product\">\n   <a href=\"{{ product.url }}\"><img src=\"{{ product.featured_image | product_img_url: 'thumb' }}\" alt=\"{{ product.title | escape }}\" /></a>\n   <h3><a href=\"{{ product.url }}\">{{ product.title }}</a></h3>\n     <p class=\"money\">{{ product.price_min | money }}</p>\n  </div>\n{% endfor %}\n</div></div></div>\n\n<div id=\"mainarticle\">\n  {% assign article = pages.frontpage %}\n\n  {% if article.content != \"\" %}\n    <h2>{{ article.title }}</h2>\n    <div class=\"article-body textile\">\n      {{ article.content }}\n    </div>\n  {% else %}\n    <div class=\"article-body textile\">\n    In <em>Admin &gt; Blogs &amp; Pages</em>, create a page with the handle <strong><code>frontpage</code></strong> and it will show up here.<br />\n    {{ \"Learn more about handles\" | link_to: \"http://wiki.shopify.com/Handle\" }}\n    </div>\n  {% endif %}\n\n</div>\n<br style=\"clear: both;\" />\n<div id=\"articles\">\n  {% for article in blogs.news.articles offset:1  %}\n    <div class=\"article\">\n    <h2>{{ article.title }}</h2>\n    <div class=\"article-body textile\">\n      {{ article.content }}\n    </div>\n  </div>\n  {% endfor %}\n</div>\n\n"
  },
  {
    "path": "performance/tests/dropify/page.liquid",
    "content": "<div id=\"page\">\n  <h2>{{page.title}}</h2>\n\n  <div class=\"article textile\">\n    {{page.content}}\n  </div>\n\n</div>\n"
  },
  {
    "path": "performance/tests/dropify/product.liquid",
    "content": "<div id=\"productpage\">\n\n  <div id=\"productimages\"><div id=\"productimages-top\"><div id=\"productimages-bottom\">\n    {% for image in product.images %}\n      {% if forloop.first %}\n        <a href=\"{{ image | product_img_url: 'large' }}\" class=\"productimage\" rel=\"lightbox\">\n          <img src=\"{{ image | product_img_url: 'medium'}}\" alt=\"{{product.title | escape }}\" />\n        </a>\n      {% else %}\n        <a href=\"{{ image | product_img_url: 'large' }}\" class=\"productimage-small\" rel=\"lightbox\">\n          <img src=\"{{ image | product_img_url: 'small'}}\" alt=\"{{product.title | escape }}\" />\n        </a>\n      {% endif %}\n    {% endfor %}\n  </div></div></div>\n\n  <h2>{{ product.title }}</h2>\n\n  <ul id=\"details\" class=\"hlist\">\n    <li>Vendor: {{ product.vendor | link_to_vendor }}</li>\n    <li>Type: {{ product.type | link_to_type }}</li>\n  </ul>\n\n  <small>{{ product.price_min | money }}{% if product.price_varies %} - {{ product.price_max | money }}{% endif %}</small>\n\n  <div id=\"variant-add\">\n    <form action=\"/cart/add\" method=\"post\">\n\n      <select id=\"variant-select\" name=\"id\" class=\"product-info-options\">\n        {% for variant in product.variants %}\n          <option value=\"{{ variant.id }}\">{{ variant.title }} - {{ variant.price | money }}</option>\n        {% endfor %}\n      </select>\n\n      <div id=\"price-field\" class=\"price\"></div>\n\n    <div style=\"text-align:center;\"><input type=\"image\" name=\"add\" value=\"Add to Cart\" id=\"add\" src=\"{{ 'addtocart.gif' | asset_url }}\" /></div>\n    </form>\n  </div>\n\n  <div class=\"description textile\">\n    {{ product.description }}\n  </div>\n</div>\n\n<script type=\"text/javascript\">\n<!--\n  // prototype callback for multi variants dropdown selector\n  var selectCallback = function(variant, selector) {\n    if (variant && variant.available == true) {\n      // selected a valid variant\n      $('add').removeClassName('disabled'); // remove unavailable class from add-to-cart button\n      $('add').disabled = false;           // reenable add-to-cart button\n      $('price-field').innerHTML = Shopify.formatMoney(variant.price, \"{{shop.money_with_currency_format}}\");  // update price field\n    } else {\n      // variant doesn't exist\n      $('add').addClassName('disabled');      // set add-to-cart button to unavailable class\n      $('add').disabled = true;              // disable add-to-cart button\n      $('price-field').innerHTML = (variant) ? \"Sold Out\" : \"Unavailable\"; // update price-field message\n    }\n  };\n\n  // initialize multi selector for product\n  Event.observe(document, 'dom:loaded', function() {\n    new Shopify.OptionSelectors(\"variant-select\", { product: {{ product | json }}, onVariantSelected: selectCallback });\n  });\n-->\n</script>\n"
  },
  {
    "path": "performance/tests/dropify/theme.liquid",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n  \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n<head>\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n  <title>{{shop.name}} - {{page_title}}</title>\n\n  {{ 'textile.css'  | global_asset_url | stylesheet_tag }}\n  {{ 'lightbox/v204/lightbox.css' | global_asset_url | stylesheet_tag }}\n\n  {{ 'prototype/1.6/prototype.js' | global_asset_url  | script_tag }}\n  {{ 'scriptaculous/1.8.2/scriptaculous.js' | global_asset_url  | script_tag }}\n  {{ 'lightbox/v204/lightbox.js'  | global_asset_url  | script_tag }}\n  {{ 'option_selection.js'        | shopify_asset_url | script_tag }}\n\n  {{ 'layout.css'   | asset_url | stylesheet_tag }}\n  {{ 'shop.js'      | asset_url | script_tag }}\n\n  {{ content_for_header }}\n</head>\n\n<body id=\"page-{{template}}\">\n\n  <p class=\"hide\"><a href=\"#rightsiders\">Skip to navigation.</a></p>\n    <!-- mini cart -->\n        {% if cart.item_count > 0 %}\n      <div id=\"minicart\" style=\"display:none;\"><div id=\"minicart-inner\">\n      <div id=\"minicart-items\">\n      <h2>There {{ cart.item_count | pluralize: 'is', 'are' }} {{ cart.item_count }} {{ cart.item_count | pluralize: 'item', 'items' }} in <a href=\"/cart\" title=\"View your cart\">your cart</a>!</h2><h4 style=\"font-size: 16px; margin: 0 0 10px 0; padding: 0;\">Your subtotal is {{ cart.total_price | money }}.</h4>\n        {% for item in cart.items %}\n        <div class=\"thumb\">\n          <div class=\"prodimage\"><a href=\"{{item.product.url}}\" onMouseover=\"tooltip('{{ item.quantity }} x {{ item.title }} ({{ item.variant.title }})', 200)\"; onMouseout=\"hidetooltip()\"><img src=\"{{ item.product.featured_image | product_img_url: 'thumb' }}\" /></a></div>\n        </div>\n        {% endfor %}\n        </div>\n       <br style=\"clear:both;\" />\n      </div></div>\n        {% endif %}\n\n  <div id=\"container\">\n    <div id=\"header\">\n      <!-- Begin Header -->\n        <h1 id=\"logo\"><a href=\"/\" title=\"Go Home\">{{shop.name}}</a></h1>\n      <div id=\"cartlinks\">\n        {% if cart.item_count > 0 %}\n          <h2 id=\"cartcount\"><a href=\"/cart\" onMouseover=\"tooltip('There {{ cart.item_count | pluralize: 'is', 'are' }} {{ cart.item_count }} {{ cart.item_count | pluralize: 'item', 'items' }} in your cart!', 200)\"; onMouseout=\"hidetooltip()\">{{ cart.item_count }} {{ cart.item_count | pluralize: 'thing', 'things' }}!</a></h2>\n      <a href=\"/cart\" id=\"minicartswitch\" onclick=\"superSwitch(this, 'minicart', 'Close Mini Cart'); return false;\" id=\"cartswitch\">View Mini Cart ({{ cart.total_price | money }})</a>\n        {% endif %}\n      </div>\n      <!-- End Header -->\n\n    </div>\n  <hr />\n<div id=\"main\">\n\n    <div id=\"content\">\n    <div id=\"innercontent\">\n      {{ content_for_layout }}\n      </div>\n    </div>\n\n  <hr />\n    <div id=\"rightsiders\">\n\n      <ul class=\"rightlinks\">\n        {% for link in linklists.main-menu.links %}\n           <li>{{ link.title | link_to: link.url }}</li>\n        {% endfor %}\n      </ul>\n\n        {% if tags %}\n        <ul class=\"rightlinks\">\n          {% for tag in collection.tags %}\n            <li><span class=\"add-link\">{{ '+' | link_to_add_tag: tag }}</span>{{ tag | highlight_active_tag | link_to_tag: tag }}</li>\n          {% endfor %}\n        </ul>\n        {% endif %}\n\n      <ul class=\"rightlinks\">\n        {% for link in linklists.footer.links %}\n           <li>{{ link.title | link_to: link.url }}</li>\n        {% endfor %}\n      </ul>\n\n    </div>\n\n  <hr /><br style=\"clear:both;\" />\n\n    <div id=\"footer\">\n      <div class=\"footerinner\">\n      All prices are in {{ shop.currency }}.\n      Powered by <a href=\"http://www.shopify.com\" title=\"Shopify, Hosted E-Commerce\">Shopify</a>.\n    </div>\n    </div>\n\n  </div>\n</div>\n\n<div id=\"tooltip\"></div>\n<img id=\"pointer\" src=\"{{ 'arrow2.gif' | asset_url }}\" />\n\n</body>\n</html>\n\n"
  },
  {
    "path": "performance/tests/ripen/article.liquid",
    "content": "<div class=\"article\">\n  <h2 class=\"article-title\">{{ article.title }}</h2>\n  <p class=\"article-details\">posted <span class=\"article-time\">{{ article.created_at | date: \"%Y %h\" }}</span> by <span class=\"article-author\">{{ article.author }}</span></p>\n\n  <div class=\"article-body textile\">\n    {{ article.content }}\n  </div>\n\n</div>\n\n<!-- Comments -->\n{% if blog.comments_enabled? %}\n<div id=\"comments\">\n  <h3>Comments</h3>\n\n  <!-- List all comments -->\n  <ul id=\"comment-list\">\n  {% for comment in article.comments %}\n    <li>\n      <div class=\"comment\">\n        {{ comment.content }}\n      </div>\n\n      <div class=\"comment-details\">\n        Posted by {{ comment.author }} on {{ comment.created_at | date: \"%B %d, %Y\" }}\n      </div>\n    </li>\n  {% endfor %}\n  </ul>\n\n  <!-- Comment Form -->\n  <div id=\"comment-form\">\n  {% form article %}\n    <h3>Leave a comment</h3>\n\n    <!-- Check if a comment has been submitted in the last request, and if yes display an appropriate message -->\n    {% if form.posted_successfully? %}\n      {% if blog.moderated? %}\n        <div class=\"notice\">\n          Successfully posted your comment.<br />\n          It will have to be approved by the blog owner first before showing up.\n        </div>\n      {% else %}\n        <div class=\"notice\">Successfully posted your comment.</div>\n      {% endif %}\n    {% endif %}\n\n    {% if form.errors %}\n      <div class=\"notice error\">Not all the fields have been filled out correctly!</div>\n    {% endif %}\n\n    <dl>\n      <dt class=\"{% if form.errors contains 'author' %}error{% endif %}\"><label for=\"comment_author\">Your name</label></dt>\n      <dd><input type=\"text\" id=\"comment_author\" name=\"comment[author]\" size=\"40\" value=\"{{form.author}}\" class=\"{% if form.errors contains 'author' %}input-error{% endif %}\" /></dd>\n\n      <dt class=\"{% if form.errors contains 'email' %}error{% endif %}\"><label for=\"comment_email\">Your email</label></dt>\n      <dd><input type=\"text\" id=\"comment_email\" name=\"comment[email]\" size=\"40\" value=\"{{form.email}}\" class=\"{% if form.errors contains 'email' %}input-error{% endif %}\" /></dd>\n\n      <dt class=\"{% if form.errors contains 'body' %}error{% endif %}\"><label for=\"comment_body\">Your comment</label></dt>\n      <dd><textarea id=\"comment_body\" name=\"comment[body]\" cols=\"40\" rows=\"5\" class=\"{% if form.errors contains 'body' %}input-error{% endif %}\">{{form.body}}</textarea></dd>\n    </dl>\n\n    {% if blog.moderated? %}\n      <p class=\"hint\">comments have to be approved before showing up</p>\n    {% endif %}\n\n    <input type=\"submit\" value=\"Post comment\" id=\"comment-submit\" />\n  {% endform %}\n  </div>\n  <!-- END Comment Form -->\n\n</div>\n{% endif %}\n<!-- END Comments -->\n"
  },
  {
    "path": "performance/tests/ripen/blog.liquid",
    "content": "<div id=\"blog-page\">\n  <h2 class=\"heading-shaded\">{{page.title}}</h2>\n   {% for article in blog.articles  %}\n      <h4>\n        {{ article.created_at | date: '%d %b' }}\n        <a href=\"{{article.url}}\">{{ article.title }}</a>\n      </h4>\n      {{ article.content }}\n      {% if blog.comments_enabled? %}\n        <p><a href=\"{{article.url}}#comments\">{{ article.comments_count }} comments</a></p>\n      {% endif %}\n  {% endfor %}\n</div>\n"
  },
  {
    "path": "performance/tests/ripen/cart.liquid",
    "content": "<script type=\"text/javascript\">\n  function remove_item(id) {\n      document.getElementById('updates_'+id).value = 0;\n      document.getElementById('cart').submit();\n  }\n</script>\n\n<div id=\"cart-page\">\n\n  {% if cart.item_count == 0 %}\n    <p>Your shopping cart is empty...</p>\n  <p><a href=\"/\"><img src=\"{{ 'continue_shopping_icon.gif' | asset_url }}\" alt=\"Continue shopping\"/></a><p>\n  {% else %}\n\n  <form action=\"/cart\" method=\"post\" id=\"cart\">\n\n  <table class=\"cart\">\n      <tr>\n        <th colspan=\"2\">Product</th>\n        <th class=\"short\">Qty</th>\n        <th>Price</th>\n        <th>Total</th>\n        <th class=\"short\">Remove</th>\n      </tr>\n\n      {% for item in cart.items %}\n      <tr class=\"{% cycle 'odd', 'even' %}\">\n        <td class=\"short\">{{ item.product.featured_image |  product_img_url: 'thumb' | img_tag }}</td>\n    <td><a href=\"{{item.product.url}}\">{{ item.title }}</a></td>\n        <td class=\"short\"><input type=\"text\" class=\"quantity\" name=\"updates[{{item.variant.id}}]\" id=\"updates_{{item.variant.id}}\" value=\"{{item.quantity}}\" onfocus=\"this.select();\"/></td>\n        <td class=\"cart-price\">{{ item.price | money }}</td>\n        <td class=\"cart-price\">{{item.line_price | money }}</td>\n        <td class=\"short\"><a href=\"#\" onclick=\"remove_item({{item.variant.id}}); return false;\" class=\"remove\"><img src=\"{{ 'cancel_icon.gif' | asset_url }}\" alt=\"Remove\" /></a></td>\n      </tr>\n      {% endfor %}\n    </table>\n    <p class=\"updatebtn\"><input type=\"image\" value=\"Update Cart\" name=\"update\" src=\"{{ 'update_icon.gif' | asset_url }}\" alt=\"Update\" /></p>\n    <p class=\"subtotal\">\n    <strong>Subtotal:</strong> {{cart.total_price | money_with_currency }}\n    </p>\n    <p class=\"checkout\"><input type=\"image\" src=\"{{ 'checkout_icon.gif' | asset_url }}\" alt=\"Proceed to Checkout\" value=\"Proceed to Checkout\" name=\"checkout\"  /></p>\n\n    {% if additional_checkout_buttons %}\n    <div class=\"additional-checkout-buttons\">\n      <p>- or -</p>\n      {{ content_for_additional_checkout_buttons }}\n    </div>\n    {% endif %}\n\n  </form>\n\n  {% endif %}\n\n</div>\n"
  },
  {
    "path": "performance/tests/ripen/collection.liquid",
    "content": "<div id=\"collection-page\">\n\n{% if collection.description %}\n  <div id=\"collection-description\" class=\"textile\">{{ collection.description }}</div>\n{% endif %}\n\n{% paginate collection.products by 20 %}\n\n<ul id=\"product-collection\">\n    {% for product in collection.products %}\n    <li class=\"single-product clearfix\">\n      <div class=\"small\">\n        <div class=\"prod-image\"><a href=\"{{product.url}}\"><img src=\"{{ product.featured_image | product_img_url: 'small' }}\" alt=\"{{ product.title | escape }}\" /></a></div>\n      </div>\n      <div class=\"prod-list-description\">\n        <h3><a href=\"{{product.url}}\">{{product.title}}</a></h3>\n        <p>{{ product.description | strip_html | truncatewords: 35 }}</p>\n      <p class=\"prd-price\">{{ product.price_min | money }}{% if product.price_varies %} - {{ product.price_max | money }}{% endif %}</p>\n     </div>\n    </li>\n    {% endfor %}\n</ul>\n\n<div id=\"pagination\">\n  {{ paginate | default_pagination }}\n</div>\n\n{% endpaginate %}\n</div>\n"
  },
  {
    "path": "performance/tests/ripen/index.liquid",
    "content": "<div id=\"home-page\">\n  <h3 class=\"heading-shaded\">Featured products...</h3>\n  <div class=\"featured-prod-row clearfix\">\n    {% for product in collections.frontpage.products %}\n      <div class=\"featured-prod-item\">\n          <p>\n        <a href=\"{{product.url}}\"><img src=\"{{ product.featured_image | product_img_url: 'small' }}\" alt=\"{{ product.title | escape }}\"/></a>\n        </p>\n        <h4><a href=\"{{product.url}}\">{{product.title}}</a></h4>\n        {% if product.compare_at_price %}\n          {% if product.price_min != product.compare_at_price %}\n            <p class=\"prd-price\">Was:<del>{{product.compare_at_price | money}}</del></p>\n            <p class=\"prd-price\"><ins>Now: {{product.price_min | money}}</ins></p>\n          {% endif %}\n        {% else %}\n          <p class=\"prd-price\"><ins>{{product.price_min | money}}</ins></p>\n        {% endif %}\n      </div>\n    {% endfor %}\n  </div>\n\n  <div id=\"articles\">\n    {% assign article = pages.frontpage %}\n    {% if article.content != \"\" %}\n      <h3>{{ article.title }}</h3>\n      {{ article.content }}\n    {% else %}\n      In <em>Admin &gt; Blogs &amp; Pages</em>, create a page with the handle <strong><code>frontpage</code></strong> and it will show up here.<br />\n      {{ \"Learn more about handles\" | link_to: \"http://wiki.shopify.com/Handle\" }}\n    {% endif %}\n  </div>\n</div>\n"
  },
  {
    "path": "performance/tests/ripen/page.liquid",
    "content": "<div id=\"single-page\">\n<h2 class=\"heading-shaded\">{{page.title}}</h2>\n  {{ page.content }}\n</div>\n"
  },
  {
    "path": "performance/tests/ripen/product.liquid",
    "content": "<div id=\"product-page\">\n  <h2 class=\"heading-shaded\">{{ product.title }}</h2>\n  <div id=\"product-details\">\n  <div id=\"product-images\">\n    {% for image in product.images %}\n      {% if forloop.first %}\n        <a href=\"{{ image | product_img_url: 'large' }}\" class=\"product-image\" rel=\"lightbox[ product]\" title=\"\">\n          <img src=\"{{ image | product_img_url: 'medium'}}\" alt=\"{{product.title | escape }}\" />\n        </a>\n      {% else %}\n        <a href=\"{{ image | product_img_url: 'large' }}\" class=\"product-image-small\" rel=\"lightbox[ product]\" title=\"\">\n          <img src=\"{{ image | product_img_url: 'small'}}\" alt=\"{{product.title | escape }}\" />\n        </a>\n      {% endif %}\n    {% endfor %}\n  </div>\n\n  <ul id=\"product-info\">\n    <li>Vendor: {{ product.vendor | link_to_vendor }}</li>\n    <li>Type: {{ product.type | link_to_type }}</li>\n    </ul>\n\n    <small>{{ product.price_min | money }}{% if product.price_varies %} - {{ product.price_max | money }}{% endif %}</small>\n\n    <div id=\"product-options\">\n              {% if product.available %}\n\n    <form action=\"/cart/add\" method=\"post\">\n\n      <select id=\"product-select\" name='id'>\n        {% for variant in product.variants %}\n          <option value=\"{{ variant.id }}\">{{ variant.title }} - {{ variant.price | money }}</option>\n        {% endfor %}\n      </select>\n\n      <div id=\"price-field\"></div>\n\n      <div class=\"add-to-cart\"><input type=\"image\" name=\"add\" value=\"Add to Cart\" id=\"add\" src=\"{{ 'add-to-cart.gif' | asset_url }}\" /></div>\n    </form>\n              {% else %}\n                  <span>Sold Out!</span>\n              {% endif %}\n    </div>\n\n    <div class=\"product-description\">\n    {{ product.description }}\n    </div>\n  </div>\n</div>\n\n\n<script type=\"text/javascript\">\n<!--\n  // mootools callback for multi variants dropdown selector\n  var selectCallback = function(variant, selector) {\n    if (variant && variant.available == true) {\n      // selected a valid variant\n      $('add').removeClass('disabled'); // remove unavailable class from add-to-cart button\n      $('add').disabled = false;           // reenable add-to-cart button\n      $('price-field').innerHTML = Shopify.formatMoney(variant.price, \"{{shop.money_with_currency_format}}\");  // update price field\n    } else {\n      // variant doesn't exist\n      $('add').addClass('disabled');      // set add-to-cart button to unavailable class\n      $('add').disabled = true;              // disable add-to-cart button\n      $('price-field').innerHTML = (variant) ? \"Sold Out\" : \"Unavailable\"; // update price-field message\n    }\n  };\n\n  // initialize multi selector for product\n  window.addEvent('domready', function() {\n    new Shopify.OptionSelectors(\"product-select\", { product: {{ product | json }}, onVariantSelected: selectCallback });\n  });\n-->\n</script>\n\n"
  },
  {
    "path": "performance/tests/ripen/theme.liquid",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\">\n<head>\n  <title>{{shop.name}} - {{page_title}}</title>\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n\n  {{ 'main.css'     | asset_url | stylesheet_tag }}\n  {{ 'shop.js'      | asset_url | script_tag }}\n\n  {{ 'mootools.js'         | asset_url         | script_tag }}\n  {{ 'slimbox.js'          | asset_url         | script_tag }}\n  {{ 'option_selection.js' | shopify_asset_url | script_tag }}\n  {{ 'slimbox.css'         | asset_url         | stylesheet_tag }}\n\n  {{ content_for_header }}\n </head>\n\n<body id=\"page-{{template}}\">\n<p class=\"hide\"><a href=\"#navigation\">Skip to navigation.</a></p>\n<div id=\"wrapper\">\n  <div class=\"content clearfix\">\n    <div id=\"header\">\n      <h2><a href=\"/\">{{shop.name}}</a></h2>\n    </div>\n    <div id=\"left-col\">\n      {{ content_for_layout }}\n    </div>\n    <div id=\"right-col\">\n      {% if template != 'cart' %}\n          <div id=\"cart-right-col\">\n          <dl id=\"cart-right-col-info\">\n            <dt>Shopping Cart</dt>\n            <dd>\n            {% if cart.item_count != 0 %}\n              <a href=\"/cart\">{{ cart.item_count }} {{ cart.item_count | pluralize: 'item', 'items' }}</a> in your cart\n            {% else %}\n              Your cart is empty\n            {% endif %}\n            </dd>\n          </dl>\n        </div>\n      {% endif %}\n      <div id=\"search\">\n        <dl id=\"searchbox\">\n        <dt>Search</dt>\n        <dd>\n        <form action=\"/search\" method=\"get\">\n        <fieldset>\n        <input class=\"search-input\" type=\"text\" onclick=\"this.select()\" value=\"Search this shop...\" name=\"q\" />\n        </fieldset>\n        </form>\n        </dd>\n        </dl>\n      </div>\n      <div id=\"navigation\">\n        <dl class=\"navbar\">\n        <dt>Navigation</dt>\n        {% for link in linklists.main-menu.links %}\n          <dd>{{ link.title | link_to: link.url }}</dd>\n        {% endfor %}\n        </dl>\n\n        {% if tags %}\n        <dl class=\"navbar\">\n        <dt>Tags</dt>\n          {% for tag in collection.tags %}\n          <dd>{{ tag | highlight_active_tag | link_to_tag: tag }}</dd>\n          {% endfor %}\n          </dl>\n        {% endif %}\n      </div>\n    </div>\n\n  </div>\n    <div id=\"content-padding\"></div>\n</div>\n\n<div id=\"footer\">\n  {% for link in linklists.footer.links %}\n  {{ link.title | link_to: link.url }} {% if forloop.rindex != 1 %} | {% endif %}\n  {% endfor %}\n</div>\n\n</body>\n</html>\n"
  },
  {
    "path": "performance/tests/tribble/404.liquid",
    "content": "  <div id=\"page\" class=\"innerpage clearfix\">\n\n    <div id=\"text-page\">\n      <div class=\"entry\">\n        <h1>Oh no!</h1>\n        <div class=\"entry-post\">\n          Seems like you are looking for something that just isn't here. <a href=\"/\">Try heading back to our main page</a>. Or you can checkout some of our featured products below.\n        </div>\n      </div>\n    </div>\n\n\n    <h1>Featured Products</h1>\n    <ul class=\"item-list clearfix\">\n\n      {% for product in collections.frontpage.products %}\n      <li>\n        <form action=\"/cart/add\" method=\"post\">\n        <div class=\"item-list-item\">\n          <div class=\"ili-top clearfix\">\n            <div class=\"ili-top-content\">\n              <h2><a href=\"{{product.url}}\">{{product.title}}</a></h2>\n              <p>{{ product.description | truncatewords: 15 }}</p>\n            </div>\n            <a href=\"{{product.url}}\" class=\"ili-top-image\"><img src=\"{{ product.featured_image | product_img_url: 'small' }}\" alt=\"{{ product.title | escape }}\"/></a>\n          </div>\n\n          <div class=\"ili-bottom clearfix\">\n            <p class=\"hiddenvariants\" style=\"display: none\">{% for variant in product.variants %}<span><input type=\"radio\" name=\"id\" value=\"{{variant.id}}\" id=\"radio_{{variant.id}}\" style=\"vertical-align: middle;\" {%if forloop.first%} checked=\"checked\" {%endif%} /><label for=\"radio_{{variant.id}}\">{{ variant.price | money_with_currency }} - {{ variant.title }}</label></span>{% endfor %}</p>\n            <input type=\"submit\" class=\"\" value=\"Add to Basket\" />\n            <p>\n              <a href=\"{{product.url}}\">View Details</a>\n\n              <span>\n                {% if product.compare_at_price %}\n                  {% if product.price_min != product.compare_at_price %}\n                    {{product.compare_at_price | money}} -\n                    {% endif %}\n                {% endif %}\n                <strong>\n                  {{product.price_min | money}}\n                </strong>\n              </span>\n            </p>\n          </div>\n        </div>\n        </form>\n      </li>\n      {% endfor %}\n\n    </ul>\n  </div>\n  <!-- end page -->\n\n\n\n"
  },
  {
    "path": "performance/tests/tribble/article.liquid",
    "content": "\n  <div id=\"page\" class=\"innerpage clearfix\">\n    <div id=\"text-page\">\n\n          <div class=\"entry\">\n            <h1><span>{{article.title}}</span></h1>\n            <div class=\"entry-post\">\n              <div class=\"meta\">{{ article.created_at | date: \"%b %d\" }}</div>\n              {{ article.content }}\n            </div>\n\n  <!-- Comments -->\n{% if blog.comments_enabled? %}\n<div id=\"comments\">\n  <h2>Comments</h2>\n\n  <!-- List all comments -->\n  <ul id=\"comment-list\">\n  {% for comment in article.comments %}\n    <li>\n      <div class=\"comment\">\n        {{ comment.content }}\n      </div>\n\n      <div class=\"comment-details\">\n        Posted by <span class=\"comment-author\">{{ comment.author }}</span> on <span class=\"comment-date\">{{ comment.created_at | date: \"%B %d, %Y\" }}</span>\n      </div>\n    </li>\n  {% endfor %}\n  </ul>\n\n  <!-- Comment Form -->\n  <div id=\"comment-form\">\n  {% form article %}\n    <h2>Leave a comment</h2>\n\n    <!-- Check if a comment has been submitted in the last request, and if yes display an appropriate message -->\n    {% if form.posted_successfully? %}\n      {% if blog.moderated? %}\n        <div class=\"notice\">\n          Successfully posted your comment.<br />\n          It will have to be approved by the blog owner first before showing up.\n        </div>\n      {% else %}\n        <div class=\"notice\">Successfully posted your comment.</div>\n      {% endif %}\n    {% endif %}\n\n    {% if form.errors %}\n      <div class=\"notice error\">Not all the fields have been filled out correctly!</div>\n    {% endif %}\n\n    <dl>\n      <dt class=\"{% if form.errors contains 'author' %}error{% endif %}\"><label for=\"comment_author\">Your name</label></dt>\n      <dd><input type=\"text\" id=\"comment_author\" name=\"comment[author]\" size=\"40\" value=\"{{form.author}}\" class=\"{% if form.errors contains 'author' %}input-error{% endif %}\" /></dd>\n\n      <dt class=\"{% if form.errors contains 'email' %}error{% endif %}\"><label for=\"comment_email\">Your email</label></dt>\n      <dd><input type=\"text\" id=\"comment_email\" name=\"comment[email]\" size=\"40\" value=\"{{form.email}}\" class=\"{% if form.errors contains 'email' %}input-error{% endif %}\" /></dd>\n\n      <dt class=\"{% if form.errors contains 'body' %}error{% endif %}\"><label for=\"comment_body\">Your comment</label></dt>\n      <dd><textarea id=\"comment_body\" name=\"comment[body]\" cols=\"40\" rows=\"5\" class=\"{% if form.errors contains 'body' %}input-error{% endif %}\">{{form.body}}</textarea></dd>\n    </dl>\n\n    {% if blog.moderated? %}\n      <p class=\"hint\">comments have to be approved before showing up</p>\n    {% endif %}\n\n    <input type=\"submit\" value=\"Post comment\" id=\"comment-submit\" />\n  {% endform %}\n  </div>\n  <!-- END Comment Form -->\n\n</div>\n{% endif %}\n<!-- END Comments -->\n\n\n          </div>\n    </div>\n\n    <div id=\"three-reasons\" class=\"clearfix\">\n      <h3>Why Shop With Us?</h3>\n      <ul>\n        <li class=\"two-a\">\n          <h4>24 Hours</h4>\n          <p>We're always here to help.</p>\n        </li>\n        <li class=\"two-c\">\n          <h4>No Spam</h4>\n          <p>We'll never share your info.</p>\n        </li>\n        <li class=\"two-d\">\n          <h4>Secure Servers</h4>\n          <p>Checkout is 256bit encrypted.</p>\n        </li>\n      </ul>\n    </div>\n  </div>\n"
  },
  {
    "path": "performance/tests/tribble/blog.liquid",
    "content": "  <div id=\"page\" class=\"innerpage clearfix\">\n    <div id=\"text-page\">\n      <h1>Post from our blog...</h1>\n      {% paginate blog.articles by 20 %}\n          {% for article in blog.articles  %}\n\n          <div class=\"entry\">\n            <h1><span><a href=\"{{ article.url }}\">{{ article.title }}</a></span></h1>\n            <div class=\"entry-post\">\n              <div class=\"meta\">{{ article.created_at | date: \"%b %d\" }}</div>\n              {{ article.content }}\n            </div>\n          </div>\n\n        {% endfor %}\n\n        <div class=\"paginate clearfix\">\n            {{ paginate | default_pagination }}\n        </div>\n\n      {% endpaginate %}\n    </div>\n\n    <div id=\"three-reasons\" class=\"clearfix\">\n      <h3>Why Shop With Us?</h3>\n      <ul>\n        <li class=\"two-a\">\n          <h4>24 Hours</h4>\n          <p>We're always here to help.</p>\n        </li>\n        <li class=\"two-c\">\n          <h4>No Spam</h4>\n          <p>We'll never share your info.</p>\n        </li>\n        <li class=\"two-d\">\n          <h4>Secure Servers</h4>\n          <p>Checkout is 256bit encrypted.</p>\n        </li>\n      </ul>\n    </div>\n  </div>\n"
  },
  {
    "path": "performance/tests/tribble/cart.liquid",
    "content": "<script type=\"text/javascript\">\n  function remove_item(id) {\n      document.getElementById('updates_'+id).value = 0;\n      document.getElementById('cart').submit();\n  }\n</script>\n\n  <div id=\"page\" class=\"innerpage clearfix\">.\n    {% if cart.item_count == 0 %}\n         <h1>Your cart is currently empty.</h1>\n      {% else %}\n\n    <h1>Your Cart <span>({{ cart.item_count }} {{ cart.item_count | pluralize: 'item', 'items' }}, {{cart.total_price | money_with_currency }} total)</span></h1>\n\n    <form action=\"/cart\" method=\"post\" id=\"cart-form\">\n\n    <div id=\"cart-wrap\">\n      <table width=\"100%\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n        <tr>\n          <th scope=\"col\" class=\"td-image\"><label>Image</label></th>\n          <th scope=\"col\" class=\"td-title\"><label>Product Title</label></th>\n          <th scope=\"col\" class=\"td-count\"><label>Count</label></th>\n          <th scope=\"col\" class=\"td-price\"><label>Cost</label></th>\n          <th scope=\"col\" class=\"td-delete\"><label>Remove</label></th>\n        </tr>\n\n        {% for item in cart.items %}\n        <tr class=\"{% cycle 'reg', 'alt' %}\">\n          <td colspan=\"5\">\n            <table width=\"100%\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n              <tr>\n                <td class=\"td-image\"><a href=\"{{item.product.url}}\">{{ item.product.featured_image |  product_img_url: 'thumb' | img_tag }}</a></td>\n                <td class=\"td-title\"><p>{{ item.title }}</p></td>\n                <td class=\"td-count\"><label>Count:</label> <input type=\"text\" class=\"quantity item-count\" name=\"updates[{{item.variant.id}}]\" id=\"updates_{{item.variant.id}}\" value=\"{{item.quantity}}\" onfocus=\"this.select();\"/></td>\n                <td class=\"td-price\">{{item.line_price | money }}</td>\n                <td class=\"td-delete\"><a href=\"#\" onclick=\"remove_item({{item.variant.id}}); return false;\">Remove</a></td>\n              </tr>\n            </table>\n          </td>\n        </tr>\n        {% endfor %}\n      </table>\n\n      <div id=\"finish-up\">\n\n        <div class=\"latest-news-box\">\n          {{ pages.shopping-cart.content }}\n        </div>\n\n        <p class=\"order-total\">\n          <span><strong>Order Total:</strong> {{cart.total_price | money_with_currency }}</span>\n        </p>\n\n        <p class=\"update-cart\"><input type=\"submit\" value=\"Refresh Cart\" name=\"update\" /></p>\n\n          <p class=\"go-checkout\"><input type=\"submit\" value=\"Proceed to Checkout\" name=\"checkout\"  /></p>\n\n        {% if additional_checkout_buttons %}\n        <div class=\"additional-checkout-buttons\">\n          <p>- or -</p>\n          {{ content_for_additional_checkout_buttons }}\n        </div>\n        {% endif %}\n\n      </div>\n\n    </div>\n\n    </form>\n\n    {% endif %}\n\n\n\n    <h1 class=\"other-products\"><span>Other Products You Might Enjoy</span></h1>\n    <ul class=\"item-list clearfix\">\n\n      {% for product in collections.frontpage.products limit:2 %}\n      <li>\n        <form action=\"/cart/add\" method=\"post\">\n        <div class=\"item-list-item\">\n          <div class=\"ili-top clearfix\">\n            <div class=\"ili-top-content\">\n              <h2><a href=\"{{product.url}}\">{{product.title}}</a></h2>\n              <p>{{ product.description | truncatewords: 15 }}</p>\n            </div>\n            <a href=\"{{product.url}}\" class=\"ili-top-image\"><img src=\"{{ product.featured_image | product_img_url: 'small' }}\" alt=\"{{ product.title | escape }}\"/></a>\n          </div>\n\n          <div class=\"ili-bottom clearfix\">\n            <p class=\"hiddenvariants\" style=\"display: none\">{% for variant in product.variants %}<span><input type=\"radio\" name=\"id\" value=\"{{variant.id}}\" id=\"radio_{{variant.id}}\" style=\"vertical-align: middle;\" {%if forloop.first%} checked=\"checked\" {%endif%} /><label for=\"radio_{{variant.id}}\">{{ variant.price | money_with_currency }} - {{ variant.title }}</label></span>{% endfor %}</p>\n            <input type=\"submit\" class=\"\" value=\"Add to Basket\" />\n            <p>\n              <a href=\"{{product.url}}\">View Details</a>\n\n              <span>\n                {% if product.compare_at_price %}\n                  {% if product.price_min != product.compare_at_price %}\n                    {{product.compare_at_price | money}} -\n                    {% endif %}\n                {% endif %}\n                <strong>\n                  {{product.price_min | money}}\n                </strong>\n              </span>\n            </p>\n          </div>\n        </div>\n        </form>\n      </li>\n      {% endfor %}\n\n    </ul>\n\n    <div id=\"three-reasons\" class=\"clearfix\">\n      <h3>Why Shop With Us?</h3>\n      <ul>\n        <li class=\"two-a\">\n          <h4>24 Hours</h4>\n          <p>We're always here to help.</p>\n        </li>\n        <li class=\"two-c\">\n          <h4>No Spam</h4>\n          <p>We'll never share your info.</p>\n        </li>\n        <li class=\"two-d\">\n          <h4>Secure Servers</h4>\n          <p>Checkout is 256bit encrypted.</p>\n        </li>\n      </ul>\n    </div>\n\n  </div>\n  <!-- end page -->\n"
  },
  {
    "path": "performance/tests/tribble/collection.liquid",
    "content": "  <div id=\"page\" class=\"innerpage clearfix\">\n    <h1>{{ collection.title }}</h1>\n    {% if collection.description.size > 0 %}\n      <div class=\"latest-news\">{{ collection.description }}</div>\n    {% endif %}\n\n    {% paginate collection.products by 8 %}\n\n    <ul class=\"item-list clearfix\">\n    {% for product in collection.products  %}\n      <li>\n        <form action=\"/cart/add\" method=\"post\">\n        <div class=\"item-list-item\">\n          <div class=\"ili-top clearfix\">\n            <div class=\"ili-top-content\">\n              <h2><a href=\"{{product.url}}\">{{product.title}}</a></h2>\n              <p>{{ product.description | truncatewords: 15 }}</p>\n            </div>\n            <a href=\"{{product.url}}\" class=\"ili-top-image\"><img src=\"{{ product.featured_image | product_img_url: 'small' }}\" alt=\"{{ product.title | escape }}\"/></a>\n          </div>\n\n          <div class=\"ili-bottom clearfix\">\n            <p class=\"hiddenvariants\" style=\"display: none\">{% for variant in product.variants %}<span><input type=\"radio\" name=\"id\" value=\"{{variant.id}}\" id=\"radio_{{variant.id}}\" style=\"vertical-align: middle;\" {%if forloop.first%} checked=\"checked\" {%endif%} /><label for=\"radio_{{variant.id}}\">{{ variant.price | money_with_currency }} - {{ variant.title }}</label></span>{% endfor %}</p>\n            <input type=\"submit\" class=\"\" value=\"Add to Basket\" />\n            <p>\n              <a href=\"{{product.url}}\">View Details</a>\n\n              <span>\n                {% if product.compare_at_price %}\n                  {% if product.price_min != product.compare_at_price %}\n                    {{product.compare_at_price | money}} -\n                    {% endif %}\n                {% endif %}\n                <strong>\n                  {{product.price_min | money}}\n                </strong>\n              </span>\n            </p>\n          </div>\n        </div>\n        </form>\n      </li>\n    {% endfor %}\n    </ul>\n\n    <div class=\"paginate clearfix\">\n      {{ paginate | default_pagination }}\n    </div>\n\n\n    <div id=\"three-reasons\" class=\"clearfix\">\n      <h3>Why Shop With Us?</h3>\n      <ul>\n        <li class=\"two-a\">\n          <h4>24 Hours</h4>\n          <p>We're always here to help.</p>\n        </li>\n        <li class=\"two-c\">\n          <h4>No Spam</h4>\n          <p>We'll never share your info.</p>\n        </li>\n        <li class=\"two-d\">\n          <h4>Secure Servers</h4>\n          <p>Checkout is 256bit encrypted.</p>\n        </li>\n      </ul>\n    </div>\n  </div>\n\n{% endpaginate %}\n"
  },
  {
    "path": "performance/tests/tribble/index.liquid",
    "content": "  <div id=\"gwrap\">\n    <div id=\"gbox\">\n      <h1>Three Great Reasons You Should Shop With Us...</h1>\n      <ul>\n        <li class=\"gbox1\">\n          <h2>Free Shipping</h2>\n          <p>On all orders over $25</p>\n        </li>\n        <li class=\"gbox2\">\n          <h2>Top Quality</h2>\n          <p>Hand made in our shop</p>\n        </li>\n        <li class=\"gbox3\">\n          <h2>100% Guarantee</h2>\n          <p>Any time, any reason</p>\n        </li>\n      </ul>\n    </div>\n  </div>\n\n  <div id=\"page\" class=\"clearfix\">\n\n    <div class=\"latest-news\">{{pages.alert.content}}</div>\n\n    <ul class=\"item-list clearfix\">\n\n      {% for product in collections.frontpage.products %}\n      <li>\n        <form action=\"/cart/add\" method=\"post\">\n        <div class=\"item-list-item\">\n          <div class=\"ili-top clearfix\">\n            <div class=\"ili-top-content\">\n              <h2><a href=\"{{product.url}}\">{{product.title}}</a></h2>\n              {{ product.description | truncatewords: 15 }}</p> <!-- extra cloding <p> tag for truncation -->\n            </div>\n            <a href=\"{{product.url}}\" class=\"ili-top-image\"><img src=\"{{ product.featured_image | product_img_url: 'small' }}\" alt=\"{{ product.title | escape }}\"/></a>\n          </div>\n\n          <div class=\"ili-bottom clearfix\">\n            <p class=\"hiddenvariants\" style=\"display: none\">{% for variant in product.variants %}<span><input type=\"radio\" name=\"id\" value=\"{{variant.id}}\" id=\"radio_{{variant.id}}\" style=\"vertical-align: middle;\" {%if forloop.first%} checked=\"checked\" {%endif%} /><label for=\"radio_{{variant.id}}\">{{ variant.price | money_with_currency }} - {{ variant.title }}</label></span>{% endfor %}</p>\n            <input type=\"submit\" class=\"\" value=\"Add to Basket\" />\n            <p>\n              <a href=\"{{product.url}}\">View Details</a>\n\n              <span>\n                {% if product.compare_at_price %}\n                  {% if product.price_min != product.compare_at_price %}\n                    {{product.compare_at_price | money}} -\n                    {% endif %}\n                {% endif %}\n                <strong>\n                  {{product.price_min | money}}\n                </strong>\n              </span>\n            </p>\n          </div>\n        </div>\n        </form>\n      </li>\n      {% endfor %}\n\n    </ul>\n\n    <div id=\"one-two\">\n      <div id=\"two\">\n        <h3>Why Shop With Us?</h3>\n        <ul>\n          <li class=\"two-a\">\n            <h4>24 Hours</h4>\n            <p>We're always here to help.</p>\n          </li>\n          <li class=\"two-c\">\n            <h4>No Spam</h4>\n            <p>We'll never share your info.</p>\n          </li>\n          <li class=\"two-b\">\n            <h4>Save Energy</h4>\n            <p>We're green, all the way.</p>\n          </li>\n          <li class=\"two-d\">\n            <h4>Secure Servers</h4>\n            <p>Checkout is 256bits encrypted.</p>\n          </li>\n        </ul>\n      </div>\n\n      <div id=\"one\">\n        <h3>Our Company</h3>\n        {{pages.about-us.content | truncatewords: 49}} <a href=\"/pages/about-us\">read more</a></p>\n      </div>\n    </div>\n\n  </div>\n  <!-- end page -->\n"
  },
  {
    "path": "performance/tests/tribble/page.liquid",
    "content": "  <div id=\"page\" class=\"innerpage clearfix\">\n\n    <div id=\"text-page\">\n      <div class=\"entry\">\n        <h1>{{page.title}}</h1>\n        <div class=\"entry-post\">\n          {{page.content}}\n        </div>\n      </div>\n    </div>\n\n\n    <h1>Featured Products</h1>\n    <ul class=\"item-list clearfix\">\n\n      {% for product in collections.frontpage.products %}\n      <li>\n        <form action=\"/cart/add\" method=\"post\">\n        <div class=\"item-list-item\">\n          <div class=\"ili-top clearfix\">\n            <div class=\"ili-top-content\">\n              <h2><a href=\"{{product.url}}\">{{product.title}}</a></h2>\n              <p>{{ product.description | truncatewords: 15 }}</p>\n            </div>\n            <a href=\"{{product.url}}\" class=\"ili-top-image\"><img src=\"{{ product.featured_image | product_img_url: 'small' }}\" alt=\"{{ product.title | escape }}\"/></a>\n          </div>\n\n          <div class=\"ili-bottom clearfix\">\n            <p class=\"hiddenvariants\" style=\"display: none\">{% for variant in product.variants %}<span><input type=\"radio\" name=\"id\" value=\"{{variant.id}}\" id=\"radio_{{variant.id}}\" style=\"vertical-align: middle;\" {%if forloop.first%} checked=\"checked\" {%endif%} /><label for=\"radio_{{variant.id}}\">{{ variant.price | money_with_currency }} - {{ variant.title }}</label></span>{% endfor %}</p>\n            <input type=\"submit\" class=\"\" value=\"Add to Basket\" />\n            <p>\n              <a href=\"{{product.url}}\">View Details</a>\n\n              <span>\n                {% if product.compare_at_price %}\n                  {% if product.price_min != product.compare_at_price %}\n                    {{product.compare_at_price | money}} -\n                    {% endif %}\n                {% endif %}\n                <strong>\n                  {{product.price_min | money}}\n                </strong>\n              </span>\n            </p>\n          </div>\n        </div>\n        </form>\n      </li>\n      {% endfor %}\n\n    </ul>\n  </div>\n  <!-- end page -->\n\n\n\n"
  },
  {
    "path": "performance/tests/tribble/product.liquid",
    "content": "<div id=\"page\" class=\"innerpage clearfix\">\n  <h1>{{ collection.title }} {{ product.title }}</h1>\n\n\n  <p class=\"latest-news\"><strong>Product Tags: </strong>\n    {% for tag in product.tags %}\n            <a href=\"/collections/all/{{ tag }}\">{{ tag }}</a> |\n          {% endfor %}\n  </p>\n\n  <div class=\"product clearfix\">\n    <div class=\"product-info\">\n      <h1>{{ product.title }}</h1>\n      <div class=\"product-info-description\">\n        <p>{{ product.description }}  </p>\n      </div>\n\n      {% if product.available %}\n      <form action=\"/cart/add\" method=\"post\">\n\n      <h2>Product Options:</h2>\n\n      <select id=\"product-info-options\" name=\"id\" class=\"product-info-options\">\n        {% for variant in product.variants %}\n          <option value=\"{{ variant.id }}\">{{ variant.title }} - {{ variant.price | money }}</option>\n        {% endfor %}\n      </select>\n\n      <div id=\"price-field\"></div>\n\n      <div class=\"product-purchase-btn\">\n        <input type=\"submit\" class=\"add-this-to-cart\" id=\"add-this-to-cart\" value=\"Add to Basket\" />\n      </div>\n\n      </form>\n      {% else %}\n              <h2>Sold out!</h2>\n              <p>Sorry, we're all out of this product. Check back often and order when it returns</p>\n              {% endif %}\n    </div>\n\n    <div class=\"product-images clearfix\">\n      {% for image in product.images %}\n\n      {% if forloop.first %}\n      <div class=\"product-image-large\">\n        <img src=\"{{ image | product_img_url: 'medium'}}\" alt=\"{{product.title | escape }}\" />\n      </div>\n      {% else %}\n      {% endif %}\n      {% endfor %}\n\n      <ul class=\"product-thumbs clearfix\">\n      {% for image in product.images %}\n      {% if forloop.first %}\n      {% else %}\n\n      <li>\n      <a href=\"{{ image | product_img_url: 'large' }}\" class=\"product-thumbs\" rel=\"lightbox[product]\" title=\"\">\n        <img src=\"{{ image | product_img_url: 'small'}}\" alt=\"{{product.title | escape }}\" />\n      </a>\n      </li>\n      {% endif %}\n      {% endfor %}\n      </ul>\n    </div>\n  </div>\n\n\n\n  <div id=\"three-reasons\" class=\"clearfix\">\n    <h3>Why Shop With Us?</h3>\n    <ul>\n      <li class=\"two-a\">\n        <h4>24 Hours</h4>\n        <p>We're always here to help.</p>\n      </li>\n      <li class=\"two-c\">\n        <h4>No Spam</h4>\n        <p>We'll never share your info.</p>\n      </li>\n      <li class=\"two-d\">\n        <h4>Secure Servers</h4>\n        <p>Checkout is 256bit encrypted.</p>\n      </li>\n    </ul>\n  </div>\n\n</div>\n<!-- end page -->\n\n<script type=\"text/javascript\">\n<!--\n  // prototype callback for multi variants dropdown selector\n  var selectCallback = function(variant, selector) {\n    if (variant && variant.available == true) {\n      // selected a valid variant\n      $('add-this-to-cart').removeClassName('disabled'); // remove unavailable class from add-to-cart button\n      $('add-this-to-cart').disabled = false;           // reenable add-to-cart button\n      $('price-field').innerHTML = Shopify.formatMoney(variant.price, \"{{shop.money_with_currency_format}}\");  // update price field\n    } else {\n      // variant doesn't exist\n      $('add-this-to-cart').addClassName('disabled');      // set add-to-cart button to unavailable class\n      $('add-this-to-cart').disabled = true;              // disable add-to-cart button\n      $('price-field').innerHTML = (variant) ? \"Sold Out\" : \"Unavailable\"; // update price-field message\n    }\n  };\n\n  // initialize multi selector for product\n  Event.observe(document, 'dom:loaded', function() {\n    new Shopify.OptionSelectors(\"product-info-options\", { product: {{ product | json }}, onVariantSelected: selectCallback });\n  });\n-->\n</script>\n\n\n"
  },
  {
    "path": "performance/tests/tribble/search.liquid",
    "content": "\n\n\n\n    <div id=\"page\" class=\"innerpage clearfix\">\n    <h1>Search Results</h1>\n    {% if search.performed %}\n\n     {% paginate search.results by 10 %}\n\n     {% if search.results == empty %}\n      <div class=\"latest-news\">Your search for \"{{search.terms | escape}}\" did not yield any results</div>\n        {% else %}\n\n\n    <ul class=\"search-list clearfix\">\n     {% for item in search.results %}\n      <li>\n          <h3 class=\"stitle\">{{ item.title | link_to: item.url }}</h3>\n          <p class=\"sinfo\">{{ item.content | strip_html | truncatewords: 65 | highlight: search.terms }} ... <a href=\"{{item.url}}\" title=\"\">view this item</a></p>\n      </li>\n    {% endfor %}\n    </ul>\n     {% endif %}\n\n    <div class=\"paginate clearfix\">\n      {{ paginate | default_pagination }}\n    </div>\n\n\n    <div id=\"three-reasons\" class=\"clearfix\">\n      <h3>Why Shop With Us?</h3>\n      <ul>\n        <li class=\"two-a\">\n          <h4>24 Hours</h4>\n          <p>We're always here to help.</p>\n        </li>\n        <li class=\"two-c\">\n          <h4>No Spam</h4>\n          <p>We'll never share your info.</p>\n        </li>\n        <li class=\"two-d\">\n          <h4>Secure Servers</h4>\n          <p>Checkout is 256bit encrypted.</p>\n        </li>\n      </ul>\n    </div>\n  </div>\n\n{% endpaginate %}\n{% endif %}\n"
  },
  {
    "path": "performance/tests/tribble/theme.liquid",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n  <title>{{shop.name}} - {{page_title}}</title>\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n\n  {{ 'reset.css'     | asset_url | stylesheet_tag }}\n  {{ 'style.css'     | asset_url | stylesheet_tag }}\n\n  {{ 'lightbox.css'                         | asset_url | stylesheet_tag }}\n  {{ 'prototype/1.6/prototype.js'           | global_asset_url  | script_tag }}\n  {{ 'scriptaculous/1.8.2/scriptaculous.js' | global_asset_url  | script_tag }}\n  {{ 'lightbox.js'                          | asset_url | script_tag }}\n  {{ 'option_selection.js'                  | shopify_asset_url | script_tag }}\n\n  {{ content_for_header }}\n</head>\n<body id=\"page-{{template}}\">\n\n<div id=\"wrap\">\n\n  <div id=\"top\">\n    <div id=\"cart\">\n      <h3>Shopping Cart</h3>\n      <p class=\"cart-count\">\n        {% if cart.item_count == 0 %}\n          Your cart is currently empty\n        {% else %}\n          {{ cart.item_count }} {{ cart.item_count | pluralize: 'item', 'items' }} <span>-</span> Total: {{cart.total_price | money_with_currency }} <span>-</span> <a href=\"/cart\">View Cart</a>\n        {% endif %}\n      </p>\n    </div>\n\n    <div id=\"site-title\">\n      <h3><a href=\"/\">{{shop.name}}</a></h3>\n      <h4><span>Tribble: A Shopify Theme</span></h4>\n\n    </div>\n  </div>\n\n  <ul id=\"nav\">\n    {% for link in linklists.main-menu.links %}\n     <li>{{ link.title | link_to: link.url }}</li>\n    {% endfor %}\n  </ul>\n\n  {{ content_for_layout }}\n\n  <div id=\"foot\" class=\"clearfix\">\n    <div class=\"quick-links\">\n      <h4>Quick Navigation</h4>\n      <ul class=\"clearfix\">\n        <li><a href=\"/\">Home</a></li>\n        <li><a href=\"#top\">Back to top</a></li>\n        {% for link in linklists.main-menu.links %}\n         <li>{{ link.title | link_to: link.url }}</li>\n        {% endfor %}\n      </ul>\n    </div>\n\n    <div class=\"quick-contact\">\n      <h4>Quick Contact</h4>\n      <div class=\"vcard\">\n\n          <div class=\"org fn\">\n             <div class=\"organization-name\">Really Great Widget Co.</div>\n          </div>\n        <div class=\"adr\">\n            <span class=\"street-address\">2531 Barrington Court</span>\n            <span class=\"locality\">Hayward</span>,\n          <abbr title=\"California\" class=\"region\">CA</abbr>\n            <span class=\"postal-code\">94545</span>\n         </div>\n        <a class=\"email\" href=\"mailto:email@myshopifysite.com\">\n          email@myshopifysite.com\n        </a>\n        <div class=\"tel\">\n          <span class=\"type\">Support:</span> <span class=\"value\">800-555-9954</span>\n        </div>\n      </div>\n\n    </div>\n\n    <p><a href=\"http://shopify.com\" class=\"we-made\">Powered by Shopify</a> &copy; Copyright {{ \"now\" | date: \"%Y\" }} {{ shop.name }}, All Rights Reserved.  <a href=\"/blogs/news.xml\" id=\"foot-rss\">RSS Feed</a></p>\n  </div>\n\n</div>\n\n</body>\n</html>\n"
  },
  {
    "path": "performance/tests/vogue/article.liquid",
    "content": "<div class=\"article\">\n  <h3 class=\"article-head-title\">{{ article.title }}</h3>\n  <p> posted {{ article.created_at | date: \"%Y %h\" }} by {{ article.author }}</p>\n  <div class=\"article-body textile\">\n    {{ article.content }}\n  </div>\n</div>\n\n{% if blog.comments_enabled? %}\n<div id=\"comments\">\n  <h3>Comments</h3>\n  <!-- List all comments -->\n\n  <ul>\n  {% for comment in article.comments %}\n    <li>\n      <div class=\"comment\">\n        {{ comment.content }}\n      </div>\n\n      <div class=\"comment-details\">\n        Posted by {{ comment.author }} on {{ comment.created_at | date: \"%B %d, %Y\" }}\n      </div>\n    </li>\n  {% endfor %}\n  </ul>\n\n  <!-- Comment Form -->\n  {% form article %}\n    <h3>Leave a comment</h3>\n\n    <!-- Check if a comment has been submitted in the last request, and if yes display an appropriate message -->\n    {% if form.posted_successfully? %}\n      {% if blog.moderated? %}\n        <div class=\"notice\">\n          Successfully posted your comment.<br />\n          It will have to be approved by the blog owner first before showing up.\n        </div>\n      {% else %}\n        <div class=\"notice\">Successfully posted your comment.</div>\n      {% endif %}\n    {% endif %}\n\n    {% if form.errors %}\n      <div class=\"notice error\">Not all the fields have been filled out correctly!</div>\n    {% endif %}\n\n    <dl>\n      <dt class=\"{% if form.errors contains 'author' %}error{% endif %}\"><label for=\"comment_author\">Your name</label></dt>\n      <dd><input type=\"text\" id=\"comment_author\" name=\"comment[author]\" size=\"40\" value=\"{{form.author}}\" class=\"{% if form.errors contains 'author' %}input-error{% endif %}\" /></dd>\n\n      <dt class=\"{% if form.errors contains 'email' %}error{% endif %}\"><label for=\"comment_email\">Your email</label></dt>\n      <dd><input type=\"text\" id=\"comment_email\" name=\"comment[email]\" size=\"40\" value=\"{{form.email}}\" class=\"{% if form.errors contains 'email' %}input-error{% endif %}\" /></dd>\n\n      <dt class=\"{% if form.errors contains 'body' %}error{% endif %}\"><label for=\"comment_body\">Your comment</label></dt>\n      <dd><textarea id=\"comment_body\" name=\"comment[body]\" cols=\"40\" rows=\"5\" class=\"{% if form.errors contains 'body' %}input-error{% endif %}\">{{form.body}}</textarea></dd>\n    </dl>\n\n    {% if blog.moderated? %}\n      <p class=\"hint\">comments have to be approved before showing up</p>\n    {% endif %}\n\n    <input type=\"submit\" value=\"Post comment\" />\n  {% endform %}\n</div>\n{% endif %}\n"
  },
  {
    "path": "performance/tests/vogue/blog.liquid",
    "content": "<div id=\"shop-id-label_about\">\n<h3 class=\"article-head-title\">{{page.title}}</h3>\n\n{% paginate blog.articles by 20 %}\n\n  {% for article in blog.articles  %}\n    <div class=\"article\">\n      <h3 class=\"article-head-title\">\n        <a href=\"{{article.url}}\">{{ article.title }}</a>\n      </h3>\n\n      <p>\n      {% if blog.comments_enabled? %}\n        <a href=\"{{article.url}}#comments\">{{ article.comments_count }} comments</a>\n        &mdash;\n      {% endif %}\n      posted {{ article.created_at | date: \"%Y %h\" }} by {{ article.author }}</p>\n      <div class=\"article-body textile\">\n        {{ article.content }}\n      </div>\n    </div>\n  {% endfor %}\n\n  <div id=\"pagination\">\n    {{ paginate | default_pagination }}\n  </div>\n\n{% endpaginate %}\n\n\n</div>\n<div class=\"clear-me\"></div>\n"
  },
  {
    "path": "performance/tests/vogue/cart.liquid",
    "content": "<h1>Shopping Cart</h1>\n{% if cart.item_count == 0 %}\n  <p><strong>Your shopping basket is empty.</strong> Perhaps a featured item below is of interest...</p>\n  <table id=\"gallery\">\n  {% tablerow product in collections.frontpage.products cols: 3 limit: 12 %}\n    <div class=\"gallery-image\">\n      <a href=\"{{ product.url | within: collections.frontpage }}\" title=\"{{ product.title | escape }} &mdash; {{ product.description | strip_html | truncate: 50 | escape }}\"><img src=\"{{ product.images.first | product_img_url: 'medium' }}\" alt=\"{{ product.title | escape }}\" /></a>\n    </div>\n    <div class=\"gallery-info\">\n      <a href=\"{{ product.url | within: collections.frontpage }}\">{{ product.title | truncate: 30 }}</a><br />\n      <small>{{ product.price | money }}{% if product.compare_at_price_max > product.price %} <del>{{ product.compare_at_price_max | money }}</del>{% endif %}</small>\n    </div>\n  {% endtablerow %}\n  </table>\n{% else %}\n<script type=\"text/javascript\">\n  function remove_item(id) {\n      document.getElementById('updates_'+id).value = 0;\n      document.getElementById('cartform').submit();\n  }\n</script>\n<form action=\"/cart\" method=\"post\" id=\"cartform\">\n  <table id=\"basket\">\n    <tr>\n      <th>Item Description</th>\n      <th>Price</th>\n      <th>Qty</th>\n      <th>Delete</th>\n      <th>Total</th>\n    </tr>{% for item in cart.items %}\n    <tr class=\"basket-{% cycle 'odd', 'even' %}\">\n      <td class=\"basket-column-one\">\n        <div class=\"basket-images\">\n          <a href=\"{{ item.product.url }}\" title=\"{{ item.title | escape }} &mdash; {{ item.product.description | strip_html | truncate: 50 | escape }}\"><img src=\"{{ item.product.images.first | product_img_url: 'thumb' }}\" alt=\"{{ item.title | escape }}\" /></a>\n        </div>\n        <div class=\"basket-desc\">\n          <p><a href=\"{{ item.product.url }}\">{{ item.title }}</a></p>\n          {{ item.product.description | strip_html | truncate: 120 }}\n        </div>\n      </td>\n      <td class=\"basket-column\">{{ item.price | money }}{% if item.variant.compare_at_price > item.price %}<br /><del>{{ item.variant.compare_at_price | money }}</del>{% endif %}</td>\n      <td class=\"basket-column\"><input type=\"text\" size=\"4\" name=\"updates[{{item.variant.id}}]\" id=\"updates_{{ item.variant.id }}\" value=\"{{ item.quantity }}\" onfocus=\"this.select();\"/></td>\n      <td class=\"basket-column\"><a href=\"#\" onclick=\"remove_item({{ item.variant.id }}); return false;\">Remove</a></td>\n      <td class=\"basket-column\">{{ item.line_price | money }}</td>\n    </tr>{% endfor %}\n  </table>\n  <div id=\"basket-right\">\n    <h3>Subtotal {{ cart.total_price | money }}</h3>\n    <input type=\"image\" src=\"{{ 'update.png' | asset_url }}\" id=\"update-cart\" name=\"update\" value=\"Update\" />\n    <input type=\"image\" src=\"{{ 'checkout.png' | asset_url }}\" name=\"checkout\" value=\"Checkout\" />\n    {% if additional_checkout_buttons %}\n    <div class=\"additional-checkout-buttons\">\n      <p>- or -</p>\n      {{ content_for_additional_checkout_buttons }}\n    </div>\n    {% endif %}\n  </div>\n</form>{% endif %}\n"
  },
  {
    "path": "performance/tests/vogue/collection.liquid",
    "content": "{% paginate collection.products by 12 %}{% if collection.products.size == 0 %}\n  <strong>No products found in this collection.</strong>{% else %}\n  <h1>{{ collection.title }}</h1>\n  {{ collection.description }}\n  <table id=\"gallery\">\n  {% tablerow product in collection.products cols: 3 %}\n    <div class=\"gallery-image\">\n      <a href=\"{{ product.url | within: collection }}\" title=\"{{ product.title | escape }} &mdash; {{ product.description | strip_html | truncate: 50 | escape }}\"><img src=\"{{ product.images.first | product_img_url: 'small' }}\" alt=\"{{ product.title | escape }}\" /></a>\n    </div>\n    <div class=\"gallery-info\">\n      <a href=\"{{ product.url | within: collection }}\">{{ product.title | truncate: 30 }}</a><br />\n      <small>{{ product.price | money }}{% if product.compare_at_price_max > product.price %} <del>{{ product.compare_at_price_max | money }}</del>{% endif %}</small>\n    </div>\n  {% endtablerow %}\n  </table>{% if paginate.pages > 1 %}\n  <div id=\"paginate\">\n    {{ paginate | default_pagination }}\n  </div>{% endif %}{% endif %}\n{% endpaginate %}\n"
  },
  {
    "path": "performance/tests/vogue/index.liquid",
    "content": "  <div id=\"about-excerpt\">\n    {% assign article = pages.frontpage %}\n    {% if article.content != \"\" %}\n      <h2>{{ article.title }}</h2>\n      {{ article.content }}\n    {% else %}\n      In <em>Admin &gt; Blogs &amp; Pages</em>, create a page with the handle <strong><code>frontpage</code></strong> and it will show up here.<br />\n      {{ \"Learn more about handles\" | link_to: \"http://wiki.shopify.com/Handle\" }}\n    {% endif %}\n  </div>\n\n  <table id=\"gallery\">\n  {% tablerow product in collections.frontpage.products cols: 3 limit: 12 %}\n    <div class=\"gallery-image\">\n      <a href=\"{{ product.url | within: collections.frontpage }}\" title=\"{{ product.title | escape }} &mdash; {{ product.description | strip_html | truncate: 50 | escape }}\"><img src=\"{{ product.images.first | product_img_url: 'small' }}\" alt=\"{{ product.title | escape }}\" /></a>\n    </div>\n    <div class=\"gallery-info\">\n      <a href=\"{{ product.url | within: collections.frontpage }}\">{{ product.title | truncate: 30 }}</a><br />\n      <small>{{ product.price | money }}{% if product.compare_at_price_max > product.price %} <del>{{ product.compare_at_price_max | money }}</del>{% endif %}</small>\n    </div>\n  {% endtablerow %}\n  </table>\n"
  },
  {
    "path": "performance/tests/vogue/page.liquid",
    "content": "  <h1>{{ page.title }}</h1>\n  {{ page.content }}\n\n"
  },
  {
    "path": "performance/tests/vogue/product.liquid",
    "content": "<div id=\"product-left\">\n  {% for image in product.images %}{% if forloop.first %}<div id=\"product-image\">\n    <a href=\"{{ image | product_img_url: 'large' }}\" rel=\"lightbox[images]\" title=\"{{ product.title | escape }}\"><img src=\"{{ image | product_img_url: 'medium' }}\" alt=\"{{ product.title | escape }}\" /></a>\n  </div>{% else %}\n  <div class=\"product-images\">\n    <a href=\"{{ image | product_img_url: 'large' }}\" rel=\"lightbox[images]\" title=\"{{ product.title | escape }}\"><img src=\"{{ image | product_img_url: 'small' }}\" alt=\"{{ product.title | escape }}\" /></a>\n  </div>{% endif %}{% endfor %}\n</div>\n<div id=\"product-right\">\n  <h1>{{ product.title }}</h1>\n  {{ product.description }}\n\n  {% if product.available %}\n  <form action=\"/cart/add\" method=\"post\">\n\n    <div id=\"product-variants\">\n      <div id=\"price-field\"></div>\n\n      <select id=\"product-select\" name='id'>\n        {% for variant in product.variants %}\n          <option value=\"{{ variant.id }}\">{{ variant.title }} - {{ variant.price | money }}</option>\n        {% endfor %}\n      </select>\n    </div>\n\n    <input type=\"image\" src=\"{{ 'purchase.png' | asset_url }}\" name=\"add\" value=\"Purchase\" id=\"purchase\" />\n  </form>\n  {% else %}\n    <p class=\"bold-red\">This product is temporarily unavailable</p>\n  {% endif %}\n\n  <div id=\"product-details\">\n    <strong>Continue Shopping</strong><br />\n    Browse more {{ product.type | link_to_type }} or additional {{ product.vendor | link_to_vendor }} products.\n  </div>\n</div>\n\n\n<script type=\"text/javascript\">\n<!--\n  // mootools callback for multi variants dropdown selector\n  var selectCallback = function(variant, selector) {\n    if (variant && variant.available == true) {\n      // selected a valid variant\n      $('purchase').removeClass('disabled'); // remove unavailable class from add-to-cart button\n      $('purchase').disabled = false;           // reenable add-to-cart button\n      $('price-field').innerHTML = Shopify.formatMoney(variant.price, \"{{shop.money_with_currency_format}}\");  // update price field\n    } else {\n      // variant doesn't exist\n      $('purchase').addClass('disabled');      // set add-to-cart button to unavailable class\n      $('purchase').disabled = true;              // disable add-to-cart button\n      $('price-field').innerHTML = (variant) ? \"Sold Out\" : \"Unavailable\"; // update price-field message\n    }\n  };\n\n  // initialize multi selector for product\n  window.addEvent('domready', function() {\n    new Shopify.OptionSelectors(\"product-select\", { product: {{ product | json }}, onVariantSelected: selectCallback });\n  });\n-->\n</script>\n\n"
  },
  {
    "path": "performance/tests/vogue/theme.liquid",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n\n<head>\n<title>{{ shop.name }} &mdash; {{ page_title }}</title>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n\n{{ 'stylesheet.css' | asset_url | stylesheet_tag }}\n\n<!-- Additional colour schemes for this theme. If you want to use them, just replace the above line with one of these\n{{ 'caramel.css' | asset_url | stylesheet_tag }}\n{{ 'sea.css' | asset_url | stylesheet_tag }}\n-->\n\n{{ 'mootools.js'        | global_asset_url  | script_tag }}\n{{ 'slimbox.js'         | global_asset_url  | script_tag }}\n{{ 'option_selection.js' | shopify_asset_url | script_tag }}\n\n{{ content_for_header }}\n</head>\n\n<body id=\"page-{{ template }}\">\n\n<div id=\"header\">\n  <div class=\"container\">\n    <div id=\"logo\">\n      <h1><a href=\"/\" title=\"{{ shop.name }}\">{{ shop.name }}</a></h1>\n    </div>\n    <div id=\"navigation\">\n      <ul id=\"navigate\">\n        <li><a href=\"/cart\">View Cart</a></li>\n        {% for link in linklists.main-menu.links reversed %}\n          <li><a href=\"{{ link.url }}\">{{ link.title }}</a></li>\n        {% endfor %}\n      </ul>\n    </div>\n  </div>\n</div>\n\n<div id=\"mini-header\">\n  <div class=\"container\">\n    <div id=\"shopping-cart\">\n      <a href=\"/cart\">Your shopping cart contains {{ cart.item_count }} {{ cart.item_count | pluralize: 'item', 'items' }}</a>\n    </div>\n    <div id=\"search-box\">\n      <form action=\"/search\" method=\"get\">\n        <input type=\"text\" name=\"q\" id=\"q\" />\n        <input type=\"image\" src=\"{{ 'seek.png' | asset_url }}\" value=\"Seek\" onclick=\"this.parentNode.submit(); return false;\" id=\"seek\" />\n      </form>\n    </div>\n  </div>\n</div>\n\n<div id=\"layout\">\n  <div class=\"container\">\n    <div id=\"layout-left\" {% if template != \"cart\" %}{% if template != \"product\" %}style=\"width:619px\"{% endif %}{% endif %}>{% if template == \"search\" %}\n      <h1>Search Results</h1>{% endif %}\n      {{ content_for_layout }}\n    </div>{% if template != \"cart\" %}{% if template != \"product\" %}\n\n    <div id=\"layout-right\">\n      {% if template == \"index\" %}\n      {% if blogs.news.articles.size > 1 %}\n      <a href=\"{{ shop.url }}/blogs/news.xml\"><img src=\"{{ 'feed.png' | asset_url }}\" alt=\"Subscribe\" class=\"feed\" /></a>\n      <h3><a href=\"/blogs/news\">More news</a></h3>\n      <ul id=\"blogs\">{% for article in blogs.news.articles limit: 6 offset: 1 %}\n        <li><a href=\"{{ article.url }}\">{{ article.title | strip_html | truncate: 30 }}</a><br />\n          <small>{{ article.content | strip_html | truncatewords: 12 }}</small>\n        </li>{% endfor %}\n      </ul>\n      {% endif %}\n      {% endif %}\n\n      {% if template == \"collection\" %}\n      <h3>Collection Tags</h3>\n      <div id=\"tags\">{% if collection.tags.size == 0 %}\n        No tags found.{% else %}\n        <span class=\"tags\">{% for tag in collection.tags %}{% if current_tags contains tag %} {{ tag | highlight_active_tag | link_to_remove_tag: tag }}{% else %} {{ tag | highlight_active_tag | link_to_add_tag: tag }}{% endif %}{% unless forloop.last %}, {% endunless %}{% endfor %}</span>{% endif %}\n      </div>\n      {% endif %}\n\n      <h3>Navigation</h3>\n      <ul id=\"links\">\n      {% for link in linklists.main-menu.links %}\n        <li><a href=\"{{ link.url }}\">{{ link.title }}</a></li>\n      {% endfor %}\n      </ul>\n\n      {% if template != \"page\" %}\n      <h3>Featured Products</h3>\n      <ul id=\"featuring\">{% for product in collections.frontpage.products limit: 6 %}\n        <li class=\"featuring-list\">\n          <div class=\"featuring-image\">\n            <a href=\"{{ product.url | within: collections.frontpage }}\" title=\"{{ product.title | escape }} &mdash; {{ product.description | strip_html | truncate: 50 }}\"><img src=\"{{ product.images.first | product_img_url: 'icon' }}\" alt=\"{{ product.title | escape }}\" /></a>\n          </div>\n          <div class=\"featuring-info\">\n            <a href=\"{{ product.url | within: collections.frontpage }}\">{{ product.title | strip_html | truncate: 28 }}</a><br />\n            <small><span class=\"light\">from</span> {{ product.price | money }}</small>\n          </div>\n        </li>{% endfor %}\n      </ul>\n      {% endif %}\n    </div>{% endif %}{% endif %}\n  </div>\n</div>\n\n<div id=\"footer\">\n  <div id=\"footer-fader\">\n    <div class=\"container\">\n      <div id=\"footer-right\">{% for link in linklists.footer.links %}\n        {{ link.title | link_to: link.url }} {% unless forloop.last %}&#124;{% endunless %}{% endfor %}\n      </div>\n      <span id=\"footer-left\">\n        Copyright &copy; {{ \"now\" | date: \"%Y\" }} <a href=\"/\">{{ shop.name }}</a>. All Rights Reserved. All prices {{ shop.currency }}.<br />\n        This website is powered by <a href=\"http://www.shopify.com\">Shopify</a>.\n      </span>\n    </div>\n  </div>\n</div>\n\n</body>\n</html>\n"
  },
  {
    "path": "performance/theme_runner.rb",
    "content": "# frozen_string_literal: true\n\n# This profiler run simulates Shopify.\n# We are looking in the tests directory for liquid files and render them within the designated layout file.\n# We will also export a substantial database to liquid which the templates can render values of.\n# All this is to make the benchmark as non synthetic as possible. All templates and tests are lifted from\n# direct real-world usage and the profiler measures code that looks very similar to the way it looks in\n# Shopify which is likely the biggest user of liquid in the world which something to the tune of several\n# million Template#render calls a day.\n\nrequire_relative 'shopify/liquid'\nrequire_relative 'shopify/database'\n\nclass ThemeRunner\n  class FileSystem\n    def initialize(path)\n      @path = path\n    end\n\n    # Called by Liquid to retrieve a template file\n    def read_template_file(template_path)\n      File.read(@path + '/' + template_path + '.liquid')\n    end\n  end\n\n  # Initialize a new liquid ThemeRunner instance\n  # Will load all templates into memory, do this now so that we don't profile IO.\n  def initialize\n    @tests = Dir[__dir__ + '/tests/**/*.liquid'].collect do |test|\n      next if File.basename(test) == 'theme.liquid'\n\n      theme_path = File.dirname(test) + '/theme.liquid'\n      {\n        liquid: File.read(test),\n        layout: (File.file?(theme_path) ? File.read(theme_path) : nil),\n        template_name: test,\n      }\n    end.compact\n\n    compile_all_tests\n  end\n\n  # `compile` will test just the compilation portion of liquid without any templates\n  def compile\n    @tests.each do |test_hash|\n      Liquid::Template.new.parse(test_hash[:liquid])\n      Liquid::Template.new.parse(test_hash[:layout])\n    end\n  end\n\n  # `tokenize` will just test the tokenizen portion of liquid without any templates\n  def tokenize\n    ss = StringScanner.new(\"\")\n    @tests.each do |test_hash|\n      tokenizer = Liquid::Tokenizer.new(\n        source: test_hash[:liquid],\n        string_scanner: ss,\n        line_numbers: true,\n      )\n      while tokenizer.shift; end\n    end\n  end\n\n  # `run` is called to benchmark rendering and compiling at the same time\n  def run\n    each_test do |liquid, layout, assigns, page_template, template_name|\n      compile_and_render(liquid, layout, assigns, page_template, template_name)\n    end\n  end\n\n  # `render` is called to benchmark just the render portion of liquid\n  def render\n    @compiled_tests.each do |test|\n      tmpl    = test[:tmpl]\n      assigns = test[:assigns]\n      layout  = test[:layout]\n\n      if layout\n        assigns['content_for_layout'] = tmpl.render!(assigns)\n        layout.render!(assigns)\n      else\n        tmpl.render!(assigns)\n      end\n    end\n  end\n\n  private\n\n  def render_layout(template, layout, assigns)\n    assigns['content_for_layout'] = template.render!(assigns)\n    layout&.render!(assigns)\n  end\n\n  def compile_and_render(template, layout, assigns, page_template, template_file)\n    compiled_test = compile_test(template, layout, assigns, page_template, template_file)\n    render_layout(compiled_test[:tmpl], compiled_test[:layout], compiled_test[:assigns])\n  end\n\n  def compile_all_tests\n    @compiled_tests = []\n    each_test do |liquid, layout, assigns, page_template, template_name|\n      @compiled_tests << compile_test(liquid, layout, assigns, page_template, template_name)\n    end\n    @compiled_tests\n  end\n\n  def compile_test(template, layout, assigns, page_template, template_file)\n    tmpl            = init_template(page_template, template_file)\n    parsed_template = tmpl.parse(template).dup\n\n    if layout\n      parsed_layout = tmpl.parse(layout)\n      { tmpl: parsed_template, assigns: assigns, layout: parsed_layout }\n    else\n      { tmpl: parsed_template, assigns: assigns }\n    end\n  end\n\n  # utility method with similar functionality needed in `compile_all_tests` and `run`\n  def each_test\n    # Dup assigns because will make some changes to them\n    assigns = Database.tables.dup\n\n    @tests.each do |test_hash|\n      # Compute page_template outside of profiler run, uninteresting to profiler\n      page_template = File.basename(test_hash[:template_name], File.extname(test_hash[:template_name]))\n      yield(test_hash[:liquid], test_hash[:layout], assigns, page_template, test_hash[:template_name])\n    end\n  end\n\n  # set up a new Liquid::Template object for use in `compile_and_render` and `compile_test`\n  def init_template(page_template, template_file)\n    tmpl                         = Liquid::Template.new\n    tmpl.assigns['page_title']   = 'Page title'\n    tmpl.assigns['template']     = page_template\n    tmpl.registers[:file_system] = ThemeRunner::FileSystem.new(File.dirname(template_file))\n    tmpl\n  end\nend\n"
  },
  {
    "path": "performance/unit/expression_benchmark.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"benchmark/ips\"\n\n# benchmark liquid lexing\n\nrequire 'liquid'\n\nRubyVM::YJIT.enable\n\nSTRING_MARKUPS = [\n  \"\\\"foo\\\"\",\n  \"\\\"fooooooooooo\\\"\",\n  \"\\\"foooooooooooooooooooooooooooooo\\\"\",\n  \"'foo'\",\n  \"'fooooooooooo'\",\n  \"'foooooooooooooooooooooooooooooo'\",\n]\n\nVARIABLE_MARKUPS = [\n  \"article\",\n  \"article.title\",\n  \"article.title.size\",\n  \"very_long_variable_name_2024_11_05\",\n  \"very_long_variable_name_2024_11_05.size\",\n]\n\nNUMBER_MARKUPS = [\n  \"0\",\n  \"35\",\n  \"1241891024912849\",\n  \"3.5\",\n  \"3.51214128409128\",\n  \"12381902839.123819283910283\",\n  \"123.456.789\",\n  \"-123\",\n  \"-12.33\",\n  \"-405.231\",\n  \"-0\",\n  \"0\",\n  \"0.0\",\n  \"0.0000000000000000000000\",\n  \"0.00000000001\",\n]\n\nRANGE_MARKUPS = [\n  \"(1..30)\",\n  \"(1...30)\",\n  \"(1..30..5)\",\n  \"(1.0...30.0)\",\n  \"(1.........30)\",\n  \"(1..foo)\",\n  \"(foo..30)\",\n  \"(foo..bar)\",\n  \"(foo...bar...100)\",\n  \"(foo...bar...100.0)\",\n]\n\nLITERAL_MARKUPS = [\n  nil,\n  'nil',\n  'null',\n  '',\n  'true',\n  'false',\n  'blank',\n  'empty',\n]\n\nMARKUPS = {\n  \"string\" => STRING_MARKUPS,\n  \"literal\" => LITERAL_MARKUPS,\n  \"variable\" => VARIABLE_MARKUPS,\n  \"number\" => NUMBER_MARKUPS,\n  \"range\" => RANGE_MARKUPS,\n}\n\nBenchmark.ips do |x|\n  x.config(time: 5, warmup: 5)\n\n  MARKUPS.each do |type, markups|\n    x.report(\"Liquid::Expression#parse: #{type}\") do\n      markups.each do |markup|\n        Liquid::Expression.parse(markup)\n      end\n    end\n  end\n\n  x.report(\"Liquid::Expression#parse: all\") do\n    MARKUPS.values.flatten.each do |markup|\n      Liquid::Expression.parse(markup)\n    end\n  end\nend\n"
  },
  {
    "path": "performance/unit/lexer_benchmark.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"benchmark/ips\"\n\n# benchmark liquid lexing\n\nrequire 'liquid'\n\nRubyVM::YJIT.enable\n\nEXPRESSIONS = [\n  \"foo[1..2].baz\",\n  \"12.0\",\n  \"foo.bar.based\",\n  \"21 - 62\",\n  \"foo.bar.baz\",\n  \"foo > 12\",\n  \"foo < 12\",\n  \"foo <= 12\",\n  \"foo >= 12\",\n  \"foo <> 12\",\n  \"foo == 12\",\n  \"foo != 12\",\n  \"foo contains 12\",\n  \"foo contains 'bar'\",\n  \"foo != 'bar'\",\n  \"'foo' contains 'bar'\",\n  '234089',\n  \"foo | default: -1\",\n]\n\nBenchmark.ips do |x|\n  x.config(time: 10, warmup: 5)\n\n  x.report(\"Liquid::Lexer#tokenize\") do\n    EXPRESSIONS.each do |expr|\n      l = Liquid::Lexer.new(expr)\n      l.tokenize\n    end\n  end\n\n  x.compare!\nend\n"
  },
  {
    "path": "spec/ruby_liquid.rb",
    "content": "# frozen_string_literal: true\n\n# Liquid Spec Adapter for Shopify/liquid (Ruby reference implementation)\n#\n# Run with: bundle exec liquid-spec run spec/ruby_liquid.rb\n\n$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))\nrequire 'liquid'\n\nLiquidSpec.configure do |config|\n  # Run core Liquid specs\n  config.features = [:core]\nend\n\n# Compile a template string into a Liquid::Template\nLiquidSpec.compile do |ctx, source, options|\n  ctx[:template] = Liquid::Template.parse(source, **options)\nend\n\n# Render a compiled template with the given context\n# @param ctx [Hash] adapter context containing :template\n# @param assigns [Hash] environment variables\n# @param options [Hash] :registers, :strict_errors, :exception_renderer\nLiquidSpec.render do |ctx, assigns, options|\n  registers = Liquid::Registers.new(options[:registers] || {})\n\n  context = Liquid::Context.build(\n    static_environments: assigns,\n    registers: registers,\n    rethrow_errors: options[:strict_errors],\n  )\n\n  context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]\n\n  ctx[:template].render(context)\nend\n"
  },
  {
    "path": "spec/ruby_liquid_lax.rb",
    "content": "# frozen_string_literal: true\n\n# Liquid Spec Adapter for Shopify/liquid with lax parsing mode\n#\n# Run with: bundle exec liquid-spec run spec/ruby_liquid_lax.rb\n\n$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))\nrequire 'liquid'\n\nLiquidSpec.configure do |config|\n  config.features = [:core, :lax_parsing]\nend\n\n# Compile a template string into a Liquid::Template\nLiquidSpec.compile do |ctx, source, options|\n  # Force lax mode\n  options = options.merge(error_mode: :lax)\n  ctx[:template] = Liquid::Template.parse(source, **options)\nend\n\n# Render a compiled template with the given context\nLiquidSpec.render do |ctx, assigns, options|\n  registers = Liquid::Registers.new(options[:registers] || {})\n\n  context = Liquid::Context.build(\n    static_environments: assigns,\n    registers: registers,\n    rethrow_errors: options[:strict_errors],\n  )\n\n  context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]\n\n  ctx[:template].render(context)\nend\n"
  },
  {
    "path": "spec/ruby_liquid_with_active_support.rb",
    "content": "# frozen_string_literal: true\n\n# Liquid Spec Adapter for Shopify/liquid with ActiveSupport loaded\n#\n# Run with: bundle exec liquid-spec run spec/ruby_liquid_with_active_support.rb\n\n$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))\nrequire 'active_support/all'\nrequire 'liquid'\n\nLiquidSpec.configure do |config|\n  # Run core Liquid specs plus ActiveSupport SafeBuffer tests\n  config.features = [:core, :activesupport]\nend\n\n# Compile a template string into a Liquid::Template\nLiquidSpec.compile do |ctx, source, options|\n  ctx[:template] = Liquid::Template.parse(source, **options)\nend\n\n# Render a compiled template with the given context\n# @param ctx [Hash] adapter context containing :template\n# @param assigns [Hash] environment variables\n# @param options [Hash] :registers, :strict_errors, :exception_renderer\nLiquidSpec.render do |ctx, assigns, options|\n  registers = Liquid::Registers.new(options[:registers] || {})\n\n  context = Liquid::Context.build(\n    static_environments: assigns,\n    registers: registers,\n    rethrow_errors: options[:strict_errors],\n  )\n\n  context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]\n\n  ctx[:template].render(context)\nend\n"
  },
  {
    "path": "spec/ruby_liquid_yjit.rb",
    "content": "# frozen_string_literal: true\n\n# Liquid Spec Adapter for Shopify/liquid with YJIT + strict mode + ActiveSupport\n#\n# Run with: bundle exec liquid-spec run spec/ruby_liquid_yjit.rb\n\n$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))\n\n# Enable YJIT if available\nif defined?(RubyVM::YJIT) && RubyVM::YJIT.respond_to?(:enable)\n  RubyVM::YJIT.enable\nend\n\nrequire 'active_support/all'\nrequire 'liquid'\n\nLiquidSpec.configure do |config|\n  config.features = [:core, :activesupport]\nend\n\n# Compile a template string into a Liquid::Template\nLiquidSpec.compile do |ctx, source, options|\n  # Force strict mode\n  options = { error_mode: :strict }.merge(options)\n  ctx[:template] = Liquid::Template.parse(source, **options)\nend\n\n# Render a compiled template with the given context\nLiquidSpec.render do |ctx, assigns, options|\n  registers = Liquid::Registers.new(options[:registers] || {})\n\n  context = Liquid::Context.build(\n    static_environments: assigns,\n    registers: registers,\n    rethrow_errors: options[:strict_errors],\n  )\n\n  context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]\n\n  ctx[:template].render(context)\nend\n"
  },
  {
    "path": "test/fixtures/en_locale.yml",
    "content": "---\n  simple: \"less is more\"\n  whatever: \"something %{something}\"\n  errors:\n    i18n:\n      undefined_interpolation: \"undefined key %{key}\"\n      unknown_translation: \"translation '%{name}' wasn't found\"\n    syntax:\n      oops: \"something wasn't right\"\n"
  },
  {
    "path": "test/integration/assign_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass AssignTest < Minitest::Test\n  include Liquid\n\n  def test_assign_with_hyphen_in_variable_name\n    template_source = <<~END_TEMPLATE\n      {% assign this-thing = 'Print this-thing' -%}\n      {{ this-thing -}}\n    END_TEMPLATE\n    assert_template_result(\"Print this-thing\", template_source)\n  end\n\n  def test_assigned_variable\n    assert_template_result(\n      '.foo.',\n      '{% assign foo = values %}.{{ foo[0] }}.',\n      { 'values' => %w(foo bar baz) },\n    )\n\n    assert_template_result(\n      '.bar.',\n      '{% assign foo = values %}.{{ foo[1] }}.',\n      { 'values' => %w(foo bar baz) },\n    )\n  end\n\n  def test_assign_with_filter\n    assert_template_result(\n      '.bar.',\n      '{% assign foo = values | split: \",\" %}.{{ foo[1] }}.',\n      { 'values' => \"foo,bar,baz\" },\n    )\n  end\n\n  def test_assign_syntax_error\n    assert_match_syntax_error(/assign/, '{% assign foo not values %}.')\n  end\n\n  def test_assign_uses_error_mode\n    assert_match_syntax_error(\n      \"Expected dotdot but found pipe in \",\n      \"{% assign foo = ('X' | downcase) %}\",\n      error_mode: :strict,\n    )\n    assert_template_result(\"\", \"{% assign foo = ('X' | downcase) %}\", error_mode: :lax)\n  end\n\n  def test_expression_with_whitespace_in_square_brackets\n    source = \"{% assign r = a[ 'b' ] %}{{ r }}\"\n    assert_template_result('result', source, { 'a' => { 'b' => 'result' } })\n  end\n\n  def test_assign_score_exceeding_resource_limit\n    t = Template.parse(\"{% assign foo = 42 %}{% assign bar = 23 %}\")\n    t.resource_limits.assign_score_limit = 1\n    assert_equal(\"Liquid error: Memory limits exceeded\", t.render)\n    assert(t.resource_limits.reached?)\n\n    t.resource_limits.assign_score_limit = 2\n    assert_equal(\"\", t.render!)\n    refute_nil(t.resource_limits.assign_score)\n  end\n\n  def test_assign_score_exceeding_limit_from_composite_object\n    t = Template.parse(\"{% assign foo = 'aaaa' | reverse %}\")\n\n    t.resource_limits.assign_score_limit = 3\n    assert_equal(\"Liquid error: Memory limits exceeded\", t.render)\n    assert(t.resource_limits.reached?)\n\n    t.resource_limits.assign_score_limit = 5\n    assert_equal(\"\", t.render!)\n  end\n\n  def test_assign_score_of_int\n    assert_equal(1, assign_score_of(123))\n  end\n\n  def test_assign_score_of_string_counts_bytes\n    assert_equal(3, assign_score_of('123'))\n    assert_equal(5, assign_score_of('12345'))\n    assert_equal(9, assign_score_of('すごい'))\n  end\n\n  def test_assign_score_of_array\n    assert_equal(1, assign_score_of([]))\n    assert_equal(2, assign_score_of([123]))\n    assert_equal(6, assign_score_of([123, 'abcd']))\n  end\n\n  def test_assign_score_of_hash\n    assert_equal(1, assign_score_of({}))\n    assert_equal(5, assign_score_of('int' => 123))\n    assert_equal(12, assign_score_of('int' => 123, 'str' => 'abcd'))\n  end\n\n  private\n\n  class ObjectWrapperDrop < Liquid::Drop\n    def initialize(obj)\n      @obj = obj\n    end\n\n    def value\n      @obj\n    end\n  end\n\n  def assign_score_of(obj)\n    context = Liquid::Context.new('drop' => ObjectWrapperDrop.new(obj))\n    Liquid::Template.parse('{% assign obj = drop.value %}').render!(context)\n    context.resource_limits.assign_score\n  end\nend\n"
  },
  {
    "path": "test/integration/blank_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass FoobarTag < Liquid::Tag\n  def render_to_output_buffer(_context, output)\n    output << ' '\n    output\n  end\nend\n\nclass BlankTest < Minitest::Test\n  include Liquid\n  N = 10\n\n  def wrap_in_for(body)\n    \"{% for i in (1..#{N}) %}#{body}{% endfor %}\"\n  end\n\n  def wrap_in_if(body)\n    \"{% if true %}#{body}{% endif %}\"\n  end\n\n  def wrap(body)\n    wrap_in_for(body) + wrap_in_if(body)\n  end\n\n  def test_new_tags_are_not_blank_by_default\n    with_custom_tag('foobar', FoobarTag) do\n      assert_equal(\" \" * N, Liquid::Template.parse(wrap_in_for(\"{% foobar %}\")).render!)\n    end\n  end\n\n  def test_loops_are_blank\n    assert_template_result(\"\", wrap_in_for(\" \"))\n  end\n\n  def test_if_else_are_blank\n    assert_template_result(\"\", \"{% if true %} {% elsif false %} {% else %} {% endif %}\")\n  end\n\n  def test_unless_is_blank\n    assert_template_result(\"\", wrap(\"{% unless true %} {% endunless %}\"))\n  end\n\n  def test_mark_as_blank_only_during_parsing\n    assert_template_result(\" \" * (N + 1), wrap(\" {% if false %} this never happens, but still, this block is not blank {% endif %}\"))\n  end\n\n  def test_comments_are_blank\n    assert_template_result(\"\", wrap(\" {% comment %} whatever {% endcomment %} \"))\n  end\n\n  def test_captures_are_blank\n    assert_template_result(\"\", wrap(\" {% capture foo %} whatever {% endcapture %} \"))\n  end\n\n  def test_nested_blocks_are_blank_but_only_if_all_children_are\n    assert_template_result(\"\", wrap(wrap(\" \")))\n    assert_template_result(\n      \"\\n       but this is not \" * (N + 1),\n      wrap('{% if true %} {% comment %} this is blank {% endcomment %} {% endif %}\n      {% if true %} but this is not {% endif %}'),\n    )\n  end\n\n  def test_assigns_are_blank\n    assert_template_result(\"\", wrap(' {% assign foo = \"bar\" %} '))\n  end\n\n  def test_whitespace_is_blank\n    assert_template_result(\"\", wrap(\" \"))\n    assert_template_result(\"\", wrap(\"\\t\"))\n  end\n\n  def test_whitespace_is_not_blank_if_other_stuff_is_present\n    body = \"     x \"\n    assert_template_result(body * (N + 1), wrap(body))\n  end\n\n  def test_increment_is_not_blank\n    assert_template_result(\" 0\" * 2 * (N + 1), wrap(\"{% assign foo = 0 %} {% increment foo %} {% decrement foo %}\"))\n  end\n\n  def test_cycle_is_not_blank\n    assert_template_result(\"  \" * ((N + 1) / 2) + \" \", wrap(\"{% cycle ' ', ' ' %}\"))\n  end\n\n  def test_raw_is_not_blank\n    assert_template_result(\"  \" * (N + 1), wrap(\" {% raw %} {% endraw %}\"))\n  end\n\n  def test_include_is_blank\n    assert_template_result(\n      \"foobar\" * (N + 1),\n      wrap(\"{% include 'foobar' %}\"),\n      partials: { 'foobar' => 'foobar' },\n    )\n    assert_template_result(\n      \" foobar \" * (N + 1),\n      wrap(\"{% include ' foobar ' %}\"),\n      partials: { ' foobar ' => ' foobar ' },\n    )\n    assert_template_result(\n      \"   \" * (N + 1),\n      wrap(\" {% include ' ' %} \"),\n      partials: { ' ' => ' ' },\n    )\n  end\n\n  def test_case_is_blank\n    assert_template_result(\"\", wrap(\" {% assign foo = 'bar' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} {% endcase %} \"))\n    assert_template_result(\"\", wrap(\" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} {% endcase %} \"))\n    assert_template_result(\"   x  \" * (N + 1), wrap(\" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} x {% endcase %} \"))\n  end\nend\n"
  },
  {
    "path": "test/integration/block_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass BlockTest < Minitest::Test\n  include Liquid\n\n  def test_unexpected_end_tag\n    source = '{% if true %}{% endunless %}'\n    assert_match_syntax_error(\"Liquid syntax error (line 1): 'endunless' is not a valid delimiter for if tags. use endif\", source)\n  end\n\n  def test_with_custom_tag\n    with_custom_tag('testtag', Block) do\n      assert(Liquid::Template.parse(\"{% testtag %} {% endtesttag %}\"))\n    end\n  end\n\n  def test_custom_block_tags_have_a_default_render_to_output_buffer_method_for_backwards_compatibility\n    klass1 = Class.new(Block) do\n      def render(*)\n        'hello'\n      end\n    end\n\n    with_custom_tag('blabla', klass1) do\n      template = Liquid::Template.parse(\"{% blabla %} bla {% endblabla %}\")\n\n      assert_equal('hello', template.render)\n\n      buf    = +''\n      output = template.render({}, output: buf)\n      assert_equal('hello', output)\n      assert_equal('hello', buf)\n      assert_equal(buf.object_id, output.object_id)\n    end\n\n    klass2 = Class.new(klass1) do\n      def render(*)\n        'foo' + super + 'bar'\n      end\n    end\n\n    with_custom_tag('blabla', klass2) do\n      template = Liquid::Template.parse(\"{% blabla %} foo {% endblabla %}\")\n\n      assert_equal('foohellobar', template.render)\n\n      buf    = +''\n      output = template.render({}, output: buf)\n      assert_equal('foohellobar', output)\n      assert_equal('foohellobar', buf)\n      assert_equal(buf.object_id, output.object_id)\n    end\n  end\nend\n"
  },
  {
    "path": "test/integration/capture_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass CaptureTest < Minitest::Test\n  include Liquid\n\n  def test_captures_block_content_in_variable\n    assert_template_result(\"test string\", \"{% capture 'var' %}test string{% endcapture %}{{var}}\", {})\n  end\n\n  def test_capture_with_hyphen_in_variable_name\n    template_source = <<~END_TEMPLATE\n      {% capture this-thing %}Print this-thing{% endcapture -%}\n      {{ this-thing -}}\n    END_TEMPLATE\n    assert_template_result(\"Print this-thing\", template_source)\n  end\n\n  def test_capture_to_variable_from_outer_scope_if_existing\n    template_source = <<~END_TEMPLATE\n      {% assign var = '' -%}\n      {% if true -%}\n        {% capture var %}first-block-string{% endcapture -%}\n      {% endif -%}\n      {% if true -%}\n        {% capture var %}test-string{% endcapture -%}\n      {% endif -%}\n      {{var-}}\n    END_TEMPLATE\n    assert_template_result(\"test-string\", template_source)\n  end\n\n  def test_assigning_from_capture\n    template_source = <<~END_TEMPLATE\n      {% assign first = '' -%}\n      {% assign second = '' -%}\n      {% for number in (1..3) -%}\n        {% capture first %}{{number}}{% endcapture -%}\n        {% assign second = first -%}\n      {% endfor -%}\n      {{ first }}-{{ second -}}\n    END_TEMPLATE\n    assert_template_result(\"3-3\", template_source)\n  end\n\n  def test_increment_assign_score_by_bytes_not_characters\n    t = Template.parse(\"{% capture foo %}すごい{% endcapture %}\")\n    t.render!\n    assert_equal(9, t.resource_limits.assign_score)\n  end\nend\n"
  },
  {
    "path": "test/integration/context_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass HundredCentes\n  def to_liquid\n    100\n  end\nend\n\nclass CentsDrop < Liquid::Drop\n  def amount\n    HundredCentes.new\n  end\n\n  def non_zero?\n    true\n  end\nend\n\nclass ContextSensitiveDrop < Liquid::Drop\n  def test\n    @context['test']\n  end\nend\n\nclass Category\n  attr_accessor :name\n\n  def initialize(name)\n    @name = name\n  end\n\n  def to_liquid\n    CategoryDrop.new(self)\n  end\nend\n\nclass ProductsDrop < Liquid::Drop\n  def initialize(products)\n    @products = products\n  end\n\n  def size\n    @products.size\n  end\n\n  def to_liquid\n    if @context[\"forloop\"]\n      @products.first(@context[\"forloop\"].length)\n    else\n      @products\n    end\n  end\nend\n\nclass CategoryDrop < Liquid::Drop\n  attr_accessor :category, :context\n\n  def initialize(category)\n    @category = category\n  end\nend\n\nclass CounterDrop < Liquid::Drop\n  def count\n    @count ||= 0\n    @count  += 1\n  end\nend\n\nclass ArrayLike\n  def fetch(index)\n  end\n\n  def [](index)\n    @counts        ||= []\n    @counts[index] ||= 0\n    @counts[index]  += 1\n  end\n\n  def to_liquid\n    self\n  end\nend\n\nclass ContextTest < Minitest::Test\n  include Liquid\n\n  def setup\n    @context = Liquid::Context.new\n  end\n\n  def test_variables\n    @context['string'] = 'string'\n    assert_equal('string', @context['string'])\n\n    @context['num'] = 5\n    assert_equal(5, @context['num'])\n\n    @context['time'] = Time.parse('2006-06-06 12:00:00')\n    assert_equal(Time.parse('2006-06-06 12:00:00'), @context['time'])\n\n    @context['date'] = Date.today\n    assert_equal(Date.today, @context['date'])\n\n    now = Time.now\n    @context['datetime'] = now\n    assert_equal(now, @context['datetime'])\n\n    @context['bool'] = true\n    assert_equal(true, @context['bool'])\n\n    @context['bool'] = false\n    assert_equal(false, @context['bool'])\n\n    @context['nil'] = nil\n    assert_nil(@context['nil'])\n    assert_nil(@context['nil'])\n  end\n\n  def test_variables_not_existing\n    assert_template_result(\"true\", \"{% if does_not_exist == nil %}true{% endif %}\")\n  end\n\n  def test_scoping\n    @context.push\n    @context.pop\n\n    assert_raises(Liquid::ContextError) do\n      @context.pop\n    end\n\n    assert_raises(Liquid::ContextError) do\n      @context.push\n      @context.pop\n      @context.pop\n    end\n  end\n\n  def test_length_query\n    assert_template_result(\n      \"true\",\n      \"{% if numbers.size == 4 %}true{% endif %}\",\n      { \"numbers\" => [1, 2, 3, 4] },\n    )\n\n    assert_template_result(\n      \"true\",\n      \"{% if numbers.size == 4 %}true{% endif %}\",\n      { \"numbers\" => { 1 => 1, 2 => 2, 3 => 3, 4 => 4 } },\n    )\n\n    assert_template_result(\n      \"true\",\n      \"{% if numbers.size == 1000 %}true{% endif %}\",\n      { \"numbers\" => { 1 => 1, 2 => 2, 3 => 3, 4 => 4, 'size' => 1000 } },\n    )\n  end\n\n  def test_hyphenated_variable\n    assert_template_result(\"godz\", \"{{ oh-my }}\", { \"oh-my\" => 'godz' })\n  end\n\n  def test_add_filter\n    filter = Module.new do\n      def hi(output)\n        output + ' hi!'\n      end\n    end\n\n    context = Context.new\n    context.add_filters(filter)\n    assert_equal('hi? hi!', context.invoke(:hi, 'hi?'))\n\n    context = Context.new\n    assert_equal('hi?', context.invoke(:hi, 'hi?'))\n\n    context.add_filters(filter)\n    assert_equal('hi? hi!', context.invoke(:hi, 'hi?'))\n  end\n\n  def test_only_intended_filters_make_it_there\n    filter = Module.new do\n      def hi(output)\n        output + ' hi!'\n      end\n    end\n\n    context = Context.new\n    assert_equal(\"Wookie\", context.invoke(\"hi\", \"Wookie\"))\n\n    context.add_filters(filter)\n    assert_equal(\"Wookie hi!\", context.invoke(\"hi\", \"Wookie\"))\n  end\n\n  def test_add_item_in_outer_scope\n    @context['test'] = 'test'\n    @context.push\n    assert_equal('test', @context['test'])\n    @context.pop\n    assert_equal('test', @context['test'])\n  end\n\n  def test_add_item_in_inner_scope\n    @context.push\n    @context['test'] = 'test'\n    assert_equal('test', @context['test'])\n    @context.pop\n    assert_nil(@context['test'])\n  end\n\n  def test_hierachical_data\n    assigns = { 'hash' => { \"name\" => 'tobi' } }\n    assert_template_result(\"tobi\", \"{{ hash.name }}\", assigns)\n    assert_template_result(\"tobi\", '{{ hash[\"name\"] }}', assigns)\n  end\n\n  def test_keywords\n    assert_template_result(\"pass\", \"{% if true == expect %}pass{% endif %}\", { \"expect\" => true })\n    assert_template_result(\"pass\", \"{% if false == expect %}pass{% endif %}\", { \"expect\" => false })\n  end\n\n  def test_digits\n    assert_template_result(\"pass\", \"{% if 100 == expect %}pass{% endif %}\", { \"expect\" => 100 })\n    assert_template_result(\"pass\", \"{% if 100.00 == expect %}pass{% endif %}\", { \"expect\" => 100.00 })\n  end\n\n  def test_strings\n    assert_template_result(\"hello!\", '{{ \"hello!\" }}')\n    assert_template_result(\"hello!\", \"{{ 'hello!' }}\")\n  end\n\n  def test_merge\n    @context.merge(\"test\" => \"test\")\n    assert_equal('test', @context['test'])\n    @context.merge(\"test\" => \"newvalue\", \"foo\" => \"bar\")\n    assert_equal('newvalue', @context['test'])\n    assert_equal('bar', @context['foo'])\n  end\n\n  def test_array_notation\n    assigns = { \"test\" => [\"a\", \"b\"] }\n    assert_template_result(\"a\", \"{{ test[0] }}\", assigns)\n    assert_template_result(\"b\", \"{{ test[1] }}\", assigns)\n    assert_template_result(\"pass\", \"{% if test[2] == nil %}pass{% endif %}\", assigns)\n  end\n\n  def test_recoursive_array_notation\n    assigns = { \"test\" => { 'test' => [1, 2, 3, 4, 5] } }\n    assert_template_result(\"1\", \"{{ test.test[0] }}\", assigns)\n\n    assigns = { \"test\" => [{ 'test' => 'worked' }] }\n    assert_template_result(\"worked\", \"{{ test[0].test }}\", assigns)\n  end\n\n  def test_hash_to_array_transition\n    assigns = {\n      'colors' => {\n        'Blue' => ['003366', '336699', '6699CC', '99CCFF'],\n        'Green' => ['003300', '336633', '669966', '99CC99'],\n        'Yellow' => ['CC9900', 'FFCC00', 'FFFF99', 'FFFFCC'],\n        'Red' => ['660000', '993333', 'CC6666', 'FF9999'],\n      },\n    }\n\n    assert_template_result(\"003366\", \"{{ colors.Blue[0] }}\", assigns)\n    assert_template_result(\"FF9999\", \"{{ colors.Red[3] }}\", assigns)\n  end\n\n  def test_try_first\n    assigns = { 'test' => [1, 2, 3, 4, 5] }\n    assert_template_result(\"1\", \"{{ test.first }}\", assigns)\n    assert_template_result(\"pass\", \"{% if test.last == 5 %}pass{% endif %}\", assigns)\n\n    assigns = { \"test\" => { \"test\" => [1, 2, 3, 4, 5] } }\n    assert_template_result(\"1\", \"{{ test.test.first }}\", assigns)\n    assert_template_result(\"5\", \"{{ test.test.last }}\", assigns)\n\n    assigns = { \"test\" => [1] }\n    assert_template_result(\"1\", \"{{ test.first }}\", assigns)\n    assert_template_result(\"1\", \"{{ test.last }}\", assigns)\n  end\n\n  def test_access_hashes_with_hash_notation\n    assigns = { 'products' => { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] } }\n    assert_template_result(\"5\", '{{ products[\"count\"] }}', assigns)\n    assert_template_result(\"deepsnow\", '{{ products[\"tags\"][0] }}', assigns)\n    assert_template_result(\"deepsnow\", '{{ products[\"tags\"].first }}', assigns)\n\n    assigns = { 'product' => { 'variants' => [{ 'title' => 'draft151cm' }, { 'title' => 'element151cm' }] } }\n    assert_template_result(\"draft151cm\", '{{ product[\"variants\"][0][\"title\"] }}', assigns)\n    assert_template_result(\"element151cm\", '{{ product[\"variants\"][1][\"title\"] }}', assigns)\n    assert_template_result(\"draft151cm\", '{{ product[\"variants\"].first[\"title\"] }}', assigns)\n    assert_template_result(\"element151cm\", '{{ product[\"variants\"].last[\"title\"] }}', assigns)\n  end\n\n  def test_access_variable_with_hash_notation\n    assert_template_result('baz', '{{ [\"foo\"] }}', { \"foo\" => \"baz\" })\n    assert_template_result('baz', '{{ [bar] }}', { 'foo' => 'baz', 'bar' => 'foo' })\n  end\n\n  def test_access_hashes_with_hash_access_variables\n    assigns = {\n      'var' => 'tags',\n      'nested' => { 'var' => 'tags' },\n      'products' => { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] },\n    }\n\n    assert_template_result('deepsnow', '{{ products[var].first }}', assigns)\n    assert_template_result('freestyle', '{{ products[nested.var].last }}', assigns)\n  end\n\n  def test_hash_notation_only_for_hash_access\n    assigns = { \"array\" => [1, 2, 3, 4, 5] }\n    assert_template_result(\"1\", \"{{ array.first }}\", assigns)\n    assert_template_result(\"pass\", '{% if array[\"first\"] == nil %}pass{% endif %}', assigns)\n\n    assert_template_result(\"Hello\", '{{ hash[\"first\"] }}', { \"hash\" => { \"first\" => \"Hello\" } })\n  end\n\n  def test_first_can_appear_in_middle_of_callchain\n    assigns = { \"product\" => { 'variants' => [{ 'title' => 'draft151cm' }, { 'title' => 'element151cm' }] } }\n\n    assert_template_result('draft151cm', '{{ product.variants[0].title }}', assigns)\n    assert_template_result('element151cm', '{{ product.variants[1].title }}', assigns)\n    assert_template_result('draft151cm', '{{ product.variants.first.title }}', assigns)\n    assert_template_result('element151cm', '{{ product.variants.last.title }}', assigns)\n  end\n\n  def test_cents\n    @context.merge(\"cents\" => HundredCentes.new)\n    assert_equal(100, @context['cents'])\n  end\n\n  def test_nested_cents\n    @context.merge(\"cents\" => { 'amount' => HundredCentes.new })\n    assert_equal(100, @context['cents.amount'])\n\n    @context.merge(\"cents\" => { 'cents' => { 'amount' => HundredCentes.new } })\n    assert_equal(100, @context['cents.cents.amount'])\n  end\n\n  def test_cents_through_drop\n    @context.merge(\"cents\" => CentsDrop.new)\n    assert_equal(100, @context['cents.amount'])\n  end\n\n  def test_nested_cents_through_drop\n    @context.merge(\"vars\" => { \"cents\" => CentsDrop.new })\n    assert_equal(100, @context['vars.cents.amount'])\n  end\n\n  def test_drop_methods_with_question_marks\n    @context.merge(\"cents\" => CentsDrop.new)\n    assert(@context['cents.non_zero?'])\n  end\n\n  def test_context_from_within_drop\n    @context.merge(\"test\" => '123', \"vars\" => ContextSensitiveDrop.new)\n    assert_equal('123', @context['vars.test'])\n  end\n\n  def test_nested_context_from_within_drop\n    @context.merge(\"test\" => '123', \"vars\" => { \"local\" => ContextSensitiveDrop.new })\n    assert_equal('123', @context['vars.local.test'])\n  end\n\n  def test_ranges\n    assert_template_result(\"1..5\", '{{ (1..5) }}')\n    assert_template_result(\"pass\", '{% if (1..5) == expect %}pass{% endif %}', { \"expect\" => (1..5) })\n\n    assigns = { \"test\" => '5' }\n    assert_template_result(\"1..5\", \"{{ (1..test) }}\", assigns)\n    assert_template_result(\"5..5\", \"{{ (test..test) }}\", assigns)\n  end\n\n  def test_cents_through_drop_nestedly\n    @context.merge(\"cents\" => { \"cents\" => CentsDrop.new })\n    assert_equal(100, @context['cents.cents.amount'])\n\n    @context.merge(\"cents\" => { \"cents\" => { \"cents\" => CentsDrop.new } })\n    assert_equal(100, @context['cents.cents.cents.amount'])\n  end\n\n  def test_drop_with_variable_called_only_once\n    @context['counter'] = CounterDrop.new\n\n    assert_equal(1, @context['counter.count'])\n    assert_equal(2, @context['counter.count'])\n    assert_equal(3, @context['counter.count'])\n  end\n\n  def test_drop_with_key_called_only_once\n    @context['counter'] = CounterDrop.new\n\n    assert_equal(1, @context['counter[\"count\"]'])\n    assert_equal(2, @context['counter[\"count\"]'])\n    assert_equal(3, @context['counter[\"count\"]'])\n  end\n\n  def test_proc_as_variable\n    @context['dynamic'] = proc { 'Hello' }\n\n    assert_equal('Hello', @context['dynamic'])\n  end\n\n  def test_lambda_as_variable\n    @context['dynamic'] = proc { 'Hello' }\n\n    assert_equal('Hello', @context['dynamic'])\n  end\n\n  def test_nested_lambda_as_variable\n    @context['dynamic'] = { \"lambda\" => proc { 'Hello' } }\n\n    assert_equal('Hello', @context['dynamic.lambda'])\n  end\n\n  def test_array_containing_lambda_as_variable\n    @context['dynamic'] = [1, 2, proc { 'Hello' }, 4, 5]\n\n    assert_equal('Hello', @context['dynamic[2]'])\n  end\n\n  def test_lambda_is_called_once\n    @global = 0\n\n    @context['callcount'] = proc {\n      @global += 1\n      @global.to_s\n    }\n\n    assert_equal('1', @context['callcount'])\n    assert_equal('1', @context['callcount'])\n    assert_equal('1', @context['callcount'])\n  end\n\n  def test_nested_lambda_is_called_once\n    @global = 0\n\n    @context['callcount'] = {\n      \"lambda\" => proc {\n                    @global += 1\n                    @global.to_s\n                  },\n    }\n\n    assert_equal('1', @context['callcount.lambda'])\n    assert_equal('1', @context['callcount.lambda'])\n    assert_equal('1', @context['callcount.lambda'])\n  end\n\n  def test_lambda_in_array_is_called_once\n    @global = 0\n\n    p = proc {\n      @global += 1\n      @global.to_s\n    }\n    @context['callcount'] = [1, 2, p, 4, 5]\n\n    assert_equal('1', @context['callcount[2]'])\n    assert_equal('1', @context['callcount[2]'])\n    assert_equal('1', @context['callcount[2]'])\n  end\n\n  def test_access_to_context_from_proc\n    @context.registers[:magic] = 345392\n\n    @context['magic'] = proc { @context.registers[:magic] }\n\n    assert_equal(345392, @context['magic'])\n  end\n\n  def test_to_liquid_and_context_at_first_level\n    @context['category'] = Category.new(\"foobar\")\n    assert_kind_of(CategoryDrop, @context['category'])\n    assert_equal(@context, @context['category'].context)\n  end\n\n  def test_interrupt_avoids_object_allocations\n    @context.interrupt? # ruby 3.0.0 allocates on the first call\n    assert_no_object_allocations do\n      @context.interrupt?\n    end\n  end\n\n  def test_context_initialization_with_a_proc_in_environment\n    contx = Context.new([test: ->(c) { c['poutine'] }], test: :foo)\n\n    assert(contx)\n    assert_nil(contx['poutine'])\n  end\n\n  def test_apply_global_filter\n    global_filter_proc = ->(output) { \"#{output} filtered\" }\n\n    context = Context.new\n    context.global_filter = global_filter_proc\n\n    assert_equal('hi filtered', context.apply_global_filter('hi'))\n  end\n\n  def test_static_environments_are_read_with_lower_priority_than_environments\n    context = Context.build(\n      static_environments: { 'shadowed' => 'static', 'unshadowed' => 'static' },\n      environments: { 'shadowed' => 'dynamic' },\n    )\n\n    assert_equal('dynamic', context['shadowed'])\n    assert_equal('static', context['unshadowed'])\n  end\n\n  def test_apply_global_filter_when_no_global_filter_exist\n    context = Context.new\n    assert_equal('hi', context.apply_global_filter('hi'))\n  end\n\n  def test_new_isolated_subcontext_does_not_inherit_variables\n    super_context = Context.new\n    super_context['my_variable'] = 'some value'\n    subcontext = super_context.new_isolated_subcontext\n\n    assert_nil(subcontext['my_variable'])\n  end\n\n  def test_new_isolated_subcontext_inherits_static_environment\n    super_context = Context.build(static_environments: { 'my_environment_value' => 'my value' })\n    subcontext    = super_context.new_isolated_subcontext\n\n    assert_equal('my value', subcontext['my_environment_value'])\n  end\n\n  def test_new_isolated_subcontext_inherits_resource_limits\n    resource_limits = ResourceLimits.new({})\n    super_context   = Context.new({}, {}, {}, false, resource_limits)\n    subcontext      = super_context.new_isolated_subcontext\n    assert_equal(resource_limits, subcontext.resource_limits)\n  end\n\n  def test_new_isolated_subcontext_inherits_exception_renderer\n    super_context = Context.new\n    super_context.exception_renderer = ->(_e) { 'my exception message' }\n    subcontext = super_context.new_isolated_subcontext\n    assert_equal('my exception message', subcontext.handle_error(Liquid::Error.new))\n  end\n\n  def test_new_isolated_subcontext_does_not_inherit_non_static_registers\n    registers = {\n      my_register: :my_value,\n    }\n    super_context = Context.new({}, {}, Registers.new(registers))\n    super_context.registers[:my_register] = :my_alt_value\n    subcontext                            = super_context.new_isolated_subcontext\n    assert_equal(:my_value, subcontext.registers[:my_register])\n  end\n\n  def test_new_isolated_subcontext_inherits_static_registers\n    super_context = Context.build(registers: { my_register: :my_value })\n    subcontext    = super_context.new_isolated_subcontext\n    assert_equal(:my_value, subcontext.registers[:my_register])\n  end\n\n  def test_new_isolated_subcontext_registers_do_not_pollute_context\n    super_context                      = Context.build(registers: { my_register: :my_value })\n    subcontext                         = super_context.new_isolated_subcontext\n    subcontext.registers[:my_register] = :my_alt_value\n    assert_equal(:my_value, super_context.registers[:my_register])\n  end\n\n  def test_new_isolated_subcontext_inherits_filters\n    my_filter = Module.new do\n      def my_filter(*)\n        'my filter result'\n      end\n    end\n\n    super_context = Context.new\n    super_context.add_filters([my_filter])\n    subcontext    = super_context.new_isolated_subcontext\n    template      = Template.parse('{{ 123 | my_filter }}')\n    assert_equal('my filter result', template.render(subcontext))\n  end\n\n  def test_disables_tag_specified\n    context = Context.new\n    context.with_disabled_tags(%w(foo bar)) do\n      assert_equal(true, context.tag_disabled?(\"foo\"))\n      assert_equal(true, context.tag_disabled?(\"bar\"))\n      assert_equal(false, context.tag_disabled?(\"unknown\"))\n    end\n  end\n\n  def test_disables_nested_tags\n    context = Context.new\n    context.with_disabled_tags([\"foo\"]) do\n      context.with_disabled_tags([\"foo\"]) do\n        assert_equal(true, context.tag_disabled?(\"foo\"))\n        assert_equal(false, context.tag_disabled?(\"bar\"))\n      end\n      context.with_disabled_tags([\"bar\"]) do\n        assert_equal(true, context.tag_disabled?(\"foo\"))\n        assert_equal(true, context.tag_disabled?(\"bar\"))\n        context.with_disabled_tags([\"foo\"]) do\n          assert_equal(true, context.tag_disabled?(\"foo\"))\n          assert_equal(true, context.tag_disabled?(\"bar\"))\n        end\n      end\n      assert_equal(true, context.tag_disabled?(\"foo\"))\n      assert_equal(false, context.tag_disabled?(\"bar\"))\n    end\n  end\n\n  def test_override_global_filter\n    global = Module.new do\n      def notice(output)\n        \"Global #{output}\"\n      end\n    end\n\n    local = Module.new do\n      def notice(output)\n        \"Local #{output}\"\n      end\n    end\n\n    with_global_filter(global) do\n      assert_equal('Global test', Template.parse(\"{{'test' | notice }}\").render!)\n      assert_equal('Local test', Template.parse(\"{{'test' | notice }}\").render!({}, filters: [local]))\n    end\n  end\n\n  def test_has_key_will_not_add_an_error_for_missing_keys\n    with_error_modes(:strict) do\n      context = Context.new\n      context.key?('unknown')\n      assert_empty(context.errors)\n    end\n  end\n\n  def test_key_lookup_will_raise_for_missing_keys_when_strict_variables_is_enabled\n    context = Context.new\n    context.strict_variables = true\n    assert_raises(Liquid::UndefinedVariable) do\n      context['unknown']\n    end\n  end\n\n  def test_has_key_will_not_raise_for_missing_keys_when_strict_variables_is_enabled\n    context = Context.new\n    context.strict_variables = true\n    refute(context.key?('unknown'))\n    assert_empty(context.errors)\n  end\n\n  def test_context_always_uses_static_registers\n    registers = {\n      my_register: :my_value,\n    }\n    c = Context.new({}, {}, registers)\n    assert_instance_of(Registers, c.registers)\n    assert_equal(:my_value, c.registers[:my_register])\n\n    r = Registers.new(registers)\n    c = Context.new({}, {}, r)\n    assert_instance_of(Registers, c.registers)\n    assert_equal(:my_value, c.registers[:my_register])\n  end\n\n  def test_variable_to_liquid_returns_contextual_drop\n    context = {\n      \"products\" => ProductsDrop.new([\"A\", \"B\", \"C\", \"D\", \"E\"]),\n    }\n\n    template = Liquid::Template.parse(<<~LIQUID)\n      {%- for i in (1..3) -%}\n        for_loop_products_count: {{ products | size }}\n      {% endfor %}\n\n      unscoped_products_count: {{ products | size }}\n    LIQUID\n\n    result = template.render(context)\n\n    assert_includes(result, \"for_loop_products_count: 3\")\n    assert_includes(result, \"unscoped_products_count: 5\")\n  end\n\n  def test_new_isolated_context_inherits_parent_environment\n    global_environment = Liquid::Environment.build(tags: {})\n    context = Context.build(environment: global_environment)\n\n    subcontext = context.new_isolated_subcontext\n    assert_equal(global_environment, subcontext.environment)\n  end\n\n  def test_newly_built_context_inherits_parent_environment\n    global_environment = Liquid::Environment.build(tags: {})\n    context = Context.build(environment: global_environment)\n    assert_equal(global_environment, context.environment)\n    assert(context.environment.tags.each.to_a.empty?)\n  end\n\n  private\n\n  def assert_no_object_allocations\n    unless RUBY_ENGINE == 'ruby'\n      skip(\"stackprof needed to count object allocations\")\n    end\n    require 'stackprof'\n\n    profile = StackProf.run(mode: :object) do\n      yield\n    end\n    assert_equal(0, profile[:samples])\n  end\nend # ContextTest\n"
  },
  {
    "path": "test/integration/document_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass DocumentTest < Minitest::Test\n  include Liquid\n\n  def test_unexpected_outer_tag\n    source = \"{% else %}\"\n    assert_match_syntax_error(\"Liquid syntax error (line 1): Unexpected outer 'else' tag\", source)\n  end\n\n  def test_unknown_tag\n    source = \"{% foo %}\"\n    assert_match_syntax_error(\"Liquid syntax error (line 1): Unknown tag 'foo'\", source)\n  end\nend\n"
  },
  {
    "path": "test/integration/drop_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ContextDrop < Liquid::Drop\n  def scopes\n    @context.scopes.size\n  end\n\n  def scopes_as_array\n    (1..@context.scopes.size).to_a\n  end\n\n  def loop_pos\n    @context['forloop.index']\n  end\n\n  def liquid_method_missing(method)\n    @context[method]\n  end\nend\n\nclass ProductDrop < Liquid::Drop\n  class TextDrop < Liquid::Drop\n    def array\n      ['text1', 'text2']\n    end\n\n    def text\n      'text1'\n    end\n  end\n\n  class CatchallDrop < Liquid::Drop\n    def liquid_method_missing(method)\n      \"catchall_method: #{method}\"\n    end\n  end\n\n  def texts\n    TextDrop.new\n  end\n\n  def catchall\n    CatchallDrop.new\n  end\n\n  def context\n    ContextDrop.new\n  end\n\n  protected\n\n  def callmenot\n    \"protected\"\n  end\nend\n\nclass EnumerableDrop < Liquid::Drop\n  def liquid_method_missing(method)\n    method\n  end\n\n  def size\n    3\n  end\n\n  def first\n    1\n  end\n\n  def count\n    3\n  end\n\n  def min\n    1\n  end\n\n  def max\n    3\n  end\n\n  def each\n    yield 1\n    yield 2\n    yield 3\n  end\nend\n\nclass RealEnumerableDrop < Liquid::Drop\n  include Enumerable\n\n  def liquid_method_missing(method)\n    method\n  end\n\n  def each\n    yield 1\n    yield 2\n    yield 3\n  end\nend\n\nclass DropsTest < Minitest::Test\n  include Liquid\n\n  def test_product_drop\n    tpl = Liquid::Template.parse('  ')\n    assert_equal('  ', tpl.render!('product' => ProductDrop.new))\n  end\n\n  def test_drop_does_only_respond_to_whitelisted_methods\n    assert_equal(\"\", Liquid::Template.parse(\"{{ product.inspect }}\").render!('product' => ProductDrop.new))\n    assert_equal(\"\", Liquid::Template.parse(\"{{ product.pretty_inspect }}\").render!('product' => ProductDrop.new))\n    assert_equal(\"\", Liquid::Template.parse(\"{{ product.whatever }}\").render!('product' => ProductDrop.new))\n    assert_equal(\"\", Liquid::Template.parse('{{ product | map: \"inspect\" }}').render!('product' => ProductDrop.new))\n    assert_equal(\"\", Liquid::Template.parse('{{ product | map: \"pretty_inspect\" }}').render!('product' => ProductDrop.new))\n    assert_equal(\"\", Liquid::Template.parse('{{ product | map: \"whatever\" }}').render!('product' => ProductDrop.new))\n  end\n\n  def test_drops_respond_to_to_liquid\n    assert_equal(\"text1\", Liquid::Template.parse(\"{{ product.to_liquid.texts.text }}\").render!('product' => ProductDrop.new))\n    assert_equal(\"text1\", Liquid::Template.parse('{{ product | map: \"to_liquid\" | map: \"texts\" | map: \"text\" }}').render!('product' => ProductDrop.new))\n  end\n\n  def test_text_drop\n    output = Liquid::Template.parse(' {{ product.texts.text }} ').render!('product' => ProductDrop.new)\n    assert_equal(' text1 ', output)\n  end\n\n  def test_catchall_unknown_method\n    output = Liquid::Template.parse(' {{ product.catchall.unknown }} ').render!('product' => ProductDrop.new)\n    assert_equal(' catchall_method: unknown ', output)\n  end\n\n  def test_catchall_integer_argument_drop\n    output = Liquid::Template.parse(' {{ product.catchall[8] }} ').render!('product' => ProductDrop.new)\n    assert_equal(' catchall_method: 8 ', output)\n  end\n\n  def test_text_array_drop\n    output = Liquid::Template.parse('{% for text in product.texts.array %} {{text}} {% endfor %}').render!('product' => ProductDrop.new)\n    assert_equal(' text1  text2 ', output)\n  end\n\n  def test_context_drop\n    output = Liquid::Template.parse(' {{ context.bar }} ').render!('context' => ContextDrop.new, 'bar' => \"carrot\")\n    assert_equal(' carrot ', output)\n  end\n\n  def test_context_drop_array_with_map\n    output = Liquid::Template.parse(' {{ contexts | map: \"bar\" }} ').render!('contexts' => [ContextDrop.new, ContextDrop.new], 'bar' => \"carrot\")\n    assert_equal(' carrotcarrot ', output)\n  end\n\n  def test_nested_context_drop\n    output = Liquid::Template.parse(' {{ product.context.foo }} ').render!('product' => ProductDrop.new, 'foo' => \"monkey\")\n    assert_equal(' monkey ', output)\n  end\n\n  def test_protected\n    output = Liquid::Template.parse(' {{ product.callmenot }} ').render!('product' => ProductDrop.new)\n    assert_equal('  ', output)\n  end\n\n  def test_object_methods_not_allowed\n    [:dup, :clone, :singleton_class, :eval, :class_eval, :inspect].each do |method|\n      output = Liquid::Template.parse(\" {{ product.#{method} }} \").render!('product' => ProductDrop.new)\n      assert_equal('  ', output)\n    end\n  end\n\n  def test_scope\n    assert_equal('1', Liquid::Template.parse('{{ context.scopes }}').render!('context' => ContextDrop.new))\n    assert_equal('2', Liquid::Template.parse('{%for i in dummy%}{{ context.scopes }}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1]))\n    assert_equal('3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1]))\n  end\n\n  def test_scope_though_proc\n    assert_equal('1', Liquid::Template.parse('{{ s }}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] }))\n    assert_equal('2', Liquid::Template.parse('{%for i in dummy%}{{ s }}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] }, 'dummy' => [1]))\n    assert_equal('3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] }, 'dummy' => [1]))\n  end\n\n  def test_scope_with_assigns\n    assert_equal('variable', Liquid::Template.parse('{% assign a = \"variable\"%}{{a}}').render!('context' => ContextDrop.new))\n    assert_equal('variable', Liquid::Template.parse('{% assign a = \"variable\"%}{%for i in dummy%}{{a}}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1]))\n    assert_equal('test', Liquid::Template.parse('{% assign header_gif = \"test\"%}{{header_gif}}').render!('context' => ContextDrop.new))\n    assert_equal('test', Liquid::Template.parse(\"{% assign header_gif = 'test'%}{{header_gif}}\").render!('context' => ContextDrop.new))\n  end\n\n  def test_scope_from_tags\n    assert_equal('1', Liquid::Template.parse('{% for i in context.scopes_as_array %}{{i}}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1]))\n    assert_equal('12', Liquid::Template.parse('{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1]))\n    assert_equal('123', Liquid::Template.parse('{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1]))\n  end\n\n  def test_access_context_from_drop\n    assert_equal('123', Liquid::Template.parse('{%for a in dummy%}{{ context.loop_pos }}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1, 2, 3]))\n  end\n\n  def test_enumerable_drop\n    assert_equal('123', Liquid::Template.parse('{% for c in collection %}{{c}}{% endfor %}').render!('collection' => EnumerableDrop.new))\n  end\n\n  def test_enumerable_drop_size\n    assert_equal('3', Liquid::Template.parse('{{collection.size}}').render!('collection' => EnumerableDrop.new))\n  end\n\n  def test_enumerable_drop_will_invoke_liquid_method_missing_for_clashing_method_names\n    [\"select\", \"each\", \"map\", \"cycle\"].each do |method|\n      assert_equal(method.to_s, Liquid::Template.parse(\"{{collection.#{method}}}\").render!('collection' => EnumerableDrop.new))\n      assert_equal(method.to_s, Liquid::Template.parse(\"{{collection[\\\"#{method}\\\"]}}\").render!('collection' => EnumerableDrop.new))\n      assert_equal(method.to_s, Liquid::Template.parse(\"{{collection.#{method}}}\").render!('collection' => RealEnumerableDrop.new))\n      assert_equal(method.to_s, Liquid::Template.parse(\"{{collection[\\\"#{method}\\\"]}}\").render!('collection' => RealEnumerableDrop.new))\n    end\n  end\n\n  def test_some_enumerable_methods_still_get_invoked\n    [:count, :max].each do |method|\n      assert_equal(\"3\", Liquid::Template.parse(\"{{collection.#{method}}}\").render!('collection' => RealEnumerableDrop.new))\n      assert_equal(\"3\", Liquid::Template.parse(\"{{collection[\\\"#{method}\\\"]}}\").render!('collection' => RealEnumerableDrop.new))\n      assert_equal(\"3\", Liquid::Template.parse(\"{{collection.#{method}}}\").render!('collection' => EnumerableDrop.new))\n      assert_equal(\"3\", Liquid::Template.parse(\"{{collection[\\\"#{method}\\\"]}}\").render!('collection' => EnumerableDrop.new))\n    end\n\n    assert_equal(\"yes\", Liquid::Template.parse(\"{% if collection contains 3 %}yes{% endif %}\").render!('collection' => RealEnumerableDrop.new))\n\n    [:min, :first].each do |method|\n      assert_equal(\"1\", Liquid::Template.parse(\"{{collection.#{method}}}\").render!('collection' => RealEnumerableDrop.new))\n      assert_equal(\"1\", Liquid::Template.parse(\"{{collection[\\\"#{method}\\\"]}}\").render!('collection' => RealEnumerableDrop.new))\n      assert_equal(\"1\", Liquid::Template.parse(\"{{collection.#{method}}}\").render!('collection' => EnumerableDrop.new))\n      assert_equal(\"1\", Liquid::Template.parse(\"{{collection[\\\"#{method}\\\"]}}\").render!('collection' => EnumerableDrop.new))\n    end\n  end\n\n  def test_empty_string_value_access\n    assert_equal('', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => ''))\n  end\n\n  def test_nil_value_access\n    assert_equal('', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => nil))\n  end\n\n  def test_default_to_s_on_drops\n    assert_equal('ProductDrop', Liquid::Template.parse(\"{{ product }}\").render!('product' => ProductDrop.new))\n    assert_equal('EnumerableDrop', Liquid::Template.parse('{{ collection }}').render!('collection' => EnumerableDrop.new))\n  end\n\n  def test_invokable_methods\n    assert_equal(%w(to_liquid catchall context texts).to_set, ProductDrop.invokable_methods)\n    assert_equal(%w(to_liquid scopes_as_array loop_pos scopes).to_set, ContextDrop.invokable_methods)\n    assert_equal(%w(to_liquid size max min first count).to_set, EnumerableDrop.invokable_methods)\n    assert_equal(%w(to_liquid max min sort count first).to_set, RealEnumerableDrop.invokable_methods)\n  end\nend # DropsTest\n"
  },
  {
    "path": "test/integration/error_handling_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ErrorHandlingTest < Minitest::Test\n  include Liquid\n\n  def test_templates_parsed_with_line_numbers_renders_them_in_errors\n    template = <<-LIQUID\n      Hello,\n\n      {{ errors.standard_error }} will raise a standard error.\n\n      Bla bla test.\n\n      {{ errors.syntax_error }} will raise a syntax error.\n\n      This is an argument error: {{ errors.argument_error }}\n\n      Bla.\n    LIQUID\n\n    expected = <<-TEXT\n      Hello,\n\n      Liquid error (line 3): standard error will raise a standard error.\n\n      Bla bla test.\n\n      Liquid syntax error (line 7): syntax error will raise a syntax error.\n\n      This is an argument error: Liquid error (line 9): argument error\n\n      Bla.\n    TEXT\n\n    output = Liquid::Template.parse(template, line_numbers: true).render('errors' => ErrorDrop.new)\n    assert_equal(expected, output)\n  end\n\n  def test_standard_error\n    template = Liquid::Template.parse(' {{ errors.standard_error }} ')\n    assert_equal(' Liquid error: standard error ', template.render('errors' => ErrorDrop.new))\n\n    assert_equal(1, template.errors.size)\n    assert_equal(StandardError, template.errors.first.class)\n  end\n\n  def test_syntax\n    template = Liquid::Template.parse(' {{ errors.syntax_error }} ')\n    assert_equal(' Liquid syntax error: syntax error ', template.render('errors' => ErrorDrop.new))\n\n    assert_equal(1, template.errors.size)\n    assert_equal(SyntaxError, template.errors.first.class)\n  end\n\n  def test_argument\n    template = Liquid::Template.parse(' {{ errors.argument_error }} ')\n    assert_equal(' Liquid error: argument error ', template.render('errors' => ErrorDrop.new))\n\n    assert_equal(1, template.errors.size)\n    assert_equal(ArgumentError, template.errors.first.class)\n  end\n\n  def test_missing_endtag_parse_time_error\n    assert_match_syntax_error(/: 'for' tag was never closed\\z/, ' {% for a in b %} ... ')\n  end\n\n  def test_unrecognized_operator\n    with_error_modes(:strict) do\n      assert_raises(SyntaxError) do\n        Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ')\n      end\n    end\n  end\n\n  def test_lax_unrecognized_operator\n    template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :lax)\n    assert_equal(' Liquid error: Unknown operator =! ', template.render)\n    assert_equal(1, template.errors.size)\n    assert_equal(Liquid::ArgumentError, template.errors.first.class)\n  end\n\n  def test_with_line_numbers_adds_numbers_to_parser_errors\n    source = <<~LIQUID\n      foobar\n\n      {% \"cat\" | foobar %}\n\n      bla\n    LIQUID\n    assert_match_syntax_error(/Liquid syntax error \\(line 3\\)/, source)\n  end\n\n  def test_with_line_numbers_adds_numbers_to_parser_errors_with_whitespace_trim\n    source = <<~LIQUID\n      foobar\n\n      {%- \"cat\" | foobar -%}\n\n      bla\n    LIQUID\n\n    assert_match_syntax_error(/Liquid syntax error \\(line 3\\)/, source)\n  end\n\n  def test_parsing_warn_with_line_numbers_adds_numbers_to_lexer_errors\n    template = Liquid::Template.parse(\n      '\n        foobar\n\n        {% if 1 =! 2 %}ok{% endif %}\n\n        bla\n            ',\n      error_mode: :warn,\n      line_numbers: true,\n    )\n\n    assert_equal(\n      ['Liquid syntax error (line 4): Unexpected character = in \"1 =! 2\"'],\n      template.warnings.map(&:message),\n    )\n  end\n\n  def test_parsing_strict_with_line_numbers_adds_numbers_to_lexer_errors\n    err = assert_raises(SyntaxError) do\n      Liquid::Template.parse(\n        '\n          foobar\n\n          {% if 1 =! 2 %}ok{% endif %}\n\n          bla\n                ',\n        error_mode: :strict,\n        line_numbers: true,\n      )\n    end\n\n    assert_equal('Liquid syntax error (line 4): Unexpected character = in \"1 =! 2\"', err.message)\n  end\n\n  def test_syntax_errors_in_nested_blocks_have_correct_line_number\n    source = <<~LIQUID\n      foobar\n\n      {% if 1 != 2 %}\n        {% foo %}\n      {% endif %}\n\n      bla\n    LIQUID\n\n    assert_match_syntax_error(\"Liquid syntax error (line 4): Unknown tag 'foo'\", source)\n  end\n\n  def test_strict_error_messages\n    err = assert_raises(SyntaxError) do\n      Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :strict)\n    end\n    assert_equal('Liquid syntax error: Unexpected character = in \"1 =! 2\"', err.message)\n\n    err = assert_raises(SyntaxError) do\n      Liquid::Template.parse('{{%%%}}', error_mode: :strict)\n    end\n    assert_equal('Liquid syntax error: Unexpected character % in \"{{%%%}}\"', err.message)\n  end\n\n  def test_warnings\n    template = Liquid::Template.parse('{% if ~~~ %}{{%%%}}{% else %}{{ hello. }}{% endif %}', error_mode: :warn)\n    assert_equal(3, template.warnings.size)\n    assert_equal('Unexpected character ~ in \"~~~\"', template.warnings[0].to_s(false))\n    assert_equal('Unexpected character % in \"{{%%%}}\"', template.warnings[1].to_s(false))\n    assert_equal('Expected id but found end_of_string in \"{{ hello. }}\"', template.warnings[2].to_s(false))\n    assert_equal('', template.render)\n  end\n\n  def test_warning_line_numbers\n    template = Liquid::Template.parse(\"{% if ~~~ %}\\n{{%%%}}{% else %}\\n{{ hello. }}{% endif %}\", error_mode: :warn, line_numbers: true)\n    assert_equal('Liquid syntax error (line 1): Unexpected character ~ in \"~~~\"', template.warnings[0].message)\n    assert_equal('Liquid syntax error (line 2): Unexpected character % in \"{{%%%}}\"', template.warnings[1].message)\n    assert_equal('Liquid syntax error (line 3): Expected id but found end_of_string in \"{{ hello. }}\"', template.warnings[2].message)\n    assert_equal(3, template.warnings.size)\n    assert_equal([1, 2, 3], template.warnings.map(&:line_number))\n  end\n\n  # Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError\n  def test_exceptions_propagate\n    assert_raises(Exception) do\n      template = Liquid::Template.parse('{{ errors.exception }}')\n      template.render('errors' => ErrorDrop.new)\n    end\n  end\n\n  def test_default_exception_renderer_with_internal_error\n    template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)\n\n    output = template.render('errors' => ErrorDrop.new)\n\n    assert_equal('This is a runtime error: Liquid error (line 1): internal', output)\n    assert_equal([Liquid::InternalError], template.errors.map(&:class))\n  end\n\n  def test_setting_default_exception_renderer\n    exceptions = []\n    default_exception_renderer = ->(e) {\n      exceptions << e\n      ''\n    }\n\n    env = Liquid::Environment.build(exception_renderer: default_exception_renderer)\n    template = Liquid::Template.parse('This is a runtime error: {{ errors.argument_error }}', environment: env)\n\n    output = template.render('errors' => ErrorDrop.new)\n\n    assert_equal('This is a runtime error: ', output)\n    assert_equal([Liquid::ArgumentError], template.errors.map(&:class))\n  end\n\n  def test_setting_exception_renderer_on_environment\n    exceptions = []\n    exception_renderer = ->(e) do\n      exceptions << e\n      ''\n    end\n\n    environment = Liquid::Environment.build(exception_renderer: exception_renderer)\n    template = Liquid::Template.parse('This is a runtime error: {{ errors.argument_error }}', environment: environment)\n    output = template.render('errors' => ErrorDrop.new)\n\n    assert_equal('This is a runtime error: ', output)\n    assert_equal([Liquid::ArgumentError], template.errors.map(&:class))\n  end\n\n  def test_exception_renderer_exposing_non_liquid_error\n    template   = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)\n    exceptions = []\n    handler    = ->(e) {\n      exceptions << e\n      e.cause\n    }\n\n    output = template.render({ 'errors' => ErrorDrop.new }, exception_renderer: handler)\n\n    assert_equal('This is a runtime error: runtime error', output)\n    assert_equal([Liquid::InternalError], exceptions.map(&:class))\n    assert_equal(exceptions, template.errors)\n    assert_equal('#<RuntimeError: runtime error>', exceptions.first.cause.inspect)\n  end\n\n  class TestFileSystem\n    def read_template_file(_template_path)\n      \"{{ errors.argument_error }}\"\n    end\n  end\n\n  def test_included_template_name_with_line_numbers\n    environment = Liquid::Environment.build(file_system: TestFileSystem.new)\n    template = Liquid::Template.parse(\"Argument error:\\n{% include 'product' %}\", line_numbers: true, environment: environment)\n    page     = template.render('errors' => ErrorDrop.new)\n\n    assert_equal(\"Argument error:\\nLiquid error (product line 1): argument error\", page)\n    assert_equal(\"product\", template.errors.first.template_name)\n  end\n\n  def test_bug_compatible_silencing_of_errors_in_blank_nodes\n    output = Liquid::Template.parse(\"{% assign x = 0 %}{% if 1 < '2' %}not blank{% assign x = 3 %}{% endif %}{{ x }}\").render\n    assert_equal(\"Liquid error: comparison of Integer with String failed0\", output)\n\n    output = Liquid::Template.parse(\"{% assign x = 0 %}{% if 1 < '2' %}{% assign x = 3 %}{% endif %}{{ x }}\").render\n    assert_equal(\"0\", output)\n  end\n\n  def test_syntax_error_is_raised_with_template_name\n    file_system = StubFileSystem.new(\"snippet\" => \"1\\n2\\n{{ 1\")\n\n    context = Liquid::Context.build(\n      registers: { file_system: file_system },\n    )\n\n    template = Template.parse(\n      '{% render \"snippet\" %}',\n      line_numbers: true,\n    )\n    template.name = \"template/index\"\n\n    assert_equal(\n      \"Liquid syntax error (snippet line 3): Variable '{{' was not properly terminated with regexp: /\\\\}\\\\}/\",\n      template.render(context),\n    )\n  end\n\n  def test_syntax_error_is_raised_with_template_name_from_template_factory\n    file_system = StubFileSystem.new(\"snippet\" => \"1\\n2\\n{{ 1\")\n\n    context = Liquid::Context.build(\n      registers: {\n        file_system: file_system,\n        template_factory: StubTemplateFactory.new,\n      },\n    )\n\n    template = Template.parse(\n      '{% render \"snippet\" %}',\n      line_numbers: true,\n    )\n    template.name = \"template/index\"\n\n    assert_equal(\n      \"Liquid syntax error (some/path/snippet line 3): Variable '{{' was not properly terminated with regexp: /\\\\}\\\\}/\",\n      template.render(context),\n    )\n  end\n\n  def test_error_is_raised_during_parse_with_template_name\n    depth = Liquid::Block::MAX_DEPTH + 1\n    code = \"{% if true %}\" * depth + \"rendered\" + \"{% endif %}\" * depth\n\n    template = Template.parse(\"{% render 'snippet' %}\", line_numbers: true)\n\n    context = Liquid::Context.build(\n      registers: {\n        file_system: StubFileSystem.new(\"snippet\" => code),\n        template_factory: StubTemplateFactory.new,\n      },\n    )\n\n    assert_equal(\"Liquid error (some/path/snippet line 1): Nesting too deep\", template.render(context))\n  end\n\n  def test_internal_error_is_raised_with_template_name\n    template = Template.new\n    template.parse(\n      \"{% render 'snippet' %}\",\n      line_numbers: true,\n    )\n    template.name = \"template/index\"\n\n    context = Liquid::Context.build(\n      registers: {\n        file_system: StubFileSystem.new({}),\n      },\n    )\n\n    assert_equal(\n      \"Liquid error (template/index line 1): internal\",\n      template.render(context),\n    )\n  end\nend\n"
  },
  {
    "path": "test/integration/expression_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\nrequire 'lru_redux'\n\nclass ExpressionTest < Minitest::Test\n  def test_keyword_literals\n    assert_template_result(\"true\", \"{{ true }}\")\n    assert_expression_result(true, \"true\")\n  end\n\n  def test_string\n    assert_template_result(\"single quoted\", \"{{'single quoted'}}\")\n    assert_template_result(\"double quoted\", '{{\"double quoted\"}}')\n    assert_template_result(\"spaced\", \"{{ 'spaced' }}\")\n    assert_template_result(\"spaced2\", \"{{ 'spaced2' }}\")\n    assert_template_result(\"emoji🔥\", \"{{ 'emoji🔥' }}\")\n  end\n\n  def test_int\n    assert_template_result(\"456\", \"{{ 456 }}\")\n    assert_expression_result(123, \"123\")\n    assert_expression_result(12, \"012\")\n  end\n\n  def test_float\n    assert_template_result(\"-17.42\", \"{{ -17.42 }}\")\n    assert_template_result(\"2.5\", \"{{ 2.5 }}\")\n\n    with_error_modes(:lax) do\n      assert_expression_result(0.0, \"0.....5\")\n      assert_expression_result(0.0, \"-0..1\")\n    end\n\n    assert_expression_result(1.5, \"1.5\")\n\n    # this is a unfortunate quirky behavior of Liquid\n    result = Expression.parse(\".5\")\n    assert_kind_of(Liquid::VariableLookup, result)\n\n    result = Expression.parse(\"-.5\")\n    assert_kind_of(Liquid::VariableLookup, result)\n  end\n\n  def test_range\n    assert_template_result(\"3..4\", \"{{ ( 3 .. 4 ) }}\")\n    assert_expression_result(1..2, \"(1..2)\")\n\n    assert_match_syntax_error(\n      \"Liquid syntax error (line 1): Invalid expression type 'false' in range expression\",\n      \"{{ (false..true) }}\",\n    )\n    assert_match_syntax_error(\n      \"Liquid syntax error (line 1): Invalid expression type '(1..2)' in range expression\",\n      \"{{ ((1..2)..3) }}\",\n    )\n  end\n\n  def test_quirky_negative_sign_expression_markup\n    result = Expression.parse(\"-\", nil)\n    assert(result.is_a?(VariableLookup))\n    assert_equal(\"-\", result.name)\n\n    # for this template, the expression markup is \"-\"\n    assert_template_result(\n      \"\",\n      \"{{ - 'theme.css' - }}\",\n      error_mode: :lax,\n    )\n  end\n\n  def test_expression_cache\n    skip(\"Liquid-C does not support Expression caching\") if defined?(Liquid::C) && Liquid::C.enabled\n\n    cache = {}\n    template = <<~LIQUID\n      {% assign x = 1 %}\n      {{ x }}\n      {% assign x = 2 %}\n      {{ x }}\n      {% assign y = 1 %}\n      {{ y }}\n    LIQUID\n\n    Liquid::Template.parse(template, expression_cache: cache).render\n\n    assert_equal(\n      [\"1\", \"2\", \"x\", \"y\"],\n      cache.to_a.map { _1[0] }.sort,\n    )\n  end\n\n  def test_expression_cache_with_true_boolean\n    skip(\"Liquid-C does not support Expression caching\") if defined?(Liquid::C) && Liquid::C.enabled\n\n    template = <<~LIQUID\n      {% assign x = 1 %}\n      {{ x }}\n      {% assign x = 2 %}\n      {{ x }}\n      {% assign y = 1 %}\n      {{ y }}\n    LIQUID\n\n    parse_context = ParseContext.new(expression_cache: true)\n\n    Liquid::Template.parse(template, parse_context).render\n\n    cache = parse_context.instance_variable_get(:@expression_cache)\n\n    assert_equal(\n      [\"1\", \"2\", \"x\", \"y\"],\n      cache.to_a.map { _1[0] }.sort,\n    )\n  end\n\n  def test_expression_cache_with_lru_redux\n    skip(\"Liquid-C does not support Expression caching\") if defined?(Liquid::C) && Liquid::C.enabled\n\n    cache = LruRedux::Cache.new(10)\n    template = <<~LIQUID\n      {% assign x = 1 %}\n      {{ x }}\n      {% assign x = 2 %}\n      {{ x }}\n      {% assign y = 1 %}\n      {{ y }}\n    LIQUID\n\n    Liquid::Template.parse(template, expression_cache: cache).render\n\n    assert_equal(\n      [\"1\", \"2\", \"x\", \"y\"],\n      cache.to_a.map { _1[0] }.sort,\n    )\n  end\n\n  def test_disable_expression_cache\n    skip(\"Liquid-C does not support Expression caching\") if defined?(Liquid::C) && Liquid::C.enabled\n\n    template = <<~LIQUID\n      {% assign x = 1 %}\n      {{ x }}\n      {% assign x = 2 %}\n      {{ x }}\n      {% assign y = 1 %}\n      {{ y }}\n    LIQUID\n\n    parse_context = Liquid::ParseContext.new(expression_cache: false)\n    Liquid::Template.parse(template, parse_context).render\n    assert(parse_context.instance_variable_get(:@expression_cache).nil?)\n  end\n\n  def test_safe_parse_with_variable_lookup\n    parse_context = Liquid::ParseContext.new\n    parser = parse_context.new_parser('product.title')\n    result = Liquid::Expression.safe_parse(parser)\n\n    assert_instance_of(Liquid::VariableLookup, result)\n    assert_equal('product', result.name)\n    assert_equal(['title'], result.lookups)\n  end\n\n  def test_safe_parse_with_number\n    parse_context = Liquid::ParseContext.new\n    parser = parse_context.new_parser('42')\n    result = Liquid::Expression.safe_parse(parser)\n\n    assert_equal(42, result)\n  end\n\n  def test_safe_parse_raises_syntax_error_for_invalid_expression\n    parse_context = Liquid::ParseContext.new\n    parser = parse_context.new_parser('')\n\n    error = assert_raises(Liquid::SyntaxError) do\n      Liquid::Expression.safe_parse(parser)\n    end\n\n    assert_match(/is not a valid expression/, error.message)\n  end\n\n  private\n\n  def assert_expression_result(expect, markup, **assigns)\n    liquid = \"{% if expect == #{markup} %}pass{% else %}got {{ #{markup} }}{% endif %}\"\n    assert_template_result(\"pass\", liquid, { \"expect\" => expect, **assigns })\n  end\nend\n"
  },
  {
    "path": "test/integration/filter_kwarg_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass FilterKwargTest < Minitest::Test\n  module KwargFilter\n    def html_tag(_tag, attributes)\n      attributes\n        .map { |key, value| \"#{key}='#{value}'\" }\n        .join(' ')\n    end\n  end\n\n  include Liquid\n\n  def test_can_parse_data_kwargs\n    with_global_filter(KwargFilter) do\n      assert_equal(\n        \"data-src='src' data-widths='100, 200'\",\n        Template.parse(\"{{ 'img' | html_tag: data-src: 'src', data-widths: '100, 200' }}\").render(nil, nil),\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "test/integration/filter_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nmodule MoneyFilter\n  def money(input)\n    format(' %d$ ', input)\n  end\n\n  def money_with_underscore(input)\n    format(' %d$ ', input)\n  end\nend\n\nmodule CanadianMoneyFilter\n  def money(input)\n    format(' %d$ CAD ', input)\n  end\nend\n\nmodule SubstituteFilter\n  def substitute(input, params = {})\n    input.gsub(/%\\{(\\w+)\\}/) { |_match| params[Regexp.last_match(1)] }\n  end\nend\n\nclass FiltersTest < Minitest::Test\n  include Liquid\n\n  module OverrideObjectMethodFilter\n    def tap(_input)\n      \"tap overridden\"\n    end\n  end\n\n  def setup\n    @context = Context.new\n  end\n\n  def test_local_filter\n    @context['var'] = 1000\n    @context.add_filters(MoneyFilter)\n\n    assert_equal(' 1000$ ', Template.parse(\"{{var | money}}\").render(@context))\n  end\n\n  def test_underscore_in_filter_name\n    @context['var'] = 1000\n    @context.add_filters(MoneyFilter)\n    assert_equal(' 1000$ ', Template.parse(\"{{var | money_with_underscore}}\").render(@context))\n  end\n\n  def test_second_filter_overwrites_first\n    @context['var'] = 1000\n    @context.add_filters(MoneyFilter)\n    @context.add_filters(CanadianMoneyFilter)\n\n    assert_equal(' 1000$ CAD ', Template.parse(\"{{var | money}}\").render(@context))\n  end\n\n  def test_size\n    assert_template_result(\"4\", \"{{var | size}}\", { \"var\" => 'abcd' })\n  end\n\n  def test_join\n    assert_template_result(\"1 2 3 4\", \"{{var | join}}\", { \"var\" => [1, 2, 3, 4] })\n  end\n\n  def test_sort\n    assert_template_result(\"1 2 3 4\", \"{{numbers | sort | join}}\", { \"numbers\" => [2, 1, 4, 3] })\n    assert_template_result(\n      \"alphabetic as expected\",\n      \"{{words | sort | join}}\",\n      { \"words\" => ['expected', 'as', 'alphabetic'] },\n    )\n    assert_template_result(\"3\", \"{{value | sort}}\", { \"value\" => 3 })\n    assert_template_result('are flower', \"{{arrays | sort | join}}\", { 'arrays' => ['flower', 'are'] })\n    assert_template_result(\n      \"Expected case sensitive\",\n      \"{{case_sensitive | sort | join}}\",\n      { \"case_sensitive\" => [\"sensitive\", \"Expected\", \"case\"] },\n    )\n  end\n\n  def test_sort_natural\n    # Test strings\n    assert_template_result(\n      \"Assert case Insensitive\",\n      \"{{words | sort_natural | join}}\",\n      { \"words\" => [\"case\", \"Assert\", \"Insensitive\"] },\n    )\n\n    # Test hashes\n    assert_template_result(\n      \"A b C\",\n      \"{{hashes | sort_natural: 'a' | map: 'a' | join}}\",\n      { \"hashes\" => [{ \"a\" => \"A\" }, { \"a\" => \"b\" }, { \"a\" => \"C\" }] },\n    )\n\n    # Test objects\n    @context['objects'] = [TestObject.new('A'), TestObject.new('b'), TestObject.new('C')]\n    assert_equal('A b C', Template.parse(\"{{objects | sort_natural: 'a' | map: 'a' | join}}\").render(@context))\n  end\n\n  def test_compact\n    # Test strings\n    assert_template_result(\n      \"a b c\",\n      \"{{words | compact | join}}\",\n      { \"words\" => ['a', nil, 'b', nil, 'c'] },\n    )\n\n    # Test hashes\n    assert_template_result(\n      \"A C\",\n      \"{{hashes | compact: 'a' | map: 'a' | join}}\",\n      { \"hashes\" => [{ \"a\" => \"A\" }, { \"a\" => nil }, { \"a\" => \"C\" }] },\n    )\n\n    # Test objects\n    @context['objects'] = [TestObject.new('A'), TestObject.new(nil), TestObject.new('C')]\n    assert_equal('A C', Template.parse(\"{{objects | compact: 'a' | map: 'a' | join}}\").render(@context))\n  end\n\n  def test_strip_html\n    assert_template_result(\"bla blub\", \"{{ var | strip_html }}\", { \"var\" => \"<b>bla blub</a>\" })\n  end\n\n  def test_strip_html_ignore_comments_with_html\n    assert_template_result(\n      \"bla blub\",\n      \"{{ var | strip_html }}\",\n      { \"var\" => \"<!-- split and some <ul> tag --><b>bla blub</a>\" },\n    )\n  end\n\n  def test_capitalize\n    assert_template_result(\"Blub\", \"{{ var | capitalize }}\", { \"var\" => \"blub\" })\n  end\n\n  def test_nonexistent_filter_is_ignored\n    assert_template_result(\"1000\", \"{{ var | xyzzy }}\", { \"var\" => 1000 })\n  end\n\n  def test_filter_with_keyword_arguments\n    @context['surname'] = 'john'\n    @context['input']   = 'hello %{first_name}, %{last_name}'\n    @context.add_filters(SubstituteFilter)\n    output              = Template.parse(%({{ input | substitute: first_name: surname, last_name: 'doe' }})).render(@context)\n    assert_equal('hello john, doe', output)\n  end\n\n  def test_override_object_method_in_filter\n    assert_equal(\"tap overridden\", Template.parse(\"{{var | tap}}\").render!({ 'var' => 1000 }, filters: [OverrideObjectMethodFilter]))\n\n    # tap still treated as a non-existent filter\n    assert_equal(\"1000\", Template.parse(\"{{var | tap}}\").render!('var' => 1000))\n  end\n\n  def test_liquid_argument_error\n    source = \"{{ '' | size: 'too many args' }}\"\n    exc = assert_raises(Liquid::ArgumentError) do\n      Template.parse(source).render!\n    end\n    assert_match(/\\ALiquid error: wrong number of arguments /, exc.message)\n    assert_equal(exc.message, Template.parse(source).render)\n  end\nend\n\nclass FiltersInTemplate < Minitest::Test\n  include Liquid\n\n  def test_local_global\n    with_global_filter(MoneyFilter) do\n      assert_equal(\" 1000$ \", Template.parse(\"{{1000 | money}}\").render!(nil, nil))\n      assert_equal(\" 1000$ CAD \", Template.parse(\"{{1000 | money}}\").render!(nil, filters: CanadianMoneyFilter))\n      assert_equal(\" 1000$ CAD \", Template.parse(\"{{1000 | money}}\").render!(nil, filters: [CanadianMoneyFilter]))\n    end\n  end\n\n  def test_local_filter_with_deprecated_syntax\n    assert_equal(\" 1000$ CAD \", Template.parse(\"{{1000 | money}}\").render!(nil, CanadianMoneyFilter))\n    assert_equal(\" 1000$ CAD \", Template.parse(\"{{1000 | money}}\").render!(nil, [CanadianMoneyFilter]))\n  end\nend # FiltersTest\n\nclass TestObject < Liquid::Drop\n  attr_accessor :a\n  def initialize(a)\n    @a = a\n  end\nend\n"
  },
  {
    "path": "test/integration/hash_ordering_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass HashOrderingTest < Minitest::Test\n  module MoneyFilter\n    def money(input)\n      format(' %d$ ', input)\n    end\n  end\n\n  module CanadianMoneyFilter\n    def money(input)\n      format(' %d$ CAD ', input)\n    end\n  end\n\n  include Liquid\n\n  def test_global_register_order\n    with_global_filter(MoneyFilter, CanadianMoneyFilter) do\n      assert_equal(\" 1000$ CAD \", Template.parse(\"{{1000 | money}}\").render(nil, nil))\n    end\n  end\nend\n"
  },
  {
    "path": "test/integration/hash_rendering_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass HashRenderingTest < Minitest::Test\n  def test_render_empty_hash\n    assert_template_result(\"{}\", \"{{ my_hash }}\", { \"my_hash\" => {} })\n  end\n\n  def test_render_hash_with_string_keys_and_values\n    assert_template_result(\"{\\\"key1\\\"=>\\\"value1\\\", \\\"key2\\\"=>\\\"value2\\\"}\", \"{{ my_hash }}\", { \"my_hash\" => { \"key1\" => \"value1\", \"key2\" => \"value2\" } })\n  end\n\n  def test_render_hash_with_symbol_keys_and_integer_values\n    assert_template_result(\"{:key1=>1, :key2=>2}\", \"{{ my_hash }}\", { \"my_hash\" => { key1: 1, key2: 2 } })\n  end\n\n  def test_render_nested_hash\n    assert_template_result(\"{\\\"outer\\\"=>{\\\"inner\\\"=>\\\"value\\\"}}\", \"{{ my_hash }}\", { \"my_hash\" => { \"outer\" => { \"inner\" => \"value\" } } })\n  end\n\n  def test_render_hash_with_array_values\n    assert_template_result(\"{\\\"numbers\\\"=>[1, 2, 3]}\", \"{{ my_hash }}\", { \"my_hash\" => { \"numbers\" => [1, 2, 3] } })\n  end\n\n  def test_render_recursive_hash\n    recursive_hash = { \"self\" => {} }\n    recursive_hash[\"self\"][\"self\"] = recursive_hash\n    assert_template_result(\"{\\\"self\\\"=>{\\\"self\\\"=>{...}}}\", \"{{ my_hash }}\", { \"my_hash\" => recursive_hash })\n  end\n\n  def test_hash_with_downcase_filter\n    assert_template_result(\"{\\\"key\\\"=>\\\"value\\\", \\\"anotherkey\\\"=>\\\"anothervalue\\\"}\", \"{{ my_hash | downcase }}\", { \"my_hash\" => { \"Key\" => \"Value\", \"AnotherKey\" => \"AnotherValue\" } })\n  end\n\n  def test_hash_with_upcase_filter\n    assert_template_result(\"{\\\"KEY\\\"=>\\\"VALUE\\\", \\\"ANOTHERKEY\\\"=>\\\"ANOTHERVALUE\\\"}\", \"{{ my_hash | upcase }}\", { \"my_hash\" => { \"Key\" => \"Value\", \"AnotherKey\" => \"AnotherValue\" } })\n  end\n\n  def test_hash_with_strip_filter\n    assert_template_result(\"{\\\"Key\\\"=>\\\"Value\\\", \\\"AnotherKey\\\"=>\\\"AnotherValue\\\"}\", \"{{ my_hash | strip }}\", { \"my_hash\" => { \"Key\" => \"Value\", \"AnotherKey\" => \"AnotherValue\" } })\n  end\n\n  def test_hash_with_escape_filter\n    assert_template_result(\"{&quot;Key&quot;=&gt;&quot;Value&quot;, &quot;AnotherKey&quot;=&gt;&quot;AnotherValue&quot;}\", \"{{ my_hash | escape }}\", { \"my_hash\" => { \"Key\" => \"Value\", \"AnotherKey\" => \"AnotherValue\" } })\n  end\n\n  def test_hash_with_url_encode_filter\n    assert_template_result(\"%7B%22Key%22%3D%3E%22Value%22%2C+%22AnotherKey%22%3D%3E%22AnotherValue%22%7D\", \"{{ my_hash | url_encode }}\", { \"my_hash\" => { \"Key\" => \"Value\", \"AnotherKey\" => \"AnotherValue\" } })\n  end\n\n  def test_hash_with_strip_html_filter\n    assert_template_result(\"{\\\"Key\\\"=>\\\"Value\\\", \\\"AnotherKey\\\"=>\\\"AnotherValue\\\"}\", \"{{ my_hash | strip_html }}\", { \"my_hash\" => { \"Key\" => \"Value\", \"AnotherKey\" => \"AnotherValue\" } })\n  end\n\n  def test_hash_with_truncate__20_filter\n    assert_template_result(\"{\\\"Key\\\"=>\\\"Value\\\", ...\", \"{{ my_hash | truncate: 20 }}\", { \"my_hash\" => { \"Key\" => \"Value\", \"AnotherKey\" => \"AnotherValue\" } })\n  end\n\n  def test_hash_with_replace___key____replaced_key__filter\n    assert_template_result(\"{\\\"Key\\\"=>\\\"Value\\\", \\\"AnotherKey\\\"=>\\\"AnotherValue\\\"}\", \"{{ my_hash | replace: 'key', 'replaced_key' }}\", { \"my_hash\" => { \"Key\" => \"Value\", \"AnotherKey\" => \"AnotherValue\" } })\n  end\n\n  def test_hash_with_append____appended_text__filter\n    assert_template_result(\"{\\\"Key\\\"=>\\\"Value\\\", \\\"AnotherKey\\\"=>\\\"AnotherValue\\\"} appended text\", \"{{ my_hash | append: ' appended text' }}\", { \"my_hash\" => { \"Key\" => \"Value\", \"AnotherKey\" => \"AnotherValue\" } })\n  end\n\n  def test_hash_with_prepend___prepended_text___filter\n    assert_template_result(\"prepended text {\\\"Key\\\"=>\\\"Value\\\", \\\"AnotherKey\\\"=>\\\"AnotherValue\\\"}\", \"{{ my_hash | prepend: 'prepended text ' }}\", { \"my_hash\" => { \"Key\" => \"Value\", \"AnotherKey\" => \"AnotherValue\" } })\n  end\n\n  def test_render_hash_with_array_values_empty\n    assert_template_result(\"{\\\"numbers\\\"=>[]}\", \"{{ my_hash }}\", { \"my_hash\" => { \"numbers\" => [] } })\n  end\n\n  def test_render_hash_with_array_values_hash\n    assert_template_result(\"{\\\"numbers\\\"=>[{:foo=>42}]}\", \"{{ my_hash }}\", { \"my_hash\" => { \"numbers\" => [{ foo: 42 }] } })\n  end\n\n  def test_join_filter_with_hash\n    array = [{ \"key1\" => \"value1\" }, { \"key2\" => \"value2\" }]\n    glue = { \"lol\" => \"wut\" }\n    assert_template_result(\"{\\\"key1\\\"=>\\\"value1\\\"}{\\\"lol\\\"=>\\\"wut\\\"}{\\\"key2\\\"=>\\\"value2\\\"}\", \"{{ my_array | join: glue }}\", { \"my_array\" => array, \"glue\" => glue })\n  end\n\n  def test_render_hash_with_hash_key\n    assert_template_result(\"{{\\\"foo\\\"=>\\\"bar\\\"}=>42}\", \"{{ my_hash }}\", { \"my_hash\" => { Hash[\"foo\" => \"bar\"] => 42 } })\n  end\n\n  def test_rendering_hash_with_custom_to_s_method_uses_custom_to_s\n    assert_template_result(\"kewl\", \"{{ my_hash }}\", { \"my_hash\" => HashWithCustomToS.new })\n  end\n\n  def test_rendering_hash_without_custom_to_s_uses_default_inspect\n    my_hash = HashWithoutCustomToS.new\n    my_hash[:foo] = :bar\n\n    assert_template_result(\"{:foo=>:bar}\", \"{{ my_hash }}\", { \"my_hash\" => my_hash })\n  end\nend\n"
  },
  {
    "path": "test/integration/output_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nmodule FunnyFilter\n  def make_funny(_input)\n    'LOL'\n  end\n\n  def cite_funny(input)\n    \"LOL: #{input}\"\n  end\n\n  def add_smiley(input, smiley = \":-)\")\n    \"#{input} #{smiley}\"\n  end\n\n  def add_tag(input, tag = \"p\", id = \"foo\")\n    %(<#{tag} id=\"#{id}\">#{input}</#{tag}>)\n  end\n\n  def paragraph(input)\n    \"<p>#{input}</p>\"\n  end\n\n  def link_to(name, url)\n    %(<a href=\"#{url}\">#{name}</a>)\n  end\nend\n\nclass OutputTest < Minitest::Test\n  include Liquid\n\n  def setup\n    @assigns = {\n      'car' => { 'bmw' => 'good', 'gm' => 'bad' },\n    }\n  end\n\n  def test_variable\n    assert_template_result(\" bmw \", \" {{best_cars}} \", { \"best_cars\" => \"bmw\" })\n  end\n\n  def test_variable_traversing_with_two_brackets\n    source = \"{{ site.data.menu[include.menu][include.locale] }}\"\n    assert_template_result(\"it works!\", source, {\n      \"site\" => { \"data\" => { \"menu\" => { \"foo\" => { \"bar\" => \"it works!\" } } } },\n      \"include\" => { \"menu\" => \"foo\", \"locale\" => \"bar\" },\n    })\n  end\n\n  def test_variable_traversing\n    source = \" {{car.bmw}} {{car.gm}} {{car.bmw}} \"\n    assert_template_result(\" good bad good \", source, @assigns)\n  end\n\n  def test_variable_piping\n    text     = %( {{ car.gm | make_funny }} )\n    expected = %( LOL )\n\n    assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))\n  end\n\n  def test_variable_piping_with_input\n    text     = %( {{ car.gm | cite_funny }} )\n    expected = %( LOL: bad )\n\n    assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))\n  end\n\n  def test_variable_piping_with_args\n    text     = %! {{ car.gm | add_smiley : ':-(' }} !\n    expected = %| bad :-( |\n\n    assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))\n  end\n\n  def test_variable_piping_with_no_args\n    text     = %( {{ car.gm | add_smiley }} )\n    expected = %| bad :-) |\n\n    assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))\n  end\n\n  def test_multiple_variable_piping_with_args\n    text     = %! {{ car.gm | add_smiley : ':-(' | add_smiley : ':-('}} !\n    expected = %| bad :-( :-( |\n\n    assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))\n  end\n\n  def test_variable_piping_with_multiple_args\n    text     = %( {{ car.gm | add_tag : 'span', 'bar'}} )\n    expected = %( <span id=\"bar\">bad</span> )\n\n    assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))\n  end\n\n  def test_variable_piping_with_variable_args\n    text     = %( {{ car.gm | add_tag : 'span', car.bmw}} )\n    expected = %( <span id=\"good\">bad</span> )\n\n    assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))\n  end\n\n  def test_multiple_pipings\n    assigns = { 'best_cars' => 'bmw' }\n    text     = %( {{ best_cars | cite_funny | paragraph }} )\n    expected = %( <p>LOL: bmw</p> )\n\n    assert_equal(expected, Template.parse(text).render!(assigns, filters: [FunnyFilter]))\n  end\n\n  def test_link_to\n    text     = %( {{ 'Typo' | link_to: 'http://typo.leetsoft.com' }} )\n    expected = %( <a href=\"http://typo.leetsoft.com\">Typo</a> )\n\n    assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))\n  end\nend # OutputTest\n"
  },
  {
    "path": "test/integration/parsing_quirks_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ParsingQuirksTest < Minitest::Test\n  include Liquid\n\n  def test_parsing_css\n    text = \" div { font-weight: bold; } \"\n    assert_equal(text, Template.parse(text).render!)\n  end\n\n  def test_raise_on_single_close_bracet\n    assert_raises(SyntaxError) do\n      Template.parse(\"text {{method} oh nos!\")\n    end\n  end\n\n  def test_raise_on_label_and_no_close_bracets\n    assert_raises(SyntaxError) do\n      Template.parse(\"TEST {{ \")\n    end\n  end\n\n  def test_raise_on_label_and_no_close_bracets_percent\n    assert_raises(SyntaxError) do\n      Template.parse(\"TEST {% \")\n    end\n  end\n\n  def test_error_on_empty_filter\n    assert(Template.parse(\"{{test}}\"))\n\n    with_error_modes(:lax) do\n      assert(Template.parse(\"{{|test}}\"))\n    end\n\n    with_error_modes(:strict) do\n      assert_raises(SyntaxError) { Template.parse(\"{{|test}}\") }\n      assert_raises(SyntaxError) { Template.parse(\"{{test |a|b|}}\") }\n    end\n  end\n\n  def test_meaningless_parens_error\n    with_error_modes(:strict) do\n      assert_raises(SyntaxError) do\n        markup = \"a == 'foo' or (b == 'bar' and c == 'baz') or false\"\n        Template.parse(\"{% if #{markup} %} YES {% endif %}\")\n      end\n    end\n  end\n\n  def test_unexpected_characters_syntax_error\n    with_error_modes(:strict) do\n      assert_raises(SyntaxError) do\n        markup = \"true && false\"\n        Template.parse(\"{% if #{markup} %} YES {% endif %}\")\n      end\n      assert_raises(SyntaxError) do\n        markup = \"false || true\"\n        Template.parse(\"{% if #{markup} %} YES {% endif %}\")\n      end\n    end\n  end\n\n  def test_no_error_on_lax_empty_filter\n    assert(Template.parse(\"{{test |a|b|}}\", error_mode: :lax))\n    assert(Template.parse(\"{{test}}\", error_mode: :lax))\n    assert(Template.parse(\"{{|test|}}\", error_mode: :lax))\n  end\n\n  def test_meaningless_parens_lax\n    with_error_modes(:lax) do\n      assigns = { 'b' => 'bar', 'c' => 'baz' }\n      markup  = \"a == 'foo' or (b == 'bar' and c == 'baz') or false\"\n      assert_template_result(' YES ', \"{% if #{markup} %} YES {% endif %}\", assigns)\n    end\n  end\n\n  def test_unexpected_characters_silently_eat_logic_lax\n    with_error_modes(:lax) do\n      markup = \"true && false\"\n      assert_template_result(' YES ', \"{% if #{markup} %} YES {% endif %}\")\n      markup = \"false || true\"\n      assert_template_result('', \"{% if #{markup} %} YES {% endif %}\")\n    end\n  end\n\n  def test_raise_on_invalid_tag_delimiter\n    assert_raises(Liquid::SyntaxError) do\n      Template.new.parse('{% end %}')\n    end\n  end\n\n  def test_unanchored_filter_arguments\n    with_error_modes(:lax) do\n      assert_template_result('hi', \"{{ 'hi there' | split$$$:' ' | first }}\")\n\n      assert_template_result('x', \"{{ 'X' | downcase) }}\")\n\n      # After the messed up quotes a filter without parameters (reverse) should work\n      # but one with parameters (remove) shouldn't be detected.\n      assert_template_result('here',  \"{{ 'hi there' | split:\\\"t\\\"\\\" | reverse | first}}\")\n      assert_template_result('hi ', \"{{ 'hi there' | split:\\\"t\\\"\\\" | remove:\\\"i\\\" | first}}\")\n    end\n  end\n\n  def test_invalid_variables_work\n    with_error_modes(:lax) do\n      assert_template_result('bar', \"{% assign 123foo = 'bar' %}{{ 123foo }}\")\n      assert_template_result('123', \"{% assign 123 = 'bar' %}{{ 123 }}\")\n    end\n  end\n\n  def test_extra_dots_in_ranges\n    with_error_modes(:lax) do\n      assert_template_result('12345', \"{% for i in (1...5) %}{{ i }}{% endfor %}\")\n    end\n  end\n\n  def test_blank_variable_markup\n    assert_template_result('', \"{{}}\")\n  end\n\n  def test_lookup_on_var_with_literal_name\n    assigns = { \"blank\" => { \"x\" => \"result\" } }\n    assert_template_result('result', \"{{ blank.x }}\", assigns)\n    assert_template_result('result', \"{{ blank['x'] }}\", assigns)\n  end\n\n  def test_contains_in_id\n    assert_template_result(' YES ', '{% if containsallshipments == true %} YES {% endif %}', { 'containsallshipments' => true })\n  end\n\n  def test_incomplete_expression\n    with_error_modes(:lax) do\n      assert_template_result(\"false\", \"{{ false - }}\")\n      assert_template_result(\"false\", \"{{ false > }}\")\n      assert_template_result(\"false\", \"{{ false < }}\")\n      assert_template_result(\"false\", \"{{ false = }}\")\n      assert_template_result(\"false\", \"{{ false ! }}\")\n      assert_template_result(\"false\", \"{{ false 1 }}\")\n      assert_template_result(\"false\", \"{{ false a }}\")\n\n      assert_template_result(\"false\", \"{% liquid assign foo = false -\\n%}{{ foo }}\")\n      assert_template_result(\"false\", \"{% liquid assign foo = false >\\n%}{{ foo }}\")\n      assert_template_result(\"false\", \"{% liquid assign foo = false <\\n%}{{ foo }}\")\n      assert_template_result(\"false\", \"{% liquid assign foo = false =\\n%}{{ foo }}\")\n      assert_template_result(\"false\", \"{% liquid assign foo = false !\\n%}{{ foo }}\")\n      assert_template_result(\"false\", \"{% liquid assign foo = false 1\\n%}{{ foo }}\")\n      assert_template_result(\"false\", \"{% liquid assign foo = false a\\n%}{{ foo }}\")\n    end\n  end\nend # ParsingQuirksTest\n"
  },
  {
    "path": "test/integration/profiler_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ProfilerTest < Minitest::Test\n  class TestDrop < Liquid::Drop\n    def initialize(value)\n      super()\n      @value = value\n    end\n\n    def to_s\n      artificial_execution_time\n\n      @value\n    end\n\n    private\n\n    # Monotonic clock precision fluctuate based on the operating system\n    # By introducing a small sleep we ensure ourselves to register a non zero unit of time\n    def artificial_execution_time\n      sleep(Process.clock_getres(Process::CLOCK_MONOTONIC))\n    end\n  end\n\n  include Liquid\n\n  class ProfilingFileSystem\n    def read_template_file(template_path)\n      \"Rendering template {% assign template_name = '#{template_path}'%}\\n{{ template_name }}\"\n    end\n  end\n\n  def setup\n    Liquid::Environment.default.file_system = ProfilingFileSystem.new\n  end\n\n  def test_template_allows_flagging_profiling\n    t = Template.parse(\"{{ 'a string' | upcase }}\")\n    t.render!\n\n    assert_nil(t.profiler)\n  end\n\n  def test_parse_makes_available_simple_profiling\n    t = Template.parse(\"{{ 'a string' | upcase }}\", profile: true)\n    t.render!\n\n    assert_equal(1, t.profiler.length)\n\n    node = t.profiler[0]\n    assert_equal(\" 'a string' | upcase \", node.code)\n  end\n\n  def test_render_ignores_raw_strings_when_profiling\n    t = Template.parse(\"This is raw string\\nstuff\\nNewline\", profile: true)\n    t.render!\n\n    assert_equal(0, t.profiler.length)\n  end\n\n  def test_profiling_includes_line_numbers_of_liquid_nodes\n    t = Template.parse(\"{{ 'a string' | upcase }}\\n{% increment test %}\", profile: true)\n    t.render!\n    assert_equal(2, t.profiler.length)\n\n    # {{ 'a string' | upcase }}\n    assert_equal(1, t.profiler[0].line_number)\n    # {{ increment test }}\n    assert_equal(2, t.profiler[1].line_number)\n  end\n\n  def test_profiling_includes_line_numbers_of_included_partials\n    t = Template.parse(\"{% include 'a_template' %}\", profile: true)\n    t.render!\n\n    included_children = t.profiler[0].children\n\n    # {% assign template_name = 'a_template' %}\n    assert_equal(1, included_children[0].line_number)\n    # {{ template_name }}\n    assert_equal(2, included_children[1].line_number)\n  end\n\n  def test_profiling_render_tag\n    t = Template.parse(\"{% render 'a_template' %}\", profile: true)\n    t.render!\n\n    render_children = t.profiler[0].children\n    render_children.each do |timing|\n      assert_equal('a_template', timing.partial)\n    end\n    assert_equal([1, 2], render_children.map(&:line_number))\n  end\n\n  def test_profiling_times_the_rendering_of_tokens\n    t = Template.parse(\"{% include 'a_template' %}\", profile: true)\n    t.render!\n\n    node = t.profiler[0]\n    refute_nil(node.render_time)\n  end\n\n  def test_profiling_times_the_entire_render\n    t = Template.parse(\"{% include 'a_template' %}\", profile: true)\n    t.render!\n\n    assert(t.profiler.total_render_time >= 0, \"Total render time was not calculated\")\n  end\n\n  class SleepTag < Liquid::Tag\n    def initialize(tag_name, markup, parse_context)\n      super\n      @duration = Float(markup)\n    end\n\n    def render_to_output_buffer(_context, _output)\n      sleep(@duration)\n    end\n  end\n\n  def test_profiling_multiple_renders\n    with_custom_tag('sleep', SleepTag) do\n      context = Liquid::Context.new\n      t = Liquid::Template.parse(\"{% sleep 0.001 %}\", profile: true)\n      context.template_name = 'index'\n      t.render!(context)\n      context.template_name = 'layout'\n      first_render_time = context.profiler.total_time\n      t.render!(context)\n\n      profiler = context.profiler\n      children = profiler.children\n      assert_operator(first_render_time, :>=, 0.001)\n      assert_operator(profiler.total_time, :>=, 0.001 + first_render_time)\n      assert_equal([\"index\", \"layout\"], children.map(&:template_name))\n      assert_equal([nil, nil], children.map(&:code))\n      assert_equal(profiler.total_time, children.map(&:total_time).reduce(&:+))\n    end\n  end\n\n  def test_profiling_uses_include_to_mark_children\n    t = Template.parse(\"{{ 'a string' | upcase }}\\n{% include 'a_template' %}\", profile: true)\n    t.render!\n\n    include_node = t.profiler[1]\n    assert_equal(2, include_node.children.length)\n  end\n\n  def test_profiling_marks_children_with_the_name_of_included_partial\n    t = Template.parse(\"{{ 'a string' | upcase }}\\n{% include 'a_template' %}\", profile: true)\n    t.render!\n\n    include_node = t.profiler[1]\n    include_node.children.each do |child|\n      assert_equal(\"a_template\", child.partial)\n    end\n  end\n\n  def test_profiling_supports_multiple_templates\n    t = Template.parse(\"{{ 'a string' | upcase }}\\n{% include 'a_template' %}\\n{% include 'b_template' %}\", profile: true)\n    t.render!\n\n    a_template = t.profiler[1]\n    a_template.children.each do |child|\n      assert_equal(\"a_template\", child.partial)\n    end\n\n    b_template = t.profiler[2]\n    b_template.children.each do |child|\n      assert_equal(\"b_template\", child.partial)\n    end\n  end\n\n  def test_profiling_supports_rendering_the_same_partial_multiple_times\n    t = Template.parse(\"{{ 'a string' | upcase }}\\n{% include 'a_template' %}\\n{% include 'a_template' %}\", profile: true)\n    t.render!\n\n    a_template1 = t.profiler[1]\n    a_template1.children.each do |child|\n      assert_equal(\"a_template\", child.partial)\n    end\n\n    a_template2 = t.profiler[2]\n    a_template2.children.each do |child|\n      assert_equal(\"a_template\", child.partial)\n    end\n  end\n\n  def test_can_iterate_over_each_profiling_entry\n    t = Template.parse(\"{{ 'a string' | upcase }}\\n{% increment test %}\", profile: true)\n    t.render!\n\n    timing_count = 0\n    t.profiler.each do |_timing|\n      timing_count += 1\n    end\n\n    assert_equal(2, timing_count)\n  end\n\n  def test_profiling_marks_children_of_if_blocks\n    t = Template.parse(\"{% if true %} {% increment test %} {{ test }} {% endif %}\", profile: true)\n    t.render!\n\n    assert_equal(1, t.profiler.length)\n    assert_equal(2, t.profiler[0].children.length)\n  end\n\n  def test_profiling_marks_children_of_for_blocks\n    t = Template.parse(\"{% for item in collection %} {{ item }} {% endfor %}\", profile: true)\n    t.render!(\"collection\" => [\"one\", \"two\"])\n\n    assert_equal(1, t.profiler.length)\n    # Will profile each invocation of the for block\n    assert_equal(2, t.profiler[0].children.length)\n  end\n\n  def test_profiling_supports_self_time\n    t = Template.parse(\"{% for item in collection %} {{ item }} {% endfor %}\", profile: true)\n    collection = [\n      TestDrop.new(\"one\"),\n      TestDrop.new(\"two\"),\n    ]\n    output = t.render!(\"collection\" => collection)\n    assert_equal(\" one  two \", output)\n\n    leaf = t.profiler[0].children[0]\n    assert_operator(leaf.self_time, :>, 0.0)\n  end\n\n  def test_profiling_supports_total_time\n    t = Template.parse(\"{% if true %} {{ test }} {% endif %}\", profile: true)\n    output = t.render!(\"test\" => TestDrop.new(\"one\"))\n    assert_equal(\" one \", output)\n\n    assert_operator(t.profiler[0].total_time, :>, 0.0)\n  end\nend\n"
  },
  {
    "path": "test/integration/security_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nmodule SecurityFilter\n  def add_one(input)\n    \"#{input} + 1\"\n  end\nend\n\nclass SecurityTest < Minitest::Test\n  include Liquid\n\n  def setup\n    @assigns = {}\n  end\n\n  def test_no_instance_eval\n    text     = %( {{ '1+1' | instance_eval }} )\n    expected = %( 1+1 )\n\n    assert_equal(expected, Template.parse(text).render!(@assigns))\n  end\n\n  def test_no_existing_instance_eval\n    text     = %( {{ '1+1' | __instance_eval__ }} )\n    expected = %( 1+1 )\n\n    assert_equal(expected, Template.parse(text).render!(@assigns))\n  end\n\n  def test_no_instance_eval_after_mixing_in_new_filter\n    text     = %( {{ '1+1' | instance_eval }} )\n    expected = %( 1+1 )\n\n    assert_equal(expected, Template.parse(text).render!(@assigns))\n  end\n\n  def test_no_instance_eval_later_in_chain\n    text     = %( {{ '1+1' | add_one | instance_eval }} )\n    expected = %( 1+1 + 1 )\n\n    assert_equal(expected, Template.parse(text).render!(@assigns, filters: SecurityFilter))\n  end\n\n  def test_does_not_permanently_add_filters_to_symbol_table\n    current_symbols = Symbol.all_symbols\n\n    # MRI imprecisely marks objects found on the C stack, which can result\n    # in uninitialized memory being marked. This can even result in the test failing\n    # deterministically for a given compilation of ruby. Using a separate thread will\n    # keep these writes of the symbol pointer on a separate stack that will be garbage\n    # collected after Thread#join.\n    Thread.new do\n      test = %( {{ \"some_string\" | a_bad_filter }} )\n      Template.parse(test).render!\n      nil\n    end.join\n\n    GC.start\n\n    assert_equal([], Symbol.all_symbols - current_symbols)\n  end\n\n  def test_does_not_add_drop_methods_to_symbol_table\n    current_symbols = Symbol.all_symbols\n\n    assigns = { 'drop' => Drop.new }\n    assert_equal(\"\", Template.parse(\"{{ drop.custom_method_1 }}\", assigns).render!)\n    assert_equal(\"\", Template.parse(\"{{ drop.custom_method_2 }}\", assigns).render!)\n    assert_equal(\"\", Template.parse(\"{{ drop.custom_method_3 }}\", assigns).render!)\n\n    assert_equal([], Symbol.all_symbols - current_symbols)\n  end\n\n  def test_max_depth_nested_blocks_does_not_raise_exception\n    depth = Liquid::Block::MAX_DEPTH\n    code  = \"{% if true %}\" * depth + \"rendered\" + \"{% endif %}\" * depth\n    assert_equal(\"rendered\", Template.parse(code).render!)\n  end\n\n  def test_more_than_max_depth_nested_blocks_raises_exception\n    depth = Liquid::Block::MAX_DEPTH + 1\n    code  = \"{% if true %}\" * depth + \"rendered\" + \"{% endif %}\" * depth\n    assert_raises(Liquid::StackLevelError) do\n      Template.parse(code).render!\n    end\n  end\nend # SecurityTest\n"
  },
  {
    "path": "test/integration/standard_filter_test.rb",
    "content": "# encoding: utf-8\n# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass TestThing\n  attr_reader :foo\n\n  def initialize\n    @foo = 0\n  end\n\n  def to_s\n    \"woot: #{@foo}\"\n  end\n\n  def [](_whatever)\n    to_s\n  end\n\n  def to_liquid\n    @foo += 1\n    self\n  end\nend\n\nclass TestDrop < Liquid::Drop\n  def initialize(value:)\n    @value = value\n  end\n\n  attr_reader :value\n\n  def registers\n    \"{#{@value.inspect}=>#{@context.registers[@value].inspect}}\"\n  end\nend\n\nclass TestModel\n  def initialize(value:)\n    @value = value\n  end\n\n  def to_liquid\n    TestDrop.new(value: @value)\n  end\nend\n\nclass TestEnumerable < Liquid::Drop\n  include Enumerable\n\n  def each(&block)\n    [{ \"foo\" => 1, \"bar\" => 2 }, { \"foo\" => 2, \"bar\" => 1 }, { \"foo\" => 3, \"bar\" => 3 }].each(&block)\n  end\nend\n\nclass NumberLikeThing < Liquid::Drop\n  def initialize(amount)\n    @amount = amount\n  end\n\n  def to_number\n    @amount\n  end\nend\n\nclass StandardFiltersTest < Minitest::Test\n  Filters = Class.new(Liquid::StrainerTemplate)\n  Filters.add_filter(Liquid::StandardFilters)\n\n  include Liquid\n\n  def setup\n    @filters = Filters.new(Context.new)\n  end\n\n  def test_size\n    assert_equal(3, @filters.size([1, 2, 3]))\n    assert_equal(0, @filters.size([]))\n    assert_equal(0, @filters.size(nil))\n  end\n\n  def test_downcase\n    assert_equal('testing', @filters.downcase(\"Testing\"))\n    assert_equal('', @filters.downcase(nil))\n  end\n\n  def test_upcase\n    assert_equal('TESTING', @filters.upcase(\"Testing\"))\n    assert_equal('', @filters.upcase(nil))\n  end\n\n  def test_slice\n    assert_equal('oob', @filters.slice('foobar', 1, 3))\n    assert_equal('oobar', @filters.slice('foobar', 1, 1000))\n    assert_equal('', @filters.slice('foobar', 1, 0))\n    assert_equal('o', @filters.slice('foobar', 1, 1))\n    assert_equal('bar', @filters.slice('foobar', 3, 3))\n    assert_equal('ar', @filters.slice('foobar', -2, 2))\n    assert_equal('ar', @filters.slice('foobar', -2, 1000))\n    assert_equal('r', @filters.slice('foobar', -1))\n    assert_equal('', @filters.slice(nil, 0))\n    assert_equal('', @filters.slice('foobar', 100, 10))\n    assert_equal('', @filters.slice('foobar', -100, 10))\n    assert_equal('oob', @filters.slice('foobar', '1', '3'))\n    assert_raises(Liquid::ArgumentError) do\n      @filters.slice('foobar', nil)\n    end\n    assert_raises(Liquid::ArgumentError) do\n      @filters.slice('foobar', 0, \"\")\n    end\n    assert_equal(\"\", @filters.slice(\"foobar\", 0, -(1 << 64)))\n    assert_equal(\"foobar\", @filters.slice(\"foobar\", 0, 1 << 63))\n    assert_equal(\"\", @filters.slice(\"foobar\", 1 << 63, 6))\n    assert_equal(\"\", @filters.slice(\"foobar\", -(1 << 63), 6))\n  end\n\n  def test_slice_on_arrays\n    input = 'foobar'.split('')\n    assert_equal(%w(o o b), @filters.slice(input, 1, 3))\n    assert_equal(%w(o o b a r), @filters.slice(input, 1, 1000))\n    assert_equal(%w(), @filters.slice(input, 1, 0))\n    assert_equal(%w(o), @filters.slice(input, 1, 1))\n    assert_equal(%w(b a r), @filters.slice(input, 3, 3))\n    assert_equal(%w(a r), @filters.slice(input, -2, 2))\n    assert_equal(%w(a r), @filters.slice(input, -2, 1000))\n    assert_equal(%w(r), @filters.slice(input, -1))\n    assert_equal(%w(), @filters.slice(input, 100, 10))\n    assert_equal(%w(), @filters.slice(input, -100, 10))\n    assert_equal([], @filters.slice(input, 0, -(1 << 64)))\n    assert_equal(input, @filters.slice(input, 0, 1 << 63))\n    assert_equal([], @filters.slice(input, 1 << 63, 6))\n    assert_equal([], @filters.slice(input, -(1 << 63), 6))\n  end\n\n  def test_find_on_empty_array\n    assert_nil(@filters.find([], 'foo', 'bar'))\n  end\n\n  def test_find_index_on_empty_array\n    assert_nil(@filters.find_index([], 'foo', 'bar'))\n  end\n\n  def test_has_on_empty_array\n    refute(@filters.has([], 'foo', 'bar'))\n  end\n\n  def test_truncate\n    assert_equal('1234...', @filters.truncate('1234567890', 7))\n    assert_equal('1234567890', @filters.truncate('1234567890', 20))\n    assert_equal('...', @filters.truncate('1234567890', 0))\n    assert_equal('1234567890', @filters.truncate('1234567890'))\n    assert_equal(\"测试...\", @filters.truncate(\"测试测试测试测试\", 5))\n    assert_equal('12341', @filters.truncate(\"1234567890\", 5, 1))\n    assert_equal(\"foobar\", @filters.truncate(\"foobar\", 1 << 63))\n    assert_equal(\"...\", @filters.truncate(\"foobar\", -(1 << 63)))\n  end\n\n  def test_split\n    assert_equal(['12', '34'], @filters.split('12~34', '~'))\n    assert_equal(['A? ', ' ,Z'], @filters.split('A? ~ ~ ~ ,Z', '~ ~ ~'))\n    assert_equal(['A?Z'], @filters.split('A?Z', '~'))\n    assert_equal([], @filters.split(nil, ' '))\n    assert_equal(['A', 'Z'], @filters.split('A1Z', 1))\n  end\n\n  def test_squish_filter\n    assert_equal(\"foo bar boo\", Liquid::Template.parse(%({{ \" foo   bar\n\\t   boo   \" | squish }})).render)\n    assert_equal(\"\", Liquid::Template.parse('{{ nil | squish }}').render)\n    assert_equal(\"\", Liquid::Template.parse('{{ \" \" | squish }}').render)\n  end\n\n  def test_escape\n    assert_equal('&lt;strong&gt;', @filters.escape('<strong>'))\n    assert_equal('1', @filters.escape(1))\n    assert_equal('2001-02-03', @filters.escape(Date.new(2001, 2, 3)))\n    assert_nil(@filters.escape(nil))\n  end\n\n  def test_h\n    assert_equal('&lt;strong&gt;', @filters.h('<strong>'))\n    assert_equal('1', @filters.h(1))\n    assert_equal('2001-02-03', @filters.h(Date.new(2001, 2, 3)))\n    assert_nil(@filters.h(nil))\n  end\n\n  def test_escape_once\n    assert_equal('&lt;strong&gt;Hulk&lt;/strong&gt;', @filters.escape_once('&lt;strong&gt;Hulk</strong>'))\n  end\n\n  def test_base64_encode\n    assert_equal('b25lIHR3byB0aHJlZQ==', @filters.base64_encode('one two three'))\n    assert_equal('', @filters.base64_encode(nil))\n  end\n\n  def test_base64_decode\n    decoded = @filters.base64_decode('b25lIHR3byB0aHJlZQ==')\n    assert_equal('one two three', decoded)\n    assert_equal(Encoding::UTF_8, decoded.encoding)\n\n    decoded = @filters.base64_decode('4pyF')\n    assert_equal('✅', decoded)\n    assert_equal(Encoding::UTF_8, decoded.encoding)\n\n    decoded = @filters.base64_decode(\"/w==\")\n    assert_equal(Encoding::ASCII_8BIT, decoded.encoding)\n    assert_equal((+\"\\xFF\").force_encoding(Encoding::ASCII_8BIT), decoded)\n\n    exception = assert_raises(Liquid::ArgumentError) do\n      @filters.base64_decode(\"invalidbase64\")\n    end\n\n    assert_equal('Liquid error: invalid base64 provided to base64_decode', exception.message)\n  end\n\n  def test_base64_url_safe_encode\n    assert_equal(\n      'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMTIzNDU2Nzg5MCAhQCMkJV4mKigpLT1fKy8_Ljo7W117fVx8',\n      @filters.base64_url_safe_encode('abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 !@#$%^&*()-=_+/?.:;[]{}\\|'),\n    )\n    assert_equal('', @filters.base64_url_safe_encode(nil))\n  end\n\n  def test_base64_url_safe_decode\n    decoded = @filters.base64_url_safe_decode('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMTIzNDU2Nzg5MCAhQCMkJV4mKigpLT1fKy8_Ljo7W117fVx8')\n    assert_equal(\n      'abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 !@#$%^&*()-=_+/?.:;[]{}\\|',\n      decoded,\n    )\n    assert_equal(Encoding::UTF_8, decoded.encoding)\n\n    decoded = @filters.base64_url_safe_decode('4pyF')\n    assert_equal('✅', decoded)\n    assert_equal(Encoding::UTF_8, decoded.encoding)\n\n    decoded = @filters.base64_url_safe_decode(\"_w==\")\n    assert_equal(Encoding::ASCII_8BIT, decoded.encoding)\n    assert_equal((+\"\\xFF\").force_encoding(Encoding::ASCII_8BIT), decoded)\n\n    exception = assert_raises(Liquid::ArgumentError) do\n      @filters.base64_url_safe_decode(\"invalidbase64\")\n    end\n    assert_equal('Liquid error: invalid base64 provided to base64_url_safe_decode', exception.message)\n  end\n\n  def test_url_encode\n    assert_equal('foo%2B1%40example.com', @filters.url_encode('foo+1@example.com'))\n    assert_equal('1', @filters.url_encode(1))\n    assert_equal('2001-02-03', @filters.url_encode(Date.new(2001, 2, 3)))\n    assert_nil(@filters.url_encode(nil))\n  end\n\n  def test_url_decode\n    assert_equal('foo bar', @filters.url_decode('foo+bar'))\n    assert_equal('foo bar', @filters.url_decode('foo%20bar'))\n    assert_equal('foo+1@example.com', @filters.url_decode('foo%2B1%40example.com'))\n    assert_equal('1', @filters.url_decode(1))\n    assert_equal('2001-02-03', @filters.url_decode(Date.new(2001, 2, 3)))\n    assert_nil(@filters.url_decode(nil))\n    exception = assert_raises(Liquid::ArgumentError) do\n      @filters.url_decode('%ff')\n    end\n    assert_equal('Liquid error: invalid byte sequence in UTF-8', exception.message)\n  end\n\n  def test_truncatewords\n    assert_equal('one two three', @filters.truncatewords('one two three', 4))\n    assert_equal('one two...', @filters.truncatewords('one two three', 2))\n    assert_equal('one two three', @filters.truncatewords('one two three'))\n    assert_equal(\n      'Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221;...',\n      @filters.truncatewords('Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221; x 16&#8221; x 10.5&#8221; high) with cover.', 15),\n    )\n    assert_equal(\"测试测试测试测试\", @filters.truncatewords('测试测试测试测试', 5))\n    assert_equal('one two1', @filters.truncatewords(\"one two three\", 2, 1))\n    assert_equal('one two three...', @filters.truncatewords(\"one  two\\tthree\\nfour\", 3))\n    assert_equal('one two...', @filters.truncatewords(\"one two three four\", 2))\n    assert_equal('one...', @filters.truncatewords(\"one two three four\", 0))\n    assert_equal('one two three four', @filters.truncatewords(\"one two three four\", 1 << 31))\n    assert_equal('one...', @filters.truncatewords(\"one two three four\", -(1 << 32)))\n  end\n\n  def test_strip_html\n    assert_equal('test', @filters.strip_html(\"<div>test</div>\"))\n    assert_equal('test', @filters.strip_html(\"<div id='test'>test</div>\"))\n    assert_equal('', @filters.strip_html(\"<script type='text/javascript'>document.write('some stuff');</script>\"))\n    assert_equal('', @filters.strip_html(\"<style type='text/css'>foo bar</style>\"))\n    assert_equal('test', @filters.strip_html(\"<div\\nclass='multiline'>test</div>\"))\n    assert_equal('test', @filters.strip_html(\"<!-- foo bar \\n test -->test\"))\n    assert_equal('', @filters.strip_html(nil))\n\n    # Quirk of the existing implementation\n    assert_equal('foo;', @filters.strip_html(\"<<<script </script>script>foo;</script>\"))\n  end\n\n  def test_join\n    assert_equal('1 2 3 4', @filters.join([1, 2, 3, 4]))\n    assert_equal('1 - 2 - 3 - 4', @filters.join([1, 2, 3, 4], ' - '))\n    assert_equal('1121314', @filters.join([1, 2, 3, 4], 1))\n  end\n\n  def test_join_calls_to_liquid_on_each_element\n    assert_equal('i did it, i did it', @filters.join([CustomToLiquidDrop.new('i did it'), CustomToLiquidDrop.new('i did it')], \", \"))\n  end\n\n  def test_sort\n    assert_equal([1, 2, 3, 4], @filters.sort([4, 3, 2, 1]))\n    assert_equal([{ \"a\" => 1 }, { \"a\" => 2 }, { \"a\" => 3 }, { \"a\" => 4 }], @filters.sort([{ \"a\" => 4 }, { \"a\" => 3 }, { \"a\" => 1 }, { \"a\" => 2 }], \"a\"))\n  end\n\n  def test_sort_with_nils\n    assert_equal([1, 2, 3, 4, nil], @filters.sort([nil, 4, 3, 2, 1]))\n    assert_equal([{ \"a\" => 1 }, { \"a\" => 2 }, { \"a\" => 3 }, { \"a\" => 4 }, {}], @filters.sort([{ \"a\" => 4 }, { \"a\" => 3 }, {}, { \"a\" => 1 }, { \"a\" => 2 }], \"a\"))\n  end\n\n  def test_sort_when_property_is_sometimes_missing_puts_nils_last\n    input       = [\n      { \"price\" => 4, \"handle\" => \"alpha\" },\n      { \"handle\" => \"beta\" },\n      { \"price\" => 1, \"handle\" => \"gamma\" },\n      { \"handle\" => \"delta\" },\n      { \"price\" => 2, \"handle\" => \"epsilon\" },\n    ]\n    expectation = [\n      { \"price\" => 1, \"handle\" => \"gamma\" },\n      { \"price\" => 2, \"handle\" => \"epsilon\" },\n      { \"price\" => 4, \"handle\" => \"alpha\" },\n      { \"handle\" => \"beta\" },\n      { \"handle\" => \"delta\" },\n    ]\n    assert_equal(expectation, @filters.sort(input, \"price\"))\n  end\n\n  def test_sort_natural\n    assert_equal([\"a\", \"B\", \"c\", \"D\"], @filters.sort_natural([\"c\", \"D\", \"a\", \"B\"]))\n    assert_equal([{ \"a\" => \"a\" }, { \"a\" => \"B\" }, { \"a\" => \"c\" }, { \"a\" => \"D\" }], @filters.sort_natural([{ \"a\" => \"D\" }, { \"a\" => \"c\" }, { \"a\" => \"a\" }, { \"a\" => \"B\" }], \"a\"))\n  end\n\n  def test_sort_natural_with_nils\n    assert_equal([\"a\", \"B\", \"c\", \"D\", nil], @filters.sort_natural([nil, \"c\", \"D\", \"a\", \"B\"]))\n    assert_equal([{ \"a\" => \"a\" }, { \"a\" => \"B\" }, { \"a\" => \"c\" }, { \"a\" => \"D\" }, {}], @filters.sort_natural([{ \"a\" => \"D\" }, { \"a\" => \"c\" }, {}, { \"a\" => \"a\" }, { \"a\" => \"B\" }], \"a\"))\n  end\n\n  def test_sort_natural_when_property_is_sometimes_missing_puts_nils_last\n    input       = [\n      { \"price\" => \"4\", \"handle\" => \"alpha\" },\n      { \"handle\" => \"beta\" },\n      { \"price\" => \"1\", \"handle\" => \"gamma\" },\n      { \"handle\" => \"delta\" },\n      { \"price\" => 2, \"handle\" => \"epsilon\" },\n    ]\n    expectation = [\n      { \"price\" => \"1\", \"handle\" => \"gamma\" },\n      { \"price\" => 2, \"handle\" => \"epsilon\" },\n      { \"price\" => \"4\", \"handle\" => \"alpha\" },\n      { \"handle\" => \"beta\" },\n      { \"handle\" => \"delta\" },\n    ]\n    assert_equal(expectation, @filters.sort_natural(input, \"price\"))\n  end\n\n  def test_sort_natural_case_check\n    input = [\n      { \"key\" => \"X\" },\n      { \"key\" => \"Y\" },\n      { \"key\" => \"Z\" },\n      { \"fake\" => \"t\" },\n      { \"key\" => \"a\" },\n      { \"key\" => \"b\" },\n      { \"key\" => \"c\" },\n    ]\n    expectation = [\n      { \"key\" => \"a\" },\n      { \"key\" => \"b\" },\n      { \"key\" => \"c\" },\n      { \"key\" => \"X\" },\n      { \"key\" => \"Y\" },\n      { \"key\" => \"Z\" },\n      { \"fake\" => \"t\" },\n    ]\n    assert_equal(expectation, @filters.sort_natural(input, \"key\"))\n    assert_equal([\"a\", \"b\", \"c\", \"X\", \"Y\", \"Z\"], @filters.sort_natural([\"X\", \"Y\", \"Z\", \"a\", \"b\", \"c\"]))\n  end\n\n  def test_sort_empty_array\n    assert_equal([], @filters.sort([], \"a\"))\n  end\n\n  def test_sort_invalid_property\n    foo = [\n      [1],\n      [2],\n      [3],\n    ]\n\n    assert_raises(Liquid::ArgumentError) do\n      @filters.sort(foo, \"bar\")\n    end\n  end\n\n  def test_sort_natural_empty_array\n    assert_equal([], @filters.sort_natural([], \"a\"))\n  end\n\n  def test_sort_natural_invalid_property\n    foo = [\n      [1],\n      [2],\n      [3],\n    ]\n\n    assert_raises(Liquid::ArgumentError) do\n      @filters.sort_natural(foo, \"bar\")\n    end\n  end\n\n  def test_legacy_sort_hash\n    assert_equal([{ a: 1, b: 2 }], @filters.sort(a: 1, b: 2))\n  end\n\n  def test_numerical_vs_lexicographical_sort\n    assert_equal([2, 10], @filters.sort([10, 2]))\n    assert_equal([{ \"a\" => 2 }, { \"a\" => 10 }], @filters.sort([{ \"a\" => 10 }, { \"a\" => 2 }], \"a\"))\n    assert_equal([\"10\", \"2\"], @filters.sort([\"10\", \"2\"]))\n    assert_equal([{ \"a\" => \"10\" }, { \"a\" => \"2\" }], @filters.sort([{ \"a\" => \"10\" }, { \"a\" => \"2\" }], \"a\"))\n  end\n\n  def test_uniq\n    assert_equal([\"foo\"], @filters.uniq(\"foo\"))\n    assert_equal([1, 3, 2, 4], @filters.uniq([1, 1, 3, 2, 3, 1, 4, 3, 2, 1]))\n    assert_equal([{ \"a\" => 1 }, { \"a\" => 3 }, { \"a\" => 2 }], @filters.uniq([{ \"a\" => 1 }, { \"a\" => 3 }, { \"a\" => 1 }, { \"a\" => 2 }], \"a\"))\n    test_drop = TestDrop.new(value: \"test\")\n    test_drop_alternate = TestDrop.new(value: \"test\")\n    assert_equal([test_drop], @filters.uniq([test_drop, test_drop_alternate], 'value'))\n  end\n\n  def test_uniq_empty_array\n    assert_equal([], @filters.uniq([], \"a\"))\n  end\n\n  def test_uniq_invalid_property\n    foo = [\n      [1],\n      [2],\n      [3],\n    ]\n\n    assert_raises(Liquid::ArgumentError) do\n      @filters.uniq(foo, \"bar\")\n    end\n  end\n\n  def test_compact_empty_array\n    assert_equal([], @filters.compact([], \"a\"))\n  end\n\n  def test_compact_invalid_property\n    foo = [\n      [1],\n      [2],\n      [3],\n    ]\n\n    assert_raises(Liquid::ArgumentError) do\n      @filters.compact(foo, \"bar\")\n    end\n  end\n\n  def test_reverse\n    assert_equal([4, 3, 2, 1], @filters.reverse([1, 2, 3, 4]))\n  end\n\n  def test_legacy_reverse_hash\n    assert_equal([{ a: 1, b: 2 }], @filters.reverse(a: 1, b: 2))\n  end\n\n  def test_map\n    assert_equal([1, 2, 3, 4], @filters.map([{ \"a\" => 1 }, { \"a\" => 2 }, { \"a\" => 3 }, { \"a\" => 4 }], 'a'))\n    assert_template_result(\n      'abc',\n      \"{{ ary | map:'foo' | map:'bar' }}\",\n      { 'ary' => [{ 'foo' => { 'bar' => 'a' } }, { 'foo' => { 'bar' => 'b' } }, { 'foo' => { 'bar' => 'c' } }] },\n    )\n  end\n\n  def test_map_doesnt_call_arbitrary_stuff\n    assert_template_result(\"\", '{{ \"foo\" | map: \"__id__\" }}')\n    assert_template_result(\"\", '{{ \"foo\" | map: \"inspect\" }}')\n  end\n\n  def test_map_calls_to_liquid\n    t = TestThing.new\n    assert_template_result(\"woot: 1\", '{{ foo | map: \"whatever\" }}', { \"foo\" => [t] })\n  end\n\n  def test_map_calls_context=\n    model = TestModel.new(value: :test)\n\n    template = Template.parse('{{ foo | map: \"registers\" }}')\n    template.registers[:test] = 1234\n    template.assigns['foo'] = [model]\n\n    assert_template_result(\"{:test=>1234}\", template.render!)\n  end\n\n  def test_map_on_hashes\n    assert_template_result(\n      \"4217\",\n      '{{ thing | map: \"foo\" | map: \"bar\" }}',\n      { \"thing\" => { \"foo\" => [{ \"bar\" => 42 }, { \"bar\" => 17 }] } },\n    )\n  end\n\n  def test_legacy_map_on_hashes_with_dynamic_key\n    template = \"{% assign key = 'foo' %}{{ thing | map: key | map: 'bar' }}\"\n    hash     = { \"foo\" => { \"bar\" => 42 } }\n    assert_template_result(\"42\", template, { \"thing\" => hash })\n  end\n\n  def test_sort_calls_to_liquid\n    t = TestThing.new\n    Liquid::Template.parse('{{ foo | sort: \"whatever\" }}').render(\"foo\" => [t])\n    assert(t.foo > 0)\n  end\n\n  def test_map_over_proc\n    drop  = TestDrop.new(value: \"testfoo\")\n    p     = proc { drop }\n    output = Liquid::Template.parse('{{ procs | map: \"value\" }}').render!({ \"procs\" => [p] })\n    assert_equal(\"testfoo\", output)\n  end\n\n  def test_map_over_drops_returning_procs\n    drops = [\n      {\n        \"proc\" => -> { \"foo\" },\n      },\n      {\n        \"proc\" => -> { \"bar\" },\n      },\n    ]\n    output = Liquid::Template.parse('{{ drops | map: \"proc\" }}').render!({ \"drops\" => drops })\n    assert_equal(\"foobar\", output)\n  end\n\n  def test_map_works_on_enumerables\n    output = Liquid::Template.parse('{{ foo | map: \"foo\" }}').render!({ \"foo\" => TestEnumerable.new })\n    assert_equal(\"123\", output)\n  end\n\n  def test_map_returns_empty_on_2d_input_array\n    foo = [\n      [1],\n      [2],\n      [3],\n    ]\n\n    assert_raises(Liquid::ArgumentError) do\n      @filters.map(foo, \"bar\")\n    end\n  end\n\n  def test_map_with_value_property\n    array = [\n      { \"handle\" => \"alpha\", \"value\" => \"A\" },\n      { \"handle\" => \"beta\", \"value\" => \"B\" },\n      { \"handle\" => \"gamma\", \"value\" => \"C\" }\n    ]\n\n    assert_template_result(\"A B C\", \"{{ array | map: 'value' | join: ' ' }}\", { \"array\" => array })\n  end\n\n  def test_map_returns_input_with_no_property\n    foo = [\n      [1],\n      [2],\n      [3],\n    ]\n\n    assert_raises(Liquid::ArgumentError) do\n      @filters.map(foo, nil)\n    end\n  end\n\n  def test_sort_works_on_enumerables\n    assert_template_result(\"213\", '{{ foo | sort: \"bar\" | map: \"foo\" }}', { \"foo\" => TestEnumerable.new })\n  end\n\n  def test_first_and_last_call_to_liquid\n    assert_template_result('foobar', '{{ foo | first }}', { 'foo' => [ThingWithToLiquid.new] })\n    assert_template_result('foobar', '{{ foo | last }}', { 'foo' => [ThingWithToLiquid.new] })\n  end\n\n  def test_truncate_calls_to_liquid\n    assert_template_result(\"wo...\", '{{ foo | truncate: 5 }}', { \"foo\" => TestThing.new })\n  end\n\n  def test_date\n    assert_equal('May', @filters.date(Time.parse(\"2006-05-05 10:00:00\"), \"%B\"))\n    assert_equal('June', @filters.date(Time.parse(\"2006-06-05 10:00:00\"), \"%B\"))\n    assert_equal('July', @filters.date(Time.parse(\"2006-07-05 10:00:00\"), \"%B\"))\n\n    assert_equal('May', @filters.date(\"2006-05-05 10:00:00\", \"%B\"))\n    assert_equal('June', @filters.date(\"2006-06-05 10:00:00\", \"%B\"))\n    assert_equal('July', @filters.date(\"2006-07-05 10:00:00\", \"%B\"))\n\n    assert_equal('2006-07-05 10:00:00', @filters.date(\"2006-07-05 10:00:00\", \"\"))\n    assert_equal('2006-07-05 10:00:00', @filters.date(\"2006-07-05 10:00:00\", \"\"))\n    assert_equal('2006-07-05 10:00:00', @filters.date(\"2006-07-05 10:00:00\", \"\"))\n    assert_equal('2006-07-05 10:00:00', @filters.date(\"2006-07-05 10:00:00\", nil))\n\n    assert_equal('07/05/2006', @filters.date(\"2006-07-05 10:00:00\", \"%m/%d/%Y\"))\n\n    assert_equal(\"07/16/2004\", @filters.date(\"Fri Jul 16 01:00:00 2004\", \"%m/%d/%Y\"))\n    assert_equal(Date.today.year.to_s, @filters.date('now', '%Y'))\n    assert_equal(Date.today.year.to_s, @filters.date('today', '%Y'))\n    assert_equal(Date.today.year.to_s, @filters.date('Today', '%Y'))\n\n    assert_nil(@filters.date(nil, \"%B\"))\n\n    assert_equal('', @filters.date('', \"%B\"))\n\n    with_timezone(\"UTC\") do\n      assert_equal(\"07/05/2006\", @filters.date(1152098955, \"%m/%d/%Y\"))\n      assert_equal(\"07/05/2006\", @filters.date(\"1152098955\", \"%m/%d/%Y\"))\n    end\n  end\n\n  def test_first_last\n    assert_equal(1, @filters.first([1, 2, 3]))\n    assert_equal(3, @filters.last([1, 2, 3]))\n    assert_nil(@filters.first([]))\n    assert_nil(@filters.last([]))\n  end\n\n  def test_first_last_on_strings\n    # Ruby's String class does not have first/last methods by default.\n    # ActiveSupport adds String#first and String#last to return the first/last character.\n    # Liquid must work without ActiveSupport, so the first/last filters handle strings specially.\n    #\n    # This enables template patterns like:\n    #   {{ product.title | first }}  => \"S\" (for \"Snowboard\")\n    #   {{ customer.name | last }}   => \"h\" (for \"Smith\")\n    #\n    # Note: ActiveSupport returns \"\" for empty strings, not nil.\n    assert_equal('f', @filters.first('foo'))\n    assert_equal('o', @filters.last('foo'))\n    assert_equal('', @filters.first(''))\n    assert_equal('', @filters.last(''))\n  end\n\n  def test_first_last_on_unicode_strings\n    # Unicode strings should return the first/last grapheme cluster (character),\n    # not the first/last byte. Ruby's String#[] handles this correctly with index 0/-1.\n    # This ensures international text works properly:\n    #   {{ korean_name | first }} => \"고\" (not a partial byte sequence)\n    assert_equal('고', @filters.first('고스트빈'))\n    assert_equal('빈', @filters.last('고스트빈'))\n  end\n\n  def test_first_last_on_strings_via_template\n    # Integration test to verify the filter works end-to-end in templates.\n    # Empty strings return empty output (nil renders as empty string).\n    assert_template_result('f', '{{ name | first }}', { 'name' => 'foo' })\n    assert_template_result('o', '{{ name | last }}', { 'name' => 'foo' })\n    assert_template_result('', '{{ name | first }}', { 'name' => '' })\n    assert_template_result('', '{{ name | last }}', { 'name' => '' })\n  end\n\n  def test_replace\n    assert_equal('b b b b', @filters.replace('a a a a', 'a', 'b'))\n    assert_equal('2 2 2 2', @filters.replace('1 1 1 1', 1, 2))\n    assert_equal('1 1 1 1', @filters.replace('1 1 1 1', 2, 3))\n    assert_template_result('2 2 2 2', \"{{ '1 1 1 1' | replace: '1', 2 }}\")\n\n    assert_equal('b a a a', @filters.replace_first('a a a a', 'a', 'b'))\n    assert_equal('2 1 1 1', @filters.replace_first('1 1 1 1', 1, 2))\n    assert_equal('1 1 1 1', @filters.replace_first('1 1 1 1', 2, 3))\n    assert_template_result('2 1 1 1', \"{{ '1 1 1 1' | replace_first: '1', 2 }}\")\n\n    assert_equal('a a a b', @filters.replace_last('a a a a', 'a', 'b'))\n    assert_equal('1 1 1 2', @filters.replace_last('1 1 1 1', 1, 2))\n    assert_equal('1 1 1 1', @filters.replace_last('1 1 1 1', 2, 3))\n    assert_template_result('1 1 1 2', \"{{ '1 1 1 1' | replace_last: '1', 2 }}\")\n  end\n\n  def test_remove\n    assert_equal('   ', @filters.remove(\"a a a a\", 'a'))\n    assert_template_result('   ', \"{{ '1 1 1 1' | remove: 1 }}\")\n\n    assert_equal('b a a', @filters.remove_first(\"a b a a\", 'a '))\n    assert_template_result(' 1 1 1', \"{{ '1 1 1 1' | remove_first: 1 }}\")\n\n    assert_equal('a a b', @filters.remove_last(\"a a b a\", ' a'))\n    assert_template_result('1 1 1 ', \"{{ '1 1 1 1' | remove_last: 1 }}\")\n  end\n\n  def test_pipes_in_string_arguments\n    assert_template_result('foobar', \"{{ 'foo|bar' | remove: '|' }}\")\n  end\n\n  def test_strip\n    assert_template_result('ab c', \"{{ source | strip }}\", { 'source' => \" ab c  \" })\n    assert_template_result('ab c', \"{{ source | strip }}\", { 'source' => \" \\tab c  \\n \\t\" })\n  end\n\n  def test_lstrip\n    assert_template_result('ab c  ', \"{{ source | lstrip }}\", { 'source' => \" ab c  \" })\n    assert_template_result(\"ab c  \\n \\t\", \"{{ source | lstrip }}\", { 'source' => \" \\tab c  \\n \\t\" })\n  end\n\n  def test_rstrip\n    assert_template_result(\" ab c\", \"{{ source | rstrip }}\", { 'source' => \" ab c  \" })\n    assert_template_result(\" \\tab c\", \"{{ source | rstrip }}\", { 'source' => \" \\tab c  \\n \\t\" })\n  end\n\n  def test_strip_newlines\n    assert_template_result('abc', \"{{ source | strip_newlines }}\", { 'source' => \"a\\nb\\nc\" })\n    assert_template_result('abc', \"{{ source | strip_newlines }}\", { 'source' => \"a\\r\\nb\\nc\" })\n  end\n\n  def test_newlines_to_br\n    assert_template_result(\"a<br />\\nb<br />\\nc\", \"{{ source | newline_to_br }}\", { 'source' => \"a\\nb\\nc\" })\n    assert_template_result(\"a<br />\\nb<br />\\nc\", \"{{ source | newline_to_br }}\", { 'source' => \"a\\r\\nb\\nc\" })\n  end\n\n  def test_plus\n    assert_template_result(\"2\", \"{{ 1 | plus:1 }}\")\n    assert_template_result(\"2.0\", \"{{ '1' | plus:'1.0' }}\")\n\n    assert_template_result(\"5\", \"{{ price | plus:'2' }}\", { 'price' => NumberLikeThing.new(3) })\n  end\n\n  def test_minus\n    assert_template_result(\"4\", \"{{ input | minus:operand }}\", { 'input' => 5, 'operand' => 1 })\n    assert_template_result(\"2.3\", \"{{ '4.3' | minus:'2' }}\")\n\n    assert_template_result(\"5\", \"{{ price | minus:'2' }}\", { 'price' => NumberLikeThing.new(7) })\n  end\n\n  def test_abs\n    assert_template_result(\"17\", \"{{ 17 | abs }}\")\n    assert_template_result(\"17\", \"{{ -17 | abs }}\")\n    assert_template_result(\"17\", \"{{ '17' | abs }}\")\n    assert_template_result(\"17\", \"{{ '-17' | abs }}\")\n    assert_template_result(\"0\", \"{{ 0 | abs }}\")\n    assert_template_result(\"0\", \"{{ '0' | abs }}\")\n    assert_template_result(\"17.42\", \"{{ 17.42 | abs }}\")\n    assert_template_result(\"17.42\", \"{{ -17.42 | abs }}\")\n    assert_template_result(\"17.42\", \"{{ '17.42' | abs }}\")\n    assert_template_result(\"17.42\", \"{{ '-17.42' | abs }}\")\n  end\n\n  def test_times\n    assert_template_result(\"12\", \"{{ 3 | times:4 }}\")\n    assert_template_result(\"0\", \"{{ 'foo' | times:4 }}\")\n    assert_template_result(\"6\", \"{{ '2.1' | times:3 | replace: '.','-' | plus:0}}\")\n    assert_template_result(\"7.25\", \"{{ 0.0725 | times:100 }}\")\n    assert_template_result(\"-7.25\", '{{ \"-0.0725\" | times:100 }}')\n    assert_template_result(\"7.25\", '{{ \"-0.0725\" | times: -100 }}')\n    assert_template_result(\"4\", \"{{ price | times:2 }}\", { 'price' => NumberLikeThing.new(2) })\n  end\n\n  def test_divided_by\n    assert_template_result(\"4\", \"{{ 12 | divided_by:3 }}\")\n    assert_template_result(\"4\", \"{{ 14 | divided_by:3 }}\")\n\n    assert_template_result(\"5\", \"{{ 15 | divided_by:3 }}\")\n    assert_equal(\"Liquid error: divided by 0\", Template.parse(\"{{ 5 | divided_by:0 }}\").render)\n\n    assert_template_result(\"0.5\", \"{{ 2.0 | divided_by:4 }}\")\n    assert_raises(Liquid::ZeroDivisionError) do\n      assert_template_result(\"4\", \"{{ 1 | modulo: 0 }}\")\n    end\n\n    assert_template_result(\"5\", \"{{ price | divided_by:2 }}\", { 'price' => NumberLikeThing.new(10) })\n  end\n\n  def test_modulo\n    assert_template_result(\"1\", \"{{ 3 | modulo:2 }}\")\n    assert_raises(Liquid::ZeroDivisionError) do\n      assert_template_result(\"4\", \"{{ 1 | modulo: 0 }}\")\n    end\n\n    assert_template_result(\"1\", \"{{ price | modulo:2 }}\", { 'price' => NumberLikeThing.new(3) })\n  end\n\n  def test_round\n    assert_template_result(\"5\", \"{{ input | round }}\", { 'input' => 4.6 })\n    assert_template_result(\"4\", \"{{ '4.3' | round }}\")\n    assert_template_result(\"4.56\", \"{{ input | round: 2 }}\", { 'input' => 4.5612 })\n    assert_raises(Liquid::FloatDomainError) do\n      assert_template_result(\"4\", \"{{ 1.0 | divided_by: 0.0 | round }}\")\n    end\n\n    assert_template_result(\"5\", \"{{ price | round }}\", { 'price' => NumberLikeThing.new(4.6) })\n    assert_template_result(\"4\", \"{{ price | round }}\", { 'price' => NumberLikeThing.new(4.3) })\n  end\n\n  def test_ceil\n    assert_template_result(\"5\", \"{{ input | ceil }}\", { 'input' => 4.6 })\n    assert_template_result(\"5\", \"{{ '4.3' | ceil }}\")\n    assert_raises(Liquid::FloatDomainError) do\n      assert_template_result(\"4\", \"{{ 1.0 | divided_by: 0.0 | ceil }}\")\n    end\n\n    assert_template_result(\"5\", \"{{ price | ceil }}\", { 'price' => NumberLikeThing.new(4.6) })\n  end\n\n  def test_floor\n    assert_template_result(\"4\", \"{{ input | floor }}\", { 'input' => 4.6 })\n    assert_template_result(\"4\", \"{{ '4.3' | floor }}\")\n    assert_raises(Liquid::FloatDomainError) do\n      assert_template_result(\"4\", \"{{ 1.0 | divided_by: 0.0 | floor }}\")\n    end\n\n    assert_template_result(\"5\", \"{{ price | floor }}\", { 'price' => NumberLikeThing.new(5.4) })\n  end\n\n  def test_at_most\n    assert_template_result(\"4\", \"{{ 5 | at_most:4 }}\")\n    assert_template_result(\"5\", \"{{ 5 | at_most:5 }}\")\n    assert_template_result(\"5\", \"{{ 5 | at_most:6 }}\")\n\n    assert_template_result(\"4.5\", \"{{ 4.5 | at_most:5 }}\")\n    assert_template_result(\"5\", \"{{ width | at_most:5 }}\", { 'width' => NumberLikeThing.new(6) })\n    assert_template_result(\"4\", \"{{ width | at_most:5 }}\", { 'width' => NumberLikeThing.new(4) })\n    assert_template_result(\"4\", \"{{ 5 | at_most: width }}\", { 'width' => NumberLikeThing.new(4) })\n  end\n\n  def test_at_least\n    assert_template_result(\"5\", \"{{ 5 | at_least:4 }}\")\n    assert_template_result(\"5\", \"{{ 5 | at_least:5 }}\")\n    assert_template_result(\"6\", \"{{ 5 | at_least:6 }}\")\n\n    assert_template_result(\"5\", \"{{ 4.5 | at_least:5 }}\")\n    assert_template_result(\"6\", \"{{ width | at_least:5 }}\", { 'width' => NumberLikeThing.new(6) })\n    assert_template_result(\"5\", \"{{ width | at_least:5 }}\", { 'width' => NumberLikeThing.new(4) })\n    assert_template_result(\"6\", \"{{ 5 | at_least: width }}\", { 'width' => NumberLikeThing.new(6) })\n  end\n\n  def test_append\n    assigns = { 'a' => 'bc', 'b' => 'd' }\n    assert_template_result('bcd', \"{{ a | append: 'd'}}\", assigns)\n    assert_template_result('bcd', \"{{ a | append: b}}\", assigns)\n  end\n\n  def test_concat\n    assert_equal([1, 2, 3, 4], @filters.concat([1, 2], [3, 4]))\n    assert_equal([1, 2, 'a'],  @filters.concat([1, 2], ['a']))\n    assert_equal([1, 2, 10],   @filters.concat([1, 2], [10]))\n\n    assert_raises(Liquid::ArgumentError, \"concat filter requires an array argument\") do\n      @filters.concat([1, 2], 10)\n    end\n  end\n\n  def test_prepend\n    assigns = { 'a' => 'bc', 'b' => 'a' }\n    assert_template_result('abc', \"{{ a | prepend: 'a'}}\", assigns)\n    assert_template_result('abc', \"{{ a | prepend: b}}\", assigns)\n  end\n\n  def test_default\n    assert_equal(\"foo\", @filters.default(\"foo\", \"bar\"))\n    assert_equal(\"bar\", @filters.default(nil, \"bar\"))\n    assert_equal(\"bar\", @filters.default(\"\", \"bar\"))\n    assert_equal(\"bar\", @filters.default(false, \"bar\"))\n    assert_equal(\"bar\", @filters.default([], \"bar\"))\n    assert_equal(\"bar\", @filters.default({}, \"bar\"))\n    assert_template_result('bar', \"{{ false | default: 'bar' }}\")\n    assert_template_result('bar', \"{{ drop | default: 'bar' }}\", { 'drop' => BooleanDrop.new(false) })\n    assert_template_result('Yay', \"{{ drop | default: 'bar' }}\", { 'drop' => BooleanDrop.new(true) })\n  end\n\n  def test_default_handle_false\n    assert_equal(\"foo\", @filters.default(\"foo\", \"bar\", \"allow_false\" => true))\n    assert_equal(\"bar\", @filters.default(nil, \"bar\", \"allow_false\" => true))\n    assert_equal(\"bar\", @filters.default(\"\", \"bar\", \"allow_false\" => true))\n    assert_equal(false, @filters.default(false, \"bar\", \"allow_false\" => true))\n    assert_equal(\"bar\", @filters.default([], \"bar\", \"allow_false\" => true))\n    assert_equal(\"bar\", @filters.default({}, \"bar\", \"allow_false\" => true))\n    assert_template_result('false', \"{{ false | default: 'bar', allow_false: true }}\")\n    assert_template_result('Nay', \"{{ drop | default: 'bar', allow_false: true }}\", { 'drop' => BooleanDrop.new(false) })\n    assert_template_result('Yay', \"{{ drop | default: 'bar', allow_false: true }}\", { 'drop' => BooleanDrop.new(true) })\n  end\n\n  def test_cannot_access_private_methods\n    assert_template_result('a', \"{{ 'a' | to_number }}\")\n  end\n\n  def test_date_raises_nothing\n    assert_template_result('', \"{{ '' | date: '%D' }}\")\n    assert_template_result('abc', \"{{ 'abc' | date: '%D' }}\")\n  end\n\n  def test_reject\n    array = [\n      { \"handle\" => \"alpha\", \"ok\" => true },\n      { \"handle\" => \"beta\", \"ok\" => false },\n      { \"handle\" => \"gamma\", \"ok\" => false },\n      { \"handle\" => \"delta\", \"ok\" => true },\n    ]\n\n    template = \"{{ array | reject: 'ok' | map: 'handle' | join: ' ' }}\"\n    expected_output = \"beta gamma\"\n\n    assert_template_result(expected_output, template, { \"array\" => array })\n  end\n\n  def test_reject_with_value\n    array = [\n      { \"handle\" => \"alpha\", \"ok\" => true },\n      { \"handle\" => \"beta\", \"ok\" => false },\n      { \"handle\" => \"gamma\", \"ok\" => false },\n      { \"handle\" => \"delta\", \"ok\" => true },\n    ]\n\n    template = \"{{ array | reject: 'ok', true | map: 'handle' | join: ' ' }}\"\n    expected_output = \"beta gamma\"\n\n    assert_template_result(expected_output, template, { \"array\" => array })\n  end\n\n  def test_reject_with_false_value\n    array = [\n      { \"handle\" => \"alpha\", \"ok\" => true },\n      { \"handle\" => \"beta\", \"ok\" => false },\n      { \"handle\" => \"gamma\", \"ok\" => false },\n      { \"handle\" => \"delta\", \"ok\" => true },\n    ]\n\n    template = \"{{ array | reject: 'ok', false | map: 'handle' | join: ' ' }}\"\n    expected_output = \"alpha delta\"\n\n    assert_template_result(expected_output, template, { \"array\" => array })\n  end\n\n  def test_has\n    array = [\n      { \"handle\" => \"alpha\", \"ok\" => true },\n      { \"handle\" => \"beta\", \"ok\" => false },\n      { \"handle\" => \"gamma\", \"ok\" => false },\n      { \"handle\" => \"delta\", \"ok\" => false },\n    ]\n\n    expected_output = \"true\"\n\n    assert_template_result(expected_output, \"{{ array | has: 'ok' }}\", { \"array\" => array })\n    assert_template_result(expected_output, \"{{ array | has: 'ok', true }}\", { \"array\" => array })\n  end\n\n  def test_has_when_does_not_have_it\n    array = [\n      { \"handle\" => \"alpha\", \"ok\" => false },\n      { \"handle\" => \"beta\", \"ok\" => false },\n      { \"handle\" => \"gamma\", \"ok\" => false },\n      { \"handle\" => \"delta\", \"ok\" => false },\n    ]\n\n    expected_output = \"false\"\n\n    assert_template_result(expected_output, \"{{ array | has: 'ok' }}\", { \"array\" => array })\n    assert_template_result(expected_output, \"{{ array | has: 'ok', true }}\", { \"array\" => array })\n  end\n\n  def test_has_with_empty_arrays\n    template = <<~LIQUID\n      {%- assign has_product = products | has: 'title.content', 'Not found' -%}\n      {%- unless has_product -%}\n        Product not found.\n      {%- endunless -%}\n    LIQUID\n    expected_output = \"Product not found.\"\n\n    assert_template_result(expected_output, template, { \"products\" => [] })\n  end\n\n  def test_has_with_false_value\n    array = [\n      { \"handle\" => \"alpha\", \"ok\" => true },\n      { \"handle\" => \"beta\", \"ok\" => false },\n      { \"handle\" => \"gamma\", \"ok\" => false },\n      { \"handle\" => \"delta\", \"ok\" => true },\n    ]\n\n    template = \"{{ array | has: 'ok', false }}\"\n    expected_output = \"true\"\n\n    assert_template_result(expected_output, template, { \"array\" => array })\n  end\n\n  def test_has_with_false_value_when_does_not_have_it\n    array = [\n      { \"handle\" => \"alpha\", \"ok\" => true },\n      { \"handle\" => \"beta\", \"ok\" => true },\n      { \"handle\" => \"gamma\", \"ok\" => true },\n      { \"handle\" => \"delta\", \"ok\" => true },\n    ]\n\n    template = \"{{ array | has: 'ok', false }}\"\n    expected_output = \"false\"\n\n    assert_template_result(expected_output, template, { \"array\" => array })\n  end\n\n  def test_find_with_value\n    products = [\n      { \"title\" => \"Pro goggles\",    \"price\" => 1299 },\n      { \"title\" => \"Thermal gloves\", \"price\" => 1499 },\n      { \"title\" => \"Alpine jacket\",  \"price\" => 3999 },\n      { \"title\" => \"Mountain boots\", \"price\" => 3899 },\n      { \"title\" => \"Safety helmet\",  \"price\" => 1999 }\n    ]\n\n    template = <<~LIQUID\n      {%- assign product = products | find: 'price', 3999 -%}\n      {{- product.title -}}\n    LIQUID\n    expected_output = \"Alpine jacket\"\n\n    assert_template_result(expected_output, template, { \"products\" => products })\n  end\n\n  def test_find_with_empty_arrays\n    template = <<~LIQUID\n      {%- assign product = products | find: 'title.content', 'Not found' -%}\n      {%- unless product -%}\n        Product not found.\n      {%- endunless -%}\n    LIQUID\n    expected_output = \"Product not found.\"\n\n    assert_template_result(expected_output, template, { \"products\" => [] })\n  end\n\n  def test_find_index_with_value\n    products = [\n      { \"title\" => \"Pro goggles\",    \"price\" => 1299 },\n      { \"title\" => \"Thermal gloves\", \"price\" => 1499 },\n      { \"title\" => \"Alpine jacket\",  \"price\" => 3999 },\n      { \"title\" => \"Mountain boots\", \"price\" => 3899 },\n      { \"title\" => \"Safety helmet\",  \"price\" => 1999 }\n    ]\n\n    template = <<~LIQUID\n      {%- assign index = products | find_index: 'price', 3999 -%}\n      {{- index -}}\n    LIQUID\n    expected_output = \"2\"\n\n    assert_template_result(expected_output, template, { \"products\" => products })\n  end\n\n  def test_find_index_with_empty_arrays\n    template = <<~LIQUID\n      {%- assign index = products | find_index: 'title.content', 'Not found' -%}\n      {%- unless index -%}\n        Index not found.\n      {%- endunless -%}\n    LIQUID\n    expected_output = \"Index not found.\"\n\n    assert_template_result(expected_output, template, { \"products\" => [] })\n  end\n\n  def test_where\n    array = [\n      { \"handle\" => \"alpha\", \"ok\" => true },\n      { \"handle\" => \"beta\", \"ok\" => false },\n      { \"handle\" => \"gamma\", \"ok\" => false },\n      { \"handle\" => \"delta\", \"ok\" => true },\n    ]\n\n    template = \"{{ array | where: 'ok' | map: 'handle' | join: ' ' }}\"\n    expected_output = \"alpha delta\"\n\n    assert_template_result(expected_output, template, { \"array\" => array })\n  end\n\n  def test_where_with_empty_string_is_a_no_op\n    environment = { \"array\" => [\"alpha\", \"beta\", \"gamma\"] }\n    expected_output = \"alpha beta gamma\"\n    template = \"{{ array | where: '' | join: ' ' }}\"\n\n    assert_template_result(expected_output, template, environment)\n  end\n\n  def test_where_with_nil_is_a_no_op\n    environment = { \"array\" => [\"alpha\", \"beta\", \"gamma\"] }\n    template = \"{{ array | where: nil | join: ' ' }}\"\n\n    assert_raises(Liquid::ArgumentError) do\n      assert_template_result(\"alpha beta gamma\", template, environment)\n    end\n  end\n\n  def test_where_with_value\n    array = [\n      { \"handle\" => \"alpha\", \"ok\" => true },\n      { \"handle\" => \"beta\", \"ok\" => false },\n      { \"handle\" => \"gamma\", \"ok\" => false },\n      { \"handle\" => \"delta\", \"ok\" => true },\n    ]\n\n    template = \"{{ array | where: 'ok', true | map: 'handle' | join: ' ' }}\"\n    expected_output = \"alpha delta\"\n\n    assert_template_result(expected_output, template, { \"array\" => array })\n  end\n\n  def test_where_with_false_value\n    array = [\n      { \"handle\" => \"alpha\", \"ok\" => true },\n      { \"handle\" => \"beta\", \"ok\" => false },\n      { \"handle\" => \"gamma\", \"ok\" => false },\n      { \"handle\" => \"delta\", \"ok\" => true },\n    ]\n\n    template = \"{{ array | where: 'ok', false | map: 'handle' | join: ' ' }}\"\n    expected_output = \"beta gamma\"\n\n    assert_template_result(expected_output, template, { \"array\" => array })\n  end\n\n  def test_where_string_keys\n    input = [\n      \"alpha\", \"beta\", \"gamma\", \"delta\"\n    ]\n\n    expectation = [\n      \"beta\",\n    ]\n\n    assert_equal(expectation, @filters.where(input, \"be\"))\n  end\n\n  def test_where_no_key_set\n    input = [\n      { \"handle\" => \"alpha\", \"ok\" => true },\n      { \"handle\" => \"beta\" },\n      { \"handle\" => \"gamma\" },\n      { \"handle\" => \"delta\", \"ok\" => true },\n    ]\n\n    expectation = [\n      { \"handle\" => \"alpha\", \"ok\" => true },\n      { \"handle\" => \"delta\", \"ok\" => true },\n    ]\n\n    assert_equal(expectation, @filters.where(input, \"ok\", true))\n    assert_equal(expectation, @filters.where(input, \"ok\"))\n  end\n\n  def test_where_non_array_map_input\n    assert_equal([{ \"a\" => \"ok\" }], @filters.where({ \"a\" => \"ok\" }, \"a\", \"ok\"))\n    assert_equal([], @filters.where({ \"a\" => \"not ok\" }, \"a\", \"ok\"))\n  end\n\n  def test_where_indexable_but_non_map_value\n    assert_raises(Liquid::ArgumentError) { @filters.where(1, \"ok\", true) }\n    assert_raises(Liquid::ArgumentError) { @filters.where(1, \"ok\") }\n  end\n\n  def test_where_non_boolean_value\n    input = [\n      { \"message\" => \"Bonjour!\", \"language\" => \"French\" },\n      { \"message\" => \"Hello!\", \"language\" => \"English\" },\n      { \"message\" => \"Hallo!\", \"language\" => \"German\" },\n    ]\n\n    assert_equal([{ \"message\" => \"Bonjour!\", \"language\" => \"French\" }], @filters.where(input, \"language\", \"French\"))\n    assert_equal([{ \"message\" => \"Hallo!\", \"language\" => \"German\" }], @filters.where(input, \"language\", \"German\"))\n    assert_equal([{ \"message\" => \"Hello!\", \"language\" => \"English\" }], @filters.where(input, \"language\", \"English\"))\n  end\n\n  def test_where_array_of_only_unindexable_values\n    assert_nil(@filters.where([nil], \"ok\", true))\n    assert_nil(@filters.where([nil], \"ok\"))\n  end\n\n  def test_all_filters_never_raise_non_liquid_exception\n    test_drop = TestDrop.new(value: \"test\")\n    test_drop.context = Context.new\n    test_enum = TestEnumerable.new\n    test_enum.context = Context.new\n    test_types = [\n      \"foo\",\n      123,\n      0,\n      0.0,\n      -1234.003030303,\n      -99999999,\n      1234.38383000383830003838300,\n      nil,\n      true,\n      false,\n      TestThing.new,\n      test_drop,\n      test_enum,\n      [\"foo\", \"bar\"],\n      { \"foo\" => \"bar\" },\n      { foo: \"bar\" },\n      [{ \"foo\" => \"bar\" }, { \"foo\" => 123 }, { \"foo\" => nil }, { \"foo\" => true }, { \"foo\" => [\"foo\", \"bar\"] }],\n      { 1 => \"bar\" },\n      [\"foo\", 123, nil, true, false, Drop, [\"foo\"], { foo: \"bar\" }],\n    ]\n    StandardFilters.public_instance_methods(false).each do |method|\n      arg_count = @filters.method(method).arity\n      arg_count *= -1 if arg_count < 0\n\n      test_types.repeated_permutation(arg_count) do |args|\n        @filters.send(method, *args)\n      rescue Liquid::Error\n        nil\n      end\n    end\n  end\n\n  def test_where_no_target_value\n    input = [\n      { \"foo\" => false },\n      { \"foo\" => true },\n      { \"foo\" => \"for sure\" },\n      { \"bar\" => true },\n    ]\n\n    assert_equal([{ \"foo\" => true }, { \"foo\" => \"for sure\" }], @filters.where(input, \"foo\"))\n  end\n\n  def test_sum_with_all_numbers\n    input = [1, 2]\n\n    assert_equal(3, @filters.sum(input))\n    assert_raises(Liquid::ArgumentError, \"cannot select the property 'quantity'\") do\n      @filters.sum(input, \"quantity\")\n    end\n  end\n\n  def test_sum_with_numeric_strings\n    input = [1, 2, \"3\", \"4\"]\n\n    assert_equal(10, @filters.sum(input))\n    assert_raises(Liquid::ArgumentError, \"cannot select the property 'quantity'\") do\n      @filters.sum(input, \"quantity\")\n    end\n  end\n\n  def test_sum_with_nested_arrays\n    input = [1, [2, [3, 4]]]\n\n    assert_equal(10, @filters.sum(input))\n    assert_raises(Liquid::ArgumentError, \"cannot select the property 'quantity'\") do\n      @filters.sum(input, \"quantity\")\n    end\n  end\n\n  def test_sum_with_indexable_map_values\n    input = [{ \"quantity\" => 1 }, { \"quantity\" => 2, \"weight\" => 3 }, { \"weight\" => 4 }]\n\n    assert_equal(0, @filters.sum(input))\n    assert_equal(3, @filters.sum(input, \"quantity\"))\n    assert_equal(7, @filters.sum(input, \"weight\"))\n    assert_equal(0, @filters.sum(input, \"subtotal\"))\n  end\n\n  def test_sum_with_indexable_non_map_values\n    input = [1, [2], \"foo\", { \"quantity\" => 3 }]\n\n    assert_equal(3, @filters.sum(input))\n    assert_raises(Liquid::ArgumentError, \"cannot select the property 'quantity'\") do\n      @filters.sum(input, \"quantity\")\n    end\n  end\n\n  def test_sum_with_unindexable_values\n    input = [1, true, nil, { \"quantity\" => 2 }]\n\n    assert_equal(1, @filters.sum(input))\n    assert_raises(Liquid::ArgumentError, \"cannot select the property 'quantity'\") do\n      @filters.sum(input, \"quantity\")\n    end\n  end\n\n  def test_sum_without_property_calls_to_liquid\n    t = TestThing.new\n    Liquid::Template.parse('{{ foo | sum }}').render(\"foo\" => [t])\n    assert(t.foo > 0)\n  end\n\n  def test_sum_with_property_calls_to_liquid_on_property_values\n    t = TestThing.new\n    Liquid::Template.parse('{{ foo | sum: \"quantity\" }}').render(\"foo\" => [{ \"quantity\" => t }])\n    assert(t.foo > 0)\n  end\n\n  def test_sum_of_floats\n    input = [0.1, 0.2, 0.3]\n    assert_equal(0.6, @filters.sum(input))\n    assert_template_result(\"0.6\", \"{{ input | sum }}\", { \"input\" => input })\n  end\n\n  def test_sum_of_negative_floats\n    input = [0.1, 0.2, -0.3]\n    assert_equal(0.0, @filters.sum(input))\n    assert_template_result(\"0.0\", \"{{ input | sum }}\", { \"input\" => input })\n  end\n\n  def test_sum_with_float_strings\n    input = [0.1, \"0.2\", \"0.3\"]\n    assert_equal(0.6, @filters.sum(input))\n    assert_template_result(\"0.6\", \"{{ input | sum }}\", { \"input\" => input })\n  end\n\n  def test_sum_resulting_in_negative_float\n    input = [0.1, -0.2, -0.3]\n    assert_equal(-0.4, @filters.sum(input))\n    assert_template_result(\"-0.4\", \"{{ input | sum }}\", { \"input\" => input })\n  end\n\n  def test_sum_with_floats_and_indexable_map_values\n    input = [{ \"quantity\" => 1 }, { \"quantity\" => 0.2, \"weight\" => -0.3 }, { \"weight\" => 0.4 }]\n    assert_equal(0.0, @filters.sum(input))\n    assert_equal(1.2, @filters.sum(input, \"quantity\"))\n    assert_equal(0.1, @filters.sum(input, \"weight\"))\n    assert_equal(0.0, @filters.sum(input, \"subtotal\"))\n    assert_template_result(\"0\", \"{{ input | sum }}\", { \"input\" => input })\n    assert_template_result(\"1.2\", \"{{ input | sum: 'quantity' }}\", { \"input\" => input })\n    assert_template_result(\"0.1\", \"{{ input | sum: 'weight' }}\", { \"input\" => input })\n    assert_template_result(\"0\", \"{{ input | sum: 'subtotal' }}\", { \"input\" => input })\n  end\n\n  def test_sum_with_non_string_property\n    input = [{ true => 1 }, { 1.0 => 0.2, 1 => -0.3 }, { 1..5 => 0.4 }]\n\n    assert_equal(1, @filters.sum(input, true))\n    assert_equal(0.2, @filters.sum(input, 1.0))\n    assert_equal(-0.3, @filters.sum(input, 1))\n    assert_equal(0.4, @filters.sum(input, 1..5))\n    assert_equal(0, @filters.sum(input, nil))\n    assert_equal(0, @filters.sum(input, \"\"))\n  end\n\n  def test_uniq_with_to_liquid_value\n    input = [StringDrop.new(\"foo\"), StringDrop.new(\"bar\"), \"foo\"]\n    expected = [StringDrop.new(\"foo\"), StringDrop.new(\"bar\")]\n    result = @filters.uniq(input)\n\n    assert_equal(expected, result)\n  end\n\n  def test_uniq_with_to_liquid_value_pick_correct_classes\n    input = [\"foo\", StringDrop.new(\"foo\"), StringDrop.new(\"bar\")]\n    expected = [String, StringDrop]\n    result = @filters.uniq(input).map(&:class)\n\n    assert_equal(expected, result)\n  end\n\n  private\n\n  def with_timezone(tz)\n    old_tz    = ENV['TZ']\n    ENV['TZ'] = tz\n    yield\n  ensure\n    ENV['TZ'] = old_tz\n  end\nend # StandardFiltersTest\n"
  },
  {
    "path": "test/integration/tag/disableable_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass TagDisableableTest < Minitest::Test\n  include Liquid\n\n  module RenderTagName\n    def render(_context)\n      tag_name\n    end\n  end\n\n  class Custom < Tag\n    prepend Liquid::Tag::Disableable\n    include RenderTagName\n  end\n\n  class Custom2 < Tag\n    prepend Liquid::Tag::Disableable\n    include RenderTagName\n  end\n\n  class DisableCustom < Block\n    disable_tags \"custom\"\n  end\n\n  class DisableBoth < Block\n    disable_tags \"custom\", \"custom2\"\n  end\n\n  def test_block_tag_disabling_nested_tag\n    with_disableable_tags do\n      with_custom_tag('disable', DisableCustom) do\n        output = Template.parse('{% disable %}{% custom %};{% custom2 %}{% enddisable %}').render\n        assert_equal('Liquid error: custom usage is not allowed in this context;custom2', output)\n      end\n    end\n  end\n\n  def test_block_tag_disabling_multiple_nested_tags\n    with_disableable_tags do\n      with_custom_tag('disable', DisableBoth) do\n        output = Template.parse('{% disable %}{% custom %};{% custom2 %}{% enddisable %}').render\n        assert_equal('Liquid error: custom usage is not allowed in this context;Liquid error: custom2 usage is not allowed in this context', output)\n      end\n    end\n  end\n\n  private\n\n  def with_disableable_tags\n    with_custom_tag('custom', Custom) do\n      with_custom_tag('custom2', Custom2) do\n        yield\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/integration/tag_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass TagTest < Minitest::Test\n  include Liquid\n\n  def test_custom_tags_have_a_default_render_to_output_buffer_method_for_backwards_compatibility\n    klass1 = Class.new(Tag) do\n      def render(*)\n        'hello'\n      end\n    end\n\n    with_custom_tag('blabla', klass1) do\n      template = Liquid::Template.parse(\"{% blabla %}\")\n\n      assert_equal('hello', template.render)\n\n      buf    = +''\n      output = template.render({}, output: buf)\n      assert_equal('hello', output)\n      assert_equal('hello', buf)\n      assert_equal(buf.object_id, output.object_id)\n    end\n\n    klass2 = Class.new(klass1) do\n      def render(*)\n        'foo' + super + 'bar'\n      end\n    end\n\n    with_custom_tag('blabla', klass2) do\n      template = Liquid::Template.parse(\"{% blabla %}\")\n\n      assert_equal('foohellobar', template.render)\n\n      buf    = +''\n      output = template.render({}, output: buf)\n      assert_equal('foohellobar', output)\n      assert_equal('foohellobar', buf)\n      assert_equal(buf.object_id, output.object_id)\n    end\n  end\nend\n"
  },
  {
    "path": "test/integration/tags/break_tag_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass BreakTagTest < Minitest::Test\n  include Liquid\n\n  # tests that no weird errors are raised if break is called outside of a\n  # block\n  def test_break_with_no_block\n    assigns  = { 'i' => 1 }\n    markup   = 'before{% break %}after'\n    expected = 'before'\n\n    assert_template_result(expected, markup, assigns)\n  end\nend\n"
  },
  {
    "path": "test/integration/tags/continue_tag_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ContinueTagTest < Minitest::Test\n  include Liquid\n\n  # tests that no weird errors are raised if continue is called outside of a\n  # block\n  def test_continue_with_no_block\n    assigns  = {}\n    markup   = '{% continue %}'\n    expected = ''\n\n    assert_template_result(expected, markup, assigns)\n  end\nend\n"
  },
  {
    "path": "test/integration/tags/cycle_tag_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass CycleTagTest < Minitest::Test\n  def test_simple_cycle_inside_for_loop\n    template = <<~LIQUID\n      {%- for i in (1..3) -%}\n        {%- cycle '1', '2', '3' -%}\n      {%- endfor -%}\n    LIQUID\n\n    assert_template_result(\"123\", template)\n  end\n\n  def test_cycle_with_variables_inside_for_loop\n    template = <<~LIQUID\n      {%- assign a = 1 -%}\n      {%- assign b = 2 -%}\n      {%- assign c = 3 -%}\n      {%- for i in (1..3) -%}\n        {% cycle a, b, c %}\n      {%- endfor -%}\n    LIQUID\n\n    assert_template_result(\"123\", template)\n  end\n\n  def test_cycle_named_groups_string\n    template = <<~LIQUID\n      {%- for i in (1..3) -%}\n        {%- cycle 'placeholder1': 1, 2, 3 -%}\n        {%- cycle 'placeholder2': 1, 2, 3 -%}\n      {%- endfor -%}\n    LIQUID\n\n    assert_template_result(\"112233\", template)\n  end\n\n  def test_cycle_named_groups_vlookup\n    template = <<~LIQUID\n      {%- assign placeholder1 = 'placeholder1' -%}\n      {%- assign placeholder2 = 'placeholder2' -%}\n      {%- for i in (1..3) -%}\n        {%- cycle placeholder1: 1, 2, 3 -%}\n        {%- cycle placeholder2: 1, 2, 3 -%}\n      {%- endfor -%}\n    LIQUID\n\n    assert_template_result(\"112233\", template)\n  end\n\n  def test_unnamed_cycle_have_independent_counters_when_used_with_lookups\n    template = <<~LIQUID\n      {%- assign a = \"1\" -%}\n      {%- for i in (1..3) -%}\n        {%- cycle a, \"2\" -%}\n        {%- cycle a, \"2\" -%}\n      {%- endfor -%}\n    LIQUID\n\n    assert_template_result(\"112211\", template)\n  end\n\n  def test_unnamed_cycle_dependent_counter_when_used_with_literal_values\n    template = <<~LIQUID\n      {%- cycle \"1\", \"2\" -%}\n      {%- cycle \"1\", \"2\" -%}\n      {%- cycle \"1\", \"2\" -%}\n    LIQUID\n\n    assert_template_result(\"121\", template)\n  end\n\n  def test_optional_trailing_comma\n    template = <<~LIQUID\n      {%- cycle \"1\", \"2\", -%}\n      {%- cycle \"1\", \"2\", -%}\n      {%- cycle \"1\", \"2\", -%}\n      {%- cycle \"1\", -%}\n    LIQUID\n\n    assert_template_result(\"1211\", template)\n  end\n\n  def test_cycle_tag_without_arguments\n    error = assert_raises(Liquid::SyntaxError) do\n      Template.parse(\"{% cycle %}\")\n    end\n\n    assert_match(/Syntax Error in 'cycle' - Valid syntax: cycle \\[name :\\] var/, error.message)\n  end\n\n  def test_cycle_tag_with_error_mode\n    # QuotedFragment is more permissive than what Parser#expression allows.\n    template1 = \"{% assign 5 = 'b' %}{% cycle .5, .4 %}\"\n    template2 = \"{% cycle .5: 'a', 'b' %}\"\n\n    with_error_modes(:lax, :strict) do\n      assert_template_result(\"b\", template1)\n      assert_template_result(\"a\", template2)\n    end\n\n    with_error_modes(:strict2) do\n      error1 = assert_raises(Liquid::SyntaxError) { Template.parse(template1) }\n      error2 = assert_raises(Liquid::SyntaxError) { Template.parse(template2) }\n\n      expected_error = /Liquid syntax error: \\[:dot, \".\"\\] is not a valid expression/\n\n      assert_match(expected_error, error1.message)\n      assert_match(expected_error, error2.message)\n    end\n  end\n\n  def test_cycle_with_trailing_elements\n    assignments = \"{% assign a = 'A' %}{% assign n = 'N' %}\"\n\n    template1 = \"#{assignments}{% cycle       'a'  'b', 'c' %}\"\n    template2 = \"#{assignments}{% cycle name: 'a'  'b', 'c' %}\"\n    template3 = \"#{assignments}{% cycle name: 'a', 'b'  'c' %}\"\n    template4 = \"#{assignments}{% cycle n  e: 'a', 'b', 'c' %}\"\n    template5 = \"#{assignments}{% cycle n  e  'a', 'b', 'c' %}\"\n\n    with_error_modes(:lax, :strict) do\n      assert_template_result(\"a\", template1)\n      assert_template_result(\"a\", template2)\n      assert_template_result(\"a\", template3)\n      assert_template_result(\"N\", template4)\n      assert_template_result(\"N\", template5)\n    end\n\n    with_error_modes(:strict2) do\n      error1 = assert_raises(Liquid::SyntaxError) { Template.parse(template1) }\n      error2 = assert_raises(Liquid::SyntaxError) { Template.parse(template2) }\n      error3 = assert_raises(Liquid::SyntaxError) { Template.parse(template3) }\n      error4 = assert_raises(Liquid::SyntaxError) { Template.parse(template4) }\n      error5 = assert_raises(Liquid::SyntaxError) { Template.parse(template5) }\n\n      expected_error = /Expected end_of_string but found/\n\n      assert_match(expected_error, error1.message)\n      assert_match(expected_error, error2.message)\n      assert_match(expected_error, error3.message)\n      assert_match(expected_error, error4.message)\n      assert_match(expected_error, error5.message)\n    end\n  end\n\n  def test_cycle_name_with_invalid_expression\n    template = <<~LIQUID\n      {% for i in (1..3) %}\n        {% cycle foo=>bar: \"a\", \"b\" %}\n      {% endfor %}\n    LIQUID\n\n    with_error_modes(:lax, :strict) do\n      refute_nil(Template.parse(template))\n    end\n\n    with_error_modes(:strict2) do\n      error = assert_raises(Liquid::SyntaxError) { Template.parse(template) }\n      assert_match(/Unexpected character =/, error.message)\n    end\n  end\n\n  def test_cycle_variable_with_invalid_expression\n    template = <<~LIQUID\n      {% for i in (1..3) %}\n        {% cycle foo=>bar, \"a\", \"b\" %}\n      {% endfor %}\n    LIQUID\n\n    with_error_modes(:lax, :strict) do\n      refute_nil(Template.parse(template))\n    end\n\n    with_error_modes(:strict2) do\n      error = assert_raises(Liquid::SyntaxError) { Template.parse(template) }\n      assert_match(/Unexpected character =/, error.message)\n    end\n  end\nend\n"
  },
  {
    "path": "test/integration/tags/echo_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass EchoTest < Minitest::Test\n  include Liquid\n\n  def test_echo_outputs_its_input\n    assert_template_result('BAR', <<~LIQUID, { 'variable-name' => 'bar' })\n      {%- echo variable-name | upcase -%}\n    LIQUID\n  end\nend\n"
  },
  {
    "path": "test/integration/tags/for_tag_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ThingWithValue < Liquid::Drop\n  def value\n    3\n  end\nend\n\nclass ForTagTest < Minitest::Test\n  include Liquid\n\n  def test_for\n    assert_template_result(' yo  yo  yo  yo ', '{%for item in array%} yo {%endfor%}', { 'array' => [1, 2, 3, 4] })\n    assert_template_result('yoyo', '{%for item in array%}yo{%endfor%}', { 'array' => [1, 2] })\n    assert_template_result(' yo ', '{%for item in array%} yo {%endfor%}', { 'array' => [1] })\n    assert_template_result('', '{%for item in array%}{%endfor%}', { 'array' => [1, 2] })\n    expected = <<HERE\n\n  yo\n\n  yo\n\n  yo\n\nHERE\n    template = <<~HERE\n      {%for item in array%}\n        yo\n      {%endfor%}\n    HERE\n    assert_template_result(expected, template, { 'array' => [1, 2, 3] })\n  end\n\n  def test_for_reversed\n    assigns = { 'array' => [1, 2, 3] }\n    assert_template_result('321', '{%for item in array reversed %}{{item}}{%endfor%}', assigns)\n  end\n\n  def test_for_with_range\n    assert_template_result(' 1  2  3 ', '{%for item in (1..3) %} {{item}} {%endfor%}')\n\n    assert_raises(Liquid::ArgumentError) do\n      Template.parse('{% for i in (a..2) %}{% endfor %}').render!(\"a\" => [1, 2])\n    end\n\n    assert_template_result(' 0  1  2  3 ', '{% for item in (a..3) %} {{item}} {% endfor %}', { \"a\" => \"invalid integer\" })\n  end\n\n  def test_for_with_variable_range\n    assert_template_result(' 1  2  3 ', '{%for item in (1..foobar) %} {{item}} {%endfor%}', { \"foobar\" => 3 })\n  end\n\n  def test_for_with_hash_value_range\n    foobar = { \"value\" => 3 }\n    assert_template_result(' 1  2  3 ', '{%for item in (1..foobar.value) %} {{item}} {%endfor%}', { \"foobar\" => foobar })\n  end\n\n  def test_for_with_drop_value_range\n    foobar = ThingWithValue.new\n    assert_template_result(' 1  2  3 ', '{%for item in (1..foobar.value) %} {{item}} {%endfor%}', { \"foobar\" => foobar })\n  end\n\n  def test_for_with_variable\n    assert_template_result(' 1  2  3 ', '{%for item in array%} {{item}} {%endfor%}', { 'array' => [1, 2, 3] })\n    assert_template_result('123', '{%for item in array%}{{item}}{%endfor%}', { 'array' => [1, 2, 3] })\n    assert_template_result('123', '{% for item in array %}{{item}}{% endfor %}', { 'array' => [1, 2, 3] })\n    assert_template_result('abcd', '{%for item in array%}{{item}}{%endfor%}', { 'array' => ['a', 'b', 'c', 'd'] })\n    assert_template_result('a b c', '{%for item in array%}{{item}}{%endfor%}', { 'array' => ['a', ' ', 'b', ' ', 'c'] })\n    assert_template_result('abc', '{%for item in array%}{{item}}{%endfor%}', { 'array' => ['a', '', 'b', '', 'c'] })\n  end\n\n  def test_for_helpers\n    assigns = { 'array' => [1, 2, 3] }\n    assert_template_result(\n      ' 1/3  2/3  3/3 ',\n      '{%for item in array%} {{forloop.index}}/{{forloop.length}} {%endfor%}',\n      assigns,\n    )\n    assert_template_result(' 1  2  3 ', '{%for item in array%} {{forloop.index}} {%endfor%}', assigns)\n    assert_template_result(' 0  1  2 ', '{%for item in array%} {{forloop.index0}} {%endfor%}', assigns)\n    assert_template_result(' 2  1  0 ', '{%for item in array%} {{forloop.rindex0}} {%endfor%}', assigns)\n    assert_template_result(' 3  2  1 ', '{%for item in array%} {{forloop.rindex}} {%endfor%}', assigns)\n    assert_template_result(' true  false  false ', '{%for item in array%} {{forloop.first}} {%endfor%}', assigns)\n    assert_template_result(' false  false  true ', '{%for item in array%} {{forloop.last}} {%endfor%}', assigns)\n  end\n\n  def test_for_and_if\n    assigns = { 'array' => [1, 2, 3] }\n    assert_template_result(\n      '+--',\n      '{%for item in array%}{% if forloop.first %}+{% else %}-{% endif %}{%endfor%}',\n      assigns,\n    )\n  end\n\n  def test_for_else\n    assert_template_result('+++', '{%for item in array%}+{%else%}-{%endfor%}', { 'array' => [1, 2, 3] })\n    assert_template_result('-',   '{%for item in array%}+{%else%}-{%endfor%}', { 'array' => [] })\n    assert_template_result('-',   '{%for item in array%}+{%else%}-{%endfor%}', { 'array' => nil })\n  end\n\n  def test_limiting\n    assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] }\n    assert_template_result('12', '{%for i in array limit:2 %}{{ i }}{%endfor%}', assigns)\n    assert_template_result('1234', '{%for i in array limit:4 %}{{ i }}{%endfor%}', assigns)\n    assert_template_result('3456', '{%for i in array limit:4 offset:2 %}{{ i }}{%endfor%}', assigns)\n    assert_template_result('3456', '{%for i in array limit: 4 offset: 2 %}{{ i }}{%endfor%}', assigns)\n    assert_template_result('3456', '{%for i in array, limit: 4, offset: 2 %}{{ i }}{%endfor%}', assigns)\n  end\n\n  def test_limiting_with_invalid_limit\n    assigns  = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] }\n    template = <<-MKUP\n      {% for i in array limit: true offset: 1 %}\n        {{ i }}\n      {% endfor %}\n    MKUP\n\n    exception = assert_raises(Liquid::ArgumentError) do\n      Template.parse(template).render!(assigns)\n    end\n    assert_equal(\"Liquid error: invalid integer\", exception.message)\n  end\n\n  def test_limiting_with_invalid_offset\n    assigns  = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] }\n    template = <<-MKUP\n      {% for i in array limit: 1 offset: true %}\n        {{ i }}\n      {% endfor %}\n    MKUP\n\n    exception = assert_raises(Liquid::ArgumentError) do\n      Template.parse(template).render!(assigns)\n    end\n    assert_equal(\"Liquid error: invalid integer\", exception.message)\n  end\n\n  def test_dynamic_variable_limiting\n    assigns           = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] }\n    assigns['limit']  = 2\n    assigns['offset'] = 2\n\n    assert_template_result('34', '{%for i in array limit: limit offset: offset %}{{ i }}{%endfor%}', assigns)\n  end\n\n  def test_nested_for\n    assigns = { 'array' => [[1, 2], [3, 4], [5, 6]] }\n    assert_template_result('123456', '{%for item in array%}{%for i in item%}{{ i }}{%endfor%}{%endfor%}', assigns)\n  end\n\n  def test_offset_only\n    assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] }\n    assert_template_result('890', '{%for i in array offset:7 %}{{ i }}{%endfor%}', assigns)\n  end\n\n  def test_pause_resume\n    assigns  = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }\n    markup   = <<-MKUP\n      {%for i in array.items limit: 3 %}{{i}}{%endfor%}\n      next\n      {%for i in array.items offset:continue limit: 3 %}{{i}}{%endfor%}\n      next\n      {%for i in array.items offset:continue limit: 3 %}{{i}}{%endfor%}\n      MKUP\n    expected = <<-XPCTD\n      123\n      next\n      456\n      next\n      789\n      XPCTD\n    assert_template_result(expected, markup, assigns)\n  end\n\n  def test_pause_resume_limit\n    assigns  = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }\n    markup   = <<-MKUP\n      {%for i in array.items limit:3 %}{{i}}{%endfor%}\n      next\n      {%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}\n      next\n      {%for i in array.items offset:continue limit:1 %}{{i}}{%endfor%}\n      MKUP\n    expected = <<-XPCTD\n      123\n      next\n      456\n      next\n      7\n      XPCTD\n    assert_template_result(expected, markup, assigns)\n  end\n\n  def test_pause_resume_big_limit\n    assigns  = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }\n    markup   = <<-MKUP\n      {%for i in array.items limit:3 %}{{i}}{%endfor%}\n      next\n      {%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}\n      next\n      {%for i in array.items offset:continue limit:1000 %}{{i}}{%endfor%}\n      MKUP\n    expected = <<-XPCTD\n      123\n      next\n      456\n      next\n      7890\n      XPCTD\n    assert_template_result(expected, markup, assigns)\n  end\n\n  def test_pause_resume_big_offset\n    assigns  = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }\n    markup   = '{%for i in array.items limit:3 %}{{i}}{%endfor%}\n      next\n      {%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}\n      next\n      {%for i in array.items offset:continue limit:3 offset:1000 %}{{i}}{%endfor%}'\n    expected = '123\n      next\n      456\n      next\n      '\n    assert_template_result(expected, markup, assigns)\n  end\n\n  def test_for_with_break\n    assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] } }\n\n    markup   = '{% for i in array.items %}{% break %}{% endfor %}'\n    expected = \"\"\n    assert_template_result(expected, markup, assigns)\n\n    markup   = '{% for i in array.items %}{{ i }}{% break %}{% endfor %}'\n    expected = \"1\"\n    assert_template_result(expected, markup, assigns)\n\n    markup   = '{% for i in array.items %}{% break %}{{ i }}{% endfor %}'\n    expected = \"\"\n    assert_template_result(expected, markup, assigns)\n\n    markup   = '{% for i in array.items %}{{ i }}{% if i > 3 %}{% break %}{% endif %}{% endfor %}'\n    expected = \"1234\"\n    assert_template_result(expected, markup, assigns)\n\n    # tests to ensure it only breaks out of the local for loop\n    # and not all of them.\n    assigns  = { 'array' => [[1, 2], [3, 4], [5, 6]] }\n    markup   = '{% for item in array %}' \\\n               '{% for i in item %}' \\\n                 '{% if i == 1 %}' \\\n                   '{% break %}' \\\n                 '{% endif %}' \\\n                 '{{ i }}' \\\n               '{% endfor %}' \\\n             '{% endfor %}'\n    expected = '3456'\n    assert_template_result(expected, markup, assigns)\n\n    # test break does nothing when unreached\n    assigns  = { 'array' => { 'items' => [1, 2, 3, 4, 5] } }\n    markup   = '{% for i in array.items %}{% if i == 9999 %}{% break %}{% endif %}{{ i }}{% endfor %}'\n    expected = '12345'\n    assert_template_result(expected, markup, assigns)\n  end\n\n  def test_for_with_break_after_nested_loop\n    source = <<~LIQUID.chomp\n      {% for i in (1..2) -%}\n        {% for j in (1..2) -%}\n          {{ i }}-{{ j }},\n        {%- endfor -%}\n        {% break -%}\n      {% endfor -%}\n      after\n    LIQUID\n    assert_template_result(\"1-1,1-2,after\", source)\n  end\n\n  def test_for_with_continue\n    assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5] } }\n\n    markup   = '{% for i in array.items %}{% continue %}{% endfor %}'\n    expected = \"\"\n    assert_template_result(expected, markup, assigns)\n\n    markup   = '{% for i in array.items %}{{ i }}{% continue %}{% endfor %}'\n    expected = \"12345\"\n    assert_template_result(expected, markup, assigns)\n\n    markup   = '{% for i in array.items %}{% continue %}{{ i }}{% endfor %}'\n    expected = \"\"\n    assert_template_result(expected, markup, assigns)\n\n    markup   = '{% for i in array.items %}{% if i > 3 %}{% continue %}{% endif %}{{ i }}{% endfor %}'\n    expected = \"123\"\n    assert_template_result(expected, markup, assigns)\n\n    markup   = '{% for i in array.items %}{% if i == 3 %}{% continue %}{% else %}{{ i }}{% endif %}{% endfor %}'\n    expected = \"1245\"\n    assert_template_result(expected, markup, assigns)\n\n    # tests to ensure it only continues the local for loop and not all of them.\n    assigns  = { 'array' => [[1, 2], [3, 4], [5, 6]] }\n    markup   = '{% for item in array %}' \\\n               '{% for i in item %}' \\\n                 '{% if i == 1 %}' \\\n                   '{% continue %}' \\\n                 '{% endif %}' \\\n                 '{{ i }}' \\\n               '{% endfor %}' \\\n             '{% endfor %}'\n    expected = '23456'\n    assert_template_result(expected, markup, assigns)\n\n    # test continue does nothing when unreached\n    assigns  = { 'array' => { 'items' => [1, 2, 3, 4, 5] } }\n    markup   = '{% for i in array.items %}{% if i == 9999 %}{% continue %}{% endif %}{{ i }}{% endfor %}'\n    expected = '12345'\n    assert_template_result(expected, markup, assigns)\n  end\n\n  def test_for_tag_string\n    # ruby 1.8.7 \"String\".each => Enumerator with single \"String\" element.\n    # ruby 1.9.3 no longer supports .each on String though we mimic\n    # the functionality for backwards compatibility\n\n    assert_template_result(\n      'test string',\n      '{%for val in string%}{{val}}{%endfor%}',\n      { 'string' => \"test string\" },\n    )\n\n    assert_template_result(\n      'test string',\n      '{%for val in string limit:1%}{{val}}{%endfor%}',\n      { 'string' => \"test string\" },\n    )\n\n    assert_template_result(\n      'val-string-1-1-0-1-0-true-true-test string',\n      '{%for val in string%}' \\\n      '{{forloop.name}}-' \\\n      '{{forloop.index}}-' \\\n      '{{forloop.length}}-' \\\n      '{{forloop.index0}}-' \\\n      '{{forloop.rindex}}-' \\\n      '{{forloop.rindex0}}-' \\\n      '{{forloop.first}}-' \\\n      '{{forloop.last}}-' \\\n      '{{val}}{%endfor%}',\n      { 'string' => \"test string\" },\n    )\n  end\n\n  def test_for_parentloop_references_parent_loop\n    assert_template_result(\n      '1.1 1.2 1.3 2.1 2.2 2.3 ',\n      '{% for inner in outer %}{% for k in inner %}' \\\n      '{{ forloop.parentloop.index }}.{{ forloop.index }} ' \\\n      '{% endfor %}{% endfor %}',\n      { 'outer' => [[1, 1, 1], [1, 1, 1]] },\n    )\n  end\n\n  def test_for_parentloop_nil_when_not_present\n    assert_template_result(\n      '.1 .2 ',\n      '{% for inner in outer %}' \\\n      '{{ forloop.parentloop.index }}.{{ forloop.index }} ' \\\n      '{% endfor %}',\n      { 'outer' => [[1, 1, 1], [1, 1, 1]] },\n    )\n  end\n\n  def test_inner_for_over_empty_input\n    assert_template_result('oo', '{% for a in (1..2) %}o{% for b in empty %}{% endfor %}{% endfor %}')\n  end\n\n  def test_blank_string_not_iterable\n    assert_template_result('', \"{% for char in characters %}I WILL NOT BE OUTPUT{% endfor %}\", { 'characters' => '' })\n  end\n\n  def test_bad_variable_naming_in_for_loop\n    assert_raises(Liquid::SyntaxError) do\n      Liquid::Template.parse('{% for a/b in x %}{% endfor %}')\n    end\n  end\n\n  def test_spacing_with_variable_naming_in_for_loop\n    expected = '12345'\n    template = '{% for       item   in   items %}{{item}}{% endfor %}'\n    assigns  = { 'items' => [1, 2, 3, 4, 5] }\n    assert_template_result(expected, template, assigns)\n  end\n\n  class LoaderDrop < Liquid::Drop\n    attr_accessor :each_called, :load_slice_called\n\n    def initialize(data)\n      @data = data\n    end\n\n    def each\n      @each_called = true\n      @data.each { |el| yield el }\n    end\n\n    def load_slice(from, to)\n      @load_slice_called = true\n      @data[(from..to - 1)]\n    end\n  end\n\n  def test_iterate_with_each_when_no_limit_applied\n    loader   = LoaderDrop.new([1, 2, 3, 4, 5])\n    assigns  = { 'items' => loader }\n    expected = '12345'\n    template = '{% for item in items %}{{item}}{% endfor %}'\n    assert_template_result(expected, template, assigns)\n    assert(loader.each_called)\n    assert(!loader.load_slice_called)\n  end\n\n  def test_iterate_with_load_slice_when_limit_applied\n    loader   = LoaderDrop.new([1, 2, 3, 4, 5])\n    assigns  = { 'items' => loader }\n    expected = '1'\n    template = '{% for item in items limit:1 %}{{item}}{% endfor %}'\n    assert_template_result(expected, template, assigns)\n    assert(!loader.each_called)\n    assert(loader.load_slice_called)\n  end\n\n  def test_iterate_with_load_slice_when_limit_and_offset_applied\n    loader   = LoaderDrop.new([1, 2, 3, 4, 5])\n    assigns  = { 'items' => loader }\n    expected = '34'\n    template = '{% for item in items offset:2 limit:2 %}{{item}}{% endfor %}'\n    assert_template_result(expected, template, assigns)\n    assert(!loader.each_called)\n    assert(loader.load_slice_called)\n  end\n\n  def test_iterate_with_load_slice_returns_same_results_as_without\n    loader         = LoaderDrop.new([1, 2, 3, 4, 5])\n    loader_assigns = { 'items' => loader }\n    array_assigns  = { 'items' => [1, 2, 3, 4, 5] }\n    expected       = '34'\n    template       = '{% for item in items offset:2 limit:2 %}{{item}}{% endfor %}'\n    assert_template_result(expected, template, loader_assigns)\n    assert_template_result(expected, template, array_assigns)\n  end\n\n  def test_for_cleans_up_registers\n    context = Context.new(ErrorDrop.new)\n\n    assert_raises(StandardError) do\n      Liquid::Template.parse('{% for i in (1..2) %}{{ standard_error }}{% endfor %}').render!(context)\n    end\n\n    assert(context.registers[:for_stack].empty?)\n  end\nend\n"
  },
  {
    "path": "test/integration/tags/if_else_tag_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass IfElseTagTest < Minitest::Test\n  include Liquid\n\n  def test_if\n    assert_template_result('  ', ' {% if false %} this text should not go into the output {% endif %} ')\n    assert_template_result(\n      '  this text should go into the output  ',\n      ' {% if true %} this text should go into the output {% endif %} ',\n    )\n    assert_template_result('  you rock ?', '{% if false %} you suck {% endif %} {% if true %} you rock {% endif %}?')\n  end\n\n  def test_literal_comparisons\n    assert_template_result(' NO ', '{% assign v = false %}{% if v %} YES {% else %} NO {% endif %}')\n    assert_template_result(' YES ', '{% assign v = nil %}{% if v == nil %} YES {% else %} NO {% endif %}')\n  end\n\n  def test_if_else\n    assert_template_result(' YES ', '{% if false %} NO {% else %} YES {% endif %}')\n    assert_template_result(' YES ', '{% if true %} YES {% else %} NO {% endif %}')\n    assert_template_result(' YES ', '{% if \"foo\" %} YES {% else %} NO {% endif %}')\n  end\n\n  def test_if_boolean\n    assert_template_result(' YES ', '{% if var %} YES {% endif %}', { 'var' => true })\n  end\n\n  def test_if_or\n    assert_template_result(' YES ', '{% if a or b %} YES {% endif %}', { 'a' => true, 'b' => true })\n    assert_template_result(' YES ', '{% if a or b %} YES {% endif %}', { 'a' => true, 'b' => false })\n    assert_template_result(' YES ', '{% if a or b %} YES {% endif %}', { 'a' => false, 'b' => true })\n    assert_template_result('',      '{% if a or b %} YES {% endif %}', { 'a' => false, 'b' => false })\n\n    assert_template_result(' YES ', '{% if a or b or c %} YES {% endif %}', { 'a' => false, 'b' => false, 'c' => true })\n    assert_template_result('',      '{% if a or b or c %} YES {% endif %}', { 'a' => false, 'b' => false, 'c' => false })\n  end\n\n  def test_if_or_with_operators\n    assert_template_result(' YES ', '{% if a == true or b == true %} YES {% endif %}', { 'a' => true, 'b' => true })\n    assert_template_result(' YES ', '{% if a == true or b == false %} YES {% endif %}', { 'a' => true, 'b' => true })\n    assert_template_result('', '{% if a == false or b == false %} YES {% endif %}', { 'a' => true, 'b' => true })\n  end\n\n  def test_comparison_of_strings_containing_and_or_or\n    awful_markup = \"a == 'and' and b == 'or' and c == 'foo and bar' and d == 'bar or baz' and e == 'foo' and foo and bar\"\n    assigns      = { 'a' => 'and', 'b' => 'or', 'c' => 'foo and bar', 'd' => 'bar or baz', 'e' => 'foo', 'foo' => true, 'bar' => true }\n    assert_template_result(' YES ', \"{% if #{awful_markup} %} YES {% endif %}\", assigns)\n  end\n\n  def test_comparison_of_expressions_starting_with_and_or_or\n    assigns = { 'order' => { 'items_count' => 0 }, 'android' => { 'name' => 'Roy' } }\n    assert_template_result(\n      \"YES\",\n      \"{% if android.name == 'Roy' %}YES{% endif %}\",\n      assigns,\n    )\n    assert_template_result(\n      \"YES\",\n      \"{% if order.items_count == 0 %}YES{% endif %}\",\n      assigns,\n    )\n  end\n\n  def test_if_and\n    assert_template_result(' YES ', '{% if true and true %} YES {% endif %}')\n    assert_template_result('', '{% if false and true %} YES {% endif %}')\n    assert_template_result('', '{% if true and false %} YES {% endif %}')\n  end\n\n  def test_hash_miss_generates_false\n    assert_template_result('', '{% if foo.bar %} NO {% endif %}', { 'foo' => {} })\n  end\n\n  def test_if_from_variable\n    assert_template_result('', '{% if var %} NO {% endif %}', { 'var' => false })\n    assert_template_result('', '{% if var %} NO {% endif %}', { 'var' => nil })\n    assert_template_result('', '{% if foo.bar %} NO {% endif %}', { 'foo' => { 'bar' => false } })\n    assert_template_result('', '{% if foo.bar %} NO {% endif %}', { 'foo' => {} })\n    assert_template_result('', '{% if foo.bar %} NO {% endif %}', { 'foo' => nil })\n    assert_template_result('', '{% if foo.bar %} NO {% endif %}', { 'foo' => true })\n\n    assert_template_result(' YES ', '{% if var %} YES {% endif %}', { 'var' => \"text\" })\n    assert_template_result(' YES ', '{% if var %} YES {% endif %}', { 'var' => true })\n    assert_template_result(' YES ', '{% if var %} YES {% endif %}', { 'var' => 1 })\n    assert_template_result(' YES ', '{% if var %} YES {% endif %}', { 'var' => {} })\n    assert_template_result(' YES ', '{% if var %} YES {% endif %}', { 'var' => [] })\n    assert_template_result(' YES ', '{% if \"foo\" %} YES {% endif %}')\n    assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', { 'foo' => { 'bar' => true } })\n    assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', { 'foo' => { 'bar' => \"text\" } })\n    assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', { 'foo' => { 'bar' => 1 } })\n    assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', { 'foo' => { 'bar' => {} } })\n    assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', { 'foo' => { 'bar' => [] } })\n\n    assert_template_result(' YES ', '{% if var %} NO {% else %} YES {% endif %}', { 'var' => false })\n    assert_template_result(' YES ', '{% if var %} NO {% else %} YES {% endif %}', { 'var' => nil })\n    assert_template_result(' YES ', '{% if var %} YES {% else %} NO {% endif %}', { 'var' => true })\n    assert_template_result(' YES ', '{% if \"foo\" %} YES {% else %} NO {% endif %}', { 'var' => \"text\" })\n\n    assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', { 'foo' => { 'bar' => false } })\n    assert_template_result(' YES ', '{% if foo.bar %} YES {% else %} NO {% endif %}', { 'foo' => { 'bar' => true } })\n    assert_template_result(' YES ', '{% if foo.bar %} YES {% else %} NO {% endif %}', { 'foo' => { 'bar' => \"text\" } })\n    assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', { 'foo' => { 'notbar' => true } })\n    assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', { 'foo' => {} })\n    assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', { 'notfoo' => { 'bar' => true } })\n  end\n\n  def test_nested_if\n    assert_template_result('', '{% if false %}{% if false %} NO {% endif %}{% endif %}')\n    assert_template_result('', '{% if false %}{% if true %} NO {% endif %}{% endif %}')\n    assert_template_result('', '{% if true %}{% if false %} NO {% endif %}{% endif %}')\n    assert_template_result(' YES ', '{% if true %}{% if true %} YES {% endif %}{% endif %}')\n\n    assert_template_result(' YES ', '{% if true %}{% if true %} YES {% else %} NO {% endif %}{% else %} NO {% endif %}')\n    assert_template_result(' YES ', '{% if true %}{% if false %} NO {% else %} YES {% endif %}{% else %} NO {% endif %}')\n    assert_template_result(' YES ', '{% if false %}{% if true %} NO {% else %} NONO {% endif %}{% else %} YES {% endif %}')\n  end\n\n  def test_comparisons_on_null\n    assert_template_result('', '{% if null < 10 %} NO {% endif %}')\n    assert_template_result('', '{% if null <= 10 %} NO {% endif %}')\n    assert_template_result('', '{% if null >= 10 %} NO {% endif %}')\n    assert_template_result('', '{% if null > 10 %} NO {% endif %}')\n\n    assert_template_result('', '{% if 10 < null %} NO {% endif %}')\n    assert_template_result('', '{% if 10 <= null %} NO {% endif %}')\n    assert_template_result('', '{% if 10 >= null %} NO {% endif %}')\n    assert_template_result('', '{% if 10 > null %} NO {% endif %}')\n  end\n\n  def test_else_if\n    assert_template_result('0', '{% if 0 == 0 %}0{% elsif 1 == 1%}1{% else %}2{% endif %}')\n    assert_template_result('1', '{% if 0 != 0 %}0{% elsif 1 == 1%}1{% else %}2{% endif %}')\n    assert_template_result('2', '{% if 0 != 0 %}0{% elsif 1 != 1%}1{% else %}2{% endif %}')\n\n    assert_template_result('elsif', '{% if false %}if{% elsif true %}elsif{% endif %}')\n  end\n\n  def test_syntax_error_no_variable\n    assert_raises(SyntaxError) { assert_template_result('', '{% if jerry == 1 %}') }\n  end\n\n  def test_syntax_error_no_expression\n    assert_raises(SyntaxError) { assert_template_result('', '{% if %}') }\n  end\n\n  def test_if_with_custom_condition\n    original_op = Condition.operators['contains']\n    Condition.operators['contains'] = :[]\n\n    assert_template_result('yes', %({% if 'bob' contains 'o' %}yes{% endif %}))\n    assert_template_result('no', %({% if 'bob' contains 'f' %}yes{% else %}no{% endif %}))\n  ensure\n    Condition.operators['contains'] = original_op\n  end\n\n  def test_operators_are_ignored_unless_isolated\n    original_op = Condition.operators['contains']\n    Condition.operators['contains'] = :[]\n\n    assert_template_result(\n      'yes',\n      %({% if 'gnomeslab-and-or-liquid' contains 'gnomeslab-and-or-liquid' %}yes{% endif %}),\n    )\n  ensure\n    Condition.operators['contains'] = original_op\n  end\n\n  def test_operators_are_whitelisted\n    assert_raises(SyntaxError) do\n      assert_template_result('', %({% if 1 or throw or or 1 %}yes{% endif %}))\n    end\n  end\n\n  def test_multiple_conditions\n    tpl = \"{% if a or b and c %}true{% else %}false{% endif %}\"\n\n    tests = {\n      [true, true, true] => true,\n      [true, true, false] => true,\n      [true, false, true] => true,\n      [true, false, false] => true,\n      [false, true, true] => true,\n      [false, true, false] => false,\n      [false, false, true] => false,\n      [false, false, false] => false,\n    }\n\n    tests.each do |vals, expected|\n      a, b, c = vals\n      assigns = { 'a' => a, 'b' => b, 'c' => c }\n      assert_template_result(expected.to_s, tpl, assigns, message: assigns.to_s)\n    end\n  end\nend\n"
  },
  {
    "path": "test/integration/tags/include_tag_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass TestFileSystem\n  PARTIALS = {\n    \"nested_template\" => \"{% include 'header' %} {% include 'body' %} {% include 'footer' %}\",\n    \"body\" => \"body {% include 'body_detail' %}\",\n  }\n\n  def read_template_file(template_path)\n    PARTIALS[template_path] || template_path\n  end\nend\n\nclass OtherFileSystem\n  def read_template_file(_template_path)\n    'from OtherFileSystem'\n  end\nend\n\nclass CountingFileSystem\n  attr_reader :count\n  def read_template_file(_template_path)\n    @count ||= 0\n    @count  += 1\n    'from CountingFileSystem'\n  end\nend\n\nclass CustomInclude < Liquid::Tag\n  Syntax = /(#{Liquid::QuotedFragment}+)(\\s+(?:with|for)\\s+(#{Liquid::QuotedFragment}+))?/o\n\n  def initialize(tag_name, markup, tokens)\n    markup =~ Syntax\n    @template_name = Regexp.last_match(1)\n    super\n  end\n\n  def parse(tokens)\n  end\n\n  def render_to_output_buffer(_context, output)\n    output << @template_name[1..-2]\n    output\n  end\nend\n\nclass IncludeTagTest < Minitest::Test\n  include Liquid\n\n  def test_include_tag_looks_for_file_system_in_registers_first\n    assert_equal(\n      'from OtherFileSystem',\n      Template.parse(\"{% include 'pick_a_source' %}\").render!({}, registers: { file_system: OtherFileSystem.new }),\n    )\n  end\n\n  def test_include_tag_with\n    assert_template_result(\n      \"Product: Draft 151cm \",\n      \"{% include 'product' with products[0] %}\",\n      { \"products\" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },\n      partials: { \"product\" => \"Product: {{ product.title }} \" },\n    )\n  end\n\n  def test_include_tag_with_alias\n    assert_template_result(\n      \"Product: Draft 151cm \",\n      \"{% include 'product_alias' with products[0] as product %}\",\n      { \"products\" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },\n      partials: { \"product_alias\" => \"Product: {{ product.title }} \" },\n    )\n  end\n\n  def test_include_tag_for_alias\n    assert_template_result(\n      \"Product: Draft 151cm Product: Element 155cm \",\n      \"{% include 'product_alias' for products as product %}\",\n      { \"products\" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },\n      partials: { \"product_alias\" => \"Product: {{ product.title }} \" },\n    )\n  end\n\n  def test_include_tag_with_default_name\n    assert_template_result(\n      \"Product: Draft 151cm \",\n      \"{% include 'product' %}\",\n      { \"product\" => { 'title' => 'Draft 151cm' } },\n      partials: { \"product\" => \"Product: {{ product.title }} \" },\n    )\n  end\n\n  def test_include_tag_for\n    assert_template_result(\n      \"Product: Draft 151cm Product: Element 155cm \",\n      \"{% include 'product' for products %}\",\n      { \"products\" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },\n      partials: { \"product\" => \"Product: {{ product.title }} \" },\n    )\n  end\n\n  def test_include_tag_with_local_variables\n    assert_template_result(\n      \"Locale: test123 \",\n      \"{% include 'locale_variables' echo1: 'test123' %}\",\n      partials: { \"locale_variables\" => \"Locale: {{echo1}} {{echo2}}\" },\n    )\n  end\n\n  def test_include_tag_with_multiple_local_variables\n    assert_template_result(\n      \"Locale: test123 test321\",\n      \"{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}\",\n      partials: { \"locale_variables\" => \"Locale: {{echo1}} {{echo2}}\" },\n    )\n  end\n\n  def test_include_tag_with_multiple_local_variables_from_context\n    assert_template_result(\n      \"Locale: test123 test321\",\n      \"{% include 'locale_variables' echo1: echo1, echo2: more_echos.echo2 %}\",\n      { 'echo1' => 'test123', 'more_echos' => { \"echo2\" => 'test321' } },\n      partials: { \"locale_variables\" => \"Locale: {{echo1}} {{echo2}}\" },\n    )\n  end\n\n  def test_included_templates_assigns_variables\n    assert_template_result(\n      \"bar\",\n      \"{% include 'assignments' %}{{ foo }}\",\n      partials: { 'assignments' => \"{% assign foo = 'bar' %}\" },\n    )\n  end\n\n  def test_nested_include_tag\n    partials = { \"body\" => \"body {% include 'body_detail' %}\", \"body_detail\" => \"body_detail\" }\n    assert_template_result(\"body body_detail\", \"{% include 'body' %}\", partials: partials)\n\n    partials = partials.merge({\n      \"nested_template\" => \"{% include 'header' %} {% include 'body' %} {% include 'footer' %}\",\n      \"header\" => \"header\",\n      \"footer\" => \"footer\",\n    })\n    assert_template_result(\"header body body_detail footer\", \"{% include 'nested_template' %}\", partials: partials)\n  end\n\n  def test_nested_include_with_variable\n    partials = {\n      \"nested_product_template\" => \"Product: {{ nested_product_template.title }} {%include 'details'%} \",\n      \"details\" => \"details\",\n    }\n\n    assert_template_result(\n      \"Product: Draft 151cm details \",\n      \"{% include 'nested_product_template' with product %}\",\n      { \"product\" => { \"title\" => 'Draft 151cm' } },\n      partials: partials,\n    )\n\n    assert_template_result(\n      \"Product: Draft 151cm details Product: Element 155cm details \",\n      \"{% include 'nested_product_template' for products %}\",\n      { \"products\" => [{ \"title\" => 'Draft 151cm' }, { \"title\" => 'Element 155cm' }] },\n      partials: partials,\n    )\n  end\n\n  def test_recursively_included_template_does_not_produce_endless_loop\n    infinite_file_system = Class.new do\n      def read_template_file(_template_path)\n        \"-{% include 'loop' %}\"\n      end\n    end\n\n    env = Liquid::Environment.build(file_system: infinite_file_system.new)\n\n    assert_raises(Liquid::StackLevelError) do\n      Template.parse(\"{% include 'loop' %}\", environment: env).render!\n    end\n  end\n\n  def test_dynamically_choosen_template\n    assert_template_result(\n      \"Test123\",\n      \"{% include template %}\",\n      { \"template\" => 'Test123' },\n      partials: { \"Test123\" => \"Test123\" },\n    )\n\n    assert_template_result(\n      \"Test321\",\n      \"{% include template %}\",\n      { \"template\" => 'Test321' },\n      partials: { \"Test321\" => \"Test321\" },\n    )\n\n    assert_template_result(\n      \"Product: Draft 151cm \",\n      \"{% include template for product %}\",\n      { \"template\" => 'product', 'product' => { 'title' => 'Draft 151cm' } },\n      partials: { \"product\" => \"Product: {{ product.title }} \" },\n    )\n  end\n\n  def test_strict2_parsing_errors\n    with_error_modes(:lax, :strict) do\n      assert_template_result(\n        'hello value1 value2',\n        '{% include \"snippet\" !!! arg1: \"value1\" ~~~ arg2: \"value2\" %}',\n        partials: { 'snippet' => 'hello {{ arg1 }} {{ arg2 }}' },\n      )\n    end\n\n    with_error_modes(:strict2) do\n      assert_syntax_error(\n        '{% include \"snippet\" !!! arg1: \"value1\" ~~~ arg2: \"value2\" %}',\n      )\n      assert_syntax_error(\n        '{% include \"snippet\" | filter %}',\n      )\n    end\n  end\n\n  def test_optional_commas\n    partials = { 'snippet' => 'hello {{ arg1 }} {{ arg2 }}' }\n    assert_template_result('hello value1 value2', '{% include \"snippet\", arg1: \"value1\", arg2: \"value2\" %}', partials: partials)\n    assert_template_result('hello value1 value2', '{% include \"snippet\"  arg1: \"value1\", arg2: \"value2\" %}', partials: partials)\n    assert_template_result('hello value1 value2', '{% include \"snippet\"  arg1: \"value1\"  arg2: \"value2\" %}', partials: partials)\n  end\n\n  def test_include_tag_caches_second_read_of_same_partial\n    file_system = CountingFileSystem.new\n    environment = Liquid::Environment.build(file_system: file_system)\n    assert_equal(\n      'from CountingFileSystemfrom CountingFileSystem',\n      Template.parse(\"{% include 'pick_a_source' %}{% include 'pick_a_source' %}\", environment: environment).render!({}, registers: { file_system: file_system }),\n    )\n    assert_equal(1, file_system.count)\n  end\n\n  def test_include_tag_doesnt_cache_partials_across_renders\n    file_system = CountingFileSystem.new\n    assert_equal(\n      'from CountingFileSystem',\n      Template.parse(\"{% include 'pick_a_source' %}\").render!({}, registers: { file_system: file_system }),\n    )\n    assert_equal(1, file_system.count)\n\n    assert_equal(\n      'from CountingFileSystem',\n      Template.parse(\"{% include 'pick_a_source' %}\").render!({}, registers: { file_system: file_system }),\n    )\n    assert_equal(2, file_system.count)\n  end\n\n  def test_include_tag_within_if_statement\n    assert_template_result(\n      \"foo_if_true\",\n      \"{% if true %}{% include 'foo_if_true' %}{% endif %}\",\n      partials: { \"foo_if_true\" => \"foo_if_true\" },\n    )\n  end\n\n  def test_custom_include_tag\n    original_tag = Liquid::Template.tags['include']\n    Liquid::Template.tags['include'] = CustomInclude\n    begin\n      assert_equal(\n        \"custom_foo\",\n        Template.parse(\"{% include 'custom_foo' %}\").render!,\n      )\n    ensure\n      Liquid::Template.tags['include'] = original_tag\n    end\n  end\n\n  def test_custom_include_tag_within_if_statement\n    original_tag = Liquid::Template.tags['include']\n    Liquid::Template.tags['include'] = CustomInclude\n    begin\n      assert_equal(\n        \"custom_foo_if_true\",\n        Template.parse(\"{% if true %}{% include 'custom_foo_if_true' %}{% endif %}\").render!,\n      )\n    ensure\n      Liquid::Template.tags['include'] = original_tag\n    end\n  end\n\n  def test_does_not_add_error_in_strict_mode_for_missing_variable\n    env = Liquid::Environment.build(file_system: TestFileSystem.new)\n\n    a = Liquid::Template.parse(' {% include \"nested_template\" %}', environment: env)\n    a.render!\n    assert_empty(a.errors)\n  end\n\n  def test_passing_options_to_included_templates\n    env = Liquid::Environment.build(file_system: TestFileSystem.new)\n\n    assert_raises(Liquid::SyntaxError) do\n      Template.parse(\"{% include template %}\", error_mode: :strict, environment: env).render!(\"template\" => '{{ \"X\" || downcase }}')\n    end\n    with_error_modes(:lax) do\n      assert_equal('x', Template.parse(\"{% include template %}\", error_mode: :strict, include_options_blacklist: true, environment: env).render!(\"template\" => '{{ \"X\" || downcase }}'))\n    end\n    assert_raises(Liquid::SyntaxError) do\n      Template.parse(\"{% include template %}\", error_mode: :strict, include_options_blacklist: [:locale], environment: env).render!(\"template\" => '{{ \"X\" || downcase }}')\n    end\n    with_error_modes(:lax) do\n      assert_equal('x', Template.parse(\"{% include template %}\", error_mode: :strict, include_options_blacklist: [:error_mode], environment: env).render!(\"template\" => '{{ \"X\" || downcase }}'))\n    end\n  end\n\n  def test_render_raise_argument_error_when_template_is_undefined\n    assert_template_result(\n      \"Liquid error (line 1): Argument error in tag 'include' - Illegal template name\",\n      \"{% include undefined_variable %}\",\n      render_errors: true,\n    )\n\n    assert_template_result(\n      \"Liquid error (line 1): Argument error in tag 'include' - Illegal template name\",\n      \"{% include nil %}\",\n      render_errors: true,\n    )\n  end\n\n  def test_render_raise_argument_error_when_template_is_not_a_string\n    assert_template_result(\n      \"Liquid error (line 1): Argument error in tag 'include' - Illegal template name\",\n      \"{% include 123 %}\",\n      render_errors: true,\n    )\n  end\n\n  def test_including_via_variable_value\n    assert_template_result(\n      \"from TestFileSystem\",\n      \"{% assign page = 'pick_a_source' %}{% include page %}\",\n      partials: { \"pick_a_source\" => \"from TestFileSystem\" },\n    )\n\n    partials = { \"product\" => \"Product: {{ product.title }} \" }\n\n    assert_template_result(\n      \"Product: Draft 151cm \",\n      \"{% assign page = 'product' %}{% include page %}\",\n      { \"product\" => { 'title' => 'Draft 151cm' } },\n      partials: partials,\n    )\n\n    assert_template_result(\n      \"Product: Draft 151cm \",\n      \"{% assign page = 'product' %}{% include page for foo %}\",\n      { \"foo\" => { 'title' => 'Draft 151cm' } },\n      partials: partials,\n    )\n  end\n\n  def test_including_with_strict_variables\n    env = Liquid::Environment.build(\n      file_system: StubFileSystem.new('simple' => 'simple'),\n    )\n\n    template = Liquid::Template.parse(\"{% include 'simple' %}\", error_mode: :warn, environment: env)\n    template.render(nil, strict_variables: true)\n\n    assert_equal([], template.errors)\n  end\n\n  def test_break_through_include\n    assert_template_result(\"1\", \"{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}\")\n    assert_template_result(\n      \"1\",\n      \"{% for i in (1..3) %}{{ i }}{% include 'break' %}{{ i }}{% endfor %}\",\n      partials: { 'break' => \"{% break %}\" },\n    )\n  end\n\n  def test_render_tag_renders_error_with_template_name\n    assert_template_result(\n      'Liquid error (foo line 1): standard error',\n      \"{% include 'foo' with errors %}\",\n      { 'errors' => ErrorDrop.new },\n      partials: { 'foo' => '{{ foo.standard_error }}' },\n      render_errors: true,\n    )\n  end\n\n  def test_render_tag_renders_error_with_template_name_from_template_factory\n    assert_template_result(\n      'Liquid error (some/path/foo line 1): standard error',\n      \"{% include 'foo' with errors %}\",\n      { 'errors' => ErrorDrop.new },\n      partials: { 'foo' => '{{ foo.standard_error }}' },\n      template_factory: StubTemplateFactory.new,\n      render_errors: true,\n    )\n  end\n\n  def test_include_template_with_invalid_expression\n    template = \"{% include foo=>bar %}\"\n\n    with_error_modes(:lax, :strict) do\n      refute_nil(Template.parse(template))\n    end\n\n    with_error_modes(:strict2) do\n      error = assert_raises(Liquid::SyntaxError) { Template.parse(template) }\n      assert_match(/Unexpected character =/, error.message)\n    end\n  end\n\n  def test_include_with_invalid_expression\n    template = '{% include \"snippet\" with foo=>bar %}'\n\n    with_error_modes(:lax, :strict) do\n      refute_nil(Template.parse(template))\n    end\n\n    with_error_modes(:strict2) do\n      error = assert_raises(Liquid::SyntaxError) { Template.parse(template) }\n      assert_match(/Unexpected character =/, error.message)\n    end\n  end\n\n  def test_include_attribute_with_invalid_expression\n    template = '{% include \"snippet\", key: foo=>bar %}'\n\n    with_error_modes(:lax, :strict) do\n      refute_nil(Template.parse(template))\n    end\n\n    with_error_modes(:strict2) do\n      error = assert_raises(Liquid::SyntaxError) { Template.parse(template) }\n      assert_match(/Unexpected character =/, error.message)\n    end\n  end\nend # IncludeTagTest\n"
  },
  {
    "path": "test/integration/tags/increment_tag_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass IncrementTagTest < Minitest::Test\n  include Liquid\n\n  def test_inc\n    assert_template_result('0 1', '{%increment port %} {{ port }}')\n    assert_template_result(' 0 1 2', '{{port}} {%increment port %} {%increment port%} {{port}}')\n    assert_template_result(\n      '0 0 1 2 1',\n      '{%increment port %} {%increment starboard%} ' \\\n      '{%increment port %} {%increment port%} ' \\\n      '{%increment starboard %}',\n    )\n  end\n\n  def test_dec\n    assert_template_result('-1 -1', '{%decrement port %} {{ port }}', { 'port' => 10 })\n    assert_template_result(' -1 -2 -2', '{{port}} {%decrement port %} {%decrement port%} {{port}}')\n    assert_template_result(\n      '0 1 2 0 3 1 1 3',\n      '{%increment starboard %} {%increment starboard%} {%increment starboard%} ' \\\n      '{%increment port %} {%increment starboard%} ' \\\n      '{%increment port %} {%decrement port%} ' \\\n      '{%decrement starboard %}',\n    )\n  end\nend\n"
  },
  {
    "path": "test/integration/tags/inline_comment_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass InlineCommentTest < Minitest::Test\n  include Liquid\n\n  def test_inline_comment_returns_nothing\n    assert_template_result('', '{%- # this is an inline comment -%}')\n    assert_template_result('', '{%-# this is an inline comment -%}')\n    assert_template_result('', '{% # this is an inline comment %}')\n    assert_template_result('', '{%# this is an inline comment %}')\n  end\n\n  def test_inline_comment_does_not_require_a_space_after_the_pound_sign\n    assert_template_result('', '{%#this is an inline comment%}')\n  end\n\n  def test_liquid_inline_comment_returns_nothing\n    assert_template_result('Hey there, how are you doing today?', <<~LIQUID)\n      {%- liquid\n        # This is how you'd write a block comment in a liquid tag.\n        # It looks a lot like what you'd have in ruby.\n\n        # You can use it as inline documentation in your\n        # liquid blocks to explain why you're doing something.\n        echo \"Hey there, \"\n\n        # It won't affect the output.\n        echo \"how are you doing today?\"\n      -%}\n    LIQUID\n  end\n\n  def test_inline_comment_can_be_written_on_multiple_lines\n    assert_template_result('', <<~LIQUID)\n      {%-\n        # That kind of block comment is also allowed.\n        # It would only be a stylistic difference.\n\n        # Much like JavaScript's /* */ comments and their\n        # leading * on new lines.\n      -%}\n    LIQUID\n  end\n\n  def test_inline_comment_multiple_pound_signs\n    assert_template_result('', <<~LIQUID)\n      {%- liquid\n        ######################################\n        # We support comments like this too. #\n        ######################################\n      -%}\n    LIQUID\n  end\n\n  def test_inline_comments_require_the_pound_sign_on_every_new_line\n    assert_match_syntax_error(\"Each line of comments must be prefixed by the '#' character\", <<~LIQUID)\n      {%-\n        # some comment\n        echo 'hello world'\n      -%}\n    LIQUID\n  end\n\n  def test_inline_comment_does_not_support_nested_tags\n    assert_template_result(' -%}', \"{%- # {% echo 'hello world' %} -%}\")\n  end\nend\n"
  },
  {
    "path": "test/integration/tags/liquid_tag_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass LiquidTagTest < Minitest::Test\n  include Liquid\n\n  def test_liquid_tag\n    assert_template_result('1 2 3', <<~LIQUID, { 'array' => [1, 2, 3] })\n      {%- liquid\n        echo array | join: \" \"\n      -%}\n    LIQUID\n\n    assert_template_result('1 2 3', <<~LIQUID, { 'array' => [1, 2, 3] })\n      {%- liquid\n        for value in array\n          echo value\n          unless forloop.last\n            echo \" \"\n          endunless\n        endfor\n      -%}\n    LIQUID\n\n    assert_template_result('4 8 12 6', <<~LIQUID, { 'array' => [1, 2, 3] })\n      {%- liquid\n        for value in array\n          assign double_value = value | times: 2\n          echo double_value | times: 2\n          unless forloop.last\n            echo \" \"\n          endunless\n        endfor\n\n        echo \" \"\n        echo double_value\n      -%}\n    LIQUID\n\n    assert_template_result('abc', <<~LIQUID)\n      {%- liquid echo \"a\" -%}\n      b\n      {%- liquid echo \"c\" -%}\n    LIQUID\n  end\n\n  def test_liquid_tag_errors\n    assert_match_syntax_error(\"syntax error (line 1): Unknown tag 'error'\", <<~LIQUID)\n      {%- liquid error no such tag -%}\n    LIQUID\n\n    assert_match_syntax_error(\"syntax error (line 7): Unknown tag 'error'\", <<~LIQUID)\n      {{ test }}\n\n      {%-\n      liquid\n        for value in array\n\n          error no such tag\n        endfor\n      -%}\n    LIQUID\n\n    assert_match_syntax_error(\"syntax error (line 2): Unknown tag '!!! the guards are vigilant'\", <<~LIQUID)\n      {%- liquid\n        !!! the guards are vigilant\n      -%}\n    LIQUID\n\n    assert_match_syntax_error(\"syntax error (line 4): 'for' tag was never closed\", <<~LIQUID)\n      {%- liquid\n        for value in array\n          echo 'forgot to close the for tag'\n      -%}\n    LIQUID\n  end\n\n  def test_line_number_is_correct_after_a_blank_token\n    assert_match_syntax_error(\"syntax error (line 3): Unknown tag 'error'\", \"{% liquid echo ''\\n\\n error %}\")\n    assert_match_syntax_error(\"syntax error (line 3): Unknown tag 'error'\", \"{% liquid echo ''\\n  \\n error %}\")\n  end\n\n  def test_nested_liquid_tag\n    assert_template_result('good', <<~LIQUID)\n      {%- if true %}\n        {%- liquid\n          echo \"good\"\n        %}\n      {%- endif -%}\n    LIQUID\n  end\n\n  def test_cannot_open_blocks_living_past_a_liquid_tag\n    assert_match_syntax_error(\"syntax error (line 3): 'if' tag was never closed\", <<~LIQUID)\n      {%- liquid\n        if true\n      -%}\n      {%- endif -%}\n    LIQUID\n  end\n\n  def test_cannot_close_blocks_created_before_a_liquid_tag\n    assert_match_syntax_error(\"syntax error (line 3): 'endif' is not a valid delimiter for liquid tags. use %}\", <<~LIQUID)\n      {%- if true -%}\n      42\n      {%- liquid endif -%}\n    LIQUID\n  end\n\n  def test_liquid_tag_in_raw\n    assert_template_result(\"{% liquid echo 'test' %}\\n\", <<~LIQUID)\n      {% raw %}{% liquid echo 'test' %}{% endraw %}\n    LIQUID\n  end\n\n  def test_nested_liquid_tags\n    assert_template_result('good', <<~LIQUID)\n      {%- liquid\n        liquid\n          if true\n            echo \"good\"\n          endif\n      -%}\n    LIQUID\n  end\n\n  def test_nested_liquid_tags_on_same_line\n    assert_template_result('good', <<~LIQUID)\n      {%- liquid liquid liquid echo \"good\" -%}\n    LIQUID\n  end\n\n  def test_nested_liquid_liquid_is_not_skipped_if_used_in_non_tag_position\n    assert_template_result('liquid', <<~LIQUID, { 'liquid' => 'liquid' })\n      {%- liquid liquid liquid echo liquid -%}\n    LIQUID\n  end\n\n  def test_next_liquid_with_unclosed_if_tag\n    assert_match_syntax_error(\"Liquid syntax error (line 2): 'if' tag was never closed\", <<~LIQUID)\n      {%- liquid\n        liquid if true\n          echo \"good\"\n        endif\n      -%}\n    LIQUID\n  end\nend\n"
  },
  {
    "path": "test/integration/tags/raw_tag_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass RawTagTest < Minitest::Test\n  include Liquid\n\n  def test_tag_in_raw\n    assert_template_result(\n      '{% comment %} test {% endcomment %}',\n      '{% raw %}{% comment %} test {% endcomment %}{% endraw %}',\n    )\n  end\n\n  def test_output_in_raw\n    assert_template_result('>{{ test }}<', '> {%- raw -%}{{ test }}{%- endraw -%} <')\n    assert_template_result(\"> inner  <\", \"> {%- raw -%} inner {%- endraw %} <\")\n    assert_template_result(\"> inner <\", \"> {%- raw -%} inner {%- endraw -%} <\")\n    assert_template_result(\"{Hello}\", \"{% raw %}{{% endraw %}Hello{% raw %}}{% endraw %}\")\n  end\n\n  def test_open_tag_in_raw\n    assert_template_result(' Foobar {% invalid ', '{% raw %} Foobar {% invalid {% endraw %}')\n    assert_template_result(' Foobar invalid %} ', '{% raw %} Foobar invalid %} {% endraw %}')\n    assert_template_result(' Foobar {{ invalid ', '{% raw %} Foobar {{ invalid {% endraw %}')\n    assert_template_result(' Foobar invalid }} ', '{% raw %} Foobar invalid }} {% endraw %}')\n    assert_template_result(' Foobar {% invalid {% {% endraw ', '{% raw %} Foobar {% invalid {% {% endraw {% endraw %}')\n    assert_template_result(' Foobar {% {% {% ', '{% raw %} Foobar {% {% {% {% endraw %}')\n    assert_template_result(' test {% raw %} {% endraw %}', '{% raw %} test {% raw %} {% {% endraw %}endraw %}')\n    assert_template_result(' Foobar {{ invalid 1', '{% raw %} Foobar {{ invalid {% endraw %}{{ 1 }}')\n    assert_template_result(' Foobar {% foo {% bar %}', '{% raw %} Foobar {% foo {% bar %}{% endraw %}')\n  end\n\n  def test_invalid_raw\n    assert_match_syntax_error(/tag was never closed/, '{% raw %} foo')\n    assert_match_syntax_error(/Valid syntax/, '{% raw } foo {% endraw %}')\n    assert_match_syntax_error(/Valid syntax/, '{% raw } foo %}{% endraw %}')\n  end\nend\n"
  },
  {
    "path": "test/integration/tags/render_tag_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass RenderTagTest < Minitest::Test\n  include Liquid\n\n  def test_render_with_no_arguments\n    assert_template_result(\n      'rendered content',\n      '{% render \"source\" %}',\n      partials: { 'source' => 'rendered content' },\n    )\n  end\n\n  def test_render_tag_looks_for_file_system_in_registers_first\n    assert_template_result(\n      'from register file system',\n      '{% render \"pick_a_source\" %}',\n      partials: { 'pick_a_source' => 'from register file system' },\n    )\n  end\n\n  def test_render_passes_named_arguments_into_inner_scope\n    assert_template_result(\n      'My Product',\n      '{% render \"product\", inner_product: outer_product %}',\n      { 'outer_product' => { 'title' => 'My Product' } },\n      partials: { 'product' => '{{ inner_product.title }}' },\n    )\n  end\n\n  def test_render_accepts_literals_as_arguments\n    assert_template_result(\n      '123',\n      '{% render \"snippet\", price: 123 %}',\n      partials: { 'snippet' => '{{ price }}' },\n    )\n  end\n\n  def test_render_accepts_multiple_named_arguments\n    assert_template_result(\n      '1 2',\n      '{% render \"snippet\", one: 1, two: 2 %}',\n      partials: { 'snippet' => '{{ one }} {{ two }}' },\n    )\n  end\n\n  def test_render_does_not_inherit_parent_scope_variables\n    assert_template_result(\n      '',\n      '{% assign outer_variable = \"should not be visible\" %}{% render \"snippet\" %}',\n      partials: { 'snippet' => '{{ outer_variable }}' },\n    )\n  end\n\n  def test_render_does_not_inherit_variable_with_same_name_as_snippet\n    assert_template_result(\n      '',\n      \"{% assign snippet = 'should not be visible' %}{% render 'snippet' %}\",\n      partials: { 'snippet' => '{{ snippet }}' },\n    )\n  end\n\n  def test_render_does_not_mutate_parent_scope\n    assert_template_result(\n      '',\n      \"{% render 'snippet' %}{{ inner }}\",\n      partials: { 'snippet' => '{% assign inner = 1 %}' },\n    )\n  end\n\n  def test_nested_render_tag\n    assert_template_result(\n      'one two',\n      \"{% render 'one' %}\",\n      partials: {\n        'one' => \"one {% render 'two' %}\",\n        'two' => 'two',\n      },\n    )\n  end\n\n  def test_recursively_rendered_template_does_not_produce_endless_loop\n    env = Liquid::Environment.build(\n      file_system: StubFileSystem.new('loop' => '{% render \"loop\" %}'),\n    )\n\n    assert_raises(Liquid::StackLevelError) do\n      Template.parse('{% render \"loop\" %}', environment: env).render!\n    end\n  end\n\n  def test_sub_contexts_count_towards_the_same_recursion_limit\n    env = Liquid::Environment.build(\n      file_system: StubFileSystem.new('loop_render' => '{% render \"loop_render\" %}'),\n    )\n\n    assert_raises(Liquid::StackLevelError) do\n      Template.parse('{% render \"loop_render\" %}', environment: env).render!\n    end\n  end\n\n  def test_dynamically_choosen_templates_are_not_allowed\n    assert_syntax_error(\"{% assign name = 'snippet' %}{% render name %}\")\n  end\n\n  def test_strict2_parsing_errors\n    with_error_modes(:lax, :strict) do\n      assert_template_result(\n        'hello value1 value2',\n        '{% render \"snippet\" !!! arg1: \"value1\" ~~~ arg2: \"value2\" %}',\n        partials: { 'snippet' => 'hello {{ arg1 }} {{ arg2 }}' },\n      )\n    end\n\n    with_error_modes(:strict2) do\n      assert_syntax_error(\n        '{% render \"snippet\" !!! arg1: \"value1\" ~~~ arg2: \"value2\" %}',\n      )\n      assert_syntax_error(\n        '{% render \"snippet\" | filter %}',\n      )\n    end\n  end\n\n  def test_optional_commas\n    partials = { 'snippet' => 'hello {{ arg1 }} {{ arg2 }}' }\n    assert_template_result('hello value1 value2', '{% render \"snippet\", arg1: \"value1\", arg2: \"value2\" %}', partials: partials)\n    assert_template_result('hello value1 value2', '{% render \"snippet\"  arg1: \"value1\", arg2: \"value2\" %}', partials: partials)\n    assert_template_result('hello value1 value2', '{% render \"snippet\"  arg1: \"value1\"  arg2: \"value2\" %}', partials: partials)\n  end\n\n  def test_render_tag_caches_second_read_of_same_partial\n    file_system = StubFileSystem.new('snippet' => 'echo')\n    assert_equal(\n      'echoecho',\n      Template.parse('{% render \"snippet\" %}{% render \"snippet\" %}')\n      .render!({}, registers: { file_system: file_system }),\n    )\n    assert_equal(1, file_system.file_read_count)\n  end\n\n  def test_render_tag_doesnt_cache_partials_across_renders\n    file_system = StubFileSystem.new('snippet' => 'my message')\n\n    assert_equal(\n      'my message',\n      Template.parse('{% include \"snippet\" %}').render!({}, registers: { file_system: file_system }),\n    )\n    assert_equal(1, file_system.file_read_count)\n\n    assert_equal(\n      'my message',\n      Template.parse('{% include \"snippet\" %}').render!({}, registers: { file_system: file_system }),\n    )\n    assert_equal(2, file_system.file_read_count)\n  end\n\n  def test_render_tag_within_if_statement\n    assert_template_result(\n      'my message',\n      '{% if true %}{% render \"snippet\" %}{% endif %}',\n      partials: { 'snippet' => 'my message' },\n    )\n  end\n\n  def test_break_through_render\n    options = { partials: { 'break' => '{% break %}' } }\n    assert_template_result('1', '{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}', **options)\n    assert_template_result('112233', '{% for i in (1..3) %}{{ i }}{% render \"break\" %}{{ i }}{% endfor %}', **options)\n  end\n\n  def test_increment_is_isolated_between_renders\n    assert_template_result(\n      '010',\n      '{% increment %}{% increment %}{% render \"incr\" %}',\n      partials: { 'incr' => '{% increment %}' },\n    )\n  end\n\n  def test_decrement_is_isolated_between_renders\n    assert_template_result(\n      '-1-2-1',\n      '{% decrement %}{% decrement %}{% render \"decr\" %}',\n      partials: { 'decr' => '{% decrement %}' },\n    )\n  end\n\n  def test_includes_will_not_render_inside_render_tag\n    assert_template_result(\n      'Liquid error (test_include line 1): include usage is not allowed in this context',\n      '{% render \"test_include\" %}',\n      render_errors: true,\n      partials: {\n        'foo' => 'bar',\n        'test_include' => '{% include \"foo\" %}',\n      },\n    )\n  end\n\n  def test_includes_will_not_render_inside_nested_sibling_tags\n    assert_template_result(\n      \"Liquid error (test_include line 1): include usage is not allowed in this context\" \\\n        \"Liquid error (nested_render_with_sibling_include line 1): include usage is not allowed in this context\",\n      '{% render \"nested_render_with_sibling_include\" %}',\n      partials: {\n        'foo' => 'bar',\n        'nested_render_with_sibling_include' => '{% render \"test_include\" %}{% include \"foo\" %}',\n        'test_include' => '{% include \"foo\" %}',\n      },\n      render_errors: true,\n    )\n  end\n\n  def test_render_tag_with\n    assert_template_result(\n      \"Product: Draft 151cm \",\n      \"{% render 'product' with products[0] %}\",\n      { \"products\" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },\n      partials: {\n        'product' => \"Product: {{ product.title }} \",\n        'product_alias' => \"Product: {{ product.title }} \",\n      },\n    )\n  end\n\n  def test_render_tag_with_alias\n    assert_template_result(\n      \"Product: Draft 151cm \",\n      \"{% render 'product_alias' with products[0] as product %}\",\n      { \"products\" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },\n      partials: {\n        'product' => \"Product: {{ product.title }} \",\n        'product_alias' => \"Product: {{ product.title }} \",\n      },\n    )\n  end\n\n  def test_render_tag_for_alias\n    assert_template_result(\n      \"Product: Draft 151cm Product: Element 155cm \",\n      \"{% render 'product_alias' for products as product %}\",\n      { \"products\" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },\n      partials: {\n        'product' => \"Product: {{ product.title }} \",\n        'product_alias' => \"Product: {{ product.title }} \",\n      },\n    )\n  end\n\n  def test_render_tag_for\n    assert_template_result(\n      \"Product: Draft 151cm Product: Element 155cm \",\n      \"{% render 'product' for products %}\",\n      { \"products\" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },\n      partials: {\n        'product' => \"Product: {{ product.title }} \",\n        'product_alias' => \"Product: {{ product.title }} \",\n      },\n    )\n  end\n\n  def test_render_tag_forloop\n    assert_template_result(\n      \"Product: Draft 151cm first  index:1 Product: Element 155cm  last index:2 \",\n      \"{% render 'product' for products %}\",\n      { \"products\" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },\n      partials: {\n        'product' => \"Product: {{ product.title }} {% if forloop.first %}first{% endif %} {% if forloop.last %}last{% endif %} index:{{ forloop.index }} \",\n      },\n    )\n  end\n\n  def test_render_tag_for_drop\n    assert_template_result(\n      \"123\",\n      \"{% render 'loop' for loop as value %}\",\n      { \"loop\" => TestEnumerable.new },\n      partials: {\n        'loop' => \"{{ value.foo }}\",\n      },\n    )\n  end\n\n  def test_render_tag_with_drop\n    assert_template_result(\n      \"TestEnumerable\",\n      \"{% render 'loop' with loop as value %}\",\n      { \"loop\" => TestEnumerable.new },\n      partials: {\n        'loop' => \"{{ value }}\",\n      },\n    )\n  end\n\n  def test_render_tag_renders_error_with_template_name\n    assert_template_result(\n      'Liquid error (foo line 1): standard error',\n      \"{% render 'foo' with errors %}\",\n      { 'errors' => ErrorDrop.new },\n      partials: { 'foo' => '{{ foo.standard_error }}' },\n      render_errors: true,\n    )\n  end\n\n  def test_render_tag_renders_error_with_template_name_from_template_factory\n    assert_template_result(\n      'Liquid error (some/path/foo line 1): standard error',\n      \"{% render 'foo' with errors %}\",\n      { 'errors' => ErrorDrop.new },\n      partials: { 'foo' => '{{ foo.standard_error }}' },\n      template_factory: StubTemplateFactory.new,\n      render_errors: true,\n    )\n  end\n\n  def test_render_with_invalid_expression\n    template = '{% render \"snippet\" with foo=>bar %}'\n\n    with_error_modes(:lax, :strict) do\n      refute_nil(Template.parse(template))\n    end\n\n    with_error_modes(:strict2) do\n      error = assert_raises(Liquid::SyntaxError) { Template.parse(template) }\n      assert_match(/Unexpected character =/, error.message)\n    end\n  end\n\n  def test_render_attribute_with_invalid_expression\n    template = '{% render \"snippet\", key: foo=>bar %}'\n\n    with_error_modes(:lax, :strict) do\n      refute_nil(Template.parse(template))\n    end\n\n    with_error_modes(:strict2) do\n      error = assert_raises(Liquid::SyntaxError) { Template.parse(template) }\n      assert_match(/Unexpected character =/, error.message)\n    end\n  end\nend\n"
  },
  {
    "path": "test/integration/tags/standard_tag_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass StandardTagTest < Minitest::Test\n  include Liquid\n\n  def test_no_transform\n    assert_template_result(\n      'this text should come out of the template without change...',\n      'this text should come out of the template without change...',\n    )\n\n    assert_template_result('blah', 'blah')\n    assert_template_result('<blah>', '<blah>')\n    assert_template_result('|,.:', '|,.:')\n    assert_template_result('', '')\n\n    text = %(this shouldnt see any transformation either but has multiple lines\n              as you can clearly see here ...)\n    assert_template_result(text, text)\n  end\n\n  def test_has_a_block_which_does_nothing\n    assert_template_result(\n      %(the comment block should be removed  .. right?),\n      %(the comment block should be removed {%comment%} be gone.. {%endcomment%} .. right?),\n    )\n\n    assert_template_result('', '{%comment%}{%endcomment%}')\n    assert_template_result('', '{%comment%}{% endcomment %}')\n    assert_template_result('', '{% comment %}{%endcomment%}')\n    assert_template_result('', '{% comment %}{% endcomment %}')\n    assert_template_result('', '{%comment%}comment{%endcomment%}')\n    assert_template_result('', '{% comment %}comment{% endcomment %}')\n    assert_template_result('', '{% comment %} 1 {% comment %} 2 {% endcomment %} 3 {% endcomment %}')\n\n    assert_template_result('', '{%comment%}{%blabla%}{%endcomment%}')\n    assert_template_result('', '{% comment %}{% blabla %}{% endcomment %}')\n    assert_template_result('', '{%comment%}{% endif %}{%endcomment%}')\n    assert_template_result('', '{% comment %}{% endwhatever %}{% endcomment %}')\n    assert_template_result('', '{% comment %}{% raw %} {{%%%%}}  }} { {% endcomment %} {% comment {% endraw %} {% endcomment %}')\n    assert_template_result('', '{% comment %}{% \" %}{% endcomment %}')\n    assert_template_result('', '{% comment %}{%%}{% endcomment %}')\n\n    assert_template_result('foobar', 'foo{%comment%}comment{%endcomment%}bar')\n    assert_template_result('foobar', 'foo{% comment %}comment{% endcomment %}bar')\n    assert_template_result('foobar', 'foo{%comment%} comment {%endcomment%}bar')\n    assert_template_result('foobar', 'foo{% comment %} comment {% endcomment %}bar')\n\n    assert_template_result('foo  bar', 'foo {%comment%} {%endcomment%} bar')\n    assert_template_result('foo  bar', 'foo {%comment%}comment{%endcomment%} bar')\n    assert_template_result('foo  bar', 'foo {%comment%} comment {%endcomment%} bar')\n\n    assert_template_result('foobar', 'foo{%comment%}\n                                     {%endcomment%}bar')\n  end\n\n  def test_hyphenated_assign\n    assigns = { 'a-b' => '1' }\n    assert_template_result('a-b:1 a-b:2', 'a-b:{{a-b}} {%assign a-b = 2 %}a-b:{{a-b}}', assigns)\n  end\n\n  def test_assign_with_colon_and_spaces\n    assigns = { 'var' => { 'a:b c' => { 'paged' => '1' } } }\n    assert_template_result('var2: 1', '{%assign var2 = var[\"a:b c\"].paged %}var2: {{var2}}', assigns)\n  end\n\n  def test_capture\n    assigns = { 'var' => 'content' }\n    assert_template_result(\n      'content foo content foo ',\n      '{{ var2 }}{% capture var2 %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}',\n      assigns,\n    )\n  end\n\n  def test_capture_detects_bad_syntax\n    assert_raises(SyntaxError) do\n      assert_template_result(\n        'content foo content foo ',\n        '{{ var2 }}{% capture %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}',\n        { 'var' => 'content' },\n      )\n    end\n  end\n\n  def test_case\n    assigns = { 'condition' => 2 }\n    assert_template_result(\n      ' its 2 ',\n      '{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}',\n      assigns,\n    )\n\n    assigns = { 'condition' => 1 }\n    assert_template_result(\n      ' its 1 ',\n      '{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}',\n      assigns,\n    )\n\n    assigns = { 'condition' => 3 }\n    assert_template_result(\n      '',\n      '{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}',\n      assigns,\n    )\n\n    assigns = { 'condition' => \"string here\" }\n    assert_template_result(\n      ' hit ',\n      '{% case condition %}{% when \"string here\" %} hit {% endcase %}',\n      assigns,\n    )\n\n    assigns = { 'condition' => \"bad string here\" }\n    assert_template_result(\n      '',\n      '{% case condition %}{% when \"string here\" %} hit {% endcase %}',\n      assigns,\n    )\n  end\n\n  def test_case_with_else\n    assigns = { 'condition' => 5 }\n    assert_template_result(\n      ' hit ',\n      '{% case condition %}{% when 5 %} hit {% else %} else {% endcase %}',\n      assigns,\n    )\n\n    assigns = { 'condition' => 6 }\n    assert_template_result(\n      ' else ',\n      '{% case condition %}{% when 5 %} hit {% else %} else {% endcase %}',\n      assigns,\n    )\n\n    assigns = { 'condition' => 6 }\n    assert_template_result(\n      ' else ',\n      '{% case condition %} {% when 5 %} hit {% else %} else {% endcase %}',\n      assigns,\n    )\n  end\n\n  def test_case_on_size\n    assert_template_result('',  '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', { 'a' => [] })\n    assert_template_result('1', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', { 'a' => [1] })\n    assert_template_result('2', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', { 'a' => [1, 1] })\n    assert_template_result('',  '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', { 'a' => [1, 1, 1] })\n    assert_template_result('',  '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', { 'a' => [1, 1, 1, 1] })\n    assert_template_result('',  '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', { 'a' => [1, 1, 1, 1, 1] })\n  end\n\n  def test_case_on_size_with_else\n    assert_template_result(\n      'else',\n      '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',\n      { 'a' => [] },\n    )\n\n    assert_template_result(\n      '1',\n      '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',\n      { 'a' => [1] },\n    )\n\n    assert_template_result(\n      '2',\n      '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',\n      { 'a' => [1, 1] },\n    )\n\n    assert_template_result(\n      'else',\n      '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',\n      { 'a' => [1, 1, 1] },\n    )\n\n    assert_template_result(\n      'else',\n      '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',\n      { 'a' => [1, 1, 1, 1] },\n    )\n\n    assert_template_result(\n      'else',\n      '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',\n      { 'a' => [1, 1, 1, 1, 1] },\n    )\n  end\n\n  def test_case_on_length_with_else\n    assert_template_result(\n      'else',\n      '{% case a.empty? %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',\n      {},\n    )\n\n    assert_template_result(\n      'false',\n      '{% case false %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',\n      {},\n    )\n\n    assert_template_result(\n      'true',\n      '{% case true %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',\n      {},\n    )\n\n    assert_template_result(\n      'else',\n      '{% case NULL %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',\n      {},\n    )\n  end\n\n  def test_assign_from_case\n    # Example from the shopify forums\n    code = \"{% case collection.handle %}{% when 'menswear-jackets' %}{% assign ptitle = 'menswear' %}{% when 'menswear-t-shirts' %}{% assign ptitle = 'menswear' %}{% else %}{% assign ptitle = 'womenswear' %}{% endcase %}{{ ptitle }}\"\n    assert_template_result(\"menswear\",   code, { \"collection\" => { 'handle' => 'menswear-jackets' } })\n    assert_template_result(\"menswear\",   code, { \"collection\" => { 'handle' => 'menswear-t-shirts' } })\n    assert_template_result(\"womenswear\", code, { \"collection\" => { 'handle' => 'x' } })\n    assert_template_result(\"womenswear\", code, { \"collection\" => { 'handle' => 'y' } })\n    assert_template_result(\"womenswear\", code, { \"collection\" => { 'handle' => 'z' } })\n  end\n\n  def test_case_when_or\n    code = '{% case condition %}{% when 1 or 2 or 3 %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}'\n    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 1 })\n    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 2 })\n    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 3 })\n    assert_template_result(' its 4 ', code, { 'condition' => 4 })\n    assert_template_result('', code, { 'condition' => 5 })\n\n    code = '{% case condition %}{% when 1 or \"string\" or null %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}'\n    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 1 })\n    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 'string' })\n    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => nil })\n    assert_template_result('', code, { 'condition' => 'something else' })\n  end\n\n  def test_case_when_comma\n    code = '{% case condition %}{% when 1, 2, 3 %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}'\n    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 1 })\n    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 2 })\n    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 3 })\n    assert_template_result(' its 4 ', code, { 'condition' => 4 })\n    assert_template_result('', code, { 'condition' => 5 })\n\n    code = '{% case condition %}{% when 1, \"string\", null %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}'\n    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 1 })\n    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 'string' })\n    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => nil })\n    assert_template_result('', code, { 'condition' => 'something else' })\n  end\n\n  def test_case_when_comma_and_blank_body\n    code = '{% case condition %}{% when 1, 2 %} {% assign r = \"result\" %} {% endcase %}{{ r }}'\n    assert_template_result('result', code, { 'condition' => 2 })\n  end\n\n  def test_assign\n    assert_template_result('variable', '{% assign a = \"variable\"%}{{a}}')\n  end\n\n  def test_assign_unassigned\n    assigns = { 'var' => 'content' }\n    assert_template_result('var2:  var2:content', 'var2:{{var2}} {%assign var2 = var%} var2:{{var2}}', assigns)\n  end\n\n  def test_assign_an_empty_string\n    assert_template_result('', '{% assign a = \"\"%}{{a}}')\n  end\n\n  def test_assign_is_global\n    assert_template_result('variable', '{%for i in (1..2) %}{% assign a = \"variable\"%}{% endfor %}{{a}}')\n  end\n\n  def test_case_detects_bad_syntax\n    assert_raises(SyntaxError) do\n      assert_template_result('',  '{% case false %}{% when %}true{% endcase %}', {})\n    end\n\n    assert_raises(SyntaxError) do\n      assert_template_result('',  '{% case false %}{% huh %}true{% endcase %}', {})\n    end\n  end\n\n  def test_cycle\n    assert_template_result('one', '{%cycle \"one\", \"two\"%}')\n    assert_template_result('one two', '{%cycle \"one\", \"two\"%} {%cycle \"one\", \"two\"%}')\n    assert_template_result(' two', '{%cycle \"\", \"two\"%} {%cycle \"\", \"two\"%}')\n\n    assert_template_result('one two one', '{%cycle \"one\", \"two\"%} {%cycle \"one\", \"two\"%} {%cycle \"one\", \"two\"%}')\n\n    assert_template_result(\n      'text-align: left text-align: right',\n      '{%cycle \"text-align: left\", \"text-align: right\" %} {%cycle \"text-align: left\", \"text-align: right\"%}',\n    )\n  end\n\n  def test_multiple_cycles\n    assert_template_result(\n      '1 2 1 1 2 3 1',\n      '{%cycle 1,2%} {%cycle 1,2%} {%cycle 1,2%} {%cycle 1,2,3%} {%cycle 1,2,3%} {%cycle 1,2,3%} {%cycle 1,2,3%}',\n    )\n  end\n\n  def test_multiple_named_cycles\n    assert_template_result(\n      'one one two two one one',\n      '{%cycle 1: \"one\", \"two\" %} {%cycle 2: \"one\", \"two\" %} {%cycle 1: \"one\", \"two\" %} {%cycle 2: \"one\", \"two\" %} {%cycle 1: \"one\", \"two\" %} {%cycle 2: \"one\", \"two\" %}',\n    )\n  end\n\n  def test_multiple_named_cycles_with_names_from_context\n    assigns = { \"var1\" => 1, \"var2\" => 2 }\n    assert_template_result(\n      'one one two two one one',\n      '{%cycle var1: \"one\", \"two\" %} {%cycle var2: \"one\", \"two\" %} {%cycle var1: \"one\", \"two\" %} {%cycle var2: \"one\", \"two\" %} {%cycle var1: \"one\", \"two\" %} {%cycle var2: \"one\", \"two\" %}',\n      assigns,\n    )\n  end\n\n  def test_size_of_array\n    assigns = { \"array\" => [1, 2, 3, 4] }\n    assert_template_result('array has 4 elements', \"array has {{ array.size }} elements\", assigns)\n  end\n\n  def test_size_of_hash\n    assigns = { \"hash\" => { a: 1, b: 2, c: 3, d: 4 } }\n    assert_template_result('hash has 4 elements', \"hash has {{ hash.size }} elements\", assigns)\n  end\n\n  def test_illegal_symbols\n    assert_template_result('', '{% if true == empty %}?{% endif %}', {})\n    assert_template_result('', '{% if true == null %}?{% endif %}', {})\n    assert_template_result('', '{% if empty == true %}?{% endif %}', {})\n    assert_template_result('', '{% if null == true %}?{% endif %}', {})\n  end\n\n  def test_ifchanged\n    assigns = { 'array' => [1, 1, 2, 2, 3, 3] }\n    assert_template_result('123', '{%for item in array%}{%ifchanged%}{{item}}{% endifchanged %}{%endfor%}', assigns)\n\n    assigns = { 'array' => [1, 1, 1, 1] }\n    assert_template_result('1', '{%for item in array%}{%ifchanged%}{{item}}{% endifchanged %}{%endfor%}', assigns)\n  end\n\n  def test_multiline_tag\n    assert_template_result('0 1 2 3', \"0{%\\nfor i in (1..3)\\n%} {{\\ni\\n}}{%\\nendfor\\n%}\")\n  end\nend # StandardTagTest\n"
  },
  {
    "path": "test/integration/tags/statements_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass StatementsTest < Minitest::Test\n  include Liquid\n\n  def test_true_eql_true\n    text = ' {% if true == true %} true {% else %} false {% endif %} '\n    assert_template_result('  true  ', text)\n  end\n\n  def test_true_not_eql_true\n    text = ' {% if true != true %} true {% else %} false {% endif %} '\n    assert_template_result('  false  ', text)\n  end\n\n  def test_true_lq_true\n    text = ' {% if 0 > 0 %} true {% else %} false {% endif %} '\n    assert_template_result('  false  ', text)\n  end\n\n  def test_one_lq_zero\n    text = ' {% if 1 > 0 %} true {% else %} false {% endif %} '\n    assert_template_result('  true  ', text)\n  end\n\n  def test_zero_lq_one\n    text = ' {% if 0 < 1 %} true {% else %} false {% endif %} '\n    assert_template_result('  true  ', text)\n  end\n\n  def test_zero_lq_or_equal_one\n    text = ' {% if 0 <= 0 %} true {% else %} false {% endif %} '\n    assert_template_result('  true  ', text)\n  end\n\n  def test_zero_lq_or_equal_one_involving_nil\n    text = ' {% if null <= 0 %} true {% else %} false {% endif %} '\n    assert_template_result('  false  ', text)\n\n    text = ' {% if 0 <= null %} true {% else %} false {% endif %} '\n    assert_template_result('  false  ', text)\n  end\n\n  def test_zero_lqq_or_equal_one\n    text = ' {% if 0 >= 0 %} true {% else %} false {% endif %} '\n    assert_template_result('  true  ', text)\n  end\n\n  def test_strings\n    text = \" {% if 'test' == 'test' %} true {% else %} false {% endif %} \"\n    assert_template_result('  true  ', text)\n  end\n\n  def test_strings_not_equal\n    text = \" {% if 'test' != 'test' %} true {% else %} false {% endif %} \"\n    assert_template_result('  false  ', text)\n  end\n\n  def test_var_strings_equal\n    text = ' {% if var == \"hello there!\" %} true {% else %} false {% endif %} '\n    assert_template_result('  true  ', text, { 'var' => 'hello there!' })\n  end\n\n  def test_var_strings_are_not_equal\n    text = ' {% if \"hello there!\" == var %} true {% else %} false {% endif %} '\n    assert_template_result('  true  ', text, { 'var' => 'hello there!' })\n  end\n\n  def test_var_and_long_string_are_equal\n    text = \" {% if var == 'hello there!' %} true {% else %} false {% endif %} \"\n    assert_template_result('  true  ', text, { 'var' => 'hello there!' })\n  end\n\n  def test_var_and_long_string_are_equal_backwards\n    text = \" {% if 'hello there!' == var %} true {% else %} false {% endif %} \"\n    assert_template_result('  true  ', text, { 'var' => 'hello there!' })\n  end\n\n  # def test_is_nil\n  #  text = %| {% if var != nil %} true {% else %} false {% end %} |\n  #  @template.assigns = { 'var' => 'hello there!'}\n  #  expected = %|  true  |\n  #  assert_equal expected, @template.parse(text)\n  # end\n\n  def test_is_collection_empty\n    text = ' {% if array == empty %} true {% else %} false {% endif %} '\n    assert_template_result('  true  ', text, { 'array' => [] })\n  end\n\n  def test_is_not_collection_empty\n    text = ' {% if array == empty %} true {% else %} false {% endif %} '\n    assert_template_result('  false  ', text, { 'array' => [1, 2, 3] })\n  end\n\n  def test_nil\n    text = ' {% if var == nil %} true {% else %} false {% endif %} '\n    assert_template_result('  true  ', text, { 'var' => nil })\n\n    text = ' {% if var == null %} true {% else %} false {% endif %} '\n    assert_template_result('  true  ', text, { 'var' => nil })\n  end\n\n  def test_not_nil\n    text = ' {% if var != nil %} true {% else %} false {% endif %} '\n    assert_template_result('  true  ', text, { 'var' => 1 })\n\n    text = ' {% if var != null %} true {% else %} false {% endif %} '\n    assert_template_result('  true  ', text, { 'var' => 1 })\n  end\nend # StatementsTest\n"
  },
  {
    "path": "test/integration/tags/table_row_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass TableRowTest < Minitest::Test\n  include Liquid\n\n  class ArrayDrop < Liquid::Drop\n    include Enumerable\n\n    def initialize(array)\n      @array = array\n    end\n\n    def each(&block)\n      @array.each(&block)\n    end\n  end\n\n  def test_table_row\n    assert_template_result(\n      \"<tr class=\\\"row1\\\">\\n<td class=\\\"col1\\\"> 1 </td><td class=\\\"col2\\\"> 2 </td><td class=\\\"col3\\\"> 3 </td></tr>\\n<tr class=\\\"row2\\\"><td class=\\\"col1\\\"> 4 </td><td class=\\\"col2\\\"> 5 </td><td class=\\\"col3\\\"> 6 </td></tr>\\n\",\n      '{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',\n      { 'numbers' => [1, 2, 3, 4, 5, 6] },\n    )\n\n    assert_template_result(\n      \"<tr class=\\\"row1\\\">\\n</tr>\\n\",\n      '{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',\n      { 'numbers' => [] },\n    )\n  end\n\n  def test_table_row_with_different_cols\n    assert_template_result(\n      \"<tr class=\\\"row1\\\">\\n<td class=\\\"col1\\\"> 1 </td><td class=\\\"col2\\\"> 2 </td><td class=\\\"col3\\\"> 3 </td><td class=\\\"col4\\\"> 4 </td><td class=\\\"col5\\\"> 5 </td></tr>\\n<tr class=\\\"row2\\\"><td class=\\\"col1\\\"> 6 </td></tr>\\n\",\n      '{% tablerow n in numbers cols:5%} {{n}} {% endtablerow %}',\n      { 'numbers' => [1, 2, 3, 4, 5, 6] },\n    )\n  end\n\n  def test_table_col_counter\n    assert_template_result(\n      \"<tr class=\\\"row1\\\">\\n<td class=\\\"col1\\\">1</td><td class=\\\"col2\\\">2</td></tr>\\n<tr class=\\\"row2\\\"><td class=\\\"col1\\\">1</td><td class=\\\"col2\\\">2</td></tr>\\n<tr class=\\\"row3\\\"><td class=\\\"col1\\\">1</td><td class=\\\"col2\\\">2</td></tr>\\n\",\n      '{% tablerow n in numbers cols:2%}{{tablerowloop.col}}{% endtablerow %}',\n      { 'numbers' => [1, 2, 3, 4, 5, 6] },\n    )\n  end\n\n  def test_quoted_fragment\n    assert_template_result(\n      \"<tr class=\\\"row1\\\">\\n<td class=\\\"col1\\\"> 1 </td><td class=\\\"col2\\\"> 2 </td><td class=\\\"col3\\\"> 3 </td></tr>\\n<tr class=\\\"row2\\\"><td class=\\\"col1\\\"> 4 </td><td class=\\\"col2\\\"> 5 </td><td class=\\\"col3\\\"> 6 </td></tr>\\n\",\n      \"{% tablerow n in collections.frontpage cols:3%} {{n}} {% endtablerow %}\",\n      { 'collections' => { 'frontpage' => [1, 2, 3, 4, 5, 6] } },\n    )\n    assert_template_result(\n      \"<tr class=\\\"row1\\\">\\n<td class=\\\"col1\\\"> 1 </td><td class=\\\"col2\\\"> 2 </td><td class=\\\"col3\\\"> 3 </td></tr>\\n<tr class=\\\"row2\\\"><td class=\\\"col1\\\"> 4 </td><td class=\\\"col2\\\"> 5 </td><td class=\\\"col3\\\"> 6 </td></tr>\\n\",\n      \"{% tablerow n in collections['frontpage'] cols:3%} {{n}} {% endtablerow %}\",\n      { 'collections' => { 'frontpage' => [1, 2, 3, 4, 5, 6] } },\n    )\n  end\n\n  def test_enumerable_drop\n    assert_template_result(\n      \"<tr class=\\\"row1\\\">\\n<td class=\\\"col1\\\"> 1 </td><td class=\\\"col2\\\"> 2 </td><td class=\\\"col3\\\"> 3 </td></tr>\\n<tr class=\\\"row2\\\"><td class=\\\"col1\\\"> 4 </td><td class=\\\"col2\\\"> 5 </td><td class=\\\"col3\\\"> 6 </td></tr>\\n\",\n      '{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',\n      { 'numbers' => ArrayDrop.new([1, 2, 3, 4, 5, 6]) },\n    )\n  end\n\n  def test_offset_and_limit\n    assert_template_result(\n      \"<tr class=\\\"row1\\\">\\n<td class=\\\"col1\\\"> 1 </td><td class=\\\"col2\\\"> 2 </td><td class=\\\"col3\\\"> 3 </td></tr>\\n<tr class=\\\"row2\\\"><td class=\\\"col1\\\"> 4 </td><td class=\\\"col2\\\"> 5 </td><td class=\\\"col3\\\"> 6 </td></tr>\\n\",\n      '{% tablerow n in numbers cols:3 offset:1 limit:6%} {{n}} {% endtablerow %}',\n      { 'numbers' => [0, 1, 2, 3, 4, 5, 6, 7] },\n    )\n  end\n\n  def test_blank_string_not_iterable\n    assert_template_result(\n      \"<tr class=\\\"row1\\\">\\n</tr>\\n\",\n      \"{% tablerow char in characters cols:3 %}I WILL NOT BE OUTPUT{% endtablerow %}\",\n      { 'characters' => '' },\n    )\n  end\n\n  def test_cols_nil_constant_same_as_evaluated_nil_expression\n    expect = \"<tr class=\\\"row1\\\">\\n\" \\\n      \"<td class=\\\"col1\\\">false</td>\" \\\n      \"<td class=\\\"col2\\\">false</td>\" \\\n      \"</tr>\\n\"\n\n    assert_template_result(\n      expect,\n      \"{% tablerow i in (1..2) cols:nil %}{{ tablerowloop.col_last }}{% endtablerow %}\",\n    )\n\n    assert_template_result(\n      expect,\n      \"{% tablerow i in (1..2) cols:var %}{{ tablerowloop.col_last }}{% endtablerow %}\",\n      { \"var\" => nil },\n    )\n  end\n\n  def test_nil_limit_is_treated_as_zero\n    expect = \"<tr class=\\\"row1\\\">\\n\" \\\n      \"</tr>\\n\"\n\n    assert_template_result(\n      expect,\n      \"{% tablerow i in (1..2) limit:nil %}{{ i }}{% endtablerow %}\",\n    )\n\n    assert_template_result(\n      expect,\n      \"{% tablerow i in (1..2) limit:var %}{{ i }}{% endtablerow %}\",\n      { \"var\" => nil },\n    )\n  end\n\n  def test_nil_offset_is_treated_as_zero\n    expect = \"<tr class=\\\"row1\\\">\\n\" \\\n      \"<td class=\\\"col1\\\">1:false</td>\" \\\n      \"<td class=\\\"col2\\\">2:true</td>\" \\\n      \"</tr>\\n\"\n\n    assert_template_result(\n      expect,\n      \"{% tablerow i in (1..2) offset:nil %}{{ i }}:{{ tablerowloop.col_last }}{% endtablerow %}\",\n    )\n\n    assert_template_result(\n      expect,\n      \"{% tablerow i in (1..2) offset:var %}{{ i }}:{{ tablerowloop.col_last }}{% endtablerow %}\",\n      { \"var\" => nil },\n    )\n  end\n\n  def test_tablerow_loop_drop_attributes\n    template = <<~LIQUID.chomp\n      {% tablerow i in (1..2) %}\n      col: {{ tablerowloop.col }}\n      col0: {{ tablerowloop.col0 }}\n      col_first: {{ tablerowloop.col_first }}\n      col_last: {{ tablerowloop.col_last }}\n      first: {{ tablerowloop.first }}\n      index: {{ tablerowloop.index }}\n      index0: {{ tablerowloop.index0 }}\n      last: {{ tablerowloop.last }}\n      length: {{ tablerowloop.length }}\n      rindex: {{ tablerowloop.rindex }}\n      rindex0: {{ tablerowloop.rindex0 }}\n      row: {{ tablerowloop.row }}\n      {% endtablerow %}\n    LIQUID\n\n    expected_output = <<~OUTPUT\n      <tr class=\"row1\">\n      <td class=\"col1\">\n      col: 1\n      col0: 0\n      col_first: true\n      col_last: false\n      first: true\n      index: 1\n      index0: 0\n      last: false\n      length: 2\n      rindex: 2\n      rindex0: 1\n      row: 1\n      </td><td class=\"col2\">\n      col: 2\n      col0: 1\n      col_first: false\n      col_last: true\n      first: false\n      index: 2\n      index0: 1\n      last: true\n      length: 2\n      rindex: 1\n      rindex0: 0\n      row: 1\n      </td></tr>\n    OUTPUT\n\n    assert_template_result(expected_output, template)\n  end\n\n  def test_table_row_renders_correct_error_message_for_invalid_parameters\n    assert_template_result(\n      \"Liquid error (line 1): invalid integer\",\n      '{% tablerow n in (1...10) limit:true %} {{n}} {% endtablerow %}',\n      error_mode: :warn,\n      render_errors: true,\n    )\n\n    assert_template_result(\n      \"Liquid error (line 1): invalid integer\",\n      '{% tablerow n in (1...10) offset:true %} {{n}} {% endtablerow %}',\n      error_mode: :warn,\n      render_errors: true,\n    )\n\n    assert_template_result(\n      \"Liquid error (line 1): invalid integer\",\n      '{% tablerow n in (1...10) cols:true %} {{n}} {% endtablerow %}',\n      render_errors: true,\n      error_mode: :warn,\n    )\n  end\n\n  def test_table_row_handles_interrupts\n    assert_template_result(\n      \"<tr class=\\\"row1\\\">\\n<td class=\\\"col1\\\"> 1 </td></tr>\\n\",\n      '{% tablerow n in (1..3) cols:2 %} {{n}} {% break %} {{n}} {% endtablerow %}',\n    )\n\n    assert_template_result(\n      \"<tr class=\\\"row1\\\">\\n<td class=\\\"col1\\\"> 1 </td><td class=\\\"col2\\\"> 2 </td></tr>\\n<tr class=\\\"row2\\\"><td class=\\\"col1\\\"> 3 </td></tr>\\n\",\n      '{% tablerow n in (1..3) cols:2 %} {{n}} {% continue %} {{n}} {% endtablerow %}',\n    )\n  end\n\n  def test_table_row_does_not_leak_interrupts\n    template = <<~LIQUID\n      {% for i in (1..2) -%}\n      {% for j in (1..2) -%}\n      {% tablerow k in (1..3) %}{% break %}{% endtablerow -%}\n      loop j={{ j }}\n      {% endfor -%}\n      loop i={{ i }}\n      {% endfor -%}\n      after loop\n    LIQUID\n\n    expected = <<~STR\n      <tr class=\"row1\">\n      <td class=\"col1\"></td></tr>\n      loop j=1\n      <tr class=\"row1\">\n      <td class=\"col1\"></td></tr>\n      loop j=2\n      loop i=1\n      <tr class=\"row1\">\n      <td class=\"col1\"></td></tr>\n      loop j=1\n      <tr class=\"row1\">\n      <td class=\"col1\"></td></tr>\n      loop j=2\n      loop i=2\n      after loop\n    STR\n\n    assert_template_result(\n      expected,\n      template,\n    )\n  end\n\n  def test_tablerow_with_cols_attribute_in_strict2_mode\n    template = <<~LIQUID.chomp\n      {% tablerow i in (1..6) cols: 3 %}{{ i }}{% endtablerow %}\n    LIQUID\n\n    expected = <<~OUTPUT\n      <tr class=\"row1\">\n      <td class=\"col1\">1</td><td class=\"col2\">2</td><td class=\"col3\">3</td></tr>\n      <tr class=\"row2\"><td class=\"col1\">4</td><td class=\"col2\">5</td><td class=\"col3\">6</td></tr>\n    OUTPUT\n\n    with_error_modes(:strict2) do\n      assert_template_result(expected, template)\n    end\n  end\n\n  def test_tablerow_with_limit_attribute_in_strict2_mode\n    template = <<~LIQUID.chomp\n      {% tablerow i in (1..10) limit: 3 %}{{ i }}{% endtablerow %}\n    LIQUID\n\n    expected = <<~OUTPUT\n      <tr class=\"row1\">\n      <td class=\"col1\">1</td><td class=\"col2\">2</td><td class=\"col3\">3</td></tr>\n    OUTPUT\n\n    with_error_modes(:strict2) do\n      assert_template_result(expected, template)\n    end\n  end\n\n  def test_tablerow_with_offset_attribute_in_strict2_mode\n    template = <<~LIQUID.chomp\n      {% tablerow i in (1..5) offset: 2 %}{{ i }}{% endtablerow %}\n    LIQUID\n\n    expected = <<~OUTPUT\n      <tr class=\"row1\">\n      <td class=\"col1\">3</td><td class=\"col2\">4</td><td class=\"col3\">5</td></tr>\n    OUTPUT\n\n    with_error_modes(:strict2) do\n      assert_template_result(expected, template)\n    end\n  end\n\n  def test_tablerow_with_range_attribute_in_strict2_mode\n    template = <<~LIQUID.chomp\n      {% tablerow i in (1..3) range: (1..10) %}{{ i }}{% endtablerow %}\n    LIQUID\n\n    expected = <<~OUTPUT\n      <tr class=\"row1\">\n      <td class=\"col1\">1</td><td class=\"col2\">2</td><td class=\"col3\">3</td></tr>\n    OUTPUT\n\n    with_error_modes(:strict2) do\n      assert_template_result(expected, template)\n    end\n  end\n\n  def test_tablerow_with_multiple_attributes_in_strict2_mode\n    template = <<~LIQUID.chomp\n      {% tablerow i in (1..10) cols: 2, limit: 4, offset: 1 %}{{ i }}{% endtablerow %}\n    LIQUID\n\n    expected = <<~OUTPUT\n      <tr class=\"row1\">\n      <td class=\"col1\">2</td><td class=\"col2\">3</td></tr>\n      <tr class=\"row2\"><td class=\"col1\">4</td><td class=\"col2\">5</td></tr>\n    OUTPUT\n\n    with_error_modes(:strict2) do\n      assert_template_result(expected, template)\n    end\n  end\n\n  def test_tablerow_with_variable_collection_in_strict2_mode\n    template = <<~LIQUID.chomp\n      {% tablerow n in numbers cols: 2 %}{{ n }}{% endtablerow %}\n    LIQUID\n\n    expected = <<~OUTPUT\n      <tr class=\"row1\">\n      <td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n      <tr class=\"row2\"><td class=\"col1\">3</td><td class=\"col2\">4</td></tr>\n    OUTPUT\n\n    with_error_modes(:strict2) do\n      assert_template_result(expected, template, { 'numbers' => [1, 2, 3, 4] })\n    end\n  end\n\n  def test_tablerow_with_dotted_access_in_strict2_mode\n    template = <<~LIQUID.chomp\n      {% tablerow n in obj.numbers cols: 2 %}{{ n }}{% endtablerow %}\n    LIQUID\n\n    expected = <<~OUTPUT\n      <tr class=\"row1\">\n      <td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n      <tr class=\"row2\"><td class=\"col1\">3</td><td class=\"col2\">4</td></tr>\n    OUTPUT\n\n    with_error_modes(:strict2) do\n      assert_template_result(expected, template, { 'obj' => { 'numbers' => [1, 2, 3, 4] } })\n    end\n  end\n\n  def test_tablerow_with_bracketed_access_in_strict2_mode\n    template = <<~LIQUID.chomp\n      {% tablerow n in obj[\"numbers\"] cols: 2 %}{{ n }}{% endtablerow %}\n    LIQUID\n\n    expected = <<~OUTPUT\n      <tr class=\"row1\">\n      <td class=\"col1\">10</td><td class=\"col2\">20</td></tr>\n    OUTPUT\n\n    with_error_modes(:strict2) do\n      assert_template_result(expected, template, { 'obj' => { 'numbers' => [10, 20] } })\n    end\n  end\n\n  def test_tablerow_without_attributes_in_strict2_mode\n    template = <<~LIQUID.chomp\n      {% tablerow i in (1..3) %}{{ i }}{% endtablerow %}\n    LIQUID\n\n    expected = <<~OUTPUT\n      <tr class=\"row1\">\n      <td class=\"col1\">1</td><td class=\"col2\">2</td><td class=\"col3\">3</td></tr>\n    OUTPUT\n\n    with_error_modes(:strict2) do\n      assert_template_result(expected, template)\n    end\n  end\n\n  def test_tablerow_without_in_keyword_in_strict2_mode\n    template = '{% tablerow i (1..10) %}{{ i }}{% endtablerow %}'\n\n    with_error_modes(:strict2) do\n      error = assert_raises(SyntaxError) { Template.parse(template) }\n      assert_equal(\"Liquid syntax error: For loops require an 'in' clause in \\\"i (1..10)\\\"\", error.message)\n    end\n  end\n\n  def test_tablerow_with_multiple_invalid_attributes_reports_first_in_strict2_mode\n    template = '{% tablerow i in (1..10) invalid1: 5, invalid2: 10 %}{{ i }}{% endtablerow %}'\n\n    with_error_modes(:strict2) do\n      error = assert_raises(SyntaxError) { Template.parse(template) }\n      assert_equal(\"Liquid syntax error: Invalid attribute 'invalid1' in tablerow loop. Valid attributes are cols, limit, offset, and range in \\\"i in (1..10) invalid1: 5, invalid2: 10\\\"\", error.message)\n    end\n  end\n\n  def test_tablerow_with_empty_collection_in_strict2_mode\n    template = <<~LIQUID.chomp\n      {% tablerow i in empty_array cols: 2 %}{{ i }}{% endtablerow %}\n    LIQUID\n\n    expected = <<~OUTPUT\n      <tr class=\"row1\">\n      </tr>\n    OUTPUT\n\n    with_error_modes(:strict2) do\n      assert_template_result(expected, template, { 'empty_array' => [] })\n    end\n  end\n\n  def test_tablerow_with_invalid_attribute_strict_vs_strict2\n    template = '{% tablerow i in (1..5) invalid_attr: 10 %}{{ i }}{% endtablerow %}'\n\n    expected = <<~OUTPUT\n      <tr class=\"row1\">\n      <td class=\"col1\">1</td><td class=\"col2\">2</td><td class=\"col3\">3</td><td class=\"col4\">4</td><td class=\"col5\">5</td></tr>\n    OUTPUT\n\n    with_error_modes(:lax, :strict) do\n      assert_template_result(expected, template)\n    end\n\n    with_error_modes(:strict2) do\n      error = assert_raises(SyntaxError) { Template.parse(template) }\n      assert_match(/Invalid attribute 'invalid_attr'/, error.message)\n    end\n  end\n\n  def test_tablerow_with_invalid_expression_strict_vs_strict2\n    template = '{% tablerow i in (1..5) limit: foo=>bar %}{{ i }}{% endtablerow %}'\n\n    with_error_modes(:lax, :strict) do\n      expected = <<~OUTPUT\n        <tr class=\"row1\">\n        </tr>\n      OUTPUT\n      assert_template_result(expected, template)\n    end\n\n    with_error_modes(:strict2) do\n      error = assert_raises(SyntaxError) { Template.parse(template) }\n      assert_match(/Unexpected character =/, error.message)\n    end\n  end\nend\n"
  },
  {
    "path": "test/integration/tags/unless_else_tag_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UnlessElseTagTest < Minitest::Test\n  include Liquid\n\n  def test_unless\n    assert_template_result('  ', ' {% unless true %} this text should not go into the output {% endunless %} ')\n    assert_template_result(\n      '  this text should go into the output  ',\n      ' {% unless false %} this text should go into the output {% endunless %} ',\n    )\n    assert_template_result('  you rock ?', '{% unless true %} you suck {% endunless %} {% unless false %} you rock {% endunless %}?')\n  end\n\n  def test_unless_else\n    assert_template_result(' YES ', '{% unless true %} NO {% else %} YES {% endunless %}')\n    assert_template_result(' YES ', '{% unless false %} YES {% else %} NO {% endunless %}')\n    assert_template_result(' YES ', '{% unless \"foo\" %} NO {% else %} YES {% endunless %}')\n  end\n\n  def test_unless_in_loop\n    assert_template_result('23', '{% for i in choices %}{% unless i %}{{ forloop.index }}{% endunless %}{% endfor %}', { 'choices' => [1, nil, false] })\n  end\n\n  def test_unless_else_in_loop\n    assert_template_result(' TRUE  2  3 ', '{% for i in choices %}{% unless i %} {{ forloop.index }} {% else %} TRUE {% endunless %}{% endfor %}', { 'choices' => [1, nil, false] })\n  end\nend # UnlessElseTest\n"
  },
  {
    "path": "test/integration/template_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\nrequire 'timeout'\n\nclass TemplateContextDrop < Liquid::Drop\n  def liquid_method_missing(method)\n    method\n  end\n\n  def foo\n    'fizzbuzz'\n  end\n\n  def baz\n    @context.registers['lulz']\n  end\nend\n\nclass SomethingWithLength < Liquid::Drop\n  def length\n    nil\n  end\nend\n\nclass ErroneousDrop < Liquid::Drop\n  def bad_method\n    raise 'ruby error in drop'\n  end\nend\n\nclass DropWithUndefinedMethod < Liquid::Drop\n  def foo\n    'foo'\n  end\nend\n\nclass TemplateTest < Minitest::Test\n  include Liquid\n\n  def test_instance_assigns_persist_on_same_template_object_between_parses\n    t = Template.new\n    assert_equal('from instance assigns', t.parse(\"{% assign foo = 'from instance assigns' %}{{ foo }}\").render!)\n    assert_equal('from instance assigns', t.parse(\"{{ foo }}\").render!)\n  end\n\n  def test_warnings_is_not_exponential_time\n    str = \"false\"\n    100.times do\n      str = \"{% if true %}true{% else %}#{str}{% endif %}\"\n    end\n\n    t = Template.parse(str)\n    assert_equal([], Timeout.timeout(1) { t.warnings })\n  end\n\n  def test_instance_assigns_persist_on_same_template_parsing_between_renders\n    t = Template.new.parse(\"{{ foo }}{% assign foo = 'foo' %}{{ foo }}\")\n    assert_equal('foo', t.render!)\n    assert_equal('foofoo', t.render!)\n  end\n\n  def test_custom_assigns_do_not_persist_on_same_template\n    t = Template.new\n    assert_equal('from custom assigns', t.parse(\"{{ foo }}\").render!('foo' => 'from custom assigns'))\n    assert_equal('', t.parse(\"{{ foo }}\").render!)\n  end\n\n  def test_custom_assigns_squash_instance_assigns\n    t = Template.new\n    assert_equal('from instance assigns', t.parse(\"{% assign foo = 'from instance assigns' %}{{ foo }}\").render!)\n    assert_equal('from custom assigns', t.parse(\"{{ foo }}\").render!('foo' => 'from custom assigns'))\n  end\n\n  def test_persistent_assigns_squash_instance_assigns\n    t = Template.new\n    assert_equal('from instance assigns', t.parse(\"{% assign foo = 'from instance assigns' %}{{ foo }}\").render!)\n    t.assigns['foo'] = 'from persistent assigns'\n    assert_equal('from persistent assigns', t.parse(\"{{ foo }}\").render!)\n  end\n\n  def test_lambda_is_called_once_from_persistent_assigns_over_multiple_parses_and_renders\n    t = Template.new\n    t.assigns['number'] = -> {\n      @global ||= 0\n      @global  += 1\n    }\n    assert_equal('1', t.parse(\"{{number}}\").render!)\n    assert_equal('1', t.parse(\"{{number}}\").render!)\n    assert_equal('1', t.render!)\n    @global = nil\n  end\n\n  def test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders\n    t = Template.new\n    assigns = {\n      'number' => -> {\n        @global ||= 0\n        @global += 1\n      },\n    }\n    assert_equal('1', t.parse(\"{{number}}\").render!(assigns))\n    assert_equal('1', t.parse(\"{{number}}\").render!(assigns))\n    assert_equal('1', t.render!(assigns))\n    @global = nil\n  end\n\n  def test_resource_limits_works_with_custom_length_method\n    t = Template.parse(\"{% assign foo = bar %}\")\n    t.resource_limits.render_length_limit = 42\n    assert_equal(\"\", t.render!(\"bar\" => SomethingWithLength.new))\n  end\n\n  def test_resource_limits_render_length\n    t = Template.parse(\"0123456789\")\n    t.resource_limits.render_length_limit = 9\n    assert_equal(\"Liquid error: Memory limits exceeded\", t.render)\n    assert(t.resource_limits.reached?)\n\n    t.resource_limits.render_length_limit = 10\n    assert_equal(\"0123456789\", t.render!)\n  end\n\n  def test_resource_limits_render_score\n    t = Template.parse(\"{% for a in (1..10) %} {% for a in (1..10) %} foo {% endfor %} {% endfor %}\")\n    t.resource_limits.render_score_limit = 50\n    assert_equal(\"Liquid error: Memory limits exceeded\", t.render)\n    assert(t.resource_limits.reached?)\n\n    t = Template.parse(\"{% for a in (1..100) %} foo {% endfor %}\")\n    t.resource_limits.render_score_limit = 50\n    assert_equal(\"Liquid error: Memory limits exceeded\", t.render)\n    assert(t.resource_limits.reached?)\n\n    t.resource_limits.render_score_limit = 200\n    assert_equal(\" foo \" * 100, t.render!)\n    refute_nil(t.resource_limits.render_score)\n  end\n\n  def test_resource_limits_aborts_rendering_after_first_error\n    t = Template.parse(\"{% for a in (1..100) %} foo1 {% endfor %} bar {% for a in (1..100) %} foo2 {% endfor %}\")\n    t.resource_limits.render_score_limit = 50\n    assert_equal(\"Liquid error: Memory limits exceeded\", t.render)\n    assert(t.resource_limits.reached?)\n  end\n\n  def test_resource_limits_hash_in_template_gets_updated_even_if_no_limits_are_set\n    t = Template.parse(\"{% for a in (1..100) %}x{% assign foo = 1 %} {% endfor %}\")\n    t.render!\n    assert(t.resource_limits.assign_score > 0)\n    assert(t.resource_limits.render_score > 0)\n  end\n\n  def test_render_length_persists_between_blocks\n    t = Template.parse(\"{% if true %}aaaa{% endif %}\")\n    t.resource_limits.render_length_limit = 3\n    assert_equal(\"Liquid error: Memory limits exceeded\", t.render)\n    t.resource_limits.render_length_limit = 4\n    assert_equal(\"aaaa\", t.render)\n\n    t = Template.parse(\"{% if true %}aaaa{% endif %}{% if true %}bbb{% endif %}\")\n    t.resource_limits.render_length_limit = 6\n    assert_equal(\"Liquid error: Memory limits exceeded\", t.render)\n    t.resource_limits.render_length_limit = 7\n    assert_equal(\"aaaabbb\", t.render)\n\n    t = Template.parse(\"{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}\")\n    t.resource_limits.render_length_limit = 5\n    assert_equal(\"Liquid error: Memory limits exceeded\", t.render)\n    t.resource_limits.render_length_limit = 6\n    assert_equal(\"ababab\", t.render)\n  end\n\n  def test_render_length_uses_number_of_bytes_not_characters\n    t = Template.parse(\"{% if true %}すごい{% endif %}\")\n    t.resource_limits.render_length_limit = 8\n    assert_equal(\"Liquid error: Memory limits exceeded\", t.render)\n    t.resource_limits.render_length_limit = 9\n    assert_equal(\"すごい\", t.render)\n  end\n\n  def test_cumulative_render_score_limit_across_render_tags\n    file_system = StubFileSystem.new(\n      'loop' => '{% for a in (1..10) %} foo {% endfor %}',\n    )\n    environment = Liquid::Environment.build(file_system: file_system)\n\n    # Without cumulative limit, all 5 partials render successfully\n    t = Template.parse(\n      '{% render \"loop\" %}{% render \"loop\" %}{% render \"loop\" %}{% render \"loop\" %}{% render \"loop\" %}',\n      environment: environment,\n    )\n    unlimited_output = t.render!\n    total_cumulative = t.resource_limits.cumulative_render_score\n\n    # With cumulative limit set below the total, rendering stops early\n    t2 = Template.parse(\n      '{% render \"loop\" %}{% render \"loop\" %}{% render \"loop\" %}{% render \"loop\" %}{% render \"loop\" %}',\n      environment: environment,\n    )\n    t2.resource_limits.cumulative_render_score_limit = total_cumulative / 2\n    limited_output = t2.render\n    assert(t2.resource_limits.reached?)\n    assert_operator(limited_output.length, :<, unlimited_output.length)\n  end\n\n  def test_cumulative_render_score_limit_raises_on_render_bang\n    file_system = StubFileSystem.new(\n      'loop' => '{% for a in (1..10) %} foo {% endfor %}',\n    )\n    environment = Liquid::Environment.build(file_system: file_system)\n    t = Template.parse(\n      '{% render \"loop\" %}{% render \"loop\" %}{% render \"loop\" %}{% render \"loop\" %}{% render \"loop\" %}',\n      environment: environment,\n    )\n    t.resource_limits.cumulative_render_score_limit = 20\n    assert_raises(Liquid::MemoryError) do\n      t.render!\n    end\n  end\n\n  def test_cumulative_assign_score_limit_across_include_tags\n    file_system = StubFileSystem.new(\n      'assign_partial' => '{% assign x = \"a long string value here\" %}',\n    )\n    environment = Liquid::Environment.build(file_system: file_system)\n\n    # Without cumulative limit, all 5 partials render\n    t = Template.parse(\n      '{% include \"assign_partial\" %}{% include \"assign_partial\" %}{% include \"assign_partial\" %}{% include \"assign_partial\" %}{% include \"assign_partial\" %}',\n      environment: environment,\n    )\n    t.render!\n    total_cumulative = t.resource_limits.cumulative_assign_score\n\n    # With cumulative limit set below the total, rendering stops early\n    t2 = Template.parse(\n      '{% include \"assign_partial\" %}{% include \"assign_partial\" %}{% include \"assign_partial\" %}{% include \"assign_partial\" %}{% include \"assign_partial\" %}',\n      environment: environment,\n    )\n    t2.resource_limits.cumulative_assign_score_limit = total_cumulative / 2\n    t2.render\n    assert(t2.resource_limits.reached?)\n  end\n\n  def test_cumulative_render_score_tracks_across_partials_without_limit\n    file_system = StubFileSystem.new(\n      'loop' => '{% for a in (1..10) %} foo {% endfor %}',\n    )\n    environment = Liquid::Environment.build(file_system: file_system)\n    t = Template.parse(\n      '{% render \"loop\" %}{% render \"loop\" %}{% render \"loop\" %}',\n      environment: environment,\n    )\n    t.render!\n    assert(\n      t.resource_limits.cumulative_render_score > t.resource_limits.render_score,\n      \"cumulative should exceed per-template score after multiple partials\",\n    )\n  end\n\n  def test_default_resource_limits_unaffected_by_render_with_context\n    context = Context.new\n    t = Template.parse(\"{% for a in (1..100) %}x{% assign foo = 1 %} {% endfor %}\")\n    t.render!(context)\n    assert(context.resource_limits.assign_score > 0)\n    assert(context.resource_limits.render_score > 0)\n  end\n\n  def test_can_use_drop_as_context\n    t = Template.new\n    t.registers['lulz'] = 'haha'\n    drop = TemplateContextDrop.new\n    assert_equal('fizzbuzz', t.parse('{{foo}}').render!(drop))\n    assert_equal('bar', t.parse('{{bar}}').render!(drop))\n    assert_equal('haha', t.parse(\"{{baz}}\").render!(drop))\n  end\n\n  def test_render_bang_force_rethrow_errors_on_passed_context\n    context = Context.new('drop' => ErroneousDrop.new)\n    t = Template.new.parse('{{ drop.bad_method }}')\n\n    e = assert_raises(RuntimeError) do\n      t.render!(context)\n    end\n    assert_equal('ruby error in drop', e.message)\n  end\n\n  def test_exception_renderer_that_returns_string\n    exception = nil\n    handler   = ->(e) {\n      exception = e\n      '<!-- error -->'\n    }\n\n    output = Template.parse(\"{{ 1 | divided_by: 0 }}\").render({}, exception_renderer: handler)\n\n    assert(exception.is_a?(Liquid::ZeroDivisionError))\n    assert_equal('<!-- error -->', output)\n  end\n\n  def test_exception_renderer_that_raises\n    exception = nil\n    assert_raises(Liquid::ZeroDivisionError) do\n      Template.parse(\"{{ 1 | divided_by: 0 }}\").render({}, exception_renderer: ->(e) {\n                                                                                 exception = e\n                                                                                 raise\n                                                                               })\n    end\n    assert(exception.is_a?(Liquid::ZeroDivisionError))\n  end\n\n  def test_global_filter_option_on_render\n    global_filter_proc = ->(output) { \"#{output} filtered\" }\n    rendered_template  = Template.parse(\"{{name}}\").render({ \"name\" => \"bob\" }, global_filter: global_filter_proc)\n\n    assert_equal('bob filtered', rendered_template)\n  end\n\n  def test_global_filter_option_when_native_filters_exist\n    global_filter_proc = ->(output) { \"#{output} filtered\" }\n    rendered_template  = Template.parse(\"{{name | upcase}}\").render({ \"name\" => \"bob\" }, global_filter: global_filter_proc)\n\n    assert_equal('BOB filtered', rendered_template)\n  end\n\n  def test_undefined_variables\n    t      = Template.parse(\"{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}\")\n    result = t.render({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, strict_variables: true)\n\n    assert_equal('33  32  ', result)\n    assert_equal(3, t.errors.count)\n    assert_instance_of(Liquid::UndefinedVariable, t.errors[0])\n    assert_equal('Liquid error: undefined variable y', t.errors[0].message)\n    assert_instance_of(Liquid::UndefinedVariable, t.errors[1])\n    assert_equal('Liquid error: undefined variable b', t.errors[1].message)\n    assert_instance_of(Liquid::UndefinedVariable, t.errors[2])\n    assert_equal('Liquid error: undefined variable d', t.errors[2].message)\n  end\n\n  def test_nil_value_does_not_raise\n    t      = Template.parse(\"some{{x}}thing\", error_mode: :strict)\n    result = t.render!({ 'x' => nil }, strict_variables: true)\n\n    assert_equal(0, t.errors.count)\n    assert_equal('something', result)\n  end\n\n  def test_undefined_variables_raise\n    t = Template.parse(\"{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}\")\n\n    assert_raises(UndefinedVariable) do\n      t.render!({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, strict_variables: true)\n    end\n  end\n\n  def test_undefined_drop_methods\n    d = DropWithUndefinedMethod.new\n    t = Template.new.parse('{{ foo }} {{ woot }}')\n    result = t.render(d, strict_variables: true)\n\n    assert_equal('foo ', result)\n    assert_equal(1, t.errors.count)\n    assert_instance_of(Liquid::UndefinedDropMethod, t.errors[0])\n  end\n\n  def test_undefined_drop_methods_raise\n    d = DropWithUndefinedMethod.new\n    t = Template.new.parse('{{ foo }} {{ woot }}')\n\n    assert_raises(UndefinedDropMethod) do\n      t.render!(d, strict_variables: true)\n    end\n  end\n\n  def test_undefined_filters\n    t = Template.parse(\"{{a}} {{x | upcase | somefilter1 | somefilter2 | somefilter3}}\")\n    filters = Module.new do\n      def somefilter3(v)\n        \"-#{v}-\"\n      end\n    end\n    result = t.render({ 'a' => 123, 'x' => 'foo' }, filters: [filters], strict_filters: true)\n\n    assert_equal('123 ', result)\n    assert_equal(1, t.errors.count)\n    assert_instance_of(Liquid::UndefinedFilter, t.errors[0])\n    assert_equal('Liquid error: undefined filter somefilter1', t.errors[0].message)\n  end\n\n  def test_undefined_filters_raise\n    t = Template.parse(\"{{x | somefilter1 | upcase | somefilter2}}\")\n\n    assert_raises(UndefinedFilter) do\n      t.render!({ 'x' => 'foo' }, strict_filters: true)\n    end\n  end\n\n  def test_using_range_literal_works_as_expected\n    source = \"{% assign foo = (x..y) %}{{ foo }}\"\n    assert_template_result(\"1..5\", source, { \"x\" => 1, \"y\" => 5 })\n\n    source = \"{% assign nums = (x..y) %}{% for num in nums %}{{ num }}{% endfor %}\"\n    assert_template_result(\"12345\", source, { \"x\" => 1, \"y\" => 5 })\n  end\n\n  def test_source_string_subclass\n    string_subclass = Class.new(String) do\n      # E.g. ActiveSupport::SafeBuffer does this, so don't just rely on to_s to return a String\n      def to_s\n        self\n      end\n    end\n    source = string_subclass.new(\"{% assign x = 2 -%} x= {{- x }}\")\n    assert_instance_of(string_subclass, source)\n    output = Template.parse(source).render!\n    assert_equal(\"x=2\", output)\n    assert_instance_of(String, output)\n  end\n\n  def test_raises_error_with_invalid_utf8\n    e = assert_raises(TemplateEncodingError) do\n      Template.parse(<<~LIQUID)\n        {% comment %}\n          \\xC0\n        {% endcomment %}\n      LIQUID\n    end\n\n    assert_equal('Liquid error: Invalid template encoding', e.message)\n  end\n\n  def test_allows_non_string_values_as_source\n    assert_equal('', Template.parse(nil).render)\n    assert_equal('1', Template.parse(1).render)\n    assert_equal('true', Template.parse(true).render)\n  end\nend\n"
  },
  {
    "path": "test/integration/trim_mode_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass TrimModeTest < Minitest::Test\n  include Liquid\n\n  # Make sure the trim isn't applied to standard output\n  def test_standard_output\n    text = <<-END_TEMPLATE\n      <div>\n        <p>\n          {{ 'John' }}\n        </p>\n      </div>\n    END_TEMPLATE\n    expected = <<-END_EXPECTED\n      <div>\n        <p>\n          John\n        </p>\n      </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n  end\n\n  def test_variable_output_with_multiple_blank_lines\n    text = <<-END_TEMPLATE\n      <div>\n        <p>\n\n\n          {{- 'John' -}}\n\n\n        </p>\n      </div>\n    END_TEMPLATE\n    expected = <<-END_EXPECTED\n      <div>\n        <p>John</p>\n      </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n  end\n\n  def test_tag_output_with_multiple_blank_lines\n    text = <<-END_TEMPLATE\n      <div>\n        <p>\n\n\n          {%- if true -%}\n          yes\n          {%- endif -%}\n\n\n        </p>\n      </div>\n    END_TEMPLATE\n    expected = <<-END_EXPECTED\n      <div>\n        <p>yes</p>\n      </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n  end\n\n  # Make sure the trim isn't applied to standard tags\n  def test_standard_tags\n    whitespace = '          '\n    text       = <<-END_TEMPLATE\n      <div>\n        <p>\n          {% if true %}\n          yes\n          {% endif %}\n        </p>\n      </div>\n    END_TEMPLATE\n    expected = <<~END_EXPECTED\n            <div>\n              <p>\n      #{whitespace}\n                yes\n      #{whitespace}\n              </p>\n            </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n\n    text = <<-END_TEMPLATE\n      <div>\n        <p>\n          {% if false %}\n          no\n          {% endif %}\n        </p>\n      </div>\n    END_TEMPLATE\n    expected = <<~END_EXPECTED\n            <div>\n              <p>\n      #{whitespace}\n              </p>\n            </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n  end\n\n  # Make sure the trim isn't too agressive\n  def test_no_trim_output\n    text     = '<p>{{- \\'John\\' -}}</p>'\n    expected = '<p>John</p>'\n    assert_template_result(expected, text)\n  end\n\n  # Make sure the trim isn't too agressive\n  def test_no_trim_tags\n    text     = '<p>{%- if true -%}yes{%- endif -%}</p>'\n    expected = '<p>yes</p>'\n    assert_template_result(expected, text)\n\n    text     = '<p>{%- if false -%}no{%- endif -%}</p>'\n    expected = '<p></p>'\n    assert_template_result(expected, text)\n  end\n\n  def test_single_line_outer_tag\n    text     = '<p> {%- if true %} yes {% endif -%} </p>'\n    expected = '<p> yes </p>'\n    assert_template_result(expected, text)\n\n    text     = '<p> {%- if false %} no {% endif -%} </p>'\n    expected = '<p></p>'\n    assert_template_result(expected, text)\n  end\n\n  def test_single_line_inner_tag\n    text     = '<p> {% if true -%} yes {%- endif %} </p>'\n    expected = '<p> yes </p>'\n    assert_template_result(expected, text)\n\n    text     = '<p> {% if false -%} no {%- endif %} </p>'\n    expected = '<p>  </p>'\n    assert_template_result(expected, text)\n  end\n\n  def test_single_line_post_tag\n    text     = '<p> {% if true -%} yes {% endif -%} </p>'\n    expected = '<p> yes </p>'\n    assert_template_result(expected, text)\n\n    text     = '<p> {% if false -%} no {% endif -%} </p>'\n    expected = '<p> </p>'\n    assert_template_result(expected, text)\n  end\n\n  def test_single_line_pre_tag\n    text     = '<p> {%- if true %} yes {%- endif %} </p>'\n    expected = '<p> yes </p>'\n    assert_template_result(expected, text)\n\n    text     = '<p> {%- if false %} no {%- endif %} </p>'\n    expected = '<p> </p>'\n    assert_template_result(expected, text)\n  end\n\n  def test_pre_trim_output\n    text = <<-END_TEMPLATE\n      <div>\n        <p>\n          {{- 'John' }}\n        </p>\n      </div>\n    END_TEMPLATE\n    expected = <<-END_EXPECTED\n      <div>\n        <p>John\n        </p>\n      </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n  end\n\n  def test_pre_trim_tags\n    text = <<-END_TEMPLATE\n      <div>\n        <p>\n          {%- if true %}\n          yes\n          {%- endif %}\n        </p>\n      </div>\n    END_TEMPLATE\n    expected = <<-END_EXPECTED\n      <div>\n        <p>\n          yes\n        </p>\n      </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n\n    text = <<-END_TEMPLATE\n      <div>\n        <p>\n          {%- if false %}\n          no\n          {%- endif %}\n        </p>\n      </div>\n    END_TEMPLATE\n    expected = <<-END_EXPECTED\n      <div>\n        <p>\n        </p>\n      </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n  end\n\n  def test_post_trim_output\n    text = <<-END_TEMPLATE\n      <div>\n        <p>\n          {{ 'John' -}}\n        </p>\n      </div>\n    END_TEMPLATE\n    expected = <<-END_EXPECTED\n      <div>\n        <p>\n          John</p>\n      </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n  end\n\n  def test_post_trim_tags\n    text = <<-END_TEMPLATE\n      <div>\n        <p>\n          {% if true -%}\n          yes\n          {% endif -%}\n        </p>\n      </div>\n    END_TEMPLATE\n    expected = <<-END_EXPECTED\n      <div>\n        <p>\n          yes\n          </p>\n      </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n\n    text = <<-END_TEMPLATE\n      <div>\n        <p>\n          {% if false -%}\n          no\n          {% endif -%}\n        </p>\n      </div>\n    END_TEMPLATE\n    expected = <<-END_EXPECTED\n      <div>\n        <p>\n          </p>\n      </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n  end\n\n  def test_pre_and_post_trim_tags\n    text = <<-END_TEMPLATE\n      <div>\n        <p>\n          {%- if true %}\n          yes\n          {% endif -%}\n        </p>\n      </div>\n    END_TEMPLATE\n    expected = <<-END_EXPECTED\n      <div>\n        <p>\n          yes\n          </p>\n      </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n\n    text = <<-END_TEMPLATE\n      <div>\n        <p>\n          {%- if false %}\n          no\n          {% endif -%}\n        </p>\n      </div>\n    END_TEMPLATE\n    expected = <<-END_EXPECTED\n      <div>\n        <p></p>\n      </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n  end\n\n  def test_post_and_pre_trim_tags\n    text = <<-END_TEMPLATE\n      <div>\n        <p>\n          {% if true -%}\n          yes\n          {%- endif %}\n        </p>\n      </div>\n    END_TEMPLATE\n    expected = <<-END_EXPECTED\n      <div>\n        <p>\n          yes\n        </p>\n      </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n\n    whitespace = '          '\n    text       = <<-END_TEMPLATE\n      <div>\n        <p>\n          {% if false -%}\n          no\n          {%- endif %}\n        </p>\n      </div>\n    END_TEMPLATE\n    expected = <<~END_EXPECTED\n            <div>\n              <p>\n      #{whitespace}\n              </p>\n            </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n  end\n\n  def test_trim_output\n    text = <<-END_TEMPLATE\n      <div>\n        <p>\n          {{- 'John' -}}\n        </p>\n      </div>\n    END_TEMPLATE\n    expected = <<-END_EXPECTED\n      <div>\n        <p>John</p>\n      </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n  end\n\n  def test_trim_tags\n    text = <<-END_TEMPLATE\n      <div>\n        <p>\n          {%- if true -%}\n          yes\n          {%- endif -%}\n        </p>\n      </div>\n    END_TEMPLATE\n    expected = <<-END_EXPECTED\n      <div>\n        <p>yes</p>\n      </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n\n    text = <<-END_TEMPLATE\n      <div>\n        <p>\n          {%- if false -%}\n          no\n          {%- endif -%}\n        </p>\n      </div>\n    END_TEMPLATE\n    expected = <<-END_EXPECTED\n      <div>\n        <p></p>\n      </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n  end\n\n  def test_whitespace_trim_output\n    text = <<-END_TEMPLATE\n      <div>\n        <p>\n          {{- 'John' -}},\n          {{- '30' -}}\n        </p>\n      </div>\n    END_TEMPLATE\n    expected = <<-END_EXPECTED\n      <div>\n        <p>John,30</p>\n      </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n  end\n\n  def test_whitespace_trim_tags\n    text = <<-END_TEMPLATE\n      <div>\n        <p>\n          {%- if true -%}\n          yes\n          {%- endif -%}\n        </p>\n      </div>\n    END_TEMPLATE\n    expected = <<-END_EXPECTED\n      <div>\n        <p>yes</p>\n      </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n\n    text = <<-END_TEMPLATE\n      <div>\n        <p>\n          {%- if false -%}\n          no\n          {%- endif -%}\n        </p>\n      </div>\n    END_TEMPLATE\n    expected = <<-END_EXPECTED\n      <div>\n        <p></p>\n      </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n  end\n\n  def test_complex_trim_output\n    text = <<-END_TEMPLATE\n      <div>\n        <p>\n          {{- 'John' -}}\n          {{- '30' -}}\n        </p>\n        <b>\n          {{ 'John' -}}\n          {{- '30' }}\n        </b>\n        <i>\n          {{- 'John' }}\n          {{ '30' -}}\n        </i>\n      </div>\n    END_TEMPLATE\n    expected = <<-END_EXPECTED\n      <div>\n        <p>John30</p>\n        <b>\n          John30\n        </b>\n        <i>John\n          30</i>\n      </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n  end\n\n  def test_complex_trim\n    text = <<-END_TEMPLATE\n      <div>\n        {%- if true -%}\n          {%- if true -%}\n            <p>\n              {{- 'John' -}}\n            </p>\n          {%- endif -%}\n        {%- endif -%}\n      </div>\n    END_TEMPLATE\n    expected = <<-END_EXPECTED\n      <div><p>John</p></div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n  end\n\n  def test_right_trim_followed_by_tag\n    assert_template_result('ab c', '{{ \"a\" -}}{{ \"b\" }} c')\n  end\n\n  def test_raw_output\n    whitespace = '        '\n    text       = <<-END_TEMPLATE\n      <div>\n        {% raw %}\n          {%- if true -%}\n            <p>\n              {{- 'John' -}}\n            </p>\n          {%- endif -%}\n        {% endraw %}\n      </div>\n    END_TEMPLATE\n    expected = <<~END_EXPECTED\n            <div>\n      #{whitespace}\n                {%- if true -%}\n                  <p>\n                    {{- 'John' -}}\n                  </p>\n                {%- endif -%}\n      #{whitespace}\n            </div>\n    END_EXPECTED\n    assert_template_result(expected, text)\n  end\n\n  def test_pre_trim_blank_preceding_text\n    assert_template_result(\"\", \"\\n{%- raw %}{% endraw %}\")\n    assert_template_result(\"\", \"\\n{%- if true %}{% endif %}\")\n    assert_template_result(\"BC\", \"{{ 'B' }} \\n{%- if true %}C{% endif %}\")\n  end\n\n  def test_bug_compatible_pre_trim\n    template = Liquid::Template.parse(\"\\n {%- raw %}{% endraw %}\", bug_compatible_whitespace_trimming: true)\n    assert_equal(\"\\n\", template.render)\n\n    template = Liquid::Template.parse(\"\\n {%- if true %}{% endif %}\", bug_compatible_whitespace_trimming: true)\n    assert_equal(\"\\n\", template.render)\n\n    template = Liquid::Template.parse(\"{{ 'B' }} \\n{%- if true %}C{% endif %}\", bug_compatible_whitespace_trimming: true)\n    assert_equal(\"B C\", template.render)\n\n    template = Liquid::Template.parse(\"B\\n {%- raw %}{% endraw %}\", bug_compatible_whitespace_trimming: true)\n    assert_equal(\"B\", template.render)\n\n    template = Liquid::Template.parse(\"B\\n {%- if true %}{% endif %}\", bug_compatible_whitespace_trimming: true)\n    assert_equal(\"B\", template.render)\n  end\n\n  def test_trim_blank\n    assert_template_result('foobar', 'foo {{-}} bar')\n  end\nend # TrimModeTest\n"
  },
  {
    "path": "test/integration/variable_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\nrequire 'timeout'\n\nclass VariableTest < Minitest::Test\n  include Liquid\n\n  def test_simple_variable\n    assert_template_result('worked', \"{{test}}\", { 'test' => 'worked' })\n    assert_template_result('worked wonderfully', \"{{test}}\", { 'test' => 'worked wonderfully' })\n  end\n\n  def test_variable_render_calls_to_liquid\n    assert_template_result('foobar', '{{ foo }}', { 'foo' => ThingWithToLiquid.new })\n  end\n\n  def test_variable_lookup_calls_to_liquid_value\n    assert_template_result('1', '{{ foo }}', { 'foo' => IntegerDrop.new('1') })\n    assert_template_result('2', '{{ list[foo] }}', { 'foo' => IntegerDrop.new('1'), 'list' => [1, 2, 3] })\n    assert_template_result('one', '{{ list[foo] }}', { 'foo' => IntegerDrop.new('1'), 'list' => { 1 => 'one' } })\n    assert_template_result('Yay', '{{ foo }}', { 'foo' => BooleanDrop.new(true) })\n    assert_template_result('YAY', '{{ foo | upcase }}', { 'foo' => BooleanDrop.new(true) })\n  end\n\n  def test_if_tag_calls_to_liquid_value\n    assert_template_result('one', '{% if foo == 1 %}one{% endif %}', { 'foo' => IntegerDrop.new('1') })\n    assert_template_result('one', '{% if foo == eqv %}one{% endif %}', { 'foo' => IntegerDrop.new(1), 'eqv' => IntegerDrop.new(1) })\n    assert_template_result('one', '{% if 0 < foo %}one{% endif %}', { 'foo' => IntegerDrop.new('1') })\n    assert_template_result('one', '{% if foo > 0 %}one{% endif %}', { 'foo' => IntegerDrop.new('1') })\n    assert_template_result('one', '{% if b > a %}one{% endif %}', { 'b' => IntegerDrop.new(1), 'a' => IntegerDrop.new(0) })\n    assert_template_result('true', '{% if foo == true %}true{% endif %}', { 'foo' => BooleanDrop.new(true) })\n    assert_template_result('true', '{% if foo %}true{% endif %}', { 'foo' => BooleanDrop.new(true) })\n\n    assert_template_result('', '{% if foo %}true{% endif %}', { 'foo' => BooleanDrop.new(false) })\n    assert_template_result('', '{% if foo == true %}True{% endif %}', { 'foo' => BooleanDrop.new(false) })\n    assert_template_result('', '{% if foo and true %}SHOULD NOT HAPPEN{% endif %}', { 'foo' => BooleanDrop.new(false) })\n\n    assert_template_result('one', '{% if a contains x %}one{% endif %}', { 'a' => [1], 'x' => IntegerDrop.new(1) })\n  end\n\n  def test_unless_tag_calls_to_liquid_value\n    assert_template_result('', '{% unless foo %}true{% endunless %}', { 'foo' => BooleanDrop.new(true) })\n    assert_template_result('true', '{% unless foo %}true{% endunless %}', { 'foo' => BooleanDrop.new(false) })\n  end\n\n  def test_case_tag_calls_to_liquid_value\n    assert_template_result('One', '{% case foo %}{% when 1 %}One{% endcase %}', { 'foo' => IntegerDrop.new('1') })\n  end\n\n  def test_simple_with_whitespaces\n    assert_template_result(\"  worked  \", \"  {{ test }}  \", { \"test\" => \"worked\" })\n    assert_template_result(\"  worked wonderfully  \", \"  {{ test }}  \", { \"test\" => \"worked wonderfully\" })\n  end\n\n  def test_expression_with_whitespace_in_square_brackets\n    assert_template_result('result', \"{{ a[ 'b' ] }}\", { 'a' => { 'b' => 'result' } })\n    assert_template_result('result', \"{{ a[ [ 'b' ] ] }}\", { 'b' => 'c', 'a' => { 'c' => 'result' } })\n  end\n\n  def test_ignore_unknown\n    assert_template_result(\"\", \"{{ test }}\")\n  end\n\n  def test_using_blank_as_variable_name\n    assert_template_result(\"\", \"{% assign foo = blank %}{{ foo }}\")\n  end\n\n  def test_using_empty_as_variable_name\n    assert_template_result(\"\", \"{% assign foo = empty %}{{ foo }}\")\n  end\n\n  def test_hash_scoping\n    assert_template_result('worked', \"{{ test.test }}\", { 'test' => { 'test' => 'worked' } })\n    assert_template_result('worked', \"{{ test . test }}\", { 'test' => { 'test' => 'worked' } })\n  end\n\n  def test_false_renders_as_false\n    assert_template_result(\"false\", \"{{ foo }}\", { 'foo' => false })\n    assert_template_result(\"false\", \"{{ false }}\")\n  end\n\n  def test_nil_renders_as_empty_string\n    assert_template_result(\"\", \"{{ nil }}\")\n    assert_template_result(\"cat\", \"{{ nil | append: 'cat' }}\")\n  end\n\n  def test_preset_assigns\n    template                 = Template.parse(%({{ test }}))\n    template.assigns['test'] = 'worked'\n    assert_equal('worked', template.render!)\n  end\n\n  def test_reuse_parsed_template\n    template                     = Template.parse(%({{ greeting }} {{ name }}))\n    template.assigns['greeting'] = 'Goodbye'\n    assert_equal('Hello Tobi', template.render!('greeting' => 'Hello', 'name' => 'Tobi'))\n    assert_equal('Hello ', template.render!('greeting' => 'Hello', 'unknown' => 'Tobi'))\n    assert_equal('Hello Brian', template.render!('greeting' => 'Hello', 'name' => 'Brian'))\n    assert_equal('Goodbye Brian', template.render!('name' => 'Brian'))\n    assert_equal({ 'greeting' => 'Goodbye' }, template.assigns)\n  end\n\n  def test_assigns_not_polluted_from_template\n    template                 = Template.parse(%({{ test }}{% assign test = 'bar' %}{{ test }}))\n    template.assigns['test'] = 'baz'\n    assert_equal('bazbar', template.render!)\n    assert_equal('bazbar', template.render!)\n    assert_equal('foobar', template.render!('test' => 'foo'))\n    assert_equal('bazbar', template.render!)\n  end\n\n  def test_hash_with_default_proc\n    template        = Template.parse(%(Hello {{ test }}))\n    assigns         = Hash.new { |_h, k| raise \"Unknown variable '#{k}'\" }\n    assigns['test'] = 'Tobi'\n    assert_equal('Hello Tobi', template.render!(assigns))\n    assigns.delete('test')\n    e = assert_raises(RuntimeError) do\n      template.render!(assigns)\n    end\n    assert_equal(\"Unknown variable 'test'\", e.message)\n  end\n\n  def test_multiline_variable\n    assert_template_result(\"worked\", \"{{\\ntest\\n}}\", { \"test\" => \"worked\" })\n  end\n\n  def test_render_symbol\n    assert_template_result('bar', '{{ foo }}', { 'foo' => :bar })\n  end\n\n  def test_nested_array\n    assert_template_result('', '{{ foo }}', { 'foo' => [[nil]] })\n  end\n\n  def test_dynamic_find_var\n    assert_template_result('bar', '{{ [key] }}', { 'key' => 'foo', 'foo' => 'bar' })\n  end\n\n  def test_raw_value_variable\n    assert_template_result('bar', '{{ [key] }}', { 'key' => 'foo', 'foo' => 'bar' })\n  end\n\n  def test_dynamic_find_var_with_drop\n    assert_template_result(\n      'bar',\n      '{{ [list[settings.zero]] }}',\n      {\n        'list' => ['foo'],\n        'settings' => SettingsDrop.new(\"zero\" => 0),\n        'foo' => 'bar',\n      },\n    )\n\n    assert_template_result(\n      'foo',\n      '{{ [list[settings.zero][\"foo\"]] }}',\n      {\n        'list' => [{ 'foo' => 'bar' }],\n        'settings' => SettingsDrop.new(\"zero\" => 0),\n        'bar' => 'foo',\n      },\n    )\n  end\n\n  def test_double_nested_variable_lookup\n    assert_template_result(\n      'bar',\n      '{{ list[list[settings.zero]][\"foo\"] }}',\n      {\n        'list' => [1, { 'foo' => 'bar' }],\n        'settings' => SettingsDrop.new(\"zero\" => 0),\n        'bar' => 'foo',\n      },\n    )\n  end\n\n  def test_variable_lookup_should_not_hang_with_invalid_syntax\n    Timeout.timeout(1) do\n      assert_template_result(\n        'bar',\n        \"{{['foo'}}\",\n        {\n          'foo' => 'bar',\n        },\n        error_mode: :lax,\n      )\n    end\n\n    very_long_key = \"1234567890\" * 100\n\n    template_list = [\n      \"{{['#{very_long_key}']}}\", # valid\n      \"{{['#{very_long_key}'}}\", # missing closing bracket\n      \"{{[['#{very_long_key}']}}\", # extra open bracket\n    ]\n\n    template_list.each do |template|\n      Timeout.timeout(1) do\n        assert_template_result(\n          'bar',\n          template,\n          {\n            very_long_key => 'bar',\n          },\n          error_mode: :lax,\n        )\n      end\n    end\n  end\n\n  def test_filter_with_single_trailing_comma\n    template = '{{ \"hello\" | append: \"world\", }}'\n\n    with_error_modes(:strict) do\n      error = assert_raises(Liquid::SyntaxError) { Template.parse(template) }\n      assert_match(/is not a valid expression/, error.message)\n    end\n\n    with_error_modes(:strict2) do\n      assert_template_result('helloworld', template)\n    end\n  end\n\n  def test_multiple_filters_with_trailing_commas\n    template = '{{ \"hello\" | append: \"1\", | append: \"2\", }}'\n\n    with_error_modes(:strict) do\n      error = assert_raises(Liquid::SyntaxError) { Template.parse(template) }\n      assert_match(/is not a valid expression/, error.message)\n    end\n\n    with_error_modes(:strict2) do\n      assert_template_result('hello12', template)\n    end\n  end\n\n  def test_filter_with_colon_but_no_arguments\n    template = '{{ \"test\" | upcase: }}'\n\n    with_error_modes(:strict) do\n      error = assert_raises(Liquid::SyntaxError) { Template.parse(template) }\n      assert_match(/is not a valid expression/, error.message)\n    end\n\n    with_error_modes(:strict2) do\n      assert_template_result('TEST', template)\n    end\n  end\n\n  def test_filter_chain_with_colon_no_args\n    template = '{{ \"test\" | append: \"x\" | upcase: }}'\n\n    with_error_modes(:strict) do\n      error = assert_raises(Liquid::SyntaxError) { Template.parse(template) }\n      assert_match(/is not a valid expression/, error.message)\n    end\n\n    with_error_modes(:strict2) do\n      assert_template_result('TESTX', template)\n    end\n  end\n\n  def test_combining_trailing_comma_and_empty_args\n    template = '{{ \"test\" | append: \"x\", | upcase: }}'\n\n    with_error_modes(:strict) do\n      error = assert_raises(Liquid::SyntaxError) { Template.parse(template) }\n      assert_match(/is not a valid expression/, error.message)\n    end\n\n    with_error_modes(:strict2) do\n      assert_template_result('TESTX', template)\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_helper.rb",
    "content": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nENV[\"MT_NO_EXPECTATIONS\"] = \"1\"\nrequire 'minitest/autorun'\n\n$LOAD_PATH.unshift(File.join(File.expand_path(__dir__), '..', 'lib'))\nrequire 'liquid.rb'\nrequire 'liquid/profiler'\n\nmode = :strict\nif (env_mode = ENV['LIQUID_PARSER_MODE'])\n  puts \"-- #{env_mode.upcase} ERROR MODE\"\n  mode = env_mode.to_sym\nend\nLiquid::Environment.default.error_mode = mode\n\nif Minitest.const_defined?('Test')\n  # We're on Minitest 5+. Nothing to do here.\nelse\n  # Minitest 4 doesn't have Minitest::Test yet.\n  Minitest::Test = MiniTest::Unit::TestCase\nend\n\nmodule Minitest\n  class Test\n    def fixture(name)\n      File.join(File.expand_path(__dir__), \"fixtures\", name)\n    end\n  end\n\n  module Assertions\n    include Liquid\n\n    def assert_template_result(\n      expected, template, assigns = {},\n      message: nil, partials: nil, error_mode: Liquid::Environment.default.error_mode, render_errors: false,\n      template_factory: nil\n    )\n      file_system = StubFileSystem.new(partials || {})\n      environment = Liquid::Environment.build(file_system: file_system)\n      template = Liquid::Template.parse(template, line_numbers: true, error_mode: error_mode&.to_sym, environment: environment)\n      registers = Liquid::Registers.new(file_system: file_system, template_factory: template_factory)\n      context = Liquid::Context.build(static_environments: assigns, rethrow_errors: !render_errors, registers: registers, environment: environment)\n      output = template.render(context)\n      assert_equal(expected, output, message)\n    end\n\n    def assert_match_syntax_error(match, template, error_mode: nil)\n      exception = assert_raises(Liquid::SyntaxError) do\n        Template.parse(template, line_numbers: true, error_mode: error_mode&.to_sym).render\n      end\n      assert_match(match, exception.message)\n    end\n\n    def assert_syntax_error(template, error_mode: nil)\n      assert_match_syntax_error(\"\", template, error_mode: error_mode)\n    end\n\n    def assert_usage_increment(name, times: 1)\n      old_method = Liquid::Usage.method(:increment)\n      calls = 0\n      begin\n        Liquid::Usage.singleton_class.send(:remove_method, :increment)\n        Liquid::Usage.define_singleton_method(:increment) do |got_name|\n          calls += 1 if got_name == name\n          old_method.call(got_name)\n        end\n        yield\n      ensure\n        Liquid::Usage.singleton_class.send(:remove_method, :increment)\n        Liquid::Usage.define_singleton_method(:increment, old_method)\n      end\n      assert_equal(times, calls, \"Number of calls to Usage.increment with #{name.inspect}\")\n    end\n\n    def with_global_filter(*globals, &blk)\n      environment = Liquid::Environment.build do |w|\n        w.register_filters(globals)\n      end\n\n      Environment.dangerously_override(environment, &blk)\n    end\n\n    def with_error_modes(*modes)\n      old_mode = Liquid::Environment.default.error_mode\n      modes.each do |mode|\n        Liquid::Environment.default.error_mode = mode\n        yield\n      end\n    ensure\n      Liquid::Environment.default.error_mode = old_mode\n    end\n\n    def with_custom_tag(tag_name, tag_class, &block)\n      environment = Liquid::Environment.default.dup\n      environment.register_tag(tag_name, tag_class)\n\n      Environment.dangerously_override(environment, &block)\n    end\n  end\nend\n\nclass ThingWithToLiquid\n  def to_liquid\n    'foobar'\n  end\nend\n\nclass SettingsDrop < Liquid::Drop\n  def initialize(settings)\n    super()\n    @settings = settings\n  end\n\n  def liquid_method_missing(key)\n    @settings[key]\n  end\nend\n\nclass IntegerDrop < Liquid::Drop\n  def initialize(value)\n    super()\n    @value = value.to_i\n  end\n\n  def to_s\n    @value.to_s\n  end\n\n  def to_liquid_value\n    @value\n  end\nend\n\nclass BooleanDrop < Liquid::Drop\n  def initialize(value)\n    super()\n    @value = value\n  end\n\n  def to_liquid_value\n    @value\n  end\n\n  def to_s\n    @value ? \"Yay\" : \"Nay\"\n  end\nend\n\nclass StringDrop < Liquid::Drop\n  include Comparable\n\n  def initialize(value)\n    super()\n    @value = value\n  end\n\n  def to_liquid_value\n    @value\n  end\n\n  def to_s\n    @value\n  end\n\n  def to_str\n    @value\n  end\n\n  def inspect\n    \"#<StringDrop @value=#{@value.inspect}>\"\n  end\n\n  def <=>(other)\n    to_liquid_value <=> Liquid::Utils.to_liquid_value(other)\n  end\nend\n\nclass ErrorDrop < Liquid::Drop\n  def standard_error\n    raise Liquid::StandardError, 'standard error'\n  end\n\n  def argument_error\n    raise Liquid::ArgumentError, 'argument error'\n  end\n\n  def syntax_error\n    raise Liquid::SyntaxError, 'syntax error'\n  end\n\n  def runtime_error\n    raise 'runtime error'\n  end\n\n  def exception\n    raise Exception, 'exception'\n  end\nend\n\nclass CustomToLiquidDrop < Liquid::Drop\n  def initialize(value)\n    @value = value\n    super()\n  end\n\n  def to_liquid\n    @value\n  end\nend\n\nclass HashWithCustomToS < Hash\n  def to_s\n    \"kewl\"\n  end\nend\n\nclass HashWithoutCustomToS < Hash\nend\n\nclass StubFileSystem\n  attr_reader :file_read_count\n\n  def initialize(values)\n    @file_read_count = 0\n    @values          = values\n  end\n\n  def read_template_file(template_path)\n    @file_read_count += 1\n    @values.fetch(template_path)\n  end\nend\n\nclass StubTemplateFactory\n  attr_reader :count\n\n  def initialize\n    @count = 0\n  end\n\n  def for(template_name)\n    @count += 1\n    template = Liquid::Template.new\n    template.name = \"some/path/\" + template_name\n    template\n  end\nend\n"
  },
  {
    "path": "test/unit/block_unit_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass BlockUnitTest < Minitest::Test\n  include Liquid\n\n  def test_blankspace\n    template = Liquid::Template.parse(\"  \")\n    assert_equal([\"  \"], template.root.nodelist)\n  end\n\n  def test_variable_beginning\n    template = Liquid::Template.parse(\"{{funk}}  \")\n    assert_equal(2, template.root.nodelist.size)\n    assert_equal(Variable, template.root.nodelist[0].class)\n    assert_equal(String, template.root.nodelist[1].class)\n  end\n\n  def test_variable_end\n    template = Liquid::Template.parse(\"  {{funk}}\")\n    assert_equal(2, template.root.nodelist.size)\n    assert_equal(String, template.root.nodelist[0].class)\n    assert_equal(Variable, template.root.nodelist[1].class)\n  end\n\n  def test_variable_middle\n    template = Liquid::Template.parse(\"  {{funk}}  \")\n    assert_equal(3, template.root.nodelist.size)\n    assert_equal(String, template.root.nodelist[0].class)\n    assert_equal(Variable, template.root.nodelist[1].class)\n    assert_equal(String, template.root.nodelist[2].class)\n  end\n\n  def test_variable_with_multibyte_character\n    template = Liquid::Template.parse(\"{{ '❤️' }}\")\n    assert_equal(1, template.root.nodelist.size)\n    assert_equal(Variable, template.root.nodelist[0].class)\n  end\n\n  def test_variable_many_embedded_fragments\n    template = Liquid::Template.parse(\"  {{funk}} {{so}} {{brother}} \")\n    assert_equal(7, template.root.nodelist.size)\n    assert_equal(\n      [String, Variable, String, Variable, String, Variable, String],\n      block_types(template.root.nodelist),\n    )\n  end\n\n  def test_comment_tag_with_block\n    template = Liquid::Template.parse(\"  {% comment %} {% endcomment %} \")\n    assert_equal([String, Comment, String], block_types(template.root.nodelist))\n    assert_equal(3, template.root.nodelist.size)\n  end\n\n  def test_doc_tag_with_block\n    template = Liquid::Template.parse(\"  {% doc %} {% enddoc %} \")\n    assert_equal([String, Doc, String], block_types(template.root.nodelist))\n    assert_equal(3, template.root.nodelist.size)\n  end\n\n  private\n\n  def block_types(nodelist)\n    nodelist.collect(&:class)\n  end\nend\n"
  },
  {
    "path": "test/unit/condition_unit_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ConditionUnitTest < Minitest::Test\n  include Liquid\n\n  def setup\n    @context = Liquid::Context.new\n  end\n\n  def test_basic_condition\n    assert_equal(false, Condition.new(1, '==', 2).evaluate(Context.new))\n    assert_equal(true,  Condition.new(1, '==', 1).evaluate(Context.new))\n  end\n\n  def test_default_operators_evalute_true\n    assert_evaluates_true(1, '==', 1)\n    assert_evaluates_true(1, '!=', 2)\n    assert_evaluates_true(1, '<>', 2)\n    assert_evaluates_true(1, '<', 2)\n    assert_evaluates_true(2, '>', 1)\n    assert_evaluates_true(1, '>=', 1)\n    assert_evaluates_true(2, '>=', 1)\n    assert_evaluates_true(1, '<=', 2)\n    assert_evaluates_true(1, '<=', 1)\n    # negative numbers\n    assert_evaluates_true(1, '>', -1)\n    assert_evaluates_true(-1, '<', 1)\n    assert_evaluates_true(1.0, '>', -1.0)\n    assert_evaluates_true(-1.0, '<', 1.0)\n  end\n\n  def test_default_operators_evalute_false\n    assert_evaluates_false(1, '==', 2)\n    assert_evaluates_false(1, '!=', 1)\n    assert_evaluates_false(1, '<>', 1)\n    assert_evaluates_false(1, '<', 0)\n    assert_evaluates_false(2, '>', 4)\n    assert_evaluates_false(1, '>=', 3)\n    assert_evaluates_false(2, '>=', 4)\n    assert_evaluates_false(1, '<=', 0)\n    assert_evaluates_false(1, '<=', 0)\n  end\n\n  def test_contains_works_on_strings\n    assert_evaluates_true('bob', 'contains', 'o')\n    assert_evaluates_true('bob', 'contains', 'b')\n    assert_evaluates_true('bob', 'contains', 'bo')\n    assert_evaluates_true('bob', 'contains', 'ob')\n    assert_evaluates_true('bob', 'contains', 'bob')\n\n    assert_evaluates_false('bob', 'contains', 'bob2')\n    assert_evaluates_false('bob', 'contains', 'a')\n    assert_evaluates_false('bob', 'contains', '---')\n  end\n\n  def test_contains_binary_encoding_compatibility_with_utf8\n    assert_evaluates_true('🙈'.b, 'contains', '🙈')\n    assert_evaluates_true('🙈', 'contains', '🙈'.b)\n  end\n\n  def test_invalid_comparation_operator\n    assert_evaluates_argument_error(1, '~~', 0)\n  end\n\n  def test_comparation_of_int_and_str\n    assert_evaluates_argument_error('1', '>', 0)\n    assert_evaluates_argument_error('1', '<', 0)\n    assert_evaluates_argument_error('1', '>=', 0)\n    assert_evaluates_argument_error('1', '<=', 0)\n  end\n\n  def test_hash_compare_backwards_compatibility\n    assert_nil(Condition.new({}, '>', 2).evaluate(Context.new))\n    assert_nil(Condition.new(2, '>', {}).evaluate(Context.new))\n    assert_equal(false, Condition.new({}, '==', 2).evaluate(Context.new))\n    assert_equal(true, Condition.new({ 'a' => 1 }, '==', 'a' => 1).evaluate(Context.new))\n    assert_equal(true, Condition.new({ 'a' => 2 }, 'contains', 'a').evaluate(Context.new))\n  end\n\n  def test_contains_works_on_arrays\n    @context          = Liquid::Context.new\n    @context['array'] = [1, 2, 3, 4, 5]\n    array_expr        = VariableLookup.new(\"array\")\n\n    assert_evaluates_false(array_expr, 'contains', 0)\n    assert_evaluates_true(array_expr, 'contains', 1)\n    assert_evaluates_true(array_expr, 'contains', 2)\n    assert_evaluates_true(array_expr, 'contains', 3)\n    assert_evaluates_true(array_expr, 'contains', 4)\n    assert_evaluates_true(array_expr, 'contains', 5)\n    assert_evaluates_false(array_expr, 'contains', 6)\n    assert_evaluates_false(array_expr, 'contains', \"1\")\n  end\n\n  def test_contains_returns_false_for_nil_operands\n    @context = Liquid::Context.new\n    assert_evaluates_false(VariableLookup.new('not_assigned'), 'contains', '0')\n    assert_evaluates_false(0, 'contains', VariableLookup.new('not_assigned'))\n  end\n\n  def test_contains_return_false_on_wrong_data_type\n    assert_evaluates_false(1, 'contains', 0)\n  end\n\n  def test_contains_with_string_left_operand_coerces_right_operand_to_string\n    assert_evaluates_true(' 1 ', 'contains', 1)\n    assert_evaluates_false(' 1 ', 'contains', 2)\n  end\n\n  def test_or_condition\n    condition = Condition.new(1, '==', 2)\n    assert_equal(false, condition.evaluate(Context.new))\n\n    condition.or(Condition.new(2, '==', 1))\n\n    assert_equal(false, condition.evaluate(Context.new))\n\n    condition.or(Condition.new(1, '==', 1))\n\n    assert_equal(true, condition.evaluate(Context.new))\n  end\n\n  def test_and_condition\n    condition = Condition.new(1, '==', 1)\n\n    assert_equal(true, condition.evaluate(Context.new))\n\n    condition.and(Condition.new(2, '==', 2))\n\n    assert_equal(true, condition.evaluate(Context.new))\n\n    condition.and(Condition.new(2, '==', 1))\n\n    assert_equal(false, condition.evaluate(Context.new))\n  end\n\n  def test_should_allow_custom_proc_operator\n    Condition.operators['starts_with'] = proc { |_cond, left, right| left =~ /^#{right}/ }\n\n    assert_evaluates_true('bob', 'starts_with', 'b')\n    assert_evaluates_false('bob', 'starts_with', 'o')\n  ensure\n    Condition.operators.delete('starts_with')\n  end\n\n  def test_left_or_right_may_contain_operators\n    @context        = Liquid::Context.new\n    @context['one'] = @context['another'] = \"gnomeslab-and-or-liquid\"\n\n    assert_evaluates_true(VariableLookup.new(\"one\"), '==', VariableLookup.new(\"another\"))\n  end\n\n  def test_default_context_is_deprecated\n    if Gem::Version.new(Liquid::VERSION) >= Gem::Version.new('6.0.0')\n      flunk(\"Condition#evaluate without a context argument is to be removed\")\n    end\n\n    _out, err = capture_io do\n      assert_equal(true, Condition.new(1, '==', 1).evaluate)\n    end\n\n    expected = \"DEPRECATION WARNING: Condition#evaluate without a context argument is deprecated \" \\\n      \"and will be removed from Liquid 6.0.0.\"\n    assert_includes(err.lines.map(&:strip), expected)\n  end\n\n  def test_parse_expression_in_strict_mode\n    environment = Environment.build(error_mode: :strict)\n    parse_context = ParseContext.new(environment: environment)\n    result = Condition.parse_expression(parse_context, 'product.title')\n\n    assert_instance_of(VariableLookup, result)\n    assert_equal('product', result.name)\n    assert_equal(['title'], result.lookups)\n  end\n\n  def test_parse_expression_in_strict2_mode_raises_internal_error\n    environment = Environment.build(error_mode: :strict2)\n    parse_context = ParseContext.new(environment: environment)\n\n    error = assert_raises(Liquid::InternalError) do\n      Condition.parse_expression(parse_context, 'product.title')\n    end\n\n    assert_match(/unsafe parse_expression cannot be used in strict2 mode/, error.message)\n  end\n\n  def test_parse_expression_with_safe_true_in_strict2_mode\n    environment = Environment.build(error_mode: :strict2)\n    parse_context = ParseContext.new(environment: environment)\n    result = Condition.parse_expression(parse_context, 'product.title', safe: true)\n\n    assert_instance_of(VariableLookup, result)\n    assert_equal('product', result.name)\n    assert_equal(['title'], result.lookups)\n  end\n\n  # Tests for blank? comparison without ActiveSupport\n  #\n  # Ruby's standard library does not include blank? on String, Array, Hash, etc.\n  # ActiveSupport adds blank? but Liquid must work without it. These tests verify\n  # that Liquid implements blank? semantics internally for use in templates like:\n  #   {% if x == blank %}...{% endif %}\n  #\n  # The blank? semantics match ActiveSupport's behavior:\n  # - nil and false are blank\n  # - Strings are blank if empty or contain only whitespace\n  # - Arrays and Hashes are blank if empty\n  # - true and numbers are never blank\n\n  def test_blank_with_whitespace_string\n    # Template authors expect \"   \" to be blank since it has no visible content.\n    # This matches ActiveSupport's String#blank? which returns true for whitespace-only strings.\n    @context['whitespace'] = '   '\n    blank_literal = Condition.class_variable_get(:@@method_literals)['blank']\n\n    assert_evaluates_true(VariableLookup.new('whitespace'), '==', blank_literal)\n  end\n\n  def test_blank_with_empty_string\n    # An empty string has no content, so it should be considered blank.\n    # This is the most basic case of a blank string.\n    @context['empty_string'] = ''\n    blank_literal = Condition.class_variable_get(:@@method_literals)['blank']\n\n    assert_evaluates_true(VariableLookup.new('empty_string'), '==', blank_literal)\n  end\n\n  def test_blank_with_empty_array\n    # Empty arrays have no elements, so they are blank.\n    # Useful for checking if a collection has items: {% if products == blank %}\n    @context['empty_array'] = []\n    blank_literal = Condition.class_variable_get(:@@method_literals)['blank']\n\n    assert_evaluates_true(VariableLookup.new('empty_array'), '==', blank_literal)\n  end\n\n  def test_blank_with_empty_hash\n    # Empty hashes have no key-value pairs, so they are blank.\n    # Useful for checking if settings/options exist: {% if settings == blank %}\n    @context['empty_hash'] = {}\n    blank_literal = Condition.class_variable_get(:@@method_literals)['blank']\n\n    assert_evaluates_true(VariableLookup.new('empty_hash'), '==', blank_literal)\n  end\n\n  def test_blank_with_nil\n    # nil represents \"nothing\" and is the canonical blank value.\n    # Unassigned variables resolve to nil, so this enables: {% if missing_var == blank %}\n    @context['nil_value'] = nil\n    blank_literal = Condition.class_variable_get(:@@method_literals)['blank']\n\n    assert_evaluates_true(VariableLookup.new('nil_value'), '==', blank_literal)\n  end\n\n  def test_blank_with_false\n    # false is considered blank to match ActiveSupport semantics.\n    # This allows {% if some_flag == blank %} to work when flag is false.\n    @context['false_value'] = false\n    blank_literal = Condition.class_variable_get(:@@method_literals)['blank']\n\n    assert_evaluates_true(VariableLookup.new('false_value'), '==', blank_literal)\n  end\n\n  def test_not_blank_with_true\n    # true is a definite value, not blank.\n    # Ensures {% if flag == blank %} works correctly for boolean flags.\n    @context['true_value'] = true\n    blank_literal = Condition.class_variable_get(:@@method_literals)['blank']\n\n    assert_evaluates_false(VariableLookup.new('true_value'), '==', blank_literal)\n  end\n\n  def test_not_blank_with_number\n    # Numbers (including zero) are never blank - they represent actual values.\n    # 0 is a valid quantity, not the absence of a value.\n    @context['number'] = 42\n    blank_literal = Condition.class_variable_get(:@@method_literals)['blank']\n\n    assert_evaluates_false(VariableLookup.new('number'), '==', blank_literal)\n  end\n\n  def test_not_blank_with_string_content\n    # A string with actual content is not blank.\n    # This is the expected behavior for most template string comparisons.\n    @context['string'] = 'hello'\n    blank_literal = Condition.class_variable_get(:@@method_literals)['blank']\n\n    assert_evaluates_false(VariableLookup.new('string'), '==', blank_literal)\n  end\n\n  def test_not_blank_with_non_empty_array\n    # An array with elements has content, so it's not blank.\n    # Enables patterns like {% unless products == blank %}Show products{% endunless %}\n    @context['array'] = [1, 2, 3]\n    blank_literal = Condition.class_variable_get(:@@method_literals)['blank']\n\n    assert_evaluates_false(VariableLookup.new('array'), '==', blank_literal)\n  end\n\n  def test_not_blank_with_non_empty_hash\n    # A hash with key-value pairs has content, so it's not blank.\n    # Useful for checking if configuration exists: {% if config != blank %}\n    @context['hash'] = { 'a' => 1 }\n    blank_literal = Condition.class_variable_get(:@@method_literals)['blank']\n\n    assert_evaluates_false(VariableLookup.new('hash'), '==', blank_literal)\n  end\n\n  # Tests for empty? comparison without ActiveSupport\n  #\n  # empty? is distinct from blank? - it only checks if a collection has zero elements.\n  # For strings, empty? checks length == 0, NOT whitespace content.\n  # Ruby's standard library has empty? on String, Array, and Hash, but Liquid\n  # provides a fallback implementation for consistency.\n\n  def test_empty_with_empty_string\n    # An empty string (\"\") has length 0, so it's empty.\n    # Different from blank - empty is a stricter check.\n    @context['empty_string'] = ''\n    empty_literal = Condition.class_variable_get(:@@method_literals)['empty']\n\n    assert_evaluates_true(VariableLookup.new('empty_string'), '==', empty_literal)\n  end\n\n  def test_empty_with_whitespace_string_not_empty\n    # Whitespace strings have length > 0, so they are NOT empty.\n    # This is the key difference between empty and blank:\n    # \"   \".empty? => false, but \"   \".blank? => true\n    @context['whitespace'] = '   '\n    empty_literal = Condition.class_variable_get(:@@method_literals)['empty']\n\n    assert_evaluates_false(VariableLookup.new('whitespace'), '==', empty_literal)\n  end\n\n  def test_empty_with_empty_array\n    # An array with no elements is empty.\n    # [].empty? => true\n    @context['empty_array'] = []\n    empty_literal = Condition.class_variable_get(:@@method_literals)['empty']\n\n    assert_evaluates_true(VariableLookup.new('empty_array'), '==', empty_literal)\n  end\n\n  def test_empty_with_empty_hash\n    # A hash with no key-value pairs is empty.\n    # {}.empty? => true\n    @context['empty_hash'] = {}\n    empty_literal = Condition.class_variable_get(:@@method_literals)['empty']\n\n    assert_evaluates_true(VariableLookup.new('empty_hash'), '==', empty_literal)\n  end\n\n  def test_nil_is_not_empty\n    # nil is NOT empty - empty? checks if a collection has zero elements.\n    # nil is not a collection, so it cannot be empty.\n    # This differs from blank: nil IS blank, but nil is NOT empty.\n    @context['nil_value'] = nil\n    empty_literal = Condition.class_variable_get(:@@method_literals)['empty']\n\n    assert_evaluates_false(VariableLookup.new('nil_value'), '==', empty_literal)\n  end\n\n  private\n\n  def assert_evaluates_true(left, op, right)\n    assert(\n      Condition.new(left, op, right).evaluate(@context),\n      \"Evaluated false: #{left.inspect} #{op} #{right.inspect}\",\n    )\n  end\n\n  def assert_evaluates_false(left, op, right)\n    assert(\n      !Condition.new(left, op, right).evaluate(@context),\n      \"Evaluated true: #{left.inspect} #{op} #{right.inspect}\",\n    )\n  end\n\n  def assert_evaluates_argument_error(left, op, right)\n    assert_raises(Liquid::ArgumentError) do\n      Condition.new(left, op, right).evaluate(@context)\n    end\n  end\nend # ConditionTest\n"
  },
  {
    "path": "test/unit/environment_filter_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass EnvironmentFilterTest < Minitest::Test\n  include Liquid\n\n  module AccessScopeFilters\n    def public_filter\n      \"public\"\n    end\n\n    def private_filter\n      \"private\"\n    end\n    private :private_filter\n  end\n\n  module LateAddedFilter\n    def late_added_filter(_input)\n      \"filtered\"\n    end\n  end\n\n  def setup\n    @environment = Liquid::Environment.build do |env|\n      env.register_filter(AccessScopeFilters)\n    end\n\n    @context = Context.build(environment: @environment)\n  end\n\n  def test_strainer\n    strainer = @environment.create_strainer(@context)\n    assert_equal(5, strainer.invoke('size', 'input'))\n    assert_equal(\"public\", strainer.invoke(\"public_filter\"))\n  end\n\n  def test_strainer_raises_argument_error\n    strainer = @environment.create_strainer(@context)\n    assert_raises(Liquid::ArgumentError) do\n      strainer.invoke(\"public_filter\", 1)\n    end\n  end\n\n  def test_strainer_argument_error_contains_backtrace\n    strainer = @environment.create_strainer(@context)\n\n    exception = assert_raises(Liquid::ArgumentError) do\n      strainer.invoke(\"public_filter\", 1)\n    end\n\n    assert_match(\n      /\\ALiquid error: wrong number of arguments \\((1 for 0|given 1, expected 0)\\)\\z/,\n      exception.message,\n    )\n\n    source = AccessScopeFilters.instance_method(:public_filter).source_location\n    assert_equal(source[0..1].map(&:to_s), exception.backtrace[0].split(':')[0..1])\n  end\n\n  def test_strainer_only_invokes_public_filter_methods\n    strainer = @environment.create_strainer(@context)\n    assert_equal(false, strainer.class.invokable?('__test__'))\n    assert_equal(false, strainer.class.invokable?('test'))\n    assert_equal(false, strainer.class.invokable?('instance_eval'))\n    assert_equal(false, strainer.class.invokable?('__send__'))\n    assert_equal(true, strainer.class.invokable?('size')) # from the standard lib\n  end\n\n  def test_strainer_returns_nil_if_no_filter_method_found\n    strainer = @environment.create_strainer(@context)\n    assert_nil(strainer.invoke(\"private_filter\"))\n    assert_nil(strainer.invoke(\"undef_the_filter\"))\n  end\n\n  def test_strainer_returns_first_argument_if_no_method_and_arguments_given\n    strainer = @environment.create_strainer(@context)\n    assert_equal(\"password\", strainer.invoke(\"undef_the_method\", \"password\"))\n  end\n\n  def test_strainer_only_allows_methods_defined_in_filters\n    strainer = @environment.create_strainer(@context)\n    assert_equal(\"1 + 1\", strainer.invoke(\"instance_eval\", \"1 + 1\"))\n    assert_equal(\"puts\",  strainer.invoke(\"__send__\", \"puts\", \"Hi Mom\"))\n    assert_equal(\"has_method?\", strainer.invoke(\"invoke\", \"has_method?\", \"invoke\"))\n  end\n\n  def test_strainer_uses_a_class_cache_to_avoid_method_cache_invalidation\n    a = Module.new\n    b = Module.new\n\n    strainer = @environment.create_strainer(@context, [a, b])\n\n    assert_kind_of(StrainerTemplate, strainer)\n    assert_kind_of(a, strainer)\n    assert_kind_of(b, strainer)\n    assert_kind_of(Liquid::StandardFilters, strainer)\n  end\n\n  def test_add_global_filter_clears_cache\n    assert_equal('input', @environment.create_strainer(@context).invoke('late_added_filter', 'input'))\n\n    @environment.register_filter(LateAddedFilter)\n\n    assert_equal('filtered', @environment.create_strainer(nil).invoke('late_added_filter', 'input'))\n  end\nend\n"
  },
  {
    "path": "test/unit/environment_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass EnvironmentTest < Minitest::Test\n  include Liquid\n\n  class UnsubscribeFooter < Liquid::Tag\n    def render(_context)\n      'Unsubscribe Footer'\n    end\n  end\n\n  def test_custom_tag\n    email_environment = Liquid::Environment.build do |environment|\n      environment.register_tag(\"unsubscribe_footer\", UnsubscribeFooter)\n    end\n\n    assert(email_environment.tags[\"unsubscribe_footer\"])\n    assert(email_environment.tag_for_name(\"unsubscribe_footer\"))\n    template = Liquid::Template.parse(\"{% unsubscribe_footer %}\", environment: email_environment)\n\n    assert_equal('Unsubscribe Footer', template.render)\n  end\nend\n"
  },
  {
    "path": "test/unit/file_system_unit_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass FileSystemUnitTest < Minitest::Test\n  include Liquid\n\n  def test_default\n    assert_raises(FileSystemError) do\n      BlankFileSystem.new.read_template_file(\"dummy\")\n    end\n  end\n\n  def test_local\n    file_system = Liquid::LocalFileSystem.new(\"/some/path\")\n    assert_equal(\"/some/path/_mypartial.liquid\", file_system.full_path(\"mypartial\"))\n    assert_equal(\"/some/path/dir/_mypartial.liquid\", file_system.full_path(\"dir/mypartial\"))\n\n    assert_raises(FileSystemError) do\n      file_system.full_path(\"../dir/mypartial\")\n    end\n\n    assert_raises(FileSystemError) do\n      file_system.full_path(\"/dir/../../dir/mypartial\")\n    end\n\n    assert_raises(FileSystemError) do\n      file_system.full_path(\"/etc/passwd\")\n    end\n  end\n\n  def test_custom_template_filename_patterns\n    file_system = Liquid::LocalFileSystem.new(\"/some/path\", \"%s.html\")\n    assert_equal(\"/some/path/mypartial.html\", file_system.full_path(\"mypartial\"))\n    assert_equal(\"/some/path/dir/mypartial.html\", file_system.full_path(\"dir/mypartial\"))\n  end\nend # FileSystemTest\n"
  },
  {
    "path": "test/unit/i18n_unit_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass I18nUnitTest < Minitest::Test\n  include Liquid\n\n  def setup\n    @i18n = I18n.new(fixture(\"en_locale.yml\"))\n  end\n\n  def test_simple_translate_string\n    assert_equal(\"less is more\", @i18n.translate(\"simple\"))\n  end\n\n  def test_nested_translate_string\n    assert_equal(\"something wasn't right\", @i18n.translate(\"errors.syntax.oops\"))\n  end\n\n  def test_single_string_interpolation\n    assert_equal(\"something different\", @i18n.translate(\"whatever\", something: \"different\"))\n  end\n\n  # def test_raises_translation_error_on_undefined_interpolation_key\n  #   assert_raises I18n::TranslationError do\n  #     @i18n.translate(\"whatever\", :oopstypos => \"yes\")\n  #   end\n  # end\n\n  def test_raises_unknown_translation\n    assert_raises(I18n::TranslationError) do\n      @i18n.translate(\"doesnt_exist\")\n    end\n  end\n\n  def test_sets_default_path_to_en\n    assert_equal(I18n::DEFAULT_LOCALE, I18n.new.path)\n  end\nend\n"
  },
  {
    "path": "test/unit/lexer_unit_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass LexerUnitTest < Minitest::Test\n  include Liquid\n\n  def test_strings\n    assert_equal(\n      [[:string, %('this is a test\"\"')], [:string, %(\"wat 'lol'\")], [:end_of_string]],\n      tokenize(%( 'this is a test\"\"' \"wat 'lol'\")),\n    )\n  end\n\n  def test_integer\n    assert_equal(\n      [[:id, 'hi'], [:number, '50'], [:end_of_string]],\n      tokenize('hi 50'),\n    )\n  end\n\n  def test_float\n    assert_equal(\n      [[:id, 'hi'], [:number, '5.0'], [:end_of_string]],\n      tokenize('hi 5.0'),\n    )\n  end\n\n  def test_comparison\n    assert_equal(\n      [[:comparison, '=='], [:comparison, '<>'], [:comparison, 'contains'], [:end_of_string]],\n      tokenize('== <> contains '),\n    )\n  end\n\n  def test_comparison_without_whitespace\n    assert_equal(\n      [[:number, '1'], [:comparison, '>'], [:number, '0'], [:end_of_string]],\n      tokenize('1>0'),\n    )\n  end\n\n  def test_comparison_with_negative_number\n    assert_equal(\n      [[:number, '1'], [:comparison, '>'], [:number, '-1'], [:end_of_string]],\n      tokenize('1>-1'),\n    )\n  end\n\n  def test_raise_for_invalid_comparison\n    assert_raises(SyntaxError) do\n      tokenize('1>!1')\n    end\n\n    assert_raises(SyntaxError) do\n      tokenize('1=<1')\n    end\n\n    assert_raises(SyntaxError) do\n      tokenize('1!!1')\n    end\n  end\n\n  def test_specials\n    assert_equal(\n      [[:pipe, '|'], [:dot, '.'], [:colon, ':'], [:end_of_string]],\n      tokenize('| .:'),\n    )\n\n    assert_equal(\n      [[:open_square, '['], [:comma, ','], [:close_square, ']'], [:end_of_string]],\n      tokenize('[,]'),\n    )\n  end\n\n  def test_fancy_identifiers\n    assert_equal([[:id, 'hi'], [:id, 'five?'], [:end_of_string]], tokenize('hi five?'))\n\n    assert_equal([[:number, '2'], [:id, 'foo'], [:end_of_string]], tokenize('2foo'))\n  end\n\n  def test_whitespace\n    assert_equal(\n      [[:id, 'five'], [:pipe, '|'], [:comparison, '=='], [:end_of_string]],\n      tokenize(\"five|\\n\\t ==\"),\n    )\n  end\n\n  def test_unexpected_character\n    assert_raises(SyntaxError) do\n      tokenize(\"%\")\n    end\n  end\n\n  def test_negative_numbers\n    assert_equal(\n      [[:id, 'foo'], [:pipe, '|'], [:id, 'default'], [:colon, \":\"], [:number, '-1'], [:end_of_string]],\n      tokenize(\"foo | default: -1\"),\n    )\n  end\n\n  def test_greater_than_two_digits\n    assert_equal(\n      [[:id, 'foo'], [:comparison, '>'], [:number, '12'], [:end_of_string]],\n      tokenize(\"foo > 12\"),\n    )\n  end\n\n  def test_error_with_utf8_character\n    error = assert_raises(SyntaxError) do\n      tokenize(\"1 < 1Ø\")\n    end\n\n    assert_equal(\n      'Liquid syntax error: Unexpected character Ø',\n      error.message,\n    )\n  end\n\n  def test_contains_as_attribute_name\n    assert_equal(\n      [[:id, \"a\"], [:dot, \".\"], [:id, \"contains\"], [:dot, \".\"], [:id, \"b\"], [:end_of_string]],\n      tokenize(\"a.contains.b\"),\n    )\n  end\n\n  def test_tokenize_incomplete_expression\n    assert_equal([[:id, \"false\"], [:dash, \"-\"], [:end_of_string]], tokenize(\"false -\"))\n    assert_equal([[:id, \"false\"], [:comparison, \"<\"], [:end_of_string]], tokenize(\"false <\"))\n    assert_equal([[:id, \"false\"], [:comparison, \">\"], [:end_of_string]], tokenize(\"false >\"))\n    assert_equal([[:id, \"false\"], [:number, \"1\"], [:end_of_string]], tokenize(\"false 1\"))\n  end\n\n  def test_error_with_invalid_utf8\n    error = assert_raises(SyntaxError) do\n      tokenize(\"\\x00\\xff\")\n    end\n    assert_equal(\n      'Liquid syntax error: Invalid byte sequence in UTF-8',\n      error.message,\n    )\n  end\n\n  private\n\n  def tokenize(input)\n    Lexer.tokenize(StringScanner.new(input))\n  end\nend\n"
  },
  {
    "path": "test/unit/parse_context_unit_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ParseContextUnitTest < Minitest::Test\n  include Liquid\n\n  def test_safe_parse_expression_with_variable_lookup\n    parser_strict = strict_parse_context.new_parser('product.title')\n    result_strict = strict_parse_context.safe_parse_expression(parser_strict)\n\n    parser_strict2 = strict2_parse_context.new_parser('product.title')\n    result_strict2 = strict2_parse_context.safe_parse_expression(parser_strict2)\n\n    assert_instance_of(VariableLookup, result_strict)\n    assert_equal('product', result_strict.name)\n    assert_equal(['title'], result_strict.lookups)\n\n    assert_instance_of(VariableLookup, result_strict2)\n    assert_equal('product', result_strict2.name)\n    assert_equal(['title'], result_strict2.lookups)\n  end\n\n  def test_safe_parse_expression_raises_syntax_error_for_invalid_expression\n    parser_strict = strict_parse_context.new_parser('')\n    parser_strict2 = strict2_parse_context.new_parser('')\n\n    error_strict = assert_raises(Liquid::SyntaxError) do\n      strict_parse_context.safe_parse_expression(parser_strict)\n    end\n    assert_match(/is not a valid expression/, error_strict.message)\n\n    error_strict2 = assert_raises(Liquid::SyntaxError) do\n      strict2_parse_context.safe_parse_expression(parser_strict2)\n    end\n\n    assert_match(/is not a valid expression/, error_strict2.message)\n  end\n\n  def test_parse_expression_with_variable_lookup\n    result_strict = strict_parse_context.parse_expression('product.title')\n\n    assert_instance_of(VariableLookup, result_strict)\n    assert_equal('product', result_strict.name)\n    assert_equal(['title'], result_strict.lookups)\n\n    error = assert_raises(Liquid::InternalError) do\n      strict2_parse_context.parse_expression('product.title')\n    end\n\n    assert_match(/unsafe parse_expression cannot be used in strict2 mode/, error.message)\n  end\n\n  def test_parse_expression_with_safe_true\n    result_strict = strict_parse_context.parse_expression('product.title', safe: true)\n\n    assert_instance_of(VariableLookup, result_strict)\n    assert_equal('product', result_strict.name)\n    assert_equal(['title'], result_strict.lookups)\n\n    result_strict2 = strict2_parse_context.parse_expression('product.title', safe: true)\n\n    assert_instance_of(VariableLookup, result_strict2)\n    assert_equal('product', result_strict2.name)\n    assert_equal(['title'], result_strict2.lookups)\n  end\n\n  def test_parse_expression_with_empty_string\n    result_strict = strict_parse_context.parse_expression('')\n    assert_nil(result_strict)\n\n    error = assert_raises(Liquid::InternalError) do\n      strict2_parse_context.parse_expression('')\n    end\n\n    assert_match(/unsafe parse_expression cannot be used in strict2 mode/, error.message)\n  end\n\n  def test_parse_expression_with_empty_string_and_safe_true\n    result_strict = strict_parse_context.parse_expression('', safe: true)\n    assert_nil(result_strict)\n\n    result_strict2 = strict2_parse_context.parse_expression('', safe: true)\n    assert_nil(result_strict2)\n  end\n\n  def test_safe_parse_expression_advances_parser_pointer\n    parser = strict2_parse_context.new_parser('foo, bar')\n\n    # safe_parse_expression consumes \"foo\"\n    first_result = strict2_parse_context.safe_parse_expression(parser)\n    assert_instance_of(VariableLookup, first_result)\n    assert_equal('foo', first_result.name)\n\n    parser.consume(:comma)\n\n    # safe_parse_expression consumes \"bar\"\n    second_result = strict2_parse_context.safe_parse_expression(parser)\n    assert_instance_of(VariableLookup, second_result)\n    assert_equal('bar', second_result.name)\n\n    parser.consume(:end_of_string)\n  end\n\n  def test_parse_expression_with_whitespace_in_strict2_mode\n    result = strict2_parse_context.parse_expression('   ', safe: true)\n    assert_nil(result)\n  end\n\n  private\n\n  def strict_parse_context\n    @strict_parse_context ||= ParseContext.new(\n      environment: Environment.build(error_mode: :strict),\n    )\n  end\n\n  def strict2_parse_context\n    @strict2_parse_context ||= ParseContext.new(\n      environment: Environment.build(error_mode: :strict2),\n    )\n  end\nend\n"
  },
  {
    "path": "test/unit/parse_tree_visitor_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ParseTreeVisitorTest < Minitest::Test\n  include Liquid\n\n  def test_variable\n    assert_equal(\n      [\"test\"],\n      visit(%({{ test }})),\n    )\n  end\n\n  def test_varible_with_filter\n    assert_equal(\n      [\"test\", \"infilter\"],\n      visit(%({{ test | split: infilter }})),\n    )\n  end\n\n  def test_dynamic_variable\n    assert_equal(\n      [\"test\", \"inlookup\"],\n      visit(%({{ test[inlookup] }})),\n    )\n  end\n\n  def test_echo\n    assert_equal(\n      [\"test\"],\n      visit(%({% echo test %})),\n    )\n  end\n\n  def test_if_condition\n    assert_equal(\n      [\"test\"],\n      visit(%({% if test %}{% endif %})),\n    )\n  end\n\n  def test_complex_if_condition\n    assert_equal(\n      [\"test\"],\n      visit(%({% if 1 == 1 and 2 == test %}{% endif %})),\n    )\n  end\n\n  def test_if_body\n    assert_equal(\n      [\"test\"],\n      visit(%({% if 1 == 1 %}{{ test }}{% endif %})),\n    )\n  end\n\n  def test_unless_condition\n    assert_equal(\n      [\"test\"],\n      visit(%({% unless test %}{% endunless %})),\n    )\n  end\n\n  def test_complex_unless_condition\n    assert_equal(\n      [\"test\"],\n      visit(%({% unless 1 == 1 and 2 == test %}{% endunless %})),\n    )\n  end\n\n  def test_unless_body\n    assert_equal(\n      [\"test\"],\n      visit(%({% unless 1 == 1 %}{{ test }}{% endunless %})),\n    )\n  end\n\n  def test_elsif_condition\n    assert_equal(\n      [\"test\"],\n      visit(%({% if 1 == 1 %}{% elsif test %}{% endif %})),\n    )\n  end\n\n  def test_complex_elsif_condition\n    assert_equal(\n      [\"test\"],\n      visit(%({% if 1 == 1 %}{% elsif 1 == 1 and 2 == test %}{% endif %})),\n    )\n  end\n\n  def test_elsif_body\n    assert_equal(\n      [\"test\"],\n      visit(%({% if 1 == 1 %}{% elsif 2 == 2 %}{{ test }}{% endif %})),\n    )\n  end\n\n  def test_else_body\n    assert_equal(\n      [\"test\"],\n      visit(%({% if 1 == 1 %}{% else %}{{ test }}{% endif %})),\n    )\n  end\n\n  def test_case_left\n    assert_equal(\n      [\"test\"],\n      visit(%({% case test %}{% endcase %})),\n    )\n  end\n\n  def test_case_condition\n    assert_equal(\n      [\"test\"],\n      visit(%({% case 1 %}{% when test %}{% endcase %})),\n    )\n  end\n\n  def test_case_when_body\n    assert_equal(\n      [\"test\"],\n      visit(%({% case 1 %}{% when 2 %}{{ test }}{% endcase %})),\n    )\n  end\n\n  def test_case_else_body\n    assert_equal(\n      [\"test\"],\n      visit(%({% case 1 %}{% else %}{{ test }}{% endcase %})),\n    )\n  end\n\n  def test_for_in\n    assert_equal(\n      [\"test\"],\n      visit(%({% for x in test %}{% endfor %})),\n    )\n  end\n\n  def test_for_limit\n    assert_equal(\n      [\"test\"],\n      visit(%({% for x in (1..5) limit: test %}{% endfor %})),\n    )\n  end\n\n  def test_for_offset\n    assert_equal(\n      [\"test\"],\n      visit(%({% for x in (1..5) offset: test %}{% endfor %})),\n    )\n  end\n\n  def test_for_body\n    assert_equal(\n      [\"test\"],\n      visit(%({% for x in (1..5) %}{{ test }}{% endfor %})),\n    )\n  end\n\n  def test_for_range\n    assert_equal(\n      [\"test\"],\n      visit(%({% for x in (1..test) %}{% endfor %})),\n    )\n  end\n\n  def test_tablerow_in\n    assert_equal(\n      [\"test\"],\n      visit(%({% tablerow x in test %}{% endtablerow %})),\n    )\n  end\n\n  def test_tablerow_limit\n    assert_equal(\n      [\"test\"],\n      visit(%({% tablerow x in (1..5) limit: test %}{% endtablerow %})),\n    )\n  end\n\n  def test_tablerow_offset\n    assert_equal(\n      [\"test\"],\n      visit(%({% tablerow x in (1..5) offset: test %}{% endtablerow %})),\n    )\n  end\n\n  def test_tablerow_body\n    assert_equal(\n      [\"test\"],\n      visit(%({% tablerow x in (1..5) %}{{ test }}{% endtablerow %})),\n    )\n  end\n\n  def test_cycle\n    assert_equal(\n      [\"test\"],\n      visit(%({% cycle test %})),\n    )\n  end\n\n  def test_assign\n    assert_equal(\n      [\"test\"],\n      visit(%({% assign x = test %})),\n    )\n  end\n\n  def test_capture\n    assert_equal(\n      [\"test\"],\n      visit(%({% capture x %}{{ test }}{% endcapture %})),\n    )\n  end\n\n  def test_include\n    assert_equal(\n      [\"test\"],\n      visit(%({% include test %})),\n    )\n  end\n\n  def test_include_with\n    assert_equal(\n      [\"test\"],\n      visit(%({% include \"hai\" with test %})),\n    )\n  end\n\n  def test_include_for\n    assert_equal(\n      [\"test\"],\n      visit(%({% include \"hai\" for test %})),\n    )\n  end\n\n  def test_render_with\n    assert_equal(\n      [\"test\"],\n      visit(%({% render \"hai\" with test %})),\n    )\n  end\n\n  def test_render_for\n    assert_equal(\n      [\"test\"],\n      visit(%({% render \"hai\" for test %})),\n    )\n  end\n\n  def test_preserve_tree_structure\n    assert_equal(\n      [[nil, [\n        [nil, [[nil, [[\"other\", []]]]]],\n        [\"test\", []],\n        [\"xs\", []],\n      ]]],\n      traversal(%({% for x in xs offset: test %}{{ other }}{% endfor %})).visit,\n    )\n  end\n\n  private\n\n  def traversal(template)\n    ParseTreeVisitor\n      .for(Template.parse(template).root)\n      .add_callback_for(VariableLookup) { |node| node.name } # rubocop:disable Style/SymbolProc\n  end\n\n  def visit(template)\n    traversal(template).visit.flatten.compact\n  end\nend\n"
  },
  {
    "path": "test/unit/parser_unit_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ParserUnitTest < Minitest::Test\n  include Liquid\n\n  def test_consume\n    p = new_parser(\"wat: 7\")\n    assert_equal('wat', p.consume(:id))\n    assert_equal(':', p.consume(:colon))\n    assert_equal('7', p.consume(:number))\n  end\n\n  def test_jump\n    p = new_parser(\"wat: 7\")\n    p.jump(2)\n    assert_equal('7', p.consume(:number))\n  end\n\n  def test_consume?\n    p = new_parser(\"wat: 7\")\n    assert_equal('wat', p.consume?(:id))\n    assert_equal(false, p.consume?(:dot))\n    assert_equal(':', p.consume(:colon))\n    assert_equal('7', p.consume?(:number))\n  end\n\n  def test_id?\n    p = new_parser(\"wat 6 Peter Hegemon\")\n    assert_equal('wat', p.id?('wat'))\n    assert_equal(false, p.id?('endgame'))\n    assert_equal('6', p.consume(:number))\n    assert_equal('Peter', p.id?('Peter'))\n    assert_equal(false, p.id?('Achilles'))\n  end\n\n  def test_look\n    p = new_parser(\"wat 6 Peter Hegemon\")\n    assert_equal(true, p.look(:id))\n    assert_equal('wat', p.consume(:id))\n    assert_equal(false, p.look(:comparison))\n    assert_equal(true, p.look(:number))\n    assert_equal(true, p.look(:id, 1))\n    assert_equal(false, p.look(:number, 1))\n  end\n\n  def test_expressions\n    p = new_parser(\"hi.there hi?[5].there? hi.there.bob\")\n    assert_equal('hi.there', p.expression)\n    assert_equal('hi?[5].there?', p.expression)\n    assert_equal('hi.there.bob', p.expression)\n\n    p = new_parser(\"567 6.0 'lol' \\\"wut\\\"\")\n    assert_equal('567', p.expression)\n    assert_equal('6.0', p.expression)\n    assert_equal(\"'lol'\", p.expression)\n    assert_equal('\"wut\"', p.expression)\n  end\n\n  def test_ranges\n    p = new_parser(\"(5..7) (1.5..9.6) (young..old) (hi[5].wat..old)\")\n    assert_equal('(5..7)', p.expression)\n    assert_equal('(1.5..9.6)', p.expression)\n    assert_equal('(young..old)', p.expression)\n    assert_equal('(hi[5].wat..old)', p.expression)\n  end\n\n  def test_arguments\n    p = new_parser(\"filter: hi.there[5], keyarg: 7\")\n    assert_equal('filter', p.consume(:id))\n    assert_equal(':', p.consume(:colon))\n    assert_equal('hi.there[5]', p.argument)\n    assert_equal(',', p.consume(:comma))\n    assert_equal('keyarg: 7', p.argument)\n  end\n\n  def test_invalid_expression\n    assert_raises(SyntaxError) do\n      p = new_parser(\"==\")\n      p.expression\n    end\n  end\n\n  private\n\n  def new_parser(str)\n    Parser.new(StringScanner.new(str))\n  end\nend\n"
  },
  {
    "path": "test/unit/partial_cache_unit_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass PartialCacheUnitTest < Minitest::Test\n  def test_uses_the_file_system_register_if_present\n    context = Liquid::Context.build(\n      registers: {\n        file_system: StubFileSystem.new('my_partial' => 'my partial body'),\n      },\n    )\n\n    partial = Liquid::PartialCache.load(\n      'my_partial',\n      context: context,\n      parse_context: Liquid::ParseContext.new,\n    )\n\n    assert_equal('my partial body', partial.render)\n  end\n\n  def test_reads_from_the_file_system_only_once_per_file\n    file_system = StubFileSystem.new('my_partial' => 'some partial body')\n    context     = Liquid::Context.build(\n      registers: { file_system: file_system },\n    )\n\n    2.times do\n      Liquid::PartialCache.load(\n        'my_partial',\n        context: context,\n        parse_context: Liquid::ParseContext.new,\n      )\n    end\n\n    assert_equal(1, file_system.file_read_count)\n  end\n\n  def test_cache_state_is_stored_per_context\n    parse_context      = Liquid::ParseContext.new\n    shared_file_system = StubFileSystem.new(\n      'my_partial' => 'my shared value',\n    )\n    context_one = Liquid::Context.build(\n      registers: {\n        file_system: shared_file_system,\n      },\n    )\n    context_two = Liquid::Context.build(\n      registers: {\n        file_system: shared_file_system,\n      },\n    )\n\n    2.times do\n      Liquid::PartialCache.load(\n        'my_partial',\n        context: context_one,\n        parse_context: parse_context,\n      )\n    end\n\n    Liquid::PartialCache.load(\n      'my_partial',\n      context: context_two,\n      parse_context: parse_context,\n    )\n\n    assert_equal(2, shared_file_system.file_read_count)\n  end\n\n  def test_cache_is_not_broken_when_a_different_parse_context_is_used\n    file_system = StubFileSystem.new('my_partial' => 'some partial body')\n    context     = Liquid::Context.build(\n      registers: { file_system: file_system },\n    )\n\n    Liquid::PartialCache.load(\n      'my_partial',\n      context: context,\n      parse_context: Liquid::ParseContext.new(my_key: 'value one'),\n    )\n    Liquid::PartialCache.load(\n      'my_partial',\n      context: context,\n      parse_context: Liquid::ParseContext.new(my_key: 'value two'),\n    )\n\n    # Technically what we care about is that the file was parsed twice,\n    # but measuring file reads is an OK proxy for this.\n    assert_equal(1, file_system.file_read_count)\n  end\n\n  def test_uses_default_template_factory_when_no_template_factory_found_in_register\n    context = Liquid::Context.build(\n      registers: {\n        file_system: StubFileSystem.new('my_partial' => 'my partial body'),\n      },\n    )\n\n    partial = Liquid::PartialCache.load(\n      'my_partial',\n      context: context,\n      parse_context: Liquid::ParseContext.new,\n    )\n\n    assert_equal('my partial body', partial.render)\n  end\n\n  def test_uses_template_factory_register_if_present\n    template_factory = StubTemplateFactory.new\n    context = Liquid::Context.build(\n      registers: {\n        file_system: StubFileSystem.new('my_partial' => 'my partial body'),\n        template_factory: template_factory,\n      },\n    )\n\n    partial = Liquid::PartialCache.load(\n      'my_partial',\n      context: context,\n      parse_context: Liquid::ParseContext.new,\n    )\n\n    assert_equal('my partial body', partial.render)\n    assert_equal(1, template_factory.count)\n  end\n\n  def test_cache_state_is_shared_for_subcontexts\n    parse_context      = Liquid::ParseContext.new\n    shared_file_system = StubFileSystem.new(\n      'my_partial' => 'my shared value',\n    )\n    context = Liquid::Context.build(\n      registers: Liquid::Registers.new(\n        file_system: shared_file_system,\n      ),\n    )\n    subcontext = context.new_isolated_subcontext\n\n    assert_equal(subcontext.registers[:cached_partials].object_id, context.registers[:cached_partials].object_id)\n\n    2.times do\n      Liquid::PartialCache.load(\n        'my_partial',\n        context: context,\n        parse_context: parse_context,\n      )\n\n      Liquid::PartialCache.load(\n        'my_partial',\n        context: subcontext,\n        parse_context: parse_context,\n      )\n    end\n\n    assert_equal(1, shared_file_system.file_read_count)\n  end\n\n  def test_uses_template_name_from_template_factory\n    template_factory = StubTemplateFactory.new\n    context = Liquid::Context.build(\n      registers: {\n        file_system: StubFileSystem.new('my_partial' => 'my partial body'),\n        template_factory: template_factory,\n      },\n    )\n\n    partial = Liquid::PartialCache.load(\n      'my_partial',\n      context: context,\n      parse_context: Liquid::ParseContext.new,\n    )\n\n    assert_equal('some/path/my_partial', partial.name)\n  end\n\n  def test_includes_error_mode_into_template_cache\n    template_factory = StubTemplateFactory.new\n    context = Liquid::Context.build(\n      registers: {\n        file_system: StubFileSystem.new('my_partial' => 'my partial body'),\n        template_factory: template_factory,\n      },\n    )\n\n    [:lax, :warn, :strict, :strict2].each do |error_mode|\n      Liquid::PartialCache.load(\n        'my_partial',\n        context: context,\n        parse_context: Liquid::ParseContext.new(error_mode: error_mode),\n      )\n    end\n\n    assert_equal(\n      [\"my_partial:lax\", \"my_partial:warn\", \"my_partial:strict\", \"my_partial:strict2\"],\n      context.registers[:cached_partials].keys,\n    )\n  end\nend\n"
  },
  {
    "path": "test/unit/regexp_unit_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\nrequire 'timeout'\n\nclass RegexpUnitTest < Minitest::Test\n  include Liquid\n\n  def test_empty\n    assert_equal([], ''.scan(QuotedFragment))\n  end\n\n  def test_quote\n    assert_equal(['\"arg 1\"'], '\"arg 1\"'.scan(QuotedFragment))\n  end\n\n  def test_words\n    assert_equal(['arg1', 'arg2'], 'arg1 arg2'.scan(QuotedFragment))\n  end\n\n  def test_tags\n    assert_equal(['<tr>', '</tr>'], '<tr> </tr>'.scan(QuotedFragment))\n    assert_equal(['<tr></tr>'], '<tr></tr>'.scan(QuotedFragment))\n    assert_equal(['<style', 'class=\"hello\">', '</style>'], %(<style class=\"hello\">' </style>).scan(QuotedFragment))\n  end\n\n  def test_double_quoted_words\n    assert_equal(['arg1', 'arg2', '\"arg 3\"'], 'arg1 arg2 \"arg 3\"'.scan(QuotedFragment))\n  end\n\n  def test_single_quoted_words\n    assert_equal(['arg1', 'arg2', \"'arg 3'\"], 'arg1 arg2 \\'arg 3\\''.scan(QuotedFragment))\n  end\n\n  def test_quoted_words_in_the_middle\n    assert_equal(['arg1', 'arg2', '\"arg 3\"', 'arg4'], 'arg1 arg2 \"arg 3\" arg4   '.scan(QuotedFragment))\n  end\n\n  def test_variable_parser\n    assert_equal(['var'],                               'var'.scan(VariableParser))\n    assert_equal(['[var]'],                             '[var]'.scan(VariableParser))\n    assert_equal(['var', 'method'],                     'var.method'.scan(VariableParser))\n    assert_equal(['var', '[method]'],                   'var[method]'.scan(VariableParser))\n    assert_equal(['var', '[method]', '[0]'],            'var[method][0]'.scan(VariableParser))\n    assert_equal(['var', '[\"method\"]', '[0]'],          'var[\"method\"][0]'.scan(VariableParser))\n    assert_equal(['var', '[method]', '[0]', 'method'],  'var[method][0].method'.scan(VariableParser))\n  end\n\n  def test_variable_parser_with_large_input\n    Timeout.timeout(1) { assert_equal(['[var]'], '[var]'.scan(VariableParser)) }\n\n    very_long_string = \"foo\" * 1000\n\n    # valid dynamic lookup\n    Timeout.timeout(1) { assert_equal([\"[#{very_long_string}]\"], \"[#{very_long_string}]\".scan(VariableParser)) }\n    # invalid dynamic lookup with missing closing bracket\n    Timeout.timeout(1) { assert_equal([very_long_string], \"[#{very_long_string}\".scan(VariableParser)) }\n  end\nend # RegexpTest\n"
  },
  {
    "path": "test/unit/registers_unit_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass RegistersUnitTest < Minitest::Test\n  include Liquid\n\n  def test_set\n    static_register = Registers.new(a: 1, b: 2)\n    static_register[:b] = 22\n    static_register[:c] = 33\n\n    assert_equal(1, static_register[:a])\n    assert_equal(22, static_register[:b])\n    assert_equal(33, static_register[:c])\n  end\n\n  def test_get_missing_key\n    static_register = Registers.new\n\n    assert_nil(static_register[:missing])\n  end\n\n  def test_delete\n    static_register = Registers.new(a: 1, b: 2)\n    static_register[:b] = 22\n    static_register[:c] = 33\n\n    assert_nil(static_register.delete(:a))\n\n    assert_equal(22, static_register.delete(:b))\n\n    assert_equal(33, static_register.delete(:c))\n    assert_nil(static_register[:c])\n\n    assert_nil(static_register.delete(:d))\n  end\n\n  def test_fetch\n    static_register = Registers.new(a: 1, b: 2)\n    static_register[:b] = 22\n    static_register[:c] = 33\n\n    assert_equal(1, static_register.fetch(:a))\n    assert_equal(1, static_register.fetch(:a, \"default\"))\n    assert_equal(22, static_register.fetch(:b))\n    assert_equal(22, static_register.fetch(:b, \"default\"))\n    assert_equal(33, static_register.fetch(:c))\n    assert_equal(33, static_register.fetch(:c, \"default\"))\n\n    assert_raises(KeyError) do\n      static_register.fetch(:d)\n    end\n    assert_equal(\"default\", static_register.fetch(:d, \"default\"))\n\n    result = static_register.fetch(:d) { \"default\" }\n    assert_equal(\"default\", result)\n\n    result = static_register.fetch(:d, \"default 1\") { \"default 2\" }\n    assert_equal(\"default 2\", result)\n  end\n\n  def test_key\n    static_register = Registers.new(a: 1, b: 2)\n    static_register[:b] = 22\n    static_register[:c] = 33\n\n    assert_equal(true, static_register.key?(:a))\n    assert_equal(true, static_register.key?(:b))\n    assert_equal(true, static_register.key?(:c))\n    assert_equal(false, static_register.key?(:d))\n  end\n\n  def test_static_register_can_be_frozen\n    static_register = Registers.new(a: 1)\n\n    static_register.static.freeze\n\n    assert_raises(RuntimeError) do\n      static_register.static[:a] = \"foo\"\n    end\n\n    assert_raises(RuntimeError) do\n      static_register.static[:b] = \"foo\"\n    end\n\n    assert_raises(RuntimeError) do\n      static_register.static.delete(:a)\n    end\n\n    assert_raises(RuntimeError) do\n      static_register.static.delete(:c)\n    end\n  end\n\n  def test_new_static_retains_static\n    static_register = Registers.new(a: 1, b: 2)\n    static_register[:b] = 22\n    static_register[:c] = 33\n\n    new_static_register = Registers.new(static_register)\n    new_static_register[:b] = 222\n\n    newest_static_register = Registers.new(new_static_register)\n    newest_static_register[:c] = 333\n\n    assert_equal(1, static_register[:a])\n    assert_equal(22, static_register[:b])\n    assert_equal(33, static_register[:c])\n\n    assert_equal(1, new_static_register[:a])\n    assert_equal(222, new_static_register[:b])\n    assert_nil(new_static_register[:c])\n\n    assert_equal(1, newest_static_register[:a])\n    assert_equal(2, newest_static_register[:b])\n    assert_equal(333, newest_static_register[:c])\n  end\n\n  def test_multiple_instances_are_unique\n    static_register_1 = Registers.new(a: 1, b: 2)\n    static_register_1[:b] = 22\n    static_register_1[:c] = 33\n\n    static_register_2 = Registers.new(a: 10, b: 20)\n    static_register_2[:b] = 220\n    static_register_2[:c] = 330\n\n    assert_equal({ a: 1, b: 2 }, static_register_1.static)\n    assert_equal(1, static_register_1[:a])\n    assert_equal(22, static_register_1[:b])\n    assert_equal(33, static_register_1[:c])\n\n    assert_equal({ a: 10, b: 20 }, static_register_2.static)\n    assert_equal(10, static_register_2[:a])\n    assert_equal(220, static_register_2[:b])\n    assert_equal(330, static_register_2[:c])\n  end\n\n  def test_initialization_reused_static_same_memory_object\n    static_register_1 = Registers.new(a: 1, b: 2)\n    static_register_1[:b] = 22\n    static_register_1[:c] = 33\n\n    static_register_2 = Registers.new(static_register_1)\n\n    assert_equal(1, static_register_2[:a])\n    assert_equal(2, static_register_2[:b])\n    assert_nil(static_register_2[:c])\n\n    static_register_1.static[:b] = 222\n    static_register_1.static[:c] = 333\n\n    assert_same(static_register_1.static, static_register_2.static)\n  end\nend\n"
  },
  {
    "path": "test/unit/resource_limits_unit_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ResourceLimitsUnitTest < Minitest::Test\n  def test_cumulative_scores_initialize_to_zero\n    limits = Liquid::ResourceLimits.new({})\n    assert_equal(0, limits.cumulative_render_score)\n    assert_equal(0, limits.cumulative_assign_score)\n  end\n\n  def test_cumulative_limits_default_to_nil\n    limits = Liquid::ResourceLimits.new({})\n    assert_nil(limits.cumulative_render_score_limit)\n    assert_nil(limits.cumulative_assign_score_limit)\n  end\n\n  def test_cumulative_limits_configurable_via_hash\n    limits = Liquid::ResourceLimits.new(\n      cumulative_render_score_limit: 500,\n      cumulative_assign_score_limit: 300,\n    )\n    assert_equal(500, limits.cumulative_render_score_limit)\n    assert_equal(300, limits.cumulative_assign_score_limit)\n  end\n\n  def test_cumulative_limits_configurable_via_accessor\n    limits = Liquid::ResourceLimits.new({})\n    limits.cumulative_render_score_limit = 500\n    assert_equal(500, limits.cumulative_render_score_limit)\n  end\n\n  def test_cumulative_scores_survive_reset\n    limits = Liquid::ResourceLimits.new({})\n    limits.increment_render_score(10)\n    limits.increment_assign_score(5)\n\n    limits.reset\n\n    assert_equal(0, limits.render_score)\n    assert_equal(0, limits.assign_score)\n    assert_equal(10, limits.cumulative_render_score)\n    assert_equal(5, limits.cumulative_assign_score)\n  end\n\n  def test_cumulative_scores_accumulate_across_resets\n    limits = Liquid::ResourceLimits.new({})\n    limits.increment_render_score(10)\n    limits.reset\n    limits.increment_render_score(20)\n    limits.reset\n    limits.increment_render_score(30)\n\n    assert_equal(30, limits.render_score)\n    assert_equal(60, limits.cumulative_render_score)\n  end\n\n  def test_cumulative_render_score_limit_raises\n    limits = Liquid::ResourceLimits.new(cumulative_render_score_limit: 25)\n    limits.increment_render_score(10)\n    limits.reset\n    limits.increment_render_score(10)\n    limits.reset\n\n    assert_raises(Liquid::MemoryError) do\n      limits.increment_render_score(10)\n    end\n    assert(limits.reached?)\n  end\n\n  def test_cumulative_assign_score_limit_raises\n    limits = Liquid::ResourceLimits.new(cumulative_assign_score_limit: 15)\n    limits.increment_assign_score(8)\n    limits.reset\n\n    assert_raises(Liquid::MemoryError) do\n      limits.increment_assign_score(8)\n    end\n    assert(limits.reached?)\n  end\n\n  def test_per_template_limits_still_work_with_cumulative\n    limits = Liquid::ResourceLimits.new(\n      render_score_limit: 50,\n      cumulative_render_score_limit: 1000,\n    )\n    assert_raises(Liquid::MemoryError) do\n      limits.increment_render_score(51)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/strainer_template_unit_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass StrainerTemplateUnitTest < Minitest::Test\n  include Liquid\n\n  def test_add_filter_when_wrong_filter_class\n    c = Context.new\n    s = c.strainer\n    wrong_filter = lambda(&:reverse)\n\n    exception = assert_raises(TypeError) do\n      s.class.add_filter(wrong_filter)\n    end\n    assert_equal(exception.message, \"wrong argument type Proc (expected Module)\")\n  end\n\n  module PrivateMethodOverrideFilter\n    private\n\n    def public_filter\n      \"overriden as private\"\n    end\n  end\n\n  def test_add_filter_raises_when_module_privately_overrides_registered_public_methods\n    error = assert_raises(Liquid::MethodOverrideError) do\n      Liquid::Environment.build do |env|\n        env.register_filter(PublicMethodOverrideFilter)\n        env.register_filter(PrivateMethodOverrideFilter)\n      end\n    end\n\n    assert_equal('Liquid error: Filter overrides registered public methods as non public: public_filter', error.message)\n  end\n\n  module ProtectedMethodOverrideFilter\n    protected\n\n    def public_filter\n      \"overriden as protected\"\n    end\n  end\n\n  def test_add_filter_raises_when_module_overrides_registered_public_method_as_protected\n    error = assert_raises(Liquid::MethodOverrideError) do\n      Liquid::Environment.build do |env|\n        env.register_filter(PublicMethodOverrideFilter)\n        env.register_filter(ProtectedMethodOverrideFilter)\n      end\n    end\n\n    assert_equal('Liquid error: Filter overrides registered public methods as non public: public_filter', error.message)\n  end\n\n  module PublicMethodOverrideFilter\n    def public_filter\n      \"public\"\n    end\n  end\n\n  def test_add_filter_does_not_raise_when_module_overrides_previously_registered_method\n    with_global_filter do\n      context = Context.new\n      context.add_filters([PublicMethodOverrideFilter])\n      strainer = context.strainer\n      assert(strainer.class.send(:filter_methods).include?('public_filter'))\n    end\n  end\n\n  def test_add_filter_does_not_include_already_included_module\n    mod = Module.new do\n      class << self\n        attr_accessor :include_count\n        def included(_mod)\n          self.include_count += 1\n        end\n      end\n      self.include_count = 0\n    end\n    strainer = Context.new.strainer\n    strainer.class.add_filter(mod)\n    strainer.class.add_filter(mod)\n    assert_equal(1, mod.include_count)\n  end\nend\n"
  },
  {
    "path": "test/unit/tag_unit_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass TagUnitTest < Minitest::Test\n  include Liquid\n\n  def test_tag\n    tag = Tag.parse('tag', \"\", new_tokenizer, ParseContext.new)\n    assert_equal('liquid::tag', tag.name)\n    assert_equal('', tag.render(Context.new))\n  end\n\n  def test_return_raw_text_of_tag\n    tag = Tag.parse(\"long_tag\", \"param1, param2, param3\", new_tokenizer, ParseContext.new)\n    assert_equal(\"long_tag param1, param2, param3\", tag.raw)\n  end\n\n  def test_tag_name_should_return_name_of_the_tag\n    tag = Tag.parse(\"some_tag\", \"\", new_tokenizer, ParseContext.new)\n    assert_equal('some_tag', tag.tag_name)\n  end\n\n  class CustomTag < Liquid::Tag\n    def render(_context); end\n  end\n\n  def test_tag_render_to_output_buffer_nil_value\n    custom_tag = CustomTag.parse(\"some_tag\", \"\", new_tokenizer, ParseContext.new)\n    assert_equal('some string', custom_tag.render_to_output_buffer(Context.new, \"some string\"))\n  end\n\n  private\n\n  def new_tokenizer\n    Tokenizer.new(\n      source: \"\",\n      string_scanner: StringScanner.new(\"\"),\n    )\n  end\nend\n"
  },
  {
    "path": "test/unit/tags/case_tag_unit_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass CaseTagUnitTest < Minitest::Test\n  include Liquid\n\n  def test_case_nodelist\n    template = Liquid::Template.parse('{% case var %}{% when true %}WHEN{% else %}ELSE{% endcase %}')\n    assert_equal(['WHEN', 'ELSE'], template.root.nodelist[0].nodelist.map(&:nodelist).flatten)\n  end\n\n  def test_case_with_trailing_element\n    template = <<~LIQUID\n      {%- case 1 bar -%}\n        {%- when 1 -%}\n          one\n        {%- else -%}\n          two\n      {%- endcase -%}\n    LIQUID\n\n    with_error_modes(:lax, :strict) do\n      assert_template_result(\"one\", template)\n    end\n\n    with_error_modes(:strict2) do\n      error = assert_raises(Liquid::SyntaxError) { Template.parse(template) }\n\n      assert_match(/Expected end_of_string but found/, error.message)\n    end\n  end\n\n  def test_case_when_with_trailing_element\n    template = <<~LIQUID\n      {%- case 1 -%}\n        {%- when 1 bar -%}\n          one\n        {%- else -%}\n          two\n      {%- endcase -%}\n    LIQUID\n\n    with_error_modes(:lax, :strict) do\n      assert_template_result(\"one\", template)\n    end\n\n    with_error_modes(:strict2) do\n      error = assert_raises(Liquid::SyntaxError) { Template.parse(template) }\n\n      assert_match(/Expected end_of_string but found/, error.message)\n    end\n  end\n\n  def test_case_when_with_comma\n    template = <<~LIQUID\n      {%- case 1 -%}\n        {%- when 2, 1 -%}\n          one\n        {%- else -%}\n          two\n      {%- endcase -%}\n    LIQUID\n\n    with_error_modes(:lax, :strict, :strict2) do\n      assert_template_result(\"one\", template)\n    end\n  end\n\n  def test_case_when_with_or\n    template = <<~LIQUID\n      {%- case 1 -%}\n        {%- when 2 or 1 -%}\n          one\n        {%- else -%}\n          two\n      {%- endcase -%}\n    LIQUID\n\n    with_error_modes(:lax, :strict, :strict2) do\n      assert_template_result(\"one\", template)\n    end\n  end\n\n  def test_case_when_empty\n    template = <<~LIQUID\n      {%- case x -%}\n        {%- when 2 or empty -%}\n          2 or empty\n        {%- else -%}\n          not 2 or empty\n      {%- endcase -%}\n    LIQUID\n\n    with_error_modes(:lax, :strict, :strict2) do\n      assert_template_result(\"2 or empty\", template, { 'x' => 2 })\n      assert_template_result(\"2 or empty\", template, { 'x' => {} })\n      assert_template_result(\"2 or empty\", template, { 'x' => [] })\n      assert_template_result(\"not 2 or empty\", template, { 'x' => { 'a' => 'b' } })\n      assert_template_result(\"not 2 or empty\", template, { 'x' => ['a'] })\n      assert_template_result(\"not 2 or empty\", template, { 'x' => 4 })\n    end\n  end\n\n  def test_case_with_invalid_expression\n    template = <<~LIQUID\n      {%- case foo=>bar -%}\n        {%- when 'baz' -%}\n          one\n        {%- else -%}\n          two\n      {%- endcase -%}\n    LIQUID\n    assigns = { 'foo' => { 'bar' => 'baz' } }\n\n    with_error_modes(:lax, :strict) do\n      assert_template_result(\"one\", template, assigns)\n    end\n\n    with_error_modes(:strict2) do\n      error = assert_raises(Liquid::SyntaxError) { Template.parse(template) }\n\n      assert_match(/Unexpected character =/, error.message)\n    end\n  end\n\n  def test_case_when_with_invalid_expression\n    template = <<~LIQUID\n      {%- case 'baz' -%}\n        {%- when foo=>bar -%}\n          one\n        {%- else -%}\n          two\n      {%- endcase -%}\n    LIQUID\n    assigns = { 'foo' => { 'bar' => 'baz' } }\n\n    with_error_modes(:lax, :strict) do\n      assert_template_result(\"one\", template, assigns)\n    end\n\n    with_error_modes(:strict2) do\n      error = assert_raises(Liquid::SyntaxError) { Template.parse(template) }\n\n      assert_match(/Unexpected character =/, error.message)\n    end\n  end\nend\n"
  },
  {
    "path": "test/unit/tags/comment_tag_unit_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass CommentTagUnitTest < Minitest::Test\n  def test_comment_inside_liquid_tag\n    assert_template_result(\"\", <<~LIQUID.chomp)\n      {% liquid\n        if 1 != 1\n        comment\n        else\n          echo 123\n        endcomment\n        endif\n      %}\n    LIQUID\n  end\n\n  def test_does_not_parse_nodes_inside_a_comment\n    assert_template_result(\"\", <<~LIQUID.chomp)\n      {% comment %}\n        {% if true %}\n        {% if ... %}\n        {%- for ? -%}\n        {% while true %}\n        {%\n          unless if\n        %}\n        {% endcase %}\n      {% endcomment %}\n    LIQUID\n  end\n\n  def test_allows_unclosed_tags\n    assert_template_result('', <<~LIQUID.chomp)\n      {% comment %}\n        {% if true %}\n      {% endcomment %}\n    LIQUID\n  end\n\n  def test_open_tags_in_comment\n    assert_template_result('', <<~LIQUID.chomp)\n      {% comment %}\n        {% assign a = 123 {% comment %}\n      {% endcomment %}\n    LIQUID\n\n    assert_raises(Liquid::SyntaxError) do\n      assert_template_result(\"\", <<~LIQUID.chomp)\n        {% comment %}\n          {% assign foo = \"1\"\n        {% endcomment %}\n      LIQUID\n    end\n\n    assert_raises(Liquid::SyntaxError) do\n      assert_template_result(\"\", <<~LIQUID.chomp)\n        {% comment %}\n          {% comment %}\n            {% invalid\n          {% endcomment %}\n        {% endcomment %}\n      LIQUID\n    end\n\n    assert_raises(Liquid::SyntaxError) do\n      assert_template_result(\"\", <<~LIQUID.chomp)\n        {% comment %}\n        {% {{ {%- endcomment %}\n      LIQUID\n    end\n  end\n\n  def test_child_comment_tags_need_to_be_closed\n    assert_template_result(\"\", <<~LIQUID.chomp)\n      {% comment %}\n        {% comment %}\n          {% comment %}{%    endcomment     %}\n        {% endcomment %}\n      {% endcomment %}\n    LIQUID\n\n    assert_raises(Liquid::SyntaxError) do\n      assert_template_result(\"\", <<~LIQUID.chomp)\n        {% comment %}\n          {% comment %}\n            {% comment %}\n          {% endcomment %}\n        {% endcomment %}\n      LIQUID\n    end\n  end\n\n  def test_child_raw_tags_need_to_be_closed\n    assert_template_result(\"\", <<~LIQUID.chomp)\n      {% comment %}\n        {% raw %}\n          {% endcomment %}\n        {% endraw %}\n      {% endcomment %}\n    LIQUID\n\n    assert_raises(Liquid::SyntaxError) do\n      Liquid::Template.parse(<<~LIQUID.chomp)\n        {% comment %}\n          {% raw %}\n          {% endcomment %}\n        {% endcomment %}\n      LIQUID\n    end\n  end\n\n  def test_error_line_number_is_correct\n    template = Liquid::Template.parse(<<~LIQUID.chomp, line_numbers: true)\n      {% comment %}\n        {% if true %}\n      {% endcomment %}\n      {{ errors.standard_error }}\n    LIQUID\n\n    output = template.render('errors' => ErrorDrop.new)\n    expected = <<~TEXT.chomp\n\n      Liquid error (line 4): standard error\n    TEXT\n\n    assert_equal(expected, output)\n  end\n\n  def test_comment_tag_delimiter_with_extra_strings\n    assert_template_result(\n      '',\n      <<~LIQUID.chomp,\n        {% comment %}\n          {% comment %}\n          {% endcomment\n          {% if true %}\n          {% endif %}\n        {% endcomment %}\n      LIQUID\n    )\n  end\n\n  def test_nested_comment_tag_with_extra_strings\n    assert_template_result(\n      '',\n      <<~LIQUID.chomp,\n        {% comment %}\n          {% comment\n            {% assign foo = 1 %}\n          {% endcomment\n          {% assign foo = 1 %}\n        {% endcomment %}\n      LIQUID\n    )\n  end\n\n  def test_ignores_delimiter_with_extra_strings\n    assert_template_result(\n      '',\n      <<~LIQUID.chomp,\n        {% if true %}\n          {% comment %}\n            {% commentXXXXX %}wut{% endcommentXXXXX %}\n          {% endcomment %}\n        {% endif %}\n      LIQUID\n    )\n  end\n\n  def test_delimiter_can_have_extra_strings\n    assert_template_result('', \"{% comment %}123{% endcomment xyz %}\")\n    assert_template_result('', \"{% comment %}123{% endcomment\\txyz %}\")\n    assert_template_result('', \"{% comment %}123{% endcomment\\nxyz %}\")\n    assert_template_result('', \"{% comment %}123{% endcomment\\n   xyz  endcomment %}\")\n    assert_template_result('', \"{%comment}{% assign a = 1 %}{%endcomment}{% endif %}\")\n  end\n\n  def test_with_whitespace_control\n    assert_template_result(\"Hello!\", \"      {%- comment -%}123{%- endcomment -%}Hello!\")\n    assert_template_result(\"Hello!\", \"{%- comment -%}123{%- endcomment -%}     Hello!\")\n    assert_template_result(\"Hello!\", \"      {%- comment -%}123{%- endcomment -%}     Hello!\")\n\n    assert_template_result(\"Hello!\", <<~LIQUID.chomp)\n      {%- comment %}Whitespace control!{% endcomment -%}\n      Hello!\n    LIQUID\n  end\n\n  def test_dont_override_liquid_tag_whitespace_control\n    assert_template_result(\"Hello!World!\", <<~LIQUID.chomp)\n      Hello!\n      {%- liquid\n        comment\n         this is inside a liquid tag\n        endcomment\n      -%}\n      World!\n    LIQUID\n  end\nend\n"
  },
  {
    "path": "test/unit/tags/doc_tag_unit_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass DocTagUnitTest < Minitest::Test\n  def test_doc_tag\n    template = <<~LIQUID.chomp\n      {% doc %}\n        Renders loading-spinner.\n\n        @param {string} foo - some foo\n        @param {string} [bar] - optional bar\n\n        @example\n        {% render 'loading-spinner', foo: 'foo' %}\n        {% render 'loading-spinner', foo: 'foo', bar: 'bar' %}\n      {% enddoc %}\n    LIQUID\n\n    assert_template_result('', template)\n  end\n\n  def test_doc_tag_body_content\n    doc_content = \"  Documentation content\\n  @param {string} foo - test\\n\"\n    template_source = \"{% doc %}#{doc_content}{% enddoc %}\"\n\n    doc_tag = nil\n    ParseTreeVisitor\n      .for(Template.parse(template_source).root)\n      .add_callback_for(Liquid::Doc) do |tag|\n        doc_tag = tag\n      end\n      .visit\n\n    assert_equal(doc_content, doc_tag.nodelist.first.to_s)\n  end\n\n  def test_doc_tag_does_not_support_extra_arguments\n    error = assert_raises(Liquid::SyntaxError) do\n      template = <<~LIQUID.chomp\n        {% doc extra %}\n        {% enddoc %}\n      LIQUID\n\n      Liquid::Template.parse(template)\n    end\n\n    exp_error = \"Liquid syntax error: Syntax Error in 'doc' - Valid syntax: {% doc %}{% enddoc %}\"\n    act_error = error.message\n\n    assert_equal(exp_error, act_error)\n  end\n\n  def test_doc_tag_must_support_valid_tags\n    assert_match_syntax_error(\"Liquid syntax error (line 1): 'doc' tag was never closed\", '{% doc %} foo')\n    assert_match_syntax_error(\"Liquid syntax error (line 1): Syntax Error in 'doc' - Valid syntax: {% doc %}{% enddoc %}\", '{% doc } foo {% enddoc %}')\n    assert_match_syntax_error(\"Liquid syntax error (line 1): Syntax Error in 'doc' - Valid syntax: {% doc %}{% enddoc %}\", '{% doc } foo %}{% enddoc %}')\n  end\n\n  def test_doc_tag_ignores_liquid_nodes\n    template = <<~LIQUID.chomp\n      {% doc %}\n        {% if true %}\n        {% if ... %}\n        {%- for ? -%}\n        {% while true %}\n        {%\n          unless if\n        %}\n        {% endcase %}\n      {% enddoc %}\n    LIQUID\n\n    assert_template_result('', template)\n  end\n\n  def test_doc_tag_ignores_unclosed_liquid_tags\n    template = <<~LIQUID.chomp\n      {% doc %}\n        {% if true %}\n      {% enddoc %}\n    LIQUID\n\n    assert_template_result('', template)\n  end\n\n  def test_doc_tag_does_not_allow_nested_docs\n    error = assert_raises(Liquid::SyntaxError) do\n      template = <<~LIQUID.chomp\n        {% doc %}\n          {% doc %}\n            {% doc %}\n        {% enddoc %}\n      LIQUID\n\n      Liquid::Template.parse(template)\n    end\n\n    exp_error = \"Liquid syntax error: Syntax Error in 'doc' - Nested doc tags are not allowed\"\n    act_error = error.message\n\n    assert_equal(exp_error, act_error)\n  end\n\n  def test_doc_tag_ignores_nested_raw_tags\n    template = <<~LIQUID.chomp\n      {% doc %}\n        {% raw %}\n      {% enddoc %}\n    LIQUID\n\n    assert_template_result('', template)\n  end\n\n  def test_doc_tag_ignores_unclosed_assign\n    template = <<~LIQUID.chomp\n      {% doc %}\n        {% assign foo = \"1\"\n      {% enddoc %}\n    LIQUID\n\n    assert_template_result('', template)\n  end\n\n  def test_doc_tag_ignores_malformed_syntax\n    template = <<~LIQUID.chomp\n      {% doc %}\n      {% {{ {%- enddoc %}\n    LIQUID\n\n    assert_template_result('', template)\n  end\n\n  def test_doc_tag_captures_token_before_enddoc\n    template_source = \"{% doc %}{{ incomplete{% enddoc %}\"\n\n    doc_tag = nil\n    ParseTreeVisitor\n      .for(Template.parse(template_source).root)\n      .add_callback_for(Liquid::Doc) do |tag|\n        doc_tag = tag\n      end\n      .visit\n\n    assert_equal(\"{{ incomplete\", doc_tag.nodelist.first.to_s)\n  end\n\n  def test_doc_tag_preserves_error_line_numbers\n    template = Liquid::Template.parse(<<~LIQUID.chomp, line_numbers: true)\n      {% doc %}\n        {% if true %}\n      {% enddoc %}\n      {{ errors.standard_error }}\n    LIQUID\n\n    expected = <<~TEXT.chomp\n\n      Liquid error (line 4): standard error\n    TEXT\n\n    assert_equal(expected, template.render('errors' => ErrorDrop.new))\n  end\n\n  def test_doc_tag_whitespace_control\n    # Basic whitespace control\n    assert_template_result(\"Hello!\", \"      {%- doc -%}123{%- enddoc -%}Hello!\")\n    assert_template_result(\"Hello!\", \"{%- doc -%}123{%- enddoc -%}     Hello!\")\n    assert_template_result(\"Hello!\", \"      {%- doc -%}123{%- enddoc -%}     Hello!\")\n    assert_template_result(\"Hello!\", <<~LIQUID.chomp)\n      {%- doc %}Whitespace control!{% enddoc -%}\n      Hello!\n    LIQUID\n  end\n\n  def test_doc_tag_delimiter_handling\n    assert_template_result('', <<~LIQUID.chomp)\n      {%- if true -%}\n        {%- doc -%}\n          {%- docEXTRA -%}wut{% enddocEXTRA -%}xyz\n        {%- enddoc -%}\n      {%- endif -%}\n    LIQUID\n\n    assert_template_result('', \"{% doc %}123{% enddoc xyz %}\")\n    assert_template_result('', \"{% doc %}123{% enddoc\\txyz %}\")\n    assert_template_result('', \"{% doc %}123{% enddoc\\nxyz %}\")\n    assert_template_result('', \"{% doc %}123{% enddoc\\n   xyz  enddoc %}\")\n  end\n\n  def test_doc_tag_visitor\n    template_source = '{% doc %}{% enddoc %}'\n\n    assert_equal(\n      [Liquid::Doc],\n      visit(template_source),\n    )\n  end\n\n  def test_doc_tag_blank_with_empty_content\n    template_source = \"{% doc %}{% enddoc %}\"\n\n    doc_tag = nil\n    ParseTreeVisitor\n      .for(Template.parse(template_source).root)\n      .add_callback_for(Liquid::Doc) do |tag|\n        doc_tag = tag\n      end\n      .visit\n\n    assert_equal(true, doc_tag.blank?)\n  end\n\n  def test_doc_tag_blank_with_content\n    template_source = \"{% doc %}Some documentation{% enddoc %}\"\n\n    doc_tag = nil\n    ParseTreeVisitor\n      .for(Template.parse(template_source).root)\n      .add_callback_for(Liquid::Doc) do |tag|\n        doc_tag = tag\n      end\n      .visit\n\n    assert_equal(false, doc_tag.blank?)\n  end\n\n  def test_doc_tag_blank_with_whitespace_only\n    template_source = \"{% doc %}    {% enddoc %}\"\n\n    doc_tag = nil\n    ParseTreeVisitor\n      .for(Template.parse(template_source).root)\n      .add_callback_for(Liquid::Doc) do |tag|\n        doc_tag = tag\n      end\n      .visit\n\n    assert_equal(false, doc_tag.blank?)\n  end\n\n  def test_doc_tag_nodelist_returns_array_with_body\n    doc_content = \"Documentation content\\n@param {string} foo\"\n    template_source = \"{% doc %}#{doc_content}{% enddoc %}\"\n\n    doc_tag = nil\n    ParseTreeVisitor\n      .for(Template.parse(template_source).root)\n      .add_callback_for(Liquid::Doc) do |tag|\n        doc_tag = tag\n      end\n      .visit\n\n    assert_equal([doc_content], doc_tag.nodelist)\n    assert_equal(1, doc_tag.nodelist.length)\n    assert_equal(doc_content, doc_tag.nodelist.first)\n  end\n\n  def test_doc_tag_nodelist_with_empty_content\n    template_source = \"{% doc %}{% enddoc %}\"\n\n    doc_tag = nil\n    ParseTreeVisitor\n      .for(Template.parse(template_source).root)\n      .add_callback_for(Liquid::Doc) do |tag|\n        doc_tag = tag\n      end\n      .visit\n\n    assert_equal([\"\"], doc_tag.nodelist)\n    assert_equal(1, doc_tag.nodelist.length)\n  end\n\n  private\n\n  def traversal(template)\n    ParseTreeVisitor\n      .for(Template.parse(template).root)\n      .add_callback_for(Liquid::Doc) do |tag|\n        tag_class = tag.class\n        tag_class\n      end\n  end\n\n  def visit(template)\n    traversal(template).visit.flatten.compact\n  end\nend\n"
  },
  {
    "path": "test/unit/tags/for_tag_unit_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ForTagUnitTest < Minitest::Test\n  def test_for_nodelist\n    template = Liquid::Template.parse('{% for item in items %}FOR{% endfor %}')\n    assert_equal(['FOR'], template.root.nodelist[0].nodelist.map(&:nodelist).flatten)\n  end\n\n  def test_for_else_nodelist\n    template = Liquid::Template.parse('{% for item in items %}FOR{% else %}ELSE{% endfor %}')\n    assert_equal(['FOR', 'ELSE'], template.root.nodelist[0].nodelist.map(&:nodelist).flatten)\n  end\nend\n"
  },
  {
    "path": "test/unit/tags/if_tag_unit_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass IfTagUnitTest < Minitest::Test\n  def test_if_nodelist\n    template = Liquid::Template.parse('{% if true %}IF{% else %}ELSE{% endif %}')\n    assert_equal(['IF', 'ELSE'], template.root.nodelist[0].nodelist.map(&:nodelist).flatten)\n  end\nend\n"
  },
  {
    "path": "test/unit/template_factory_unit_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass TemplateFactoryUnitTest < Minitest::Test\n  include Liquid\n\n  def test_for_returns_liquid_template_instance\n    template = TemplateFactory.new.for(\"anything\")\n    assert_instance_of(Liquid::Template, template)\n  end\nend\n"
  },
  {
    "path": "test/unit/template_unit_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass TemplateUnitTest < Minitest::Test\n  include Liquid\n\n  def test_sets_default_localization_in_document\n    t = Template.new\n    t.parse('{%comment%}{%endcomment%}')\n    assert_instance_of(I18n, t.root.nodelist[0].options[:locale])\n  end\n\n  def test_sets_default_localization_in_context_with_quick_initialization\n    t = Template.new\n    t.parse('{%comment%}{%endcomment%}', locale: I18n.new(fixture(\"en_locale.yml\")))\n\n    locale = t.root.nodelist[0].options[:locale]\n    assert_instance_of(I18n, locale)\n    assert_equal(fixture(\"en_locale.yml\"), locale.path)\n  end\n\n  class FakeTag; end\n\n  def test_tags_can_be_looped_over\n    with_custom_tag('fake', FakeTag) do\n      result = Template.tags.map { |name, klass| [name, klass] }\n      assert(result.include?([\"fake\", TemplateUnitTest::FakeTag]))\n    end\n  end\n\n  class TemplateSubclass < Liquid::Template\n  end\n\n  def test_template_inheritance\n    assert_equal(\"foo\", TemplateSubclass.parse(\"foo\").render)\n  end\n\n  def test_invalid_utf8\n    input = \"\\xff\\x00\"\n    error = assert_raises(SyntaxError) do\n      Liquid::Tokenizer.new(source: input, string_scanner: StringScanner.new(input))\n    end\n    assert_equal(\n      'Liquid syntax error: Invalid byte sequence in UTF-8',\n      error.message,\n    )\n  end\nend\n"
  },
  {
    "path": "test/unit/tokenizer_unit_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass TokenizerTest < Minitest::Test\n  def test_tokenize_strings\n    assert_equal([' '], tokenize(' '))\n    assert_equal(['hello world'], tokenize('hello world'))\n    assert_equal(['{}'], tokenize('{}'))\n  end\n\n  def test_tokenize_variables\n    assert_equal(['{{funk}}'], tokenize('{{funk}}'))\n    assert_equal([' ', '{{funk}}', ' '], tokenize(' {{funk}} '))\n    assert_equal([' ', '{{funk}}', ' ', '{{so}}', ' ', '{{brother}}', ' '], tokenize(' {{funk}} {{so}} {{brother}} '))\n    assert_equal([' ', '{{  funk  }}', ' '], tokenize(' {{  funk  }} '))\n  end\n\n  def test_tokenize_blocks\n    assert_equal(['{%comment%}'], tokenize('{%comment%}'))\n    assert_equal([' ', '{%comment%}', ' '], tokenize(' {%comment%} '))\n\n    assert_equal([' ', '{%comment%}', ' ', '{%endcomment%}', ' '], tokenize(' {%comment%} {%endcomment%} '))\n    assert_equal(['  ', '{% comment %}', ' ', '{% endcomment %}', ' '], tokenize(\"  {% comment %} {% endcomment %} \"))\n  end\n\n  def test_calculate_line_numbers_per_token_with_profiling\n    assert_equal([1],       tokenize_line_numbers(\"{{funk}}\"))\n    assert_equal([1, 1, 1], tokenize_line_numbers(\" {{funk}} \"))\n    assert_equal([1, 2, 2], tokenize_line_numbers(\"\\n{{funk}}\\n\"))\n    assert_equal([1, 1, 3], tokenize_line_numbers(\" {{\\n funk \\n}} \"))\n  end\n\n  def test_tokenize_with_nil_source_returns_empty_array\n    assert_equal([], tokenize(nil))\n  end\n\n  def test_incomplete_curly_braces\n    assert_equal([\"{{.}\", \" \"], tokenize('{{.} '))\n    assert_equal([\"{{}\", \"%}\"], tokenize('{{}%}'))\n    assert_equal([\"{{}}\", \"}\"], tokenize('{{}}}'))\n  end\n\n  def test_unmatching_start_and_end\n    assert_equal([\"{{%}\"], tokenize('{{%}'))\n    assert_equal([\"{{%%%}}\"], tokenize('{{%%%}}'))\n    assert_equal([\"{%\", \"}}\"], tokenize('{%}}'))\n    assert_equal([\"{%%}\", \"}\"], tokenize('{%%}}'))\n  end\n\n  private\n\n  def new_tokenizer(source, parse_context: Liquid::ParseContext.new, start_line_number: nil)\n    parse_context.new_tokenizer(source, start_line_number: start_line_number)\n  end\n\n  def tokenize(source)\n    tokenizer = new_tokenizer(source)\n    tokens    = []\n    # shift is private in Liquid::C::Tokenizer, since it is only for unit testing\n    while (t = tokenizer.send(:shift))\n      tokens << t\n    end\n    tokens\n  end\n\n  def tokenize_line_numbers(source)\n    tokenizer    = new_tokenizer(source, start_line_number: 1)\n    line_numbers = []\n    loop do\n      line_number = tokenizer.line_number\n      if tokenizer.send(:shift)\n        line_numbers << line_number\n      else\n        break\n      end\n    end\n    line_numbers\n  end\nend\n"
  },
  {
    "path": "test/unit/variable_unit_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass VariableUnitTest < Minitest::Test\n  include Liquid\n\n  def test_variable\n    var = create_variable('hello')\n    assert_equal(VariableLookup.new('hello'), var.name)\n  end\n\n  def test_filters\n    var = create_variable('hello | textileze')\n    assert_equal(VariableLookup.new('hello'), var.name)\n    assert_equal([['textileze', []]], var.filters)\n\n    var = create_variable('hello | textileze | paragraph')\n    assert_equal(VariableLookup.new('hello'), var.name)\n    assert_equal([['textileze', []], ['paragraph', []]], var.filters)\n\n    var = create_variable(%( hello | strftime: '%Y'))\n    assert_equal(VariableLookup.new('hello'), var.name)\n    assert_equal([['strftime', ['%Y']]], var.filters)\n\n    var = create_variable(%( 'typo' | link_to: 'Typo', true ))\n    assert_equal('typo', var.name)\n    assert_equal([['link_to', ['Typo', true]]], var.filters)\n\n    var = create_variable(%( 'typo' | link_to: 'Typo', false ))\n    assert_equal('typo', var.name)\n    assert_equal([['link_to', ['Typo', false]]], var.filters)\n\n    var = create_variable(%( 'foo' | repeat: 3 ))\n    assert_equal('foo', var.name)\n    assert_equal([['repeat', [3]]], var.filters)\n\n    var = create_variable(%( 'foo' | repeat: 3, 3 ))\n    assert_equal('foo', var.name)\n    assert_equal([['repeat', [3, 3]]], var.filters)\n\n    var = create_variable(%( 'foo' | repeat: 3, 3, 3 ))\n    assert_equal('foo', var.name)\n    assert_equal([['repeat', [3, 3, 3]]], var.filters)\n\n    var = create_variable(%( hello | strftime: '%Y, okay?'))\n    assert_equal(VariableLookup.new('hello'), var.name)\n    assert_equal([['strftime', ['%Y, okay?']]], var.filters)\n\n    var = create_variable(%( hello | things: \"%Y, okay?\", 'the other one'))\n    assert_equal(VariableLookup.new('hello'), var.name)\n    assert_equal([['things', ['%Y, okay?', 'the other one']]], var.filters)\n  end\n\n  def test_filter_with_date_parameter\n    var = create_variable(%( '2006-06-06' | date: \"%m/%d/%Y\"))\n    assert_equal('2006-06-06', var.name)\n    assert_equal([['date', ['%m/%d/%Y']]], var.filters)\n  end\n\n  def test_filters_without_whitespace\n    var = create_variable('hello | textileze | paragraph')\n    assert_equal(VariableLookup.new('hello'), var.name)\n    assert_equal([['textileze', []], ['paragraph', []]], var.filters)\n\n    var = create_variable('hello|textileze|paragraph')\n    assert_equal(VariableLookup.new('hello'), var.name)\n    assert_equal([['textileze', []], ['paragraph', []]], var.filters)\n\n    var = create_variable(\"hello|replace:'foo','bar'|textileze\")\n    assert_equal(VariableLookup.new('hello'), var.name)\n    assert_equal([['replace', ['foo', 'bar']], ['textileze', []]], var.filters)\n  end\n\n  def test_symbol\n    var = create_variable(\"http://disney.com/logo.gif | image: 'med' \", error_mode: :lax)\n    assert_equal(VariableLookup.new('http://disney.com/logo.gif'), var.name)\n    assert_equal([['image', ['med']]], var.filters)\n  end\n\n  def test_string_to_filter\n    var = create_variable(\"'http://disney.com/logo.gif' | image: 'med' \")\n    assert_equal('http://disney.com/logo.gif', var.name)\n    assert_equal([['image', ['med']]], var.filters)\n  end\n\n  def test_string_single_quoted\n    var = create_variable(%( \"hello\" ))\n    assert_equal('hello', var.name)\n  end\n\n  def test_string_double_quoted\n    var = create_variable(%( 'hello' ))\n    assert_equal('hello', var.name)\n  end\n\n  def test_integer\n    var = create_variable(%( 1000 ))\n    assert_equal(1000, var.name)\n  end\n\n  def test_float\n    var = create_variable(%( 1000.01 ))\n    assert_equal(1000.01, var.name)\n  end\n\n  def test_dashes\n    assert_equal(VariableLookup.new('foo-bar'), create_variable('foo-bar').name)\n    assert_equal(VariableLookup.new('foo-bar-2'), create_variable('foo-bar-2').name)\n\n    with_error_modes(:strict) do\n      assert_raises(Liquid::SyntaxError) { create_variable('foo - bar') }\n      assert_raises(Liquid::SyntaxError) { create_variable('-foo') }\n      assert_raises(Liquid::SyntaxError) { create_variable('2foo') }\n    end\n  end\n\n  def test_string_with_special_chars\n    var = create_variable(%( 'hello! $!@.;\"ddasd\" ' ))\n    assert_equal('hello! $!@.;\"ddasd\" ', var.name)\n  end\n\n  def test_string_dot\n    var = create_variable(%( test.test ))\n    assert_equal(VariableLookup.new('test.test'), var.name)\n  end\n\n  def test_filter_with_keyword_arguments\n    var = create_variable(%( hello | things: greeting: \"world\", farewell: 'goodbye'))\n    assert_equal(VariableLookup.new('hello'), var.name)\n    assert_equal([['things', [], { 'greeting' => 'world', 'farewell' => 'goodbye' }]], var.filters)\n  end\n\n  def test_lax_filter_argument_parsing\n    var = create_variable(%( number_of_comments | pluralize: 'comment': 'comments' ), error_mode: :lax)\n    assert_equal(VariableLookup.new('number_of_comments'), var.name)\n    assert_equal([['pluralize', ['comment', 'comments']]], var.filters)\n\n    # missing does not throws error\n    create_variable(%(n | f1: ,), error_mode: :lax)\n    create_variable(%(n | f1: ,| f2), error_mode: :lax)\n\n    # arg does not require colon, but ignores args :O, also ignores first kwarg since it splits on ':'\n    var = create_variable(%(n | f1 1 | f2 k1: v1), error_mode: :lax)\n    assert_equal([['f1', []], ['f2', [VariableLookup.new('v1')]]], var.filters)\n\n    # positional and kwargs parsing\n    var = create_variable(%(n | filter: 1, 2, 3 | filter2: k1: 1, k2: 2), error_mode: :lax)\n    assert_equal([['filter', [1, 2, 3]], ['filter2', [], { \"k1\" => 1, \"k2\" => 2 }]], var.filters)\n\n    # positional and kwargs intermixed (pos1, key1: val1, pos2)\n    var = create_variable(%(n | link_to: class: \"black\", \"https://example.com\", title: \"title\"), error_mode: :lax)\n    assert_equal([['link_to', [\"https://example.com\"], { \"class\" => \"black\", \"title\" => \"title\" }]], var.filters)\n  end\n\n  def test_strict_filter_argument_parsing\n    with_error_modes(:strict) do\n      assert_raises(SyntaxError) do\n        create_variable(%( number_of_comments | pluralize: 'comment': 'comments' ))\n      end\n    end\n  end\n\n  def test_strict2_filter_argument_parsing\n    with_error_modes(:strict2) do\n      # optional colon\n      var = create_variable(%(n | f1 | f2:))\n      assert_equal([['f1', []], ['f2', []]], var.filters)\n\n      # missing argument throws error\n      assert_raises(SyntaxError) { create_variable(%(n | f1: ,)) }\n      assert_raises(SyntaxError) { create_variable(%(n | f1: ,| f2)) }\n\n      # arg requires colon\n      assert_raises(SyntaxError) { create_variable(%(n | f1 1)) }\n\n      # trailing comma doesn't throw\n      create_variable(%(n | f1: 1, 2, 3, | f2:))\n\n      # missing comma throws error\n      assert_raises(SyntaxError) { create_variable(%(n | filter: 1 2, 3)) }\n\n      # positional and kwargs parsing\n      var = create_variable(%(n | filter: 1, 2, 3 | filter2: k1: 1, k2: 2))\n      assert_equal([['filter', [1, 2, 3]], ['filter2', [], { \"k1\" => 1, \"k2\" => 2 }]], var.filters)\n\n      # positional and kwargs mixed\n      var = create_variable(%(n | filter: 'a', 'b', key1: 1, key2: 2, 'c'))\n      assert_equal([[\"filter\", [\"a\", \"b\", \"c\"], { \"key1\" => 1, \"key2\" => 2 }]], var.filters)\n\n      # positional and kwargs intermixed (pos1, key1: val1, pos2)\n      var = create_variable(%(n | link_to: class: \"black\", \"https://example.com\", title: \"title\"))\n      assert_equal([['link_to', [\"https://example.com\"], { \"class\" => \"black\", \"title\" => \"title\" }]], var.filters)\n\n      # string key throws\n      assert_raises(SyntaxError) { create_variable(%(n | pluralize: 'comment': 'comments')) }\n    end\n  end\n\n  def test_output_raw_source_of_variable\n    var = create_variable(%( name_of_variable | upcase ))\n    assert_equal(\" name_of_variable | upcase \", var.raw)\n  end\n\n  def test_variable_lookup_interface\n    lookup = VariableLookup.new('a.b.c')\n    assert_equal('a', lookup.name)\n    assert_equal(['b', 'c'], lookup.lookups)\n  end\n\n  private\n\n  def create_variable(markup, options = {})\n    Variable.new(markup, ParseContext.new(options))\n  end\nend\n"
  }
]