Showing preview only (773K chars total). Download the full file or copy to clipboard to get everything.
Repository: Shopify/liquid
Branch: main
Commit: dd37353cca46
Files: 204
Total size: 720.9 KB
Directory structure:
gitextract_1on2h7q1/
├── .github/
│ ├── dependabot.yaml
│ └── workflows/
│ ├── cla.yml
│ └── liquid.yml
├── .gitignore
├── .rubocop.yml
├── .rubocop_todo.yml
├── .ruby-version
├── CONTRIBUTING.md
├── Gemfile
├── History.md
├── LICENSE
├── README.md
├── Rakefile
├── bin/
│ └── render
├── example/
│ └── server/
│ ├── example_servlet.rb
│ ├── liquid_servlet.rb
│ ├── server.rb
│ └── templates/
│ ├── index.liquid
│ └── products.liquid
├── lib/
│ ├── liquid/
│ │ ├── block.rb
│ │ ├── block_body.rb
│ │ ├── condition.rb
│ │ ├── const.rb
│ │ ├── context.rb
│ │ ├── deprecations.rb
│ │ ├── document.rb
│ │ ├── drop.rb
│ │ ├── environment.rb
│ │ ├── errors.rb
│ │ ├── expression.rb
│ │ ├── extensions.rb
│ │ ├── file_system.rb
│ │ ├── forloop_drop.rb
│ │ ├── i18n.rb
│ │ ├── interrupts.rb
│ │ ├── lexer.rb
│ │ ├── locales/
│ │ │ └── en.yml
│ │ ├── parse_context.rb
│ │ ├── parse_tree_visitor.rb
│ │ ├── parser.rb
│ │ ├── parser_switching.rb
│ │ ├── partial_cache.rb
│ │ ├── profiler/
│ │ │ └── hooks.rb
│ │ ├── profiler.rb
│ │ ├── range_lookup.rb
│ │ ├── registers.rb
│ │ ├── resource_limits.rb
│ │ ├── standardfilters.rb
│ │ ├── strainer_template.rb
│ │ ├── tablerowloop_drop.rb
│ │ ├── tag/
│ │ │ ├── disableable.rb
│ │ │ └── disabler.rb
│ │ ├── tag.rb
│ │ ├── tags/
│ │ │ ├── assign.rb
│ │ │ ├── break.rb
│ │ │ ├── capture.rb
│ │ │ ├── case.rb
│ │ │ ├── comment.rb
│ │ │ ├── continue.rb
│ │ │ ├── cycle.rb
│ │ │ ├── decrement.rb
│ │ │ ├── doc.rb
│ │ │ ├── echo.rb
│ │ │ ├── for.rb
│ │ │ ├── if.rb
│ │ │ ├── ifchanged.rb
│ │ │ ├── include.rb
│ │ │ ├── increment.rb
│ │ │ ├── inline_comment.rb
│ │ │ ├── raw.rb
│ │ │ ├── render.rb
│ │ │ ├── table_row.rb
│ │ │ └── unless.rb
│ │ ├── tags.rb
│ │ ├── template.rb
│ │ ├── template_factory.rb
│ │ ├── tokenizer.rb
│ │ ├── usage.rb
│ │ ├── utils.rb
│ │ ├── variable.rb
│ │ ├── variable_lookup.rb
│ │ └── version.rb
│ └── liquid.rb
├── liquid.gemspec
├── performance/
│ ├── benchmark.rb
│ ├── memory_profile.rb
│ ├── profile.rb
│ ├── shopify/
│ │ ├── comment_form.rb
│ │ ├── database.rb
│ │ ├── json_filter.rb
│ │ ├── liquid.rb
│ │ ├── money_filter.rb
│ │ ├── paginate.rb
│ │ ├── shop_filter.rb
│ │ ├── tag_filter.rb
│ │ ├── vision.database.yml
│ │ └── weight_filter.rb
│ ├── tests/
│ │ ├── dropify/
│ │ │ ├── article.liquid
│ │ │ ├── blog.liquid
│ │ │ ├── cart.liquid
│ │ │ ├── collection.liquid
│ │ │ ├── index.liquid
│ │ │ ├── page.liquid
│ │ │ ├── product.liquid
│ │ │ └── theme.liquid
│ │ ├── ripen/
│ │ │ ├── article.liquid
│ │ │ ├── blog.liquid
│ │ │ ├── cart.liquid
│ │ │ ├── collection.liquid
│ │ │ ├── index.liquid
│ │ │ ├── page.liquid
│ │ │ ├── product.liquid
│ │ │ └── theme.liquid
│ │ ├── tribble/
│ │ │ ├── 404.liquid
│ │ │ ├── article.liquid
│ │ │ ├── blog.liquid
│ │ │ ├── cart.liquid
│ │ │ ├── collection.liquid
│ │ │ ├── index.liquid
│ │ │ ├── page.liquid
│ │ │ ├── product.liquid
│ │ │ ├── search.liquid
│ │ │ └── theme.liquid
│ │ └── vogue/
│ │ ├── article.liquid
│ │ ├── blog.liquid
│ │ ├── cart.liquid
│ │ ├── collection.liquid
│ │ ├── index.liquid
│ │ ├── page.liquid
│ │ ├── product.liquid
│ │ └── theme.liquid
│ ├── theme_runner.rb
│ └── unit/
│ ├── expression_benchmark.rb
│ └── lexer_benchmark.rb
├── spec/
│ ├── ruby_liquid.rb
│ ├── ruby_liquid_lax.rb
│ ├── ruby_liquid_with_active_support.rb
│ └── ruby_liquid_yjit.rb
└── test/
├── fixtures/
│ └── en_locale.yml
├── integration/
│ ├── assign_test.rb
│ ├── blank_test.rb
│ ├── block_test.rb
│ ├── capture_test.rb
│ ├── context_test.rb
│ ├── document_test.rb
│ ├── drop_test.rb
│ ├── error_handling_test.rb
│ ├── expression_test.rb
│ ├── filter_kwarg_test.rb
│ ├── filter_test.rb
│ ├── hash_ordering_test.rb
│ ├── hash_rendering_test.rb
│ ├── output_test.rb
│ ├── parsing_quirks_test.rb
│ ├── profiler_test.rb
│ ├── security_test.rb
│ ├── standard_filter_test.rb
│ ├── tag/
│ │ └── disableable_test.rb
│ ├── tag_test.rb
│ ├── tags/
│ │ ├── break_tag_test.rb
│ │ ├── continue_tag_test.rb
│ │ ├── cycle_tag_test.rb
│ │ ├── echo_test.rb
│ │ ├── for_tag_test.rb
│ │ ├── if_else_tag_test.rb
│ │ ├── include_tag_test.rb
│ │ ├── increment_tag_test.rb
│ │ ├── inline_comment_test.rb
│ │ ├── liquid_tag_test.rb
│ │ ├── raw_tag_test.rb
│ │ ├── render_tag_test.rb
│ │ ├── standard_tag_test.rb
│ │ ├── statements_test.rb
│ │ ├── table_row_test.rb
│ │ └── unless_else_tag_test.rb
│ ├── template_test.rb
│ ├── trim_mode_test.rb
│ └── variable_test.rb
├── test_helper.rb
└── unit/
├── block_unit_test.rb
├── condition_unit_test.rb
├── environment_filter_test.rb
├── environment_test.rb
├── file_system_unit_test.rb
├── i18n_unit_test.rb
├── lexer_unit_test.rb
├── parse_context_unit_test.rb
├── parse_tree_visitor_test.rb
├── parser_unit_test.rb
├── partial_cache_unit_test.rb
├── regexp_unit_test.rb
├── registers_unit_test.rb
├── resource_limits_unit_test.rb
├── strainer_template_unit_test.rb
├── tag_unit_test.rb
├── tags/
│ ├── case_tag_unit_test.rb
│ ├── comment_tag_unit_test.rb
│ ├── doc_tag_unit_test.rb
│ ├── for_tag_unit_test.rb
│ └── if_tag_unit_test.rb
├── template_factory_unit_test.rb
├── template_unit_test.rb
├── tokenizer_unit_test.rb
└── variable_unit_test.rb
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/dependabot.yaml
================================================
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
================================================
FILE: .github/workflows/cla.yml
================================================
name: Contributor License Agreement (CLA)
on:
pull_request_target:
types: [opened, synchronize]
issue_comment:
types: [created]
jobs:
cla:
runs-on: ubuntu-latest
if: |
(github.event.issue.pull_request
&& !github.event.issue.pull_request.merged_at
&& contains(github.event.comment.body, 'signed')
)
|| (github.event.pull_request && !github.event.pull_request.merged)
steps:
- uses: Shopify/shopify-cla-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
cla-token: ${{ secrets.CLA_TOKEN }}
================================================
FILE: .github/workflows/liquid.yml
================================================
name: Liquid
on: [push]
env:
BUNDLE_JOBS: 4
BUNDLE_RETRY: 3
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
entry:
- { ruby: 3.3, allowed-failure: false } # minimum supported
- { ruby: 3.4, allowed-failure: false, rubyopt: "--yjit" }
- { ruby: 4.0, allowed-failure: false } # latest stable
- {
ruby: 4.0,
allowed-failure: false,
rubyopt: "--enable-frozen-string-literal",
}
- { ruby: 4.0, allowed-failure: false, rubyopt: "--yjit" }
- { ruby: 4.0, allowed-failure: false, rubyopt: "--zjit" }
# Head can have failures due to being in development
- { ruby: head, allowed-failure: true }
- {
ruby: head,
allowed-failure: true,
rubyopt: "--enable-frozen-string-literal",
}
- { ruby: head, allowed-failure: true, rubyopt: "--yjit" }
- { ruby: head, allowed-failure: true, rubyopt: "--zjit" }
name: Test Ruby ${{ matrix.entry.ruby }} ${{ matrix.entry.rubyopt }} --${{ matrix.entry.allowed-failure && 'allowed-failure' || 'strict' }}
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0
with:
ruby-version: ${{ matrix.entry.ruby }}
bundler-cache: true
bundler: latest
- run: bundle exec rake
continue-on-error: ${{ matrix.entry.allowed-failure }}
env:
RUBYOPT: ${{ matrix.entry.rubyopt }}
spec:
runs-on: ubuntu-latest
env:
BUNDLE_WITH: spec
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0
with:
bundler-cache: true
bundler: latest
- name: Run liquid-spec for all adapters
run: |
for adapter in spec/*.rb; do
echo "=== Running $adapter ==="
bundle exec liquid-spec run "$adapter" --no-max-failures
done
memory_profile:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0
with:
bundler-cache: true
- run: bundle exec rake memory_profile:run
================================================
FILE: .gitignore
================================================
*~
*.gem
*.swp
pkg
*.rbc
.rvmrc
.bundle
.byebug_history
Gemfile.lock
================================================
FILE: .rubocop.yml
================================================
inherit_gem:
rubocop-shopify: rubocop.yml
inherit_from:
- .rubocop_todo.yml
require: rubocop-performance
Performance:
Enabled: true
AllCops:
NewCops: disable
SuggestExtensions: false
Exclude:
- 'vendor/bundle/**/*'
Naming/MethodName:
Exclude:
- 'example/server/liquid_servlet.rb'
Style/ClassMethodsDefinitions:
Enabled: false
# liquid filter calls were being mistaken to be calls on arrays
Style/ConcatArrayLiterals:
Exclude:
- 'test/integration/standard_filter_test.rb'
================================================
FILE: .rubocop_todo.yml
================================================
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2022-05-18 19:25:47 UTC using RuboCop version 1.29.1.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 1
# This cop supports safe auto-correction (--auto-correct).
# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include.
# Include: **/*.gemspec
Gemspec/OrderedDependencies:
Exclude:
- 'liquid.gemspec'
# Offense count: 6
# This cop supports safe auto-correction (--auto-correct).
Layout/ClosingHeredocIndentation:
Exclude:
- 'test/integration/tags/for_tag_test.rb'
# Offense count: 34
# This cop supports safe auto-correction (--auto-correct).
Layout/EmptyLineAfterGuardClause:
Exclude:
- 'lib/liquid/block.rb'
- 'lib/liquid/block_body.rb'
- 'lib/liquid/context.rb'
- 'lib/liquid/drop.rb'
- 'lib/liquid/lexer.rb'
- 'lib/liquid/parser.rb'
- 'lib/liquid/profiler/hooks.rb'
- 'lib/liquid/standardfilters.rb'
- 'lib/liquid/tags/for.rb'
- 'lib/liquid/tags/if.rb'
- 'lib/liquid/utils.rb'
- 'lib/liquid/variable.rb'
- 'lib/liquid/variable_lookup.rb'
- 'performance/shopify/money_filter.rb'
- 'performance/shopify/paginate.rb'
# Offense count: 8
# This cop supports safe auto-correction (--auto-correct).
# Configuration parameters: AllowAliasSyntax, AllowedMethods.
# AllowedMethods: alias_method, public, protected, private
Layout/EmptyLinesAroundAttributeAccessor:
Exclude:
- 'lib/liquid/template.rb'
- 'test/integration/filter_test.rb'
- 'test/integration/tags/include_tag_test.rb'
- 'test/unit/strainer_template_unit_test.rb'
# Offense count: 17
# This cop supports safe auto-correction (--auto-correct).
# Configuration parameters: EnforcedStyle, IndentationWidth.
# SupportedStyles: aligned, indented
Layout/LineEndStringConcatenationIndentation:
Exclude:
- 'test/integration/tags/for_tag_test.rb'
- 'test/integration/tags/increment_tag_test.rb'
# Offense count: 1
# This cop supports safe auto-correction (--auto-correct).
# Configuration parameters: EnforcedStyle, IndentationWidth.
# SupportedStyles: aligned, indented
Layout/MultilineOperationIndentation:
Exclude:
- 'lib/liquid/expression.rb'
# Offense count: 9
Lint/MissingSuper:
Exclude:
- 'lib/liquid/forloop_drop.rb'
- 'lib/liquid/tablerowloop_drop.rb'
- 'test/integration/assign_test.rb'
- 'test/integration/context_test.rb'
- 'test/integration/filter_test.rb'
- 'test/integration/standard_filter_test.rb'
- 'test/integration/tags/for_tag_test.rb'
- 'test/integration/tags/table_row_test.rb'
# Offense count: 44
Naming/ConstantName:
Exclude:
- 'lib/liquid.rb'
- 'lib/liquid/block_body.rb'
- 'lib/liquid/tags/assign.rb'
- 'lib/liquid/tags/capture.rb'
- 'lib/liquid/tags/case.rb'
- 'lib/liquid/tags/cycle.rb'
- 'lib/liquid/tags/for.rb'
- 'lib/liquid/tags/if.rb'
- 'lib/liquid/tags/raw.rb'
- 'lib/liquid/tags/table_row.rb'
- 'lib/liquid/variable.rb'
- 'performance/shopify/comment_form.rb'
- 'performance/shopify/paginate.rb'
- 'test/integration/tags/include_tag_test.rb'
# Offense count: 9
# Configuration parameters: CheckIdentifiers, CheckConstants, CheckVariables, CheckStrings, CheckSymbols, CheckComments, CheckFilepaths, FlaggedTerms.
Naming/InclusiveLanguage:
Exclude:
- 'lib/liquid/drop.rb'
- 'lib/liquid/parse_context.rb'
- 'test/integration/drop_test.rb'
- 'test/integration/tags/if_else_tag_test.rb'
# Offense count: 2
Style/ClassVars:
Exclude:
- 'lib/liquid/condition.rb'
# Offense count: 3
# This cop supports safe auto-correction (--auto-correct).
Style/ExplicitBlockArgument:
Exclude:
- 'test/integration/context_test.rb'
- 'test/integration/tag/disableable_test.rb'
- 'test/integration/tags/for_tag_test.rb'
# Offense count: 2982
# This cop supports safe auto-correction (--auto-correct).
# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
# SupportedStyles: single_quotes, double_quotes
Style/StringLiterals:
Enabled: false
# Offense count: 20
# This cop supports safe auto-correction (--auto-correct).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: single_quotes, double_quotes
Style/StringLiteralsInInterpolation:
Exclude:
- 'lib/liquid/condition.rb'
- 'lib/liquid/strainer_template.rb'
- 'lib/liquid/tag/disableable.rb'
- 'performance/shopify/shop_filter.rb'
- 'performance/shopify/tag_filter.rb'
# Offense count: 6
# This cop supports safe auto-correction (--auto-correct).
# Configuration parameters: EnforcedStyleForMultiline.
# SupportedStylesForMultiline: comma, consistent_comma, no_comma
Style/TrailingCommaInArrayLiteral:
Exclude:
- 'example/server/example_servlet.rb'
- 'lib/liquid/condition.rb'
- 'test/integration/context_test.rb'
- 'test/integration/standard_filter_test.rb'
- 'test/unit/parse_tree_visitor_test.rb'
# Offense count: 1
# This cop supports safe auto-correction (--auto-correct).
# Configuration parameters: EnforcedStyleForMultiline.
# SupportedStylesForMultiline: comma, consistent_comma, no_comma
Style/TrailingCommaInHashLiteral:
Exclude:
- 'lib/liquid/expression.rb'
# Offense count: 19
# This cop supports safe auto-correction (--auto-correct).
# Configuration parameters: EnforcedStyle, MinSize, WordRegex.
# SupportedStyles: percent, brackets
Style/WordArray:
Exclude:
- 'lib/liquid/tags/if.rb'
- 'liquid.gemspec'
- 'test/integration/assign_test.rb'
- 'test/integration/context_test.rb'
- 'test/integration/drop_test.rb'
- 'test/integration/standard_filter_test.rb'
# Offense count: 117
# This cop supports safe auto-correction (--auto-correct).
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, AllowCopDirectives, AllowedPatterns.
# URISchemes: http, https
Layout/LineLength:
Max: 260
Naming/PredicatePrefix:
Enabled: false
# Offense count: 1
# This is intentional - early return from begin/rescue in assignment context
Lint/NoReturnInBeginEndBlocks:
Exclude:
- 'lib/liquid/standardfilters.rb'
================================================
FILE: .ruby-version
================================================
3.4.1
================================================
FILE: CONTRIBUTING.md
================================================
# How to contribute
## Things we will merge
* Bugfixes
* Performance improvements
* Features that are likely to be useful to the majority of Liquid users
* Documentation updates that are concise and likely to be useful to the majority of Liquid users
## Things we won't merge
* Code that introduces considerable performance degrations
* Code that touches performance-critical parts of Liquid and comes without benchmarks
* Features that are not important for most people (we want to keep the core Liquid code small and tidy)
* Features that can easily be implemented on top of Liquid (for example as a custom filter or custom filesystem)
* Code that does not include tests
* Code that breaks existing tests
* Documentation changes that are verbose, incorrect or not important to most people (we want to keep it simple and easy to understand)
## Workflow
* [Sign the CLA](https://cla.shopify.com/) if you haven't already
* Fork the Liquid repository
* Create a new branch in your fork
* For updating [Liquid documentation](https://shopify.github.io/liquid/), create it from `gh-pages` branch. (You can skip tests.)
* If it makes sense, add tests for your code and/or run a performance benchmark
* Make sure all tests pass (`bundle exec rake`)
* Create a pull request
## Releasing
* Bump the version in `lib/liquid/version.rb`
* Update the `History.md` file
* Open a PR like [this one](https://github.com/Shopify/liquid/pull/1894) and merge it to `main`
* Create a new release using the [GitHub UI](https://github.com/Shopify/liquid/releases/new)
================================================
FILE: Gemfile
================================================
# frozen_string_literal: true
source 'https://rubygems.org'
git_source(:github) do |repo_name|
"https://github.com/#{repo_name}.git"
end
gemspec
gem "base64"
group :benchmark, :test do
gem 'benchmark-ips'
gem 'memory_profiler'
gem 'terminal-table'
gem "lru_redux"
install_if -> { RUBY_PLATFORM !~ /mingw|mswin|java/ && RUBY_ENGINE != 'truffleruby' } do
gem 'stackprof'
end
end
group :development do
gem "webrick"
end
group :test do
gem 'benchmark'
gem 'rubocop', '~> 1.82.0'
gem 'rubocop-shopify', '~> 2.18.0', require: false
gem 'rubocop-performance', require: false
end
group :spec do
gem 'liquid-spec', github: 'Shopify/liquid-spec', branch: 'main'
gem 'activesupport', require: false
end
================================================
FILE: History.md
================================================
# Liquid Change Log
## 5.11.0
* 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]
* Rename the `:rigid` error mode to `:strict2` and display a warning when users attempt to use the `:rigid` mode [Guilherme Carreiro]
## 5.10.0
* Introduce support for Inline Snippets [Julia Boutin]
## 5.9.0
* Introduce `:rigid` error mode for stricter, safer parsing of all tags [CP Clermont, Guilherme Carreiro]
## 5.8.7
* Expose body content in the `Doc` tag [James Meng]
## 5.8.1
* Fix `{% doc %}` tag to be visitable [Guilherme Carreiro]
## 5.8.0
* Introduce the new `{% doc %}` tag [Guilherme Carreiro]
## 5.7.3
* Raise Liquid::SyntaxError when parsing invalidly encoded strings [Chris AtLee]
## 5.7.2 2025-01-31
* Fix array filters to not support nested properties [Guilherme Carreiro]
## 5.7.1 2025-01-24
* Fix the `find` and `find_index`filters to return `nil` when filtering empty arrays [Guilherme Carreiro]
* Fix the `has` filter to return `false` when filtering empty arrays [Guilherme Carreiro]
## 5.7.0 2025-01-16
### Features
* Add `find`, `find_index`, `has`, and `reject` filters to arrays [Guilherme Carreiro]
* Compatibility with Ruby 3.4 [Ian Ker-Seymer]
## 5.6.4 2025-01-14
### Fixes
* Add a default `string_scanner` to avoid errors with `Liquid::VariableLookup.parse("foo.bar")` [Ian Ker-Seymer]
## 5.6.3 2025-01-13
* Remove `lru_redux` dependency [Michael Go]
## 5.6.2 2025-01-13
### Fixes
* Preserve the old behavior of requiring floats to start with a digit [Michael Go]
## 5.6.1 2025-01-13
### Performance improvements
* Faster Expression parser / Tokenizer with StringScanner [Michael Go]
## 5.6.0 2024-12-19
### Architectural changes
* Added new `Environment` class to manage configuration and state that was previously stored in `Template` [Ian Ker-Seymer]
* Moved tag registration from `Template` to `Environment` [Ian Ker-Seymer]
* Removed `StrainerFactory` in favor of `Environment`-based strainer creation [Ian Ker-Seymer]
* Consolidated standard tags into a new `Tags` module with `STANDARD_TAGS` constant [Ian Ker-Seymer]
### Performance improvements
* Optimized `Lexer` with a new `Lexer2` implementation using jump tables for faster tokenization, requires Ruby 3.4 [Ian Ker-Seymer]
* Improved variable rendering with specialized handling for different types [Michael Go]
* Reduced array allocations by using frozen empty constants [Michael Go]
### API changes
* Deprecated several `Template` class methods in favor of `Environment` methods [Ian Ker-Seymer]
* Added deprecation warnings system [Ian Ker-Seymer]
* Changed how filters and tags are registered to use Environment [Ian Ker-Seymer]
### Fixes
* Fixed table row handling of break interrupts [Alex Coco]
* Improved variable output handling for arrays [Ian Ker-Seymer]
* Fix Tokenizer to handle null source value (#1873) [Bahar Pourazar]
## 5.5.0 2024-03-21
Please reference the GitHub release for more information.
## 5.4.0 2022-07-29
### Breaking Changes
* Drop support for end-of-life Ruby versions (2.5 and 2.6) (#1578) [Andy Waite]
### Features
* Allow `#` to be used as an inline comment tag (#1498) [CP Clermont]
### Fixes
* `PartialCache` now shares snippet cache with subcontexts by default (#1553) [Chris AtLee]
* Hash registers no longer leak into subcontexts as static registers (#1564) [Chris AtLee]
* Fix `ParseTreeVisitor` for `with` variable expressions in `Render` tag (#1596) [CP Clermont]
### Changed
* Liquid::Context#registers now always returns a Liquid::Registers object, though supports the most used Hash functions for compatibility (#1553)
## 5.3.0 2022-03-22
### Fixes
* StandardFilter: Fix missing @context on iterations (#1525) [Thierry Joyal]
* Fix warning about block and default value in `static_registers.rb` (#1531) [Peter Zhu]
### Deprecation
* Condition#evaluate to require mandatory context argument in Liquid 6.0.0 (#1527) [Thierry Joyal]
## 5.2.0 2022-03-01
### Features
* Add `remove_last`, and `replace_last` filters (#1422) [Anders Hagbard]
* Eagerly cache global filters (#1524) [Jean Boussier]
### Fixes
* Fix some internal errors in filters from invalid input (#1476) [Dylan Thacker-Smith]
* Allow dash in filter kwarg name for consistency with Liquid::C (#1518) [CP Clermont]
## 5.1.0 / 2021-09-09
### Features
* Add `base64_encode`, `base64_decode`, `base64_url_safe_encode`, and `base64_url_safe_decode` filters (#1450) [Daniel Insley]
* Introduce `to_liquid_value` in `Liquid::Drop` (#1441) [Michael Go]
### Fixes
* Fix support for using a String subclass for the liquid source (#1421) [Dylan Thacker-Smith]
* Add `ParseTreeVisitor` to `RangeLookup` (#1470) [CP Clermont]
* Translate `RangeError` to `Liquid::Error` for `truncatewords` with large int (#1431) [Dylan Thacker-Smith]
## 5.0.1 / 2021-03-24
### Fixes
* Add ParseTreeVisitor to Echo tag (#1414) [CP Clermont]
* Test with ruby 3.0 as the latest ruby version (#1398) [Dylan Thacker-Smith]
* Handle carriage return in newlines_to_br (#1391) [Unending]
### Performance Improvements
* Use split limit in truncatewords (#1361) [Dylan Thacker-Smith]
## 5.0.0 / 2021-01-06
### Features
* Add new `{% render %}` tag (#1122) [Samuel Doiron]
* Add support for `as` in `{% render %}` and `{% include %}` (#1181) [Mike Angell]
* Add `{% liquid %}` and `{% echo %}` tags (#1086) [Justin Li]
* Add [usage tracking](README.md#usage-tracking) [Mike Angell]
* Add `Tag.disable_tags` for disabling tags that prepend `Tag::Disableable` at render time (#1162, #1274, #1275) [Mike Angell]
* Support using a profiler for multiple renders (#1365, #1366) [Dylan Thacker-Smith]
### Fixes
* Fix catastrophic backtracking in `RANGES_REGEX` regular expression (#1357) [Dylan Thacker-Smith]
* Make sure the for tag's limit and offset are integers (#1094) [David Cornu]
* Invokable methods for enumerable reject include (#1151) [Thierry Joyal]
* Allow `default` filter to handle `false` as value (#1144) [Mike Angell]
* Fix render length resource limit so it doesn't multiply nested output (#1285) [Dylan Thacker-Smith]
* Fix duplication of text in raw tags (#1304) [Peter Zhu]
* Fix strict parsing of find variable with a name expression (#1317) [Dylan Thacker-Smith]
* Use monotonic time to measure durations in Liquid::Profiler (#1362) [Dylan Thacker-Smith]
### Breaking Changes
* Require Ruby >= 2.5 (#1131, #1310) [Mike Angell, Dylan Thacker-Smith]
* Remove support for taint checking (#1268) [Dylan Thacker-Smith]
* Split Strainer class into StrainerFactory and StrainerTemplate (#1208) [Thierry Joyal]
* Remove handling of a nil context in the Strainer class (#1218) [Thierry Joyal]
* Handle `BlockBody#blank?` at parse time (#1287) [Dylan Thacker-Smith]
* Pass the tag markup and tokenizer to `Document#unknown_tag` (#1290) [Dylan Thacker-Smith]
* And several internal changes
### Performance Improvements
* Reduce allocations (#1073, #1091, #1115, #1099, #1117, #1141, #1322, #1341) [Richard Monette, Florian Weingarten, Ashwin Maroli]
* Improve resources limits performance (#1093, #1323) [Florian Weingarten, Dylan Thacker-Smith]
## 4.0.3 / 2019-03-12
### Fixed
* Fix break and continue tags inside included templates in loops (#1072) [Justin Li]
## 4.0.2 / 2019-03-08
### Changed
* Add `where` filter (#1026) [Samuel Doiron]
* Add `ParseTreeVisitor` to iterate the Liquid AST (#1025) [Stephen Paul Weber]
* Improve `strip_html` performance (#1032) [printercu]
### Fixed
* Add error checking for invalid combinations of inputs to sort, sort_natural, where, uniq, map, compact filters (#1059) [Garland Zhang]
* Validate the character encoding in url_decode (#1070) [Clayton Smith]
## 4.0.1 / 2018-10-09
### Changed
* Add benchmark group in Gemfile (#855) [Jerry Liu]
* Allow benchmarks to benchmark render by itself (#851) [Jerry Liu]
* Avoid calling `line_number` on String node when rescuing a render error. (#860) [Dylan Thacker-Smith]
* Avoid duck typing to detect whether to call render on a node. [Dylan Thacker-Smith]
* Clarify spelling of `reversed` on `for` block tag (#843) [Mark Crossfield]
* Replace recursion with loop to avoid potential stack overflow from malicious input (#891, #892) [Dylan Thacker-Smith]
* Limit block tag nesting to 100 (#894) [Dylan Thacker-Smith]
* Replace `assert_equal nil` with `assert_nil` (#895) [Dylan Thacker-Smith]
* Remove Spy Gem (#896) [Dylan Thacker-Smith]
* Add `collection_name` and `variable_name` reader to `For` block (#909)
* Symbols render as strings (#920) [Justin Li]
* Remove default value from Hash objects (#932) [Maxime Bedard]
* Remove one level of nesting (#944) [Dylan Thacker-Smith]
* Update Rubocop version (#952) [Justin Li]
* Add `at_least` and `at_most` filters (#954, #958) [Nithin Bekal]
* Add a regression test for a liquid-c trim mode bug (#972) [Dylan Thacker-Smith]
* Use https rather than git protocol to fetch liquid-c [Dylan Thacker-Smith]
* Add tests against Ruby 2.4 (#963) and 2.5 (#981)
* Replace RegExp literals with constants (#988) [Ashwin Maroli]
* Replace unnecessary `#each_with_index` with `#each` (#992) [Ashwin Maroli]
* Improve the unexpected end delimiter message for block tags. (#1003) [Dylan Thacker-Smith]
* Refactor and optimize rendering (#1005) [Christopher Aue]
* Add installation instruction (#1006) [Ben Gift]
* Remove Circle CI (#1010)
* Rename deprecated `BigDecimal.new` to `BigDecimal` (#1024) [Koichi ITO]
* Rename deprecated Rubocop name (#1027) [Justin Li]
### Fixed
* Handle `join` filter on non String joiners (#857) [Richard Monette]
* Fix duplicate inclusion condition logic error of `Liquid::Strainer.add_filter` method (#861)
* Fix `escape`, `url_encode`, `url_decode` not handling non-string values (#898) [Thierry Joyal]
* Fix raise when variable is defined but nil when using `strict_variables` [Pascal Betz]
* Fix `sort` and `sort_natural` to handle arrays with nils (#930) [Eric Chan]
## 4.0.0 / 2016-12-14 / branch "4-0-stable"
### Changed
* Render an opaque internal error by default for non-Liquid::Error (#835) [Dylan Thacker-Smith]
* Ruby 2.0 support dropped (#832) [Dylan Thacker-Smith]
* Add to_number Drop method to allow custom drops to work with number filters (#731)
* Add strict_variables and strict_filters options to detect undefined references (#691)
* Improve loop performance (#681) [Florian Weingarten]
* Rename Drop method `before_method` to `liquid_method_missing` (#661) [Thierry Joyal]
* Add url_decode filter to invert url_encode (#645) [Larry Archer]
* Add global_filter to apply a filter to all output (#610) [Loren Hale]
* Add compact filter (#600) [Carson Reinke]
* Rename deprecated "has_key?" and "has_interrupt?" methods (#593) [Florian Weingarten]
* Include template name with line numbers in render errors (574) [Dylan Thacker-Smith]
* Add sort_natural filter (#554) [Martin Hanzel]
* Add forloop.parentloop as a reference to the parent loop (#520) [Justin Li]
* Block parsing moved to BlockBody class (#458) [Dylan Thacker-Smith]
* Add concat filter to concatenate arrays (#429) [Diogo Beato]
* Ruby 1.9 support dropped (#491) [Justin Li]
* Liquid::Template.file_system's read_template_file method is no longer passed the context. (#441) [James Reid-Smith]
* Remove `liquid_methods` (See https://github.com/Shopify/liquid/pull/568 for replacement)
* Liquid::Template.register_filter raises when the module overrides registered public methods as private or protected (#705) [Gaurav Chande]
### Fixed
* Fix variable names being detected as an operator when starting with contains (#788) [Michael Angell]
* Fix include tag used with strict_variables (#828) [QuickPay]
* Fix map filter when value is a Proc (#672) [Guillaume Malette]
* Fix truncate filter when value is not a string (#672) [Guillaume Malette]
* Fix behaviour of escape filter when input is nil (#665) [Tanel Jakobsoo]
* Fix sort filter behaviour with empty array input (#652) [Marcel Cary]
* Fix test failure under certain timezones (#631) [Dylan Thacker-Smith]
* Fix bug in uniq filter (#595) [Florian Weingarten]
* Fix bug when "blank" and "empty" are used as variable names (#592) [Florian Weingarten]
* Fix condition parse order in strict mode (#569) [Justin Li]
* Fix naming of the "context variable" when dynamically including a template (#559) [Justin Li]
* Gracefully accept empty strings in the date filter (#555) [Loren Hale]
* Fix capturing into variables with a hyphen in the name (#505) [Florian Weingarten]
* Fix case sensitivity regression in date standard filter (#499) [Kelley Reynolds]
* Disallow filters with no variable in strict mode (#475) [Justin Li]
* Disallow variable names in the strict parser that are not valid in the lax parser (#463) [Justin Li]
* Fix BlockBody#warnings taking exponential time to compute (#486) [Justin Li]
## 3.0.5 / 2015-07-23 / branch "3-0-stable"
* Fix test failure under certain timezones [Dylan Thacker-Smith]
## 3.0.4 / 2015-07-17
* Fix chained access to multi-dimensional hashes [Florian Weingarten]
## 3.0.3 / 2015-05-28
* Fix condition parse order in strict mode (#569) [Justin Li]
## 3.0.2 / 2015-04-24
* Expose VariableLookup private members (#551) [Justin Li]
* Documentation fixes
## 3.0.1 / 2015-01-23
* Remove duplicate `index0` key in TableRow tag (#502) [Alfred Xing]
## 3.0.0 / 2014-11-12
* Removed Block#end_tag. Instead, override parse with `super` followed by your code. See #446 [Dylan Thacker-Smith]
* Fixed condition with wrong data types (#423) [Bogdan Gusiev]
* Add url_encode to standard filters (#421) [Derrick Reimer]
* Add uniq to standard filters [Florian Weingarten]
* Add exception_handler feature (#397) and #254 [Bogdan Gusiev, Florian Weingarten]
* Optimize variable parsing to avoid repeated regex evaluation during template rendering #383 [Jason Hiltz-Laforge]
* Optimize checking for block interrupts to reduce object allocation #380 [Jason Hiltz-Laforge]
* Properly set context rethrow_errors on render! #349 [Thierry Joyal]
* Fix broken rendering of variables which are equal to false (#345) [Florian Weingarten]
* Remove ActionView template handler [Dylan Thacker-Smith]
* Freeze lots of string literals for new Ruby 2.1 optimization (#297) [Florian Weingarten]
* Allow newlines in tags and variables (#324) [Dylan Thacker-Smith]
* Tag#parse is called after initialize, which now takes options instead of tokens as the 3rd argument. See #321 [Dylan Thacker-Smith]
* Raise `Liquid::ArgumentError` instead of `::ArgumentError` when filter has wrong number of arguments #309 [Bogdan Gusiev]
* Add a to_s default for liquid drops (#306) [Adam Doeler]
* Add strip, lstrip, and rstrip to standard filters [Florian Weingarten]
* Make if, for & case tags return complete and consistent nodelists (#250) [Nick Jones]
* Prevent arbitrary method invocation on condition objects (#274) [Dylan Thacker-Smith]
* Don't call to_sym when creating conditions for security reasons (#273) [Bouke van der Bijl]
* Fix resource counting bug with respond_to?(:length) (#263) [Florian Weingarten]
* Allow specifying custom patterns for template filenames (#284) [Andrei Gladkyi]
* Allow drops to optimize loading a slice of elements (#282) [Tom Burns]
* Support for passing variables to snippets in subdirs (#271) [Joost Hietbrink]
* Add a class cache to avoid runtime extend calls (#249) [James Tucker]
* Remove some legacy Ruby 1.8 compatibility code (#276) [Florian Weingarten]
* Add default filter to standard filters (#267) [Derrick Reimer]
* Add optional strict parsing and warn parsing (#235) [Tristan Hume]
* Add I18n syntax error translation (#241) [Simon Hørup Eskildsen, Sirupsen]
* Make sort filter work on enumerable drops (#239) [Florian Weingarten]
* Fix clashing method names in enumerable drops (#238) [Florian Weingarten]
* Make map filter work on enumerable drops (#233) [Florian Weingarten]
* Improved whitespace stripping for blank blocks, related to #216 [Florian Weingarten]
## 2.6.3 / 2015-07-23 / branch "2-6-stable"
* Fix test failure under certain timezones [Dylan Thacker-Smith]
## 2.6.2 / 2015-01-23
* Remove duplicate hash key [Parker Moore]
## 2.6.1 / 2014-01-10
Security fix, cherry-picked from master (4e14a65):
* Don't call to_sym when creating conditions for security reasons (#273) [Bouke van der Bijl]
* Prevent arbitrary method invocation on condition objects (#274) [Dylan Thacker-Smith]
## 2.6.0 / 2013-11-25
IMPORTANT: Liquid 2.6 is going to be the last version of Liquid which maintains explicit Ruby 1.8 compatability.
The following releases will only be tested against Ruby 1.9 and Ruby 2.0 and are likely to break on Ruby 1.8.
* Bugfix for #106: fix example servlet [gnowoel]
* Bugfix for #97: strip_html filter supports multi-line tags [Jo Liss]
* Bugfix for #114: strip_html filter supports style tags [James Allardice]
* Bugfix for #117: 'now' support for date filter in Ruby 1.9 [Notre Dame Webgroup]
* Bugfix for #166: truncate filter on UTF-8 strings with Ruby 1.8 [Florian Weingarten]
* Bugfix for #204: 'raw' parsing bug [Florian Weingarten]
* Bugfix for #150: 'for' parsing bug [Peter Schröder]
* Bugfix for #126: Strip CRLF in strip_newline [Peter Schröder]
* Bugfix for #174, "can't convert Fixnum into String" for "replace" [jsw0528]
* Allow a Liquid::Drop to be passed into Template#render [Daniel Huckstep]
* Resource limits [Florian Weingarten]
* Add reverse filter [Jay Strybis]
* Add utf-8 support
* Use array instead of Hash to keep the registered filters [Tasos Stathopoulos]
* Cache tokenized partial templates [Tom Burns]
* Avoid warnings in Ruby 1.9.3 [Marcus Stollsteimer]
* Better documentation for 'include' tag (closes #163) [Peter Schröder]
* Use of BigDecimal on filters to have better precision (closes #155) [Arthur Nogueira Neves]
## 2.5.5 / 2014-01-10 / branch "2-5-stable"
Security fix, cherry-picked from master (4e14a65):
* Don't call to_sym when creating conditions for security reasons (#273) [Bouke van der Bijl]
* Prevent arbitrary method invocation on condition objects (#274) [Dylan Thacker-Smith]
## 2.5.4 / 2013-11-11
* Fix "can't convert Fixnum into String" for "replace" (#173), [jsw0528]
## 2.5.3 / 2013-10-09
* #232, #234, #237: Fix map filter bugs [Florian Weingarten]
## 2.5.2 / 2013-09-03 / deleted
Yanked from rubygems, as it contained too many changes that broke compatibility. Those changes will be on following major releases.
## 2.5.1 / 2013-07-24
* #230: Fix security issue with map filter, Use invoke_drop in map filter [Florian Weingarten]
## 2.5.0 / 2013-03-06
* Prevent Object methods from being called on drops
* Avoid symbol injection from liquid
* Added break and continue statements
* Fix filter parser for args without space separators
* Add support for filter keyword arguments
## 2.4.0 / 2012-08-03
* Performance improvements
* Allow filters in `assign`
* Add `modulo` filter
* Ruby 1.8, 1.9, and Rubinius compatibility fixes
* Add support for `quoted['references']` in `tablerow`
* Add support for Enumerable to `tablerow`
* `strip_html` filter removes html comments
## 2.3.0 / 2011-10-16
* Several speed/memory improvements
* Numerous bug fixes
* Added support for MRI 1.9, Rubinius, and JRuby
* Added support for integer drop parameters
* Added epoch support to `date` filter
* New `raw` tag that suppresses parsing
* Added `else` option to `for` tag
* New `increment` tag
* New `split` filter
## 2.2.1 / 2010-08-23
* Added support for literal tags
## 2.2.0 / 2010-08-22
* Compatible with Ruby 1.8.7, 1.9.1 and 1.9.2-p0
* Merged some changed made by the community
## 1.9.0 / 2008-03-04
* Fixed gem install rake task
* Improve Error encapsulation in liquid by maintaining a own set of exceptions instead of relying on ruby build ins
## Before 1.9.0
* Added If with or / and expressions
* 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.
* Added more tags to standard library
* Added include tag ( like partials in rails )
* [...] Gazillion of detail improvements
* Added strainers as filter hosts for better security [Tobias Luetke]
* Fixed that rails integration would call filter with the wrong "self" [Michael Geary]
* 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]
* Removed count helper from standard lib. use size [Tobias Luetke]
* Fixed bug with string filter parameters failing to tolerate commas in strings. [Paul Hammond]
* 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]
{{ 'Typo' | link_to: 'http://typo.leetsoft.com', 'Typo - a modern weblog engine' }}
* 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]
class ProductDrop < Liquid::Drop
def top_sales
Shop.current.products.find(:all, :order => 'sales', :limit => 10 )
end
end
t = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {% endfor %} ' )
t.render('product' => ProductDrop.new )
* Added filter parameters support. Example: {{ date | format_date: "%Y" }} [Paul Hammond]
================================================
FILE: LICENSE
================================================
Copyright (c) 2005, 2006 Tobias Luetke
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
[](https://github.com/Shopify/liquid/actions/workflows/liquid.yml)
[](http://inch-ci.org/github/Shopify/liquid)
# Liquid template engine
* [Contributing guidelines](CONTRIBUTING.md)
* [Version history](History.md)
* [Liquid documentation from Shopify](https://shopify.dev/docs/api/liquid)
* [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki)
* [Website](http://liquidmarkup.org/)
## Introduction
Liquid is a template engine which was written with very specific requirements:
* It has to have beautiful and simple markup. Template engines which don't produce good looking markup are no fun to use.
* 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.
* 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.
## Why you should use Liquid
* 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**.
* You want to render templates directly from the database.
* You like smarty (PHP) style template engines.
* You need a template engine which does HTML just as well as emails.
* You don't like the markup of your current templating engine.
## What does it look like?
```html
<ul id="products">
{% for product in products %}
<li>
<h2>{{ product.name }}</h2>
Only {{ product.price | price }}
{{ product.description | prettyprint | paragraph }}
</li>
{% endfor %}
</ul>
```
## How to use Liquid
Install Liquid by adding `gem 'liquid'` to your gemfile.
Liquid supports a very simple API based around the Liquid::Template class.
For standard use you can just pass it the content of a file and call render with a parameters hash.
```ruby
@template = Liquid::Template.parse("hi {{name}}") # Parses and compiles the template
@template.render('name' => 'tobi') # => "hi tobi"
```
### Concept of Environments
In 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.
By using environments, you can:
1. **Encapsulate Logic**: Keep the logic for different parts of your application separate.
2. **Avoid Conflicts**: Prevent custom tags and filters from clashing with each other.
3. **Improve Maintainability**: Make it easier to manage and understand the scope of customizations.
4. **Enhance Security**: Limit the availability of certain tags and filters to specific contexts.
We encourage the use of Environments over globally overriding things because it promotes better software design principles such as modularity, encapsulation, and separation of concerns.
Here's an example of how you can define and use Environments in Liquid:
```ruby
user_environment = Liquid::Environment.build do |environment|
environment.register_tag("renderobj", RenderObjTag)
end
Liquid::Template.parse(<<~LIQUID, environment: user_environment)
{% renderobj src: "path/to/model.obj" %}
LIQUID
```
In this example, `RenderObjTag` is a custom tag that is only available within the `user_environment`.
Similarly, you can define another environment for a different context, such as email templates:
```ruby
email_environment = Liquid::Environment.build do |environment|
environment.register_tag("unsubscribe_footer", UnsubscribeFooter)
end
Liquid::Template.parse(<<~LIQUID, environment: email_environment)
{% unsubscribe_footer %}
LIQUID
```
By 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`.
### Error Modes
Setting the error mode of Liquid lets you specify how strictly you want your templates to be interpreted.
Normally the parser is very lax and will accept almost anything without error. Unfortunately this can make
it very hard to debug and can lead to unexpected behaviour.
Liquid also comes with different parsers that can be used when editing templates to give better error messages
when templates are invalid. You can enable this new parser like this:
```ruby
Liquid::Environment.default.error_mode = :strict2 # Raises a SyntaxError when invalid syntax is used in all tags
Liquid::Environment.default.error_mode = :strict # Raises a SyntaxError when invalid syntax is used in some tags
Liquid::Environment.default.error_mode = :warn # Adds strict errors to template.errors but continues as normal
Liquid::Environment.default.error_mode = :lax # The default mode, accepts almost anything.
```
If you want to set the error mode only on specific templates you can pass `:error_mode` as an option to `parse`:
```ruby
Liquid::Template.parse(source, error_mode: :strict)
```
This is useful for doing things like enabling strict mode only in the theme editor.
It is recommended that you enable `:strict` or `:warn` mode on new apps to stop invalid templates from being created.
It is also recommended that you use it in the template editors of existing apps to give editors better error messages.
### Undefined variables and filters
By 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.
You can improve this situation by passing `strict_variables: true` and/or `strict_filters: true` options to the `render` method.
When 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.
Here are some examples:
```ruby
template = Liquid::Template.parse("{{x}} {{y}} {{z.a}} {{z.b}}")
template.render({ 'x' => 1, 'z' => { 'a' => 2 } }, { strict_variables: true })
#=> '1 2 ' # when a variable is undefined, it's rendered as nil
template.errors
#=> [#<Liquid::UndefinedVariable: Liquid error: undefined variable y>, #<Liquid::UndefinedVariable: Liquid error: undefined variable b>]
```
```ruby
template = Liquid::Template.parse("{{x | filter1 | upcase}}")
template.render({ 'x' => 'foo' }, { strict_filters: true })
#=> '' # when at least one filter in the filter chain is undefined, a whole expression is rendered as nil
template.errors
#=> [#<Liquid::UndefinedFilter: Liquid error: undefined filter filter1>]
```
If you want to raise on a first exception instead of pushing all of them in `errors`, you can use `render!` method:
```ruby
template = Liquid::Template.parse("{{x}} {{y}}")
template.render!({ 'x' => 1}, { strict_variables: true })
#=> Liquid::UndefinedVariable: Liquid error: undefined variable y
```
### Usage tracking
To 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.
Once 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.
================================================
FILE: Rakefile
================================================
# frozen_string_literal: true
require 'rake'
require 'rake/testtask'
$LOAD_PATH.unshift(File.expand_path("../lib", __FILE__))
require "liquid/version"
task(default: [:test, :rubocop])
desc('run test suite with default parser')
Rake::TestTask.new(:base_test) do |t|
t.libs << 'lib' << 'test'
t.test_files = FileList['test/{integration,unit}/**/*_test.rb']
t.verbose = false
end
Rake::TestTask.new(:integration_test) do |t|
t.libs << 'lib' << 'test'
t.test_files = FileList['test/integration/**/*_test.rb']
t.verbose = false
end
desc('run test suite with warn error mode')
task :warn_test do
ENV['LIQUID_PARSER_MODE'] = 'warn'
Rake::Task['base_test'].invoke
end
task :rubocop do
if RUBY_ENGINE == 'ruby'
require 'rubocop/rake_task'
RuboCop::RakeTask.new
end
end
desc('runs test suite with lax, strict, and strict2 parsers')
task :test do
ENV['LIQUID_PARSER_MODE'] = 'lax'
Rake::Task['base_test'].invoke
ENV['LIQUID_PARSER_MODE'] = 'strict'
Rake::Task['base_test'].reenable
Rake::Task['base_test'].invoke
ENV['LIQUID_PARSER_MODE'] = 'strict2'
Rake::Task['base_test'].reenable
Rake::Task['base_test'].invoke
if RUBY_ENGINE == 'ruby' || RUBY_ENGINE == 'truffleruby'
ENV['LIQUID_PARSER_MODE'] = 'lax'
Rake::Task['integration_test'].reenable
Rake::Task['integration_test'].invoke
ENV['LIQUID_PARSER_MODE'] = 'strict'
Rake::Task['integration_test'].reenable
Rake::Task['integration_test'].invoke
ENV['LIQUID_PARSER_MODE'] = 'strict2'
Rake::Task['integration_test'].reenable
Rake::Task['integration_test'].invoke
end
end
task(gem: :build)
task :build do
system "gem build liquid.gemspec"
end
task install: :build do
system "gem install liquid-#{Liquid::VERSION}.gem"
end
task release: :build do
system "git tag -a v#{Liquid::VERSION} -m 'Tagging #{Liquid::VERSION}'"
system "git push --tags"
system "gem push liquid-#{Liquid::VERSION}.gem"
system "rm liquid-#{Liquid::VERSION}.gem"
end
namespace :benchmark do
desc "Run the liquid benchmark with lax parsing"
task :lax do
ruby "./performance/benchmark.rb lax"
end
desc "Run the liquid benchmark with strict parsing"
task :strict do
ruby "./performance/benchmark.rb strict"
end
desc "Run the liquid benchmark with strict2 parsing"
task :strict2 do
ruby "./performance/benchmark.rb strict2"
end
desc "Run the liquid benchmark with lax, strict, and strict2 parsing"
task run: [:lax, :strict, :strict2]
desc "Run unit benchmarks"
namespace :unit do
task :all do
Dir["./performance/unit/*_benchmark.rb"].each do |file|
puts "🧪 Running #{file}"
ruby file
end
end
task :lexer do
Dir["./performance/unit/lexer_benchmark.rb"].each do |file|
puts "🧪 Running #{file}"
ruby file
end
end
task :expression do
Dir["./performance/unit/expression_benchmark.rb"].each do |file|
puts "🧪 Running #{file}"
ruby file
end
end
end
end
namespace :profile do
desc "Run the liquid profile/performance coverage"
task :run do
ruby "./performance/profile.rb"
end
desc "Run the liquid profile/performance coverage with strict parsing"
task :strict do
ruby "./performance/profile.rb strict"
end
end
namespace :memory_profile do
desc "Run memory profiler"
task :run do
ruby "./performance/memory_profile.rb"
end
end
desc("Run example")
task :example do
ruby "-w -d -Ilib example/server/server.rb"
end
task :console do
exec 'irb -I lib -r liquid'
end
desc('run liquid-spec suite across all adapters')
task :spec do
adapters = Dir['./spec/*.rb'].join(',')
sh "bundle exec liquid-spec matrix --adapters=#{adapters} --reference=ruby_liquid"
end
================================================
FILE: bin/render
================================================
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'bundler/setup'
require 'liquid'
class VirtualFileSystem
def initialize
snippet_1 = <<~LIQUID
<h1>
{{- greating | default: 'Hello' }}, {{ name | default: 'world' -}}!
</h1>
LIQUID
snippet_2 = <<~LIQUID
{%- for i in (1..5) -%}
> {{ i }}
{%- endfor -%}
LIQUID
@templates = {
'snippet-1' => snippet_1,
'snippet-2' => snippet_2,
}
end
def read_template_file(key)
@templates[key] || raise(Liquid::FileSystemError, "No such template '#{key}'")
end
end
def source
File.read(ARGV[0])
rescue StandardError
'Usage: bin/render example/server/templates/index.liquid'
end
def assigns
{
'date' => Time.now,
}
end
puts Liquid::Template
.parse(source, error_mode: :strict2)
.tap { |t| t.registers[:file_system] = VirtualFileSystem.new }
.render(assigns)
================================================
FILE: example/server/example_servlet.rb
================================================
# frozen_string_literal: true
module ProductsFilter
def price(integer)
format("$%.2d USD", integer / 100.0)
end
def prettyprint(text)
text.gsub(/\*(.*)\*/, '<b>\1</b>')
end
def count(array)
array.size
end
def paragraph(p)
"<p>#{p}</p>"
end
end
class Servlet < LiquidServlet
def index
{ 'date' => Time.now }
end
def products
{ 'products' => products_list, 'more_products' => more_products_list, 'description' => description, 'section' => 'Snowboards', 'cool_products' => true }
end
private
def products_list
[
{ 'name' => 'Arbor Draft', 'price' => 39900, 'description' => 'the *arbor draft* is a excellent product' },
{ 'name' => 'Arbor Element', 'price' => 40000, 'description' => 'the *arbor element* rocks for freestyling' },
{ 'name' => 'Arbor Diamond', 'price' => 59900, 'description' => 'the *arbor diamond* is a made up product because im obsessed with arbor and have no creativity' }
]
end
def more_products_list
[
{ 'name' => 'Arbor Catalyst', 'price' => 39900, 'description' => 'the *arbor catalyst* is an advanced drop-through for freestyle and flatground performance and versatility' },
{ 'name' => 'Arbor Fish', 'price' => 40000, 'description' => 'the *arbor fish* is a compact pin that features an extended wheelbase and time-honored teardrop shape' }
]
end
def description
"List of Products ~ This is a list of products with price and description."
end
end
================================================
FILE: example/server/liquid_servlet.rb
================================================
# frozen_string_literal: true
class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
handle(:get, req, res)
end
def do_POST(req, res)
handle(:post, req, res)
end
private
def handle(_type, req, res)
@request = req
@response = res
@request.path_info =~ /(\w+)\z/
@action = Regexp.last_match(1) || 'index'
@assigns = send(@action) if respond_to?(@action)
@response['Content-Type'] = "text/html"
@response.status = 200
@response.body = Liquid::Template.parse(read_template).render(@assigns, filters: [ProductsFilter])
end
def read_template(filename = @action)
File.read("#{__dir__}/templates/#{filename}.liquid")
end
end
================================================
FILE: example/server/server.rb
================================================
# frozen_string_literal: true
require 'webrick'
require 'rexml/document'
require_relative '../../lib/liquid'
require_relative 'liquid_servlet'
require_relative 'example_servlet'
# Setup webrick
server = WEBrick::HTTPServer.new(Port: ARGV[1] || 3000)
server.mount('/', Servlet)
trap("INT") { server.shutdown }
server.start
================================================
FILE: example/server/templates/index.liquid
================================================
<p>Hello world!</p>
<p>It is {{date}}</p>
<p>Check out the <a href="/products">Products</a> screen </p>
================================================
FILE: example/server/templates/products.liquid
================================================
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<title>products</title>
<meta name="ROBOTS" content="ALL" />
<meta http-equiv="imagetoolbar" content="no" />
<meta name="MSSmartTagsPreventParsing" content="true" />
<meta name="Copyright" content="(c) 2005 Copyright content: Copyright design: Tobias Luetke" />
<!-- (c) Copyright 2005 by Tobias Luetke All Rights Reserved. -->
</head>
<body>
{% assign all_products = products | concat: more_products %}
<h1>{{ description | split: '~' | first }}</h1>
<h2>{{ description | split: '~' | last }}</h2>
<h2>There are currently {{all_products | count}} products in the {{section}} catalog</h2>
{% if cool_products %}
Cool products :)
{% else %}
Uncool products :(
{% endif %}
<ul id="products">
{% for product in all_products %}
<li>
<h2>{{product.name}}</h2>
Only {{product.price | price }}
{{product.description | prettyprint | paragraph }}
{{ 'it rocks!' | paragraph }}
</li>
{% endfor %}
</ul>
</body>
</html>
================================================
FILE: lib/liquid/block.rb
================================================
# frozen_string_literal: true
module Liquid
class Block < Tag
MAX_DEPTH = 100
def initialize(tag_name, markup, options)
super
@blank = true
end
def parse(tokens)
@body = new_body
while parse_body(@body, tokens)
end
@body.freeze
end
# For backwards compatibility
def render(context)
@body.render(context)
end
def blank?
@blank
end
def nodelist
@body.nodelist
end
def unknown_tag(tag_name, _markup, _tokenizer)
Block.raise_unknown_tag(tag_name, block_name, block_delimiter, parse_context)
end
# @api private
def self.raise_unknown_tag(tag, block_name, block_delimiter, parse_context)
if tag == 'else'
raise SyntaxError, parse_context.locale.t(
"errors.syntax.unexpected_else",
block_name: block_name,
)
elsif tag.start_with?('end')
raise SyntaxError, parse_context.locale.t(
"errors.syntax.invalid_delimiter",
tag: tag,
block_name: block_name,
block_delimiter: block_delimiter,
)
else
raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag", tag: tag)
end
end
def raise_tag_never_closed(block_name)
raise SyntaxError, parse_context.locale.t("errors.syntax.tag_never_closed", block_name: block_name)
end
def block_name
@tag_name
end
def block_delimiter
@block_delimiter ||= "end#{block_name}"
end
private
# @api public
def new_body
parse_context.new_block_body
end
# @api public
def parse_body(body, tokens)
if parse_context.depth >= MAX_DEPTH
raise StackLevelError, "Nesting too deep"
end
parse_context.depth += 1
begin
body.parse(tokens, parse_context) do |end_tag_name, end_tag_params|
@blank &&= body.blank?
return false if end_tag_name == block_delimiter
raise_tag_never_closed(block_name) unless end_tag_name
# this tag is not registered with the system
# pass it to the current block for special handling or error reporting
unknown_tag(end_tag_name, end_tag_params, tokens)
end
ensure
parse_context.depth -= 1
end
true
end
end
end
================================================
FILE: lib/liquid/block_body.rb
================================================
# frozen_string_literal: true
require 'English'
module Liquid
class BlockBody
LiquidTagToken = /\A\s*(#{TagName})\s*(.*?)\z/o
FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(#{TagName})(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}#{WhitespaceControl}?\s*(\w+)\s*(.*)?#{WhitespaceControl}?#{TagEnd}\z/om
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
WhitespaceOrNothing = /\A\s*\z/
TAGSTART = "{%"
VARSTART = "{{"
attr_reader :nodelist
def initialize
@nodelist = []
@blank = true
end
def parse(tokenizer, parse_context, &block)
raise FrozenError, "can't modify frozen Liquid::BlockBody" if frozen?
parse_context.line_number = tokenizer.line_number
if tokenizer.for_liquid_tag
parse_for_liquid_tag(tokenizer, parse_context, &block)
else
parse_for_document(tokenizer, parse_context, &block)
end
end
def freeze
@nodelist.freeze
super
end
private def parse_for_liquid_tag(tokenizer, parse_context)
while (token = tokenizer.shift)
unless token.empty? || token.match?(WhitespaceOrNothing)
unless token =~ LiquidTagToken
# line isn't empty but didn't match tag syntax, yield and let the
# caller raise a syntax error
return yield token, token
end
tag_name = Regexp.last_match(1)
markup = Regexp.last_match(2)
if tag_name == 'liquid'
parse_context.line_number -= 1
next parse_liquid_tag(markup, parse_context)
end
unless (tag = parse_context.environment.tag_for_name(tag_name))
# end parsing if we reach an unknown tag and let the caller decide
# determine how to proceed
return yield tag_name, markup
end
new_tag = tag.parse(tag_name, markup, tokenizer, parse_context)
@blank &&= new_tag.blank?
@nodelist << new_tag
end
parse_context.line_number = tokenizer.line_number
end
yield nil, nil
end
# @api private
def self.unknown_tag_in_liquid_tag(tag, parse_context)
Block.raise_unknown_tag(tag, 'liquid', '%}', parse_context)
end
# @api private
def self.raise_missing_tag_terminator(token, parse_context)
raise SyntaxError, parse_context.locale.t("errors.syntax.tag_termination", token: token, tag_end: TagEnd.inspect)
end
# @api private
def self.raise_missing_variable_terminator(token, parse_context)
raise SyntaxError, parse_context.locale.t("errors.syntax.variable_termination", token: token, tag_end: VariableEnd.inspect)
end
# @api private
def self.render_node(context, output, node)
node.render_to_output_buffer(context, output)
rescue => exc
blank_tag = !node.instance_of?(Variable) && node.blank?
rescue_render_node(context, output, node.line_number, exc, blank_tag)
end
# @api private
def self.rescue_render_node(context, output, line_number, exc, blank_tag)
case exc
when MemoryError
raise
when UndefinedVariable, UndefinedDropMethod, UndefinedFilter
context.handle_error(exc, line_number)
else
error_message = context.handle_error(exc, line_number)
unless blank_tag # conditional for backwards compatibility
output << error_message
end
end
end
private def parse_liquid_tag(markup, parse_context)
liquid_tag_tokenizer = parse_context.new_tokenizer(
markup, start_line_number: parse_context.line_number, for_liquid_tag: true
)
parse_for_liquid_tag(liquid_tag_tokenizer, parse_context) do |end_tag_name, _end_tag_markup|
if end_tag_name
BlockBody.unknown_tag_in_liquid_tag(end_tag_name, parse_context)
end
end
end
private def handle_invalid_tag_token(token, parse_context)
if token.end_with?('%}')
yield token, token
else
BlockBody.raise_missing_tag_terminator(token, parse_context)
end
end
private def parse_for_document(tokenizer, parse_context, &block)
while (token = tokenizer.shift)
next if token.empty?
case
when token.start_with?(TAGSTART)
whitespace_handler(token, parse_context)
unless token =~ FullToken
return handle_invalid_tag_token(token, parse_context, &block)
end
tag_name = Regexp.last_match(2)
markup = Regexp.last_match(4)
if parse_context.line_number
# newlines inside the tag should increase the line number,
# particularly important for multiline {% liquid %} tags
parse_context.line_number += Regexp.last_match(1).count("\n") + Regexp.last_match(3).count("\n")
end
if tag_name == 'liquid'
parse_liquid_tag(markup, parse_context)
next
end
unless (tag = parse_context.environment.tag_for_name(tag_name))
# end parsing if we reach an unknown tag and let the caller decide
# determine how to proceed
return yield tag_name, markup
end
new_tag = tag.parse(tag_name, markup, tokenizer, parse_context)
@blank &&= new_tag.blank?
@nodelist << new_tag
when token.start_with?(VARSTART)
whitespace_handler(token, parse_context)
@nodelist << create_variable(token, parse_context)
@blank = false
else
if parse_context.trim_whitespace
token.lstrip!
end
parse_context.trim_whitespace = false
@nodelist << token
@blank &&= token.match?(WhitespaceOrNothing)
end
parse_context.line_number = tokenizer.line_number
end
yield nil, nil
end
def whitespace_handler(token, parse_context)
if token[2] == WhitespaceControl
previous_token = @nodelist.last
if previous_token.is_a?(String)
first_byte = previous_token.getbyte(0)
previous_token.rstrip!
if previous_token.empty? && parse_context[:bug_compatible_whitespace_trimming] && first_byte
previous_token << first_byte
end
end
end
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
end
def blank?
@blank
end
# Remove blank strings in the block body for a control flow tag (e.g. `if`, `for`, `case`, `unless`)
# with a blank body.
#
# For example, in a conditional assignment like the following
#
# ```
# {% if size > max_size %}
# {% assign size = max_size %}
# {% endif %}
# ```
#
# we assume the intention wasn't to output the blank spaces in the `if` tag's block body, so this method
# will remove them to reduce the render output size.
#
# Note that it is now preferred to use the `liquid` tag for this use case.
def remove_blank_strings
raise "remove_blank_strings only support being called on a blank block body" unless @blank
@nodelist.reject! { |node| node.instance_of?(String) }
end
def render(context)
render_to_output_buffer(context, +'')
end
def render_to_output_buffer(context, output)
freeze unless frozen?
context.resource_limits.increment_render_score(@nodelist.length)
idx = 0
while (node = @nodelist[idx])
if node.instance_of?(String)
output << node
else
render_node(context, output, node)
# If we get an Interrupt that means the block must stop processing. An
# Interrupt is any command that stops block execution such as {% break %}
# or {% continue %}. These tags may also occur through Block or Include tags.
break if context.interrupt? # might have happened in a for-block
end
idx += 1
context.resource_limits.increment_write_score(output)
end
output
end
private
def render_node(context, output, node)
BlockBody.render_node(context, output, node)
end
def create_variable(token, parse_context)
if token.end_with?("}}")
i = 2
i = 3 if token[i] == "-"
parse_end = token.length - 3
parse_end -= 1 if token[parse_end] == "-"
markup_end = parse_end - i + 1
markup = markup_end <= 0 ? "" : token.slice(i, markup_end)
return Variable.new(markup, parse_context)
end
BlockBody.raise_missing_variable_terminator(token, parse_context)
end
# @deprecated Use {.raise_missing_tag_terminator} instead
def raise_missing_tag_terminator(token, parse_context)
BlockBody.raise_missing_tag_terminator(token, parse_context)
end
# @deprecated Use {.raise_missing_variable_terminator} instead
def raise_missing_variable_terminator(token, parse_context)
BlockBody.raise_missing_variable_terminator(token, parse_context)
end
end
end
================================================
FILE: lib/liquid/condition.rb
================================================
# frozen_string_literal: true
module Liquid
# Container for liquid nodes which conveniently wraps decision making logic
#
# Example:
#
# c = Condition.new(1, '==', 1)
# c.evaluate #=> true
#
class Condition # :nodoc:
@@operators = {
'==' => ->(cond, left, right) { cond.send(:equal_variables, left, right) },
'!=' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
'<>' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
'<' => :<,
'>' => :>,
'>=' => :>=,
'<=' => :<=,
'contains' => lambda do |_cond, left, right|
if left && right && left.respond_to?(:include?)
right = right.to_s if left.is_a?(String)
left.include?(right)
else
false
end
rescue Encoding::CompatibilityError
# "✅".b.include?("✅") raises Encoding::CompatibilityError despite being materially equal
left.b.include?(right.b)
end,
}
class MethodLiteral
attr_reader :method_name, :to_s
def initialize(method_name, to_s)
@method_name = method_name
@to_s = to_s
end
end
@@method_literals = {
'blank' => MethodLiteral.new(:blank?, '').freeze,
'empty' => MethodLiteral.new(:empty?, '').freeze,
}
def self.operators
@@operators
end
def self.parse_expression(parse_context, markup, safe: false)
@@method_literals[markup] || parse_context.parse_expression(markup, safe: safe)
end
attr_reader :attachment, :child_condition
attr_accessor :left, :operator, :right
def initialize(left = nil, operator = nil, right = nil)
@left = left
@operator = operator
@right = right
@child_relation = nil
@child_condition = nil
end
def evaluate(context = deprecated_default_context)
condition = self
result = nil
loop do
result = interpret_condition(condition.left, condition.right, condition.operator, context)
case condition.child_relation
when :or
break if Liquid::Utils.to_liquid_value(result)
when :and
break unless Liquid::Utils.to_liquid_value(result)
else
break
end
condition = condition.child_condition
end
result
end
def or(condition)
@child_relation = :or
@child_condition = condition
end
def and(condition)
@child_relation = :and
@child_condition = condition
end
def attach(attachment)
@attachment = attachment
end
def else?
false
end
def inspect
"#<Condition #{[@left, @operator, @right].compact.join(' ')}>"
end
protected
attr_reader :child_relation
private
def equal_variables(left, right)
if left.is_a?(MethodLiteral)
return call_method_literal(left, right)
end
if right.is_a?(MethodLiteral)
return call_method_literal(right, left)
end
left == right
end
def call_method_literal(literal, value)
method_name = literal.method_name
# If the object responds to the method (e.g., ActiveSupport is loaded), use it
if value.respond_to?(method_name)
value.send(method_name)
else
# Emulate ActiveSupport's blank?/empty? to make Liquid invariant
# to whether ActiveSupport is loaded or not
case method_name
when :blank?
liquid_blank?(value)
when :empty?
liquid_empty?(value)
else
false
end
end
end
# Implement blank? semantics matching ActiveSupport
# blank? returns true for nil, false, empty strings, whitespace-only strings,
# empty arrays, and empty hashes
def liquid_blank?(value)
case value
when NilClass, FalseClass
true
when TrueClass, Numeric
false
when String
# Blank if empty or whitespace only (matches ActiveSupport)
value.empty? || value.match?(/\A\s*\z/)
when Array, Hash
value.empty?
else
# Fall back to empty? if available, otherwise false
value.respond_to?(:empty?) ? value.empty? : false
end
end
# Implement empty? semantics
# Note: nil is NOT empty. empty? checks if a collection has zero elements.
def liquid_empty?(value)
case value
when String, Array, Hash
value.empty?
else
value.respond_to?(:empty?) ? value.empty? : false
end
end
def interpret_condition(left, right, op, context)
# If the operator is empty this means that the decision statement is just
# a single variable. We can just poll this variable from the context and
# return this as the result.
return context.evaluate(left) if op.nil?
left = Liquid::Utils.to_liquid_value(context.evaluate(left))
right = Liquid::Utils.to_liquid_value(context.evaluate(right))
operation = self.class.operators[op] || raise(Liquid::ArgumentError, "Unknown operator #{op}")
if operation.respond_to?(:call)
operation.call(self, left, right)
elsif left.respond_to?(operation) && right.respond_to?(operation) && !left.is_a?(Hash) && !right.is_a?(Hash)
begin
left.send(operation, right)
rescue ::ArgumentError => e
raise Liquid::ArgumentError, e.message
end
end
end
def deprecated_default_context
warn("DEPRECATION WARNING: Condition#evaluate without a context argument is deprecated " \
"and will be removed from Liquid 6.0.0.")
Context.new
end
class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children
[
@node.left,
@node.right,
@node.child_condition,
@node.attachment
].compact
end
end
end
class ElseCondition < Condition
def else?
true
end
def evaluate(_context)
true
end
end
end
================================================
FILE: lib/liquid/const.rb
================================================
# frozen_string_literal: true
module Liquid
module Const
EMPTY_HASH = {}.freeze
EMPTY_ARRAY = [].freeze
end
end
================================================
FILE: lib/liquid/context.rb
================================================
# frozen_string_literal: true
module Liquid
# Context keeps the variable stack and resolves variables, as well as keywords
#
# context['variable'] = 'testing'
# context['variable'] #=> 'testing'
# context['true'] #=> true
# context['10.2232'] #=> 10.2232
#
# context.stack do
# context['bob'] = 'bobsen'
# end
#
# context['bob'] #=> nil class Context
class Context
attr_reader :scopes, :errors, :registers, :environments, :resource_limits, :static_registers, :static_environments
attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters, :environment
# rubocop:disable Metrics/ParameterLists
def self.build(environment: Environment.default, environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {}, &block)
new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_environments, environment, &block)
end
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {}, environment = Environment.default)
@environment = environment
@environments = [environments]
@environments.flatten!
@static_environments = [static_environments].flatten(1).freeze
@scopes = [outer_scope || {}]
@registers = registers.is_a?(Registers) ? registers : Registers.new(registers)
@errors = []
@partial = false
@strict_variables = false
@resource_limits = resource_limits || ResourceLimits.new(environment.default_resource_limits)
@base_scope_depth = 0
@interrupts = []
@filters = []
@global_filter = nil
@disabled_tags = {}
# Instead of constructing new StringScanner objects for each Expression parse,
# we recycle the same one.
@string_scanner = StringScanner.new("")
@registers.static[:cached_partials] ||= {}
@registers.static[:file_system] ||= environment.file_system
@registers.static[:template_factory] ||= Liquid::TemplateFactory.new
self.exception_renderer = environment.exception_renderer
if rethrow_errors
self.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
end
yield self if block_given?
# Do this last, since it could result in this object being passed to a Proc in the environment
squash_instance_assigns_with_environments
end
# rubocop:enable Metrics/ParameterLists
def warnings
@warnings ||= []
end
def strainer
@strainer ||= @environment.create_strainer(self, @filters)
end
# Adds filters to this context.
#
# Note that this does not register the filters with the main Template object. see <tt>Template.register_filter</tt>
# for that
def add_filters(filters)
filters = [filters].flatten.compact
@filters += filters
@strainer = nil
end
def apply_global_filter(obj)
global_filter.nil? ? obj : global_filter.call(obj)
end
# are there any not handled interrupts?
def interrupt?
!@interrupts.empty?
end
# push an interrupt to the stack. this interrupt is considered not handled.
def push_interrupt(e)
@interrupts.push(e)
end
# pop an interrupt from the stack
def pop_interrupt
@interrupts.pop
end
def handle_error(e, line_number = nil)
e = internal_error unless e.is_a?(Liquid::Error)
e.template_name ||= template_name
e.line_number ||= line_number
errors.push(e)
exception_renderer.call(e).to_s
end
def invoke(method, *args)
strainer.invoke(method, *args).to_liquid
end
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
def push(new_scope = {})
@scopes.unshift(new_scope)
check_overflow
end
# Merge a hash of variables in the current local scope
def merge(new_scopes)
@scopes[0].merge!(new_scopes)
end
# Pop from the stack. use <tt>Context#stack</tt> instead
def pop
raise ContextError if @scopes.size == 1
@scopes.shift
end
# Pushes a new local scope on the stack, pops it at the end of the block
#
# Example:
# context.stack do
# context['var'] = 'hi'
# end
#
# context['var'] #=> nil
def stack(new_scope = {})
push(new_scope)
yield
ensure
pop
end
# Creates a new context inheriting resource limits, filters, environment etc.,
# but with an isolated scope.
def new_isolated_subcontext
check_overflow
self.class.build(
environment: @environment,
resource_limits: resource_limits,
static_environments: static_environments,
registers: Registers.new(registers),
).tap do |subcontext|
subcontext.base_scope_depth = base_scope_depth + 1
subcontext.exception_renderer = exception_renderer
subcontext.filters = @filters
subcontext.strainer = nil
subcontext.errors = errors
subcontext.warnings = warnings
subcontext.disabled_tags = @disabled_tags
end
end
def clear_instance_assigns
@scopes[0] = {}
end
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
def []=(key, value)
@scopes[0][key] = value
end
# Look up variable, either resolve directly after considering the name. We can directly handle
# Strings, digits, floats and booleans (true,false).
# If no match is made we lookup the variable in the current scope and
# later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
# Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
#
# Example:
# products == empty #=> products.empty?
def [](expression)
evaluate(Expression.parse(expression, @string_scanner))
end
def key?(key)
find_variable(key, raise_on_not_found: false) != nil
end
def evaluate(object)
object.respond_to?(:evaluate) ? object.evaluate(self) : object
end
# Fetches an object starting at the local scope and then moving up the hierachy
def find_variable(key, raise_on_not_found: true)
# This was changed from find() to find_index() because this is a very hot
# path and find_index() is optimized in MRI to reduce object allocation
index = @scopes.find_index { |s| s.key?(key) }
variable = if index
lookup_and_evaluate(@scopes[index], key, raise_on_not_found: raise_on_not_found)
else
try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found)
end
# update variable's context before invoking #to_liquid
variable.context = self if variable.respond_to?(:context=)
liquid_variable = variable.to_liquid
liquid_variable.context = self if variable != liquid_variable && liquid_variable.respond_to?(:context=)
liquid_variable
end
def lookup_and_evaluate(obj, key, raise_on_not_found: true)
if @strict_variables && raise_on_not_found && obj.respond_to?(:key?) && !obj.key?(key)
raise Liquid::UndefinedVariable, "undefined variable #{key}"
end
value = obj[key]
if value.is_a?(Proc) && obj.respond_to?(:[]=)
obj[key] = value.arity == 0 ? value.call : value.call(self)
else
value
end
end
def with_disabled_tags(tag_names)
tag_names.each do |name|
@disabled_tags[name] = @disabled_tags.fetch(name, 0) + 1
end
yield
ensure
tag_names.each do |name|
@disabled_tags[name] -= 1
end
end
def tag_disabled?(tag_name)
@disabled_tags.fetch(tag_name, 0) > 0
end
protected
attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters, :disabled_tags
private
attr_reader :base_scope_depth
def try_variable_find_in_environments(key, raise_on_not_found:)
@environments.each do |environment|
found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)
if !found_variable.nil? || @strict_variables && raise_on_not_found
return found_variable
end
end
@static_environments.each do |environment|
found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)
if !found_variable.nil? || @strict_variables && raise_on_not_found
return found_variable
end
end
nil
end
def check_overflow
raise StackLevelError, "Nesting too deep" if overflow?
end
def overflow?
base_scope_depth + @scopes.length > Block::MAX_DEPTH
end
def internal_error
# raise and catch to set backtrace and cause on exception
raise Liquid::InternalError, 'internal'
rescue Liquid::InternalError => exc
exc
end
def squash_instance_assigns_with_environments
@scopes.last.each_key do |k|
@environments.each do |env|
if env.key?(k)
scopes.last[k] = lookup_and_evaluate(env, k)
break
end
end
end
end # squash_instance_assigns_with_environments
end # Context
end # Liquid
================================================
FILE: lib/liquid/deprecations.rb
================================================
# frozen_string_literal: true
require "set"
module Liquid
class Deprecations
class << self
attr_accessor :warned
Deprecations.warned = Set.new
def warn(name, alternative)
return if warned.include?(name)
warned << name
caller_location = caller_locations(2, 1).first
Warning.warn("[DEPRECATION] #{name} is deprecated. Use #{alternative} instead. Called from #{caller_location}\n")
end
end
end
end
================================================
FILE: lib/liquid/document.rb
================================================
# frozen_string_literal: true
module Liquid
class Document
def self.parse(tokens, parse_context)
doc = new(parse_context)
doc.parse(tokens, parse_context)
doc
end
attr_reader :parse_context, :body
def initialize(parse_context)
@parse_context = parse_context
@body = new_body
end
def nodelist
@body.nodelist
end
def parse(tokenizer, parse_context)
while parse_body(tokenizer)
end
@body.freeze
rescue SyntaxError => e
e.line_number ||= parse_context.line_number
raise
end
def unknown_tag(tag, _markup, _tokenizer)
case tag
when 'else', 'end'
raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_outer_tag", tag: tag)
else
raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag", tag: tag)
end
end
def render_to_output_buffer(context, output)
@body.render_to_output_buffer(context, output)
end
def render(context)
render_to_output_buffer(context, +'')
end
private
def new_body
parse_context.new_block_body
end
def parse_body(tokenizer)
@body.parse(tokenizer, parse_context) do |unknown_tag_name, unknown_tag_markup|
if unknown_tag_name
unknown_tag(unknown_tag_name, unknown_tag_markup, tokenizer)
true
else
false
end
end
end
end
end
================================================
FILE: lib/liquid/drop.rb
================================================
# frozen_string_literal: true
require 'set'
module Liquid
# A drop in liquid is a class which allows you to export DOM like things to liquid.
# Methods of drops are callable.
# The main use for liquid drops is to implement lazy loaded objects.
# If you would like to make data available to the web designers which you don't want loaded unless needed then
# a drop is a great way to do that.
#
# Example:
#
# class ProductDrop < Liquid::Drop
# def top_sales
# Shop.current.products.find(:all, :order => 'sales', :limit => 10 )
# end
# end
#
# tmpl = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {%endfor%} ' )
# tmpl.render('product' => ProductDrop.new ) # will invoke top_sales query.
#
# Your drop can either implement the methods sans any parameters
# or implement the liquid_method_missing(name) method which is a catch all.
class Drop
attr_writer :context
def initialize
@context = nil
end
# Catch all for the method
def liquid_method_missing(method)
return unless @context&.strict_variables
raise Liquid::UndefinedDropMethod, "undefined method #{method}"
end
# called by liquid to invoke a drop
def invoke_drop(method_or_key)
if self.class.invokable?(method_or_key)
send(method_or_key)
else
liquid_method_missing(method_or_key)
end
end
def key?(_name)
true
end
def inspect
self.class.to_s
end
def to_liquid
self
end
def to_s
self.class.name
end
alias_method :[], :invoke_drop
# Check for method existence without invoking respond_to?, which creates symbols
def self.invokable?(method_name)
invokable_methods.include?(method_name.to_s)
end
def self.invokable_methods
@invokable_methods ||= begin
blacklist = Liquid::Drop.public_instance_methods + [:each]
if include?(Enumerable)
blacklist += Enumerable.public_instance_methods
blacklist -= [:sort, :count, :first, :min, :max]
end
whitelist = [:to_liquid] + (public_instance_methods - blacklist)
Set.new(whitelist.map(&:to_s))
end
end
end
end
================================================
FILE: lib/liquid/environment.rb
================================================
# frozen_string_literal: true
module Liquid
# The Environment is the container for all configuration options of Liquid, such as
# the registered tags, filters, and the default error mode.
class Environment
# The default error mode for all templates. This can be overridden on a
# per-template basis.
attr_accessor :error_mode
# The tags that are available to use in the template.
attr_accessor :tags
# The strainer template which is used to store filters that are available to
# use in templates.
attr_accessor :strainer_template
# The exception renderer that is used to render exceptions that are raised
# when rendering a template
attr_accessor :exception_renderer
# The default file system that is used to load templates from.
attr_accessor :file_system
# The default resource limits that are used to limit the resources that a
# template can consume.
attr_accessor :default_resource_limits
class << self
# Creates a new environment instance.
#
# @param tags [Hash] The tags that are available to use in
# the template.
# @param file_system The default file system that is used
# to load templates from.
# @param error_mode [Symbol] The default error mode for all templates
# (either :strict2, :strict, :warn, or :lax).
# @param exception_renderer [Proc] The exception renderer that is used to
# render exceptions.
# @yieldparam environment [Environment] The environment instance that is being built.
# @return [Environment] The new environment instance.
def build(tags: nil, file_system: nil, error_mode: nil, exception_renderer: nil)
ret = new
ret.tags = tags if tags
ret.file_system = file_system if file_system
ret.error_mode = error_mode if error_mode
ret.exception_renderer = exception_renderer if exception_renderer
yield ret if block_given?
ret.freeze
end
# Returns the default environment instance.
#
# @return [Environment] The default environment instance.
def default
@default ||= new
end
# Sets the default environment instance for the duration of the block
#
# @param environment [Environment] The environment instance to use as the default for the
# duration of the block.
# @yield
# @return [Object] The return value of the block.
def dangerously_override(environment)
original_default = @default
@default = environment
yield
ensure
@default = original_default
end
end
# Initializes a new environment instance.
# @api private
def initialize
@tags = Tags::STANDARD_TAGS.dup
@error_mode = :lax
@strainer_template = Class.new(StrainerTemplate).tap do |klass|
klass.add_filter(StandardFilters)
end
@exception_renderer = ->(exception) { exception }
@file_system = BlankFileSystem.new
@default_resource_limits = Const::EMPTY_HASH
@strainer_template_class_cache = {}
end
# Registers a new tag with the environment.
#
# @param name [String] The name of the tag.
# @param klass [Liquid::Tag] The class that implements the tag.
# @return [void]
def register_tag(name, klass)
@tags[name] = klass
end
# Registers a new filter with the environment.
#
# @param filter [Module] The module that contains the filter methods.
# @return [void]
def register_filter(filter)
@strainer_template_class_cache.clear
@strainer_template.add_filter(filter)
end
# Registers multiple filters with this environment.
#
# @param filters [Array<Module>] The modules that contain the filter methods.
# @return [self]
def register_filters(filters)
@strainer_template_class_cache.clear
filters.each { |f| @strainer_template.add_filter(f) }
self
end
# Creates a new strainer instance with the given filters, caching the result
# for faster lookup.
#
# @param context [Liquid::Context] The context that the strainer will be
# used in.
# @param filters [Array<Module>] The filters that the strainer will have
# access to.
# @return [Liquid::Strainer] The new strainer instance.
def create_strainer(context, filters = Const::EMPTY_ARRAY)
return @strainer_template.new(context) if filters.empty?
strainer_template = @strainer_template_class_cache[filters] ||= begin
klass = Class.new(@strainer_template)
filters.each { |f| klass.add_filter(f) }
klass
end
strainer_template.new(context)
end
# Returns the names of all the filter methods that are available to use in
# the strainer template.
#
# @return [Array<String>] The names of all the filter methods.
def filter_method_names
@strainer_template.filter_method_names
end
# Returns the tag class for the given tag name.
#
# @param name [String] The name of the tag.
# @return [Liquid::Tag] The tag class.
def tag_for_name(name)
@tags[name]
end
def freeze
@tags.freeze
# TODO: freeze the tags, currently this is not possible because of liquid-c
# @strainer_template.freeze
super
end
end
end
================================================
FILE: lib/liquid/errors.rb
================================================
# frozen_string_literal: true
module Liquid
class Error < ::StandardError
attr_accessor :line_number
attr_accessor :template_name
attr_accessor :markup_context
def to_s(with_prefix = true)
str = +""
str << message_prefix if with_prefix
str << super()
if markup_context
str << " "
str << markup_context
end
str
end
private
def message_prefix
str = +""
str << if is_a?(SyntaxError)
"Liquid syntax error"
else
"Liquid error"
end
if line_number
str << " ("
str << template_name << " " if template_name
str << "line " << line_number.to_s << ")"
end
str << ": "
str
end
end
ArgumentError = Class.new(Error)
ContextError = Class.new(Error)
FileSystemError = Class.new(Error)
StandardError = Class.new(Error)
SyntaxError = Class.new(Error)
StackLevelError = Class.new(Error)
MemoryError = Class.new(Error)
ZeroDivisionError = Class.new(Error)
FloatDomainError = Class.new(Error)
UndefinedVariable = Class.new(Error)
UndefinedDropMethod = Class.new(Error)
UndefinedFilter = Class.new(Error)
MethodOverrideError = Class.new(Error)
DisabledError = Class.new(Error)
InternalError = Class.new(Error)
TemplateEncodingError = Class.new(Error)
end
================================================
FILE: lib/liquid/expression.rb
================================================
# frozen_string_literal: true
module Liquid
class Expression
LITERALS = {
nil => nil,
'nil' => nil,
'null' => nil,
'' => nil,
'true' => true,
'false' => false,
'blank' => '',
'empty' => '',
# in lax mode, minus sign can be a VariableLookup
# For simplicity and performace, we treat it like a literal
'-' => VariableLookup.parse("-", nil).freeze,
}.freeze
DOT = ".".ord
ZERO = "0".ord
NINE = "9".ord
DASH = "-".ord
# Use an atomic group (?>...) to avoid pathological backtracing from
# malicious input as described in https://github.com/Shopify/liquid/issues/1357
RANGES_REGEX = /\A\(\s*(?>(\S+)\s*\.\.)\s*(\S+)\s*\)\z/
INTEGER_REGEX = /\A(-?\d+)\z/
FLOAT_REGEX = /\A(-?\d+)\.\d+\z/
class << self
def safe_parse(parser, ss = StringScanner.new(""), cache = nil)
parse(parser.expression, ss, cache)
end
def parse(markup, ss = StringScanner.new(""), cache = nil)
return unless markup
markup = markup.strip # markup can be a frozen string
if (markup.start_with?('"') && markup.end_with?('"')) ||
(markup.start_with?("'") && markup.end_with?("'"))
return markup[1..-2]
elsif LITERALS.key?(markup)
return LITERALS[markup]
end
# Cache only exists during parsing
if cache
return cache[markup] if cache.key?(markup)
cache[markup] = inner_parse(markup, ss, cache).freeze
else
inner_parse(markup, ss, nil).freeze
end
end
def inner_parse(markup, ss, cache)
if markup.start_with?("(") && markup.end_with?(")") && markup =~ RANGES_REGEX
return RangeLookup.parse(
Regexp.last_match(1),
Regexp.last_match(2),
ss,
cache,
)
end
if (num = parse_number(markup, ss))
num
else
VariableLookup.parse(markup, ss, cache)
end
end
def parse_number(markup, ss)
# check if the markup is simple integer or float
case markup
when INTEGER_REGEX
return Integer(markup, 10)
when FLOAT_REGEX
return markup.to_f
end
ss.string = markup
# the first byte must be a digit or a dash
byte = ss.scan_byte
return false if byte != DASH && (byte < ZERO || byte > NINE)
if byte == DASH
peek_byte = ss.peek_byte
# if it starts with a dash, the next byte must be a digit
return false if peek_byte.nil? || !(peek_byte >= ZERO && peek_byte <= NINE)
end
# The markup could be a float with multiple dots
first_dot_pos = nil
num_end_pos = nil
while (byte = ss.scan_byte)
return false if byte != DOT && (byte < ZERO || byte > NINE)
# we found our number and now we are just scanning the rest of the string
next if num_end_pos
if byte == DOT
if first_dot_pos.nil?
first_dot_pos = ss.pos
else
# we found another dot, so we know that the number ends here
num_end_pos = ss.pos - 1
end
end
end
num_end_pos = markup.length if ss.eos?
if num_end_pos
# number ends with a number "123.123"
markup.byteslice(0, num_end_pos).to_f
else
# number ends with a dot "123."
markup.byteslice(0, first_dot_pos).to_f
end
end
end
end
end
================================================
FILE: lib/liquid/extensions.rb
================================================
# frozen_string_literal: true
require 'time'
require 'date'
class String # :nodoc:
def to_liquid
self
end
end
class Symbol # :nodoc:
def to_liquid
to_s
end
end
class Array # :nodoc:
def to_liquid
self
end
end
class Hash # :nodoc:
def to_liquid
self
end
end
class Numeric # :nodoc:
def to_liquid
self
end
end
class Range # :nodoc:
def to_liquid
self
end
end
class Time # :nodoc:
def to_liquid
self
end
end
class DateTime < Date # :nodoc:
def to_liquid
self
end
end
class Date # :nodoc:
def to_liquid
self
end
end
class TrueClass
def to_liquid # :nodoc:
self
end
end
class FalseClass
def to_liquid # :nodoc:
self
end
end
class NilClass
def to_liquid # :nodoc:
self
end
end
================================================
FILE: lib/liquid/file_system.rb
================================================
# frozen_string_literal: true
module Liquid
# A Liquid file system is a way to let your templates retrieve other templates for use with the include tag.
#
# You can implement subclasses that retrieve templates from the database, from the file system using a different
# path structure, you can provide them as hard-coded inline strings, or any manner that you see fit.
#
# You can add additional instance variables, arguments, or methods as needed.
#
# Example:
#
# Liquid::Template.file_system = Liquid::LocalFileSystem.new(template_path)
# liquid = Liquid::Template.parse(template)
#
# This will parse the template with a LocalFileSystem implementation rooted at 'template_path'.
class BlankFileSystem
# Called by Liquid to retrieve a template file
def read_template_file(_template_path)
raise FileSystemError, "This liquid context does not allow includes."
end
end
# This implements an abstract file system which retrieves template files named in a manner similar to Rails partials,
# ie. with the template name prefixed with an underscore. The extension ".liquid" is also added.
#
# For security reasons, template paths are only allowed to contain letters, numbers, and underscore.
#
# Example:
#
# file_system = Liquid::LocalFileSystem.new("/some/path")
#
# file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid"
# file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid"
#
# Optionally in the second argument you can specify a custom pattern for template filenames.
# The Kernel::sprintf format specification is used.
# Default pattern is "_%s.liquid".
#
# Example:
#
# file_system = Liquid::LocalFileSystem.new("/some/path", "%s.html")
#
# file_system.full_path("index") # => "/some/path/index.html"
#
class LocalFileSystem
attr_accessor :root
def initialize(root, pattern = "_%s.liquid")
@root = root
@pattern = pattern
end
def read_template_file(template_path)
full_path = full_path(template_path)
raise FileSystemError, "No such template '#{template_path}'" unless File.exist?(full_path)
File.read(full_path)
end
def full_path(template_path)
raise FileSystemError, "Illegal template name '#{template_path}'" unless %r{\A[^./][a-zA-Z0-9_/]+\z}.match?(template_path)
full_path = if template_path.include?('/')
File.join(root, File.dirname(template_path), @pattern % File.basename(template_path))
else
File.join(root, @pattern % template_path)
end
raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path).start_with?(File.expand_path(root))
full_path
end
end
end
================================================
FILE: lib/liquid/forloop_drop.rb
================================================
# frozen_string_literal: true
module Liquid
# @liquid_public_docs
# @liquid_type object
# @liquid_name forloop
# @liquid_summary
# Information about a parent [`for` loop](/docs/api/liquid/tags/for).
class ForloopDrop < Drop
def initialize(name, length, parentloop)
@name = name
@length = length
@parentloop = parentloop
@index = 0
end
# @liquid_public_docs
# @liquid_name length
# @liquid_summary
# The total number of iterations in the loop.
# @liquid_return [number]
attr_reader :length
# @liquid_public_docs
# @liquid_name parentloop
# @liquid_summary
# The parent `forloop` object.
# @liquid_description
# If the current `for` loop isn't nested inside another `for` loop, then `nil` is returned.
# @liquid_return [forloop]
attr_reader :parentloop
attr_reader :name
# @liquid_public_docs
# @liquid_summary
# The 1-based index of the current iteration.
# @liquid_return [number]
def index
@index + 1
end
# @liquid_public_docs
# @liquid_summary
# The 0-based index of the current iteration.
# @liquid_return [number]
def index0
@index
end
# @liquid_public_docs
# @liquid_summary
# The 1-based index of the current iteration, in reverse order.
# @liquid_return [number]
def rindex
@length - @index
end
# @liquid_public_docs
# @liquid_summary
# The 0-based index of the current iteration, in reverse order.
# @liquid_return [number]
def rindex0
@length - @index - 1
end
# @liquid_public_docs
# @liquid_summary
# Returns `true` if the current iteration is the first. Returns `false` if not.
# @liquid_return [boolean]
def first
@index == 0
end
# @liquid_public_docs
# @liquid_summary
# Returns `true` if the current iteration is the last. Returns `false` if not.
# @liquid_return [boolean]
def last
@index == @length - 1
end
protected
def increment!
@index += 1
end
end
end
================================================
FILE: lib/liquid/i18n.rb
================================================
# frozen_string_literal: true
require 'yaml'
module Liquid
class I18n
DEFAULT_LOCALE = File.join(File.expand_path(__dir__), "locales", "en.yml")
TranslationError = Class.new(StandardError)
attr_reader :path
def initialize(path = DEFAULT_LOCALE)
@path = path
end
def translate(name, vars = {})
interpolate(deep_fetch_translation(name), vars)
end
alias_method :t, :translate
def locale
@locale ||= YAML.load_file(@path)
end
private
def interpolate(name, vars)
name.gsub(/%\{(\w+)\}/) do
# raise TranslationError, "Undefined key #{$1} for interpolation in translation #{name}" unless vars[$1.to_sym]
vars[Regexp.last_match(1).to_sym].to_s
end
end
def deep_fetch_translation(name)
name.split('.').reduce(locale) do |level, cur|
level[cur] || raise(TranslationError, "Translation for #{name} does not exist in locale #{path}")
end
end
end
end
================================================
FILE: lib/liquid/interrupts.rb
================================================
# frozen_string_literal: true
module Liquid
# An interrupt is any command that breaks processing of a block (ex: a for loop).
class Interrupt
attr_reader :message
def initialize(message = nil)
@message = message || "interrupt"
end
end
# Interrupt that is thrown whenever a {% break %} is called.
class BreakInterrupt < Interrupt; end
# Interrupt that is thrown whenever a {% continue %} is called.
class ContinueInterrupt < Interrupt; end
end
================================================
FILE: lib/liquid/lexer.rb
================================================
# frozen_string_literal: true
module Liquid
class Lexer
CLOSE_ROUND = [:close_round, ")"].freeze
CLOSE_SQUARE = [:close_square, "]"].freeze
COLON = [:colon, ":"].freeze
COMMA = [:comma, ","].freeze
COMPARISION_NOT_EQUAL = [:comparison, "!="].freeze
COMPARISON_CONTAINS = [:comparison, "contains"].freeze
COMPARISON_EQUAL = [:comparison, "=="].freeze
COMPARISON_GREATER_THAN = [:comparison, ">"].freeze
COMPARISON_GREATER_THAN_OR_EQUAL = [:comparison, ">="].freeze
COMPARISON_LESS_THAN = [:comparison, "<"].freeze
COMPARISON_LESS_THAN_OR_EQUAL = [:comparison, "<="].freeze
COMPARISON_NOT_EQUAL_ALT = [:comparison, "<>"].freeze
DASH = [:dash, "-"].freeze
DOT = [:dot, "."].freeze
DOTDOT = [:dotdot, ".."].freeze
DOT_ORD = ".".ord
DOUBLE_STRING_LITERAL = /"[^\"]*"/
EOS = [:end_of_string].freeze
IDENTIFIER = /[a-zA-Z_][\w-]*\??/
NUMBER_LITERAL = /-?\d+(\.\d+)?/
OPEN_ROUND = [:open_round, "("].freeze
OPEN_SQUARE = [:open_square, "["].freeze
PIPE = [:pipe, "|"].freeze
QUESTION = [:question, "?"].freeze
RUBY_WHITESPACE = [" ", "\t", "\r", "\n", "\f"].freeze
SINGLE_STRING_LITERAL = /'[^\']*'/
WHITESPACE_OR_NOTHING = /\s*/
SINGLE_COMPARISON_TOKENS = [].tap do |table|
table["<".ord] = COMPARISON_LESS_THAN
table[">".ord] = COMPARISON_GREATER_THAN
table.freeze
end
TWO_CHARS_COMPARISON_JUMP_TABLE = [].tap do |table|
table["=".ord] = [].tap do |sub_table|
sub_table["=".ord] = COMPARISON_EQUAL
sub_table.freeze
end
table["!".ord] = [].tap do |sub_table|
sub_table["=".ord] = COMPARISION_NOT_EQUAL
sub_table.freeze
end
table.freeze
end
COMPARISON_JUMP_TABLE = [].tap do |table|
table["<".ord] = [].tap do |sub_table|
sub_table["=".ord] = COMPARISON_LESS_THAN_OR_EQUAL
sub_table[">".ord] = COMPARISON_NOT_EQUAL_ALT
sub_table.freeze
end
table[">".ord] = [].tap do |sub_table|
sub_table["=".ord] = COMPARISON_GREATER_THAN_OR_EQUAL
sub_table.freeze
end
table.freeze
end
NEXT_MATCHER_JUMP_TABLE = [].tap do |table|
"a".upto("z") do |c|
table[c.ord] = [:id, IDENTIFIER].freeze
table[c.upcase.ord] = [:id, IDENTIFIER].freeze
end
table["_".ord] = [:id, IDENTIFIER].freeze
"0".upto("9") do |c|
table[c.ord] = [:number, NUMBER_LITERAL].freeze
end
table["-".ord] = [:number, NUMBER_LITERAL].freeze
table["'".ord] = [:string, SINGLE_STRING_LITERAL].freeze
table["\"".ord] = [:string, DOUBLE_STRING_LITERAL].freeze
table.freeze
end
SPECIAL_TABLE = [].tap do |table|
table["|".ord] = PIPE
table[".".ord] = DOT
table[":".ord] = COLON
table[",".ord] = COMMA
table["[".ord] = OPEN_SQUARE
table["]".ord] = CLOSE_SQUARE
table["(".ord] = OPEN_ROUND
table[")".ord] = CLOSE_ROUND
table["?".ord] = QUESTION
table["-".ord] = DASH
end
NUMBER_TABLE = [].tap do |table|
"0".upto("9") do |c|
table[c.ord] = true
end
table.freeze
end
# rubocop:disable Metrics/BlockNesting
class << self
def tokenize(ss)
output = []
until ss.eos?
ss.skip(WHITESPACE_OR_NOTHING)
break if ss.eos?
start_pos = ss.pos
peeked = ss.peek_byte
if (special = SPECIAL_TABLE[peeked])
ss.scan_byte
# Special case for ".."
if special == DOT && ss.peek_byte == DOT_ORD
ss.scan_byte
output << DOTDOT
elsif special == DASH
# Special case for negative numbers
if (peeked_byte = ss.peek_byte) && NUMBER_TABLE[peeked_byte]
ss.pos -= 1
output << [:number, ss.scan(NUMBER_LITERAL)]
else
output << special
end
else
output << special
end
elsif (sub_table = TWO_CHARS_COMPARISON_JUMP_TABLE[peeked])
ss.scan_byte
if (peeked_byte = ss.peek_byte) && (found = sub_table[peeked_byte])
output << found
ss.scan_byte
else
raise_syntax_error(start_pos, ss)
end
elsif (sub_table = COMPARISON_JUMP_TABLE[peeked])
ss.scan_byte
if (peeked_byte = ss.peek_byte) && (found = sub_table[peeked_byte])
output << found
ss.scan_byte
else
output << SINGLE_COMPARISON_TOKENS[peeked]
end
else
type, pattern = NEXT_MATCHER_JUMP_TABLE[peeked]
if type && (t = ss.scan(pattern))
# Special case for "contains"
output << if type == :id && t == "contains" && output.last&.first != :dot
COMPARISON_CONTAINS
else
[type, t]
end
else
raise_syntax_error(start_pos, ss)
end
end
end
# rubocop:enable Metrics/BlockNesting
output << EOS
rescue ::ArgumentError => e
if e.message == "invalid byte sequence in #{ss.string.encoding}"
raise SyntaxError, "Invalid byte sequence in #{ss.string.encoding}"
else
raise
end
end
def raise_syntax_error(start_pos, ss)
ss.pos = start_pos
# the character could be a UTF-8 character, use getch to get all the bytes
raise SyntaxError, "Unexpected character #{ss.getch}"
end
end
end
end
================================================
FILE: lib/liquid/locales/en.yml
================================================
---
errors:
syntax:
tag_unexpected_args: "Syntax Error in '%{tag}' - Valid syntax: %{tag}"
block_tag_unexpected_args: "Syntax Error in '%{tag}' - Valid syntax: {% %{tag} %}{% end%{tag} %}"
assign: "Syntax Error in 'assign' - Valid syntax: assign [var] = [source]"
capture: "Syntax Error in 'capture' - Valid syntax: capture [var]"
case: "Syntax Error in 'case' - Valid syntax: case [condition]"
case_invalid_when: "Syntax Error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %}"
case_invalid_else: "Syntax Error in tag 'case' - Valid else condition: {% else %} (no parameters) "
cycle: "Syntax Error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]"
doc_invalid_nested: "Syntax Error in 'doc' - Nested doc tags are not allowed"
for: "Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]"
for_invalid_in: "For loops require an 'in' clause"
for_invalid_attribute: "Invalid attribute in for loop. Valid attributes are limit and offset"
if: "Syntax Error in tag 'if' - Valid syntax: if [expression]"
include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]"
inline_comment_invalid: "Syntax error in tag '#' - Each line of comments must be prefixed by the '#' character"
invalid_delimiter: "'%{tag}' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
invalid_template_encoding: "Invalid template encoding"
render: "Syntax error in tag 'render' - Template name must be a quoted string"
table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
table_row_invalid_attribute: "Invalid attribute '%{attribute}' in tablerow loop. Valid attributes are cols, limit, offset, and range"
tag_never_closed: "'%{block_name}' tag was never closed"
tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
unexpected_else: "%{block_name} tag does not expect 'else' tag"
unexpected_outer_tag: "Unexpected outer '%{tag}' tag"
unknown_tag: "Unknown tag '%{tag}'"
variable_termination: "Variable '%{token}' was not properly terminated with regexp: %{tag_end}"
argument:
include: "Argument error in tag 'include' - Illegal template name"
disabled:
tag: "usage is not allowed in this context"
================================================
FILE: lib/liquid/parse_context.rb
================================================
# frozen_string_literal: true
module Liquid
class ParseContext
attr_accessor :locale, :line_number, :trim_whitespace, :depth
attr_reader :partial, :warnings, :error_mode, :environment
def initialize(options = Const::EMPTY_HASH)
@environment = options.fetch(:environment, Environment.default)
@template_options = options ? options.dup : {}
@locale = @template_options[:locale] ||= I18n.new
@warnings = []
# constructing new StringScanner in Lexer, Tokenizer, etc is expensive
# This StringScanner will be shared by all of them
@string_scanner = StringScanner.new("")
@expression_cache = if options[:expression_cache].nil?
{}
elsif options[:expression_cache].respond_to?(:[]) && options[:expression_cache].respond_to?(:[]=)
options[:expression_cache]
elsif options[:expression_cache]
{}
end
self.depth = 0
self.partial = false
end
def [](option_key)
@options[option_key]
end
def new_block_body
Liquid::BlockBody.new
end
def new_parser(input)
@string_scanner.string = input
Parser.new(@string_scanner)
end
def new_tokenizer(source, start_line_number: nil, for_liquid_tag: false)
Tokenizer.new(
source: source,
string_scanner: @string_scanner,
line_number: start_line_number,
for_liquid_tag: for_liquid_tag,
)
end
def safe_parse_expression(parser)
Expression.safe_parse(parser, @string_scanner, @expression_cache)
end
def parse_expression(markup, safe: false)
if !safe && @error_mode == :strict2
# parse_expression is a widely used API. To maintain backward
# compatibility while raising awareness about strict2 parser standards,
# the safe flag supports API users make a deliberate decision.
#
# In strict2 mode, markup MUST come from a string returned by the parser
# (e.g., parser.expression). We're not calling the parser here to
# prevent redundant parser overhead.
raise Liquid::InternalError, "unsafe parse_expression cannot be used in strict2 mode"
end
Expression.parse(markup, @string_scanner, @expression_cache)
end
def partial=(value)
@partial = value
@options = value ? partial_options : @template_options
@error_mode = @options[:error_mode] || @environment.error_mode
end
def partial_options
@partial_options ||= begin
dont_pass = @template_options[:include_options_blacklist]
if dont_pass == true
{ locale: locale }
elsif dont_pass.is_a?(Array)
@template_options.reject { |k, _v| dont_pass.include?(k) }
else
@template_options
end
end
end
end
end
================================================
FILE: lib/liquid/parse_tree_visitor.rb
================================================
# frozen_string_literal: true
module Liquid
class ParseTreeVisitor
def self.for(node, callbacks = Hash.new(proc {}))
if defined?(node.class::ParseTreeVisitor)
node.class::ParseTreeVisitor
else
self
end.new(node, callbacks)
end
def initialize(node, callbacks)
@node = node
@callbacks = callbacks
end
def add_callback_for(*classes, &block)
callback = block
callback = ->(node, _) { yield node } if block.arity.abs == 1
callback = ->(_, _) { yield } if block.arity.zero?
classes.each { |klass| @callbacks[klass] = callback }
self
end
def visit(context = nil)
children.map do |node|
item, new_context = @callbacks[node.class].call(node, context)
[
item,
ParseTreeVisitor.for(node, @callbacks).visit(new_context || context),
]
end
end
protected
def children
@node.respond_to?(:nodelist) ? Array(@node.nodelist) : Const::EMPTY_ARRAY
end
end
end
================================================
FILE: lib/liquid/parser.rb
================================================
# frozen_string_literal: true
module Liquid
class Parser
def initialize(input)
ss = input.is_a?(StringScanner) ? input : StringScanner.new(input)
@tokens = Lexer.tokenize(ss)
@p = 0 # pointer to current location
end
def jump(point)
@p = point
end
def consume(type = nil)
token = @tokens[@p]
if type && token[0] != type
raise SyntaxError, "Expected #{type} but found #{@tokens[@p].first}"
end
@p += 1
token[1]
end
# Only consumes the token if it matches the type
# Returns the token's contents if it was consumed
# or false otherwise.
def consume?(type)
token = @tokens[@p]
return false unless token && token[0] == type
@p += 1
token[1]
end
# Like consume? Except for an :id token of a certain name
def id?(str)
token = @tokens[@p]
return false unless token && token[0] == :id
return false unless token[1] == str
@p += 1
token[1]
end
def look(type, ahead = 0)
tok = @tokens[@p + ahead]
return false unless tok
tok[0] == type
end
def expression
token = @tokens[@p]
case token[0]
when :id
str = consume
str << variable_lookups
when :open_square
str = consume.dup
str << expression
str << consume(:close_square)
str << variable_lookups
when :string, :number
consume
when :open_round
consume
first = expression
consume(:dotdot)
last = expression
consume(:close_round)
"(#{first}..#{last})"
else
raise SyntaxError, "#{token} is not a valid expression"
end
end
def argument
str = +""
# might be a keyword argument (identifier: expression)
if look(:id) && look(:colon, 1)
str << consume << consume << ' '
end
str << expression
str
end
def variable_lookups
str = +""
loop do
if look(:open_square)
str << consume
str << expression
str << consume(:close_square)
elsif look(:dot)
str << consume
str << consume(:id)
else
break
end
end
str
end
end
end
================================================
FILE: lib/liquid/parser_switching.rb
================================================
# frozen_string_literal: true
module Liquid
module ParserSwitching
# Do not use this.
#
# It's basically doing the same thing the {#parse_with_selected_parser},
# except this will try the strict parser regardless of the error mode,
# and fall back to the lax parser if the error mode is lax or warn,
# except when in strict2 mode where it uses the strict2 parser.
#
# @deprecated Use {#parse_with_selected_parser} instead.
def strict_parse_with_error_mode_fallback(markup)
return strict2_parse_with_error_context(markup) if strict2_mode?
strict_parse_with_error_context(markup)
rescue SyntaxError => e
case parse_context.error_mode
when :rigid
rigid_warn
raise
when :strict2
raise
when :strict
raise
when :warn
parse_context.warnings << e
end
lax_parse(markup)
end
def parse_with_selected_parser(markup)
case parse_context.error_mode
when :rigid then rigid_warn && strict2_parse_with_error_context(markup)
when :strict2 then strict2_parse_with_error_context(markup)
when :strict then strict_parse_with_error_context(markup)
when :lax then lax_parse(markup)
when :warn
begin
strict2_parse_with_error_context(markup)
rescue SyntaxError => e
parse_context.warnings << e
lax_parse(markup)
end
end
end
def strict2_mode?
parse_context.error_mode == :strict2 || parse_context.error_mode == :rigid
end
private
def rigid_warn
Deprecations.warn(':rigid', ':strict2')
end
def strict2_parse_with_error_context(markup)
strict2_parse(markup)
rescue SyntaxError => e
e.line_number = line_number
e.markup_context = markup_context(markup)
raise e
end
def strict_parse_with_error_context(markup)
strict_parse(markup)
rescue SyntaxError => e
e.line_number = line_number
e.markup_context = markup_context(markup)
raise e
end
def markup_context(markup)
"in \"#{markup.strip}\""
end
end
end
================================================
FILE: lib/liquid/partial_cache.rb
================================================
# frozen_string_literal: true
module Liquid
class PartialCache
def self.load(template_name, context:, parse_context:)
cached_partials = context.registers[:cached_partials]
cache_key = "#{template_name}:#{parse_context.error_mode}"
cached = cached_partials[cache_key]
return cached if cached
file_system = context.registers[:file_system]
source = file_system.read_template_file(template_name)
parse_context.partial = true
template_factory = context.registers[:template_factory]
template = template_factory.for(template_name)
begin
partial = template.parse(source, parse_context)
rescue Liquid::Error => e
e.template_name = template&.name || template_name
raise e
end
partial.name ||= template_name
cached_partials[cache_key] = partial
ensure
parse_context.partial = false
end
end
end
================================================
FILE: lib/liquid/profiler/hooks.rb
================================================
# frozen_string_literal: true
module Liquid
module BlockBodyProfilingHook
def render_node(context, output, node)
if (profiler = context.profiler)
profiler.profile_node(context.template_name, code: node.raw, line_number: node.line_number) do
super
end
else
super
end
end
end
BlockBody.prepend(BlockBodyProfilingHook)
module DocumentProfilingHook
def render_to_output_buffer(context, output)
return super unless context.profiler
context.profiler.profile(context.template_name) { super }
end
end
Document.prepend(DocumentProfilingHook)
module ContextProfilingHook
attr_accessor :profiler
def new_isolated_subcontext
new_context = super
new_context.profiler = profiler
new_context
end
end
Context.prepend(ContextProfilingHook)
end
================================================
FILE: lib/liquid/profiler.rb
================================================
# frozen_string_literal: true
require 'liquid/profiler/hooks'
module Liquid
# Profiler enables support for profiling template rendering to help track down performance issues.
#
# To enable profiling, first require 'liquid/profiler'.
# Then, to profile a parse/render cycle, pass the <tt>profile: true</tt> option to <tt>Liquid::Template.parse</tt>.
# After <tt>Liquid::Template#render</tt> is called, the template object makes available an instance of this
# class via the <tt>Liquid::Template#profiler</tt> method.
#
# template = Liquid::Template.parse(template_content, profile: true)
# output = template.render
# profile = template.profiler
#
# This object contains all profiling information, containing information on what tags were rendered,
# where in the templates these tags live, and how long each tag took to render.
#
# This is a tree structure that is Enumerable all the way down, and keeps track of tags and rendering times
# inside of <tt>{% include %}</tt> tags.
#
# profile.each do |node|
# # Access to the node itself
# node.code
#
# # Which template and line number of this node.
# # The top-level template name is `nil` by default, but can be set in the Liquid::Context before rendering.
# node.partial
# node.line_number
#
# # Render time in seconds of this node
# node.render_time
#
# # If the template used {% include %}, this node will also have children.
# node.children.each do |child2|
# # ...
# end
# end
#
# Profiler also exposes the total time of the template's render in <tt>Liquid::Profiler#total_render_time</tt>.
#
# All render times are in seconds. There is a small performance hit when profiling is enabled.
#
class Profiler
include Enumerable
class Timing
attr_reader :code, :template_name, :line_number, :children
attr_accessor :total_time
alias_method :render_time, :total_time
alias_method :partial, :template_name
def initialize(code: nil, template_name: nil, line_number: nil)
@code = code
@template_name = template_name
@line_number = line_number
@children = []
end
def self_time
@self_time ||= begin
total_children_time = 0.0
@children.each do |child|
total_children_time += child.total_time
end
@total_time - total_children_time
end
end
end
attr_reader :total_time
alias_method :total_render_time, :total_time
def initialize
@root_children = []
@current_children = nil
@total_time = 0.0
end
def profile(template_name, &block)
# nested renders are done from a tag that already has a timing node
return yield if @current_children
root_children = @root_children
render_idx = root_children.length
begin
@current_children = root_children
profile_node(template_name, &block)
ensure
@current_children = nil
if (timing = root_children[render_idx])
@total_time += timing.total_time
end
end
end
def children
children = @root_children
if children.length == 1
children.first.children
else
children
end
end
def each(&block)
children.each(&block)
end
def [](idx)
children[idx]
end
def length
children.length
end
def profile_node(template_name, code: nil, line_number: nil)
timing = Timing.new(code: code, template_name: template_name, line_number: line_number)
parent_children = @current_children
start_time = monotonic_time
begin
@current_children = timing.children
yield
ensure
@current_children = parent_children
timing.total_time = monotonic_time - start_time
parent_children << timing
end
end
private
def monotonic_time
Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
end
end
================================================
FILE: lib/liquid/range_lookup.rb
================================================
# frozen_string_literal: true
module Liquid
class RangeLookup
def self.parse(start_markup, end_markup, string_scanner, cache = nil)
start_obj = Expression.parse(start_markup, string_scanner, cache)
end_obj = Expression.parse(end_markup, string_scanner, cache)
if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate)
new(start_obj, end_obj)
else
begin
start_obj.to_i..end_obj.to_i
rescue NoMethodError
invalid_expr = start_markup unless start_obj.respond_to?(:to_i)
invalid_expr ||= end_markup unless end_obj.respond_to?(:to_i)
if invalid_expr
raise Liquid::SyntaxError, "Invalid expression type '#{invalid_expr}' in range expression"
end
raise
end
end
end
attr_reader :start_obj, :end_obj
def initialize(start_obj, end_obj)
@start_obj = start_obj
@end_obj = end_obj
end
def evaluate(context)
start_int = to_integer(context.evaluate(@start_obj))
end_int = to_integer(context.evaluate(@end_obj))
start_int..end_int
end
private
def to_integer(input)
case input
when Integer
input
when NilClass, String
input.to_i
else
Utils.to_integer(input)
end
end
class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children
[@node.start_obj, @node.end_obj]
end
end
end
end
================================================
FILE: lib/liquid/registers.rb
================================================
# frozen_string_literal: true
module Liquid
class Registers
attr_reader :static
def initialize(registers = {})
@static = registers.is_a?(Registers) ? registers.static : registers
@changes = {}
end
def []=(key, value)
@changes[key] = value
end
def [](key)
if @changes.key?(key)
@changes[key]
else
@static[key]
end
end
def delete(key)
@changes.delete(key)
end
UNDEFINED = Object.new
def fetch(key, default = UNDEFINED, &block)
if @changes.key?(key)
@changes.fetch(key)
elsif default != UNDEFINED
if block_given?
@static.fetch(key, &block)
else
@static.fetch(key, default)
end
else
@static.fetch(key, &block)
end
end
def key?(key)
@changes.key?(key) || @static.key?(key)
end
end
# Alias for backwards compatibility
StaticRegisters = Registers
end
================================================
FILE: lib/liquid/resource_limits.rb
================================================
# frozen_string_literal: true
module Liquid
class ResourceLimits
attr_accessor :render_length_limit,
:render_score_limit,
:assign_score_limit,
:cumulative_render_score_limit,
:cumulative_assign_score_limit
attr_reader :render_score,
:assign_score,
:cumulative_render_score,
:cumulative_assign_score
def initialize(limits)
@render_length_limit = limits[:render_length_limit]
@render_score_limit = limits[:render_score_limit]
@assign_score_limit = limits[:assign_score_limit]
@cumulative_render_score_limit = limits[:cumulative_render_score_limit]
@cumulative_assign_score_limit = limits[:cumulative_assign_score_limit]
@cumulative_render_score = 0
@cumulative_assign_score = 0
reset
end
def increment_render_score(amount)
@render_score += amount
@cumulative_render_score += amount
raise_limits_reached if @render_score_limit && @render_score > @render_score_limit
raise_limits_reached if @cumulative_render_score_limit && @cumulative_render_score > @cumulative_render_score_limit
end
def increment_assign_score(amount)
@assign_score += amount
@cumulative_assign_score += amount
raise_limits_reached if @assign_score_limit && @assign_score > @assign_score_limit
raise_limits_reached if @cumulative_assign_score_limit && @cumulative_assign_score > @cumulative_assign_score_limit
end
# update either render_length or assign_score based on whether or not the writes are captured
def increment_write_score(output)
if (last_captured = @last_capture_length)
captured = output.bytesize
increment = captured - last_captured
@last_capture_length = captured
increment_assign_score(increment)
elsif @render_length_limit && output.bytesize > @render_length_limit
raise_limits_reached
end
end
def raise_limits_reached
@reached_limit = true
raise MemoryError, "Memory limits exceeded"
end
def reached?
@reached_limit
end
def reset
@reached_limit = false
@last_capture_length = nil
@render_score = @assign_score = 0
raise_limits_reached if @cumulative_render_score_limit && @cumulative_render_score > @cumulative_render_score_limit
raise_limits_reached if @cumulative_assign_score_limit && @cumulative_assign_score > @cumulative_assign_score_limit
end
def with_capture
old_capture_length = @last_capture_length
begin
@last_capture_length = 0
yield
ensure
@last_capture_length = old_capture_length
end
end
end
end
================================================
FILE: lib/liquid/standardfilters.rb
================================================
# frozen_string_literal: true
require 'cgi'
require 'base64'
require 'bigdecimal'
module Liquid
module StandardFilters
MAX_I32 = (1 << 31) - 1
private_constant :MAX_I32
MIN_I64 = -(1 << 63)
MAX_I64 = (1 << 63) - 1
I64_RANGE = MIN_I64..MAX_I64
private_constant :MIN_I64, :MAX_I64, :I64_RANGE
HTML_ESCAPE = {
'&' => '&',
'>' => '>',
'<' => '<',
'"' => '"',
"'" => ''',
}.freeze
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
STRIP_HTML_BLOCKS = Regexp.union(
%r{<script.*?</script>}m,
/<!--.*?-->/m,
%r{<style.*?</style>}m,
)
STRIP_HTML_TAGS = /<.*?>/m
class << self
def try_coerce_encoding(input, encoding:)
original_encoding = input.encoding
if input.encoding != encoding
input.force_encoding(encoding)
unless input.valid_encoding?
input.force_encoding(original_encoding)
end
end
input
end
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Returns the size of a string or array.
# @liquid_description
# The size of a string is the number of characters that the string includes. The size of an array is the number of items
# in the array.
# @liquid_syntax variable | size
# @liquid_return [number]
def size(input)
input.respond_to?(:size) ? input.size : 0
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Converts a string to all lowercase characters.
# @liquid_syntax string | downcase
# @liquid_return [string]
def downcase(input)
Utils.to_s(input).downcase
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Converts a string to all uppercase characters.
# @liquid_syntax string | upcase
# @liquid_return [string]
def upcase(input)
Utils.to_s(input).upcase
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Capitalizes the first word in a string and downcases the remaining characters.
# @liquid_syntax string | capitalize
# @liquid_return [string]
def capitalize(input)
Utils.to_s(input).capitalize
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# 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.".
# @liquid_syntax string | escape
# @liquid_return [string]
def escape(input)
CGI.escapeHTML(Utils.to_s(input)) unless input.nil?
end
alias_method :h, :escape
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Escapes a string without changing characters that have already been escaped.
# @liquid_syntax string | escape_once
# @liquid_return [string]
def escape_once(input)
Utils.to_s(input).gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Converts any URL-unsafe characters in a string to the
# [percent-encoded](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding) equivalent.
# @liquid_description
# > Note:
# > Spaces are converted to a `+` character, instead of a percent-encoded character.
# @liquid_syntax string | url_encode
# @liquid_return [string]
def url_encode(input)
CGI.escape(Utils.to_s(input)) unless input.nil?
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Decodes any [percent-encoded](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding) characters
# in a string.
# @liquid_syntax string | url_decode
# @liquid_return [string]
def url_decode(input)
return if input.nil?
result = CGI.unescape(Utils.to_s(input))
raise Liquid::ArgumentError, "invalid byte sequence in #{result.encoding}" unless result.valid_encoding?
result
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Encodes a string to [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).
# @liquid_syntax string | base64_encode
# @liquid_return [string]
def base64_encode(input)
Base64.strict_encode64(Utils.to_s(input))
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Decodes a string in [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).
# @liquid_syntax string | base64_decode
# @liquid_return [string]
def base64_decode(input)
input = Utils.to_s(input)
StandardFilters.try_coerce_encoding(Base64.strict_decode64(input), encoding: input.encoding)
rescue ::ArgumentError
raise Liquid::ArgumentError, "invalid base64 provided to base64_decode"
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Encodes a string to URL-safe [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).
# @liquid_syntax string | base64_url_safe_encode
# @liquid_return [string]
def base64_url_safe_encode(input)
Base64.urlsafe_encode64(Utils.to_s(input))
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Decodes a string in URL-safe [Base64 format](https://developer.mozilla.org/en-US/docs/Glossary/Base64).
# @liquid_syntax string | base64_url_safe_decode
# @liquid_return [string]
def base64_url_safe_decode(input)
input = Utils.to_s(input)
StandardFilters.try_coerce_encoding(Base64.urlsafe_decode64(input), encoding: input.encoding)
rescue ::ArgumentError
raise Liquid::ArgumentError, "invalid base64 provided to base64_url_safe_decode"
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Returns a substring or series of array items, starting at a given 0-based index.
# @liquid_description
# By default, the substring has a length of one character, and the array series has one array item. However, you can
# provide a second parameter to specify the number of characters or array items.
# @liquid_syntax string | slice
# @liquid_return [string]
def slice(input, offset, length = nil)
offset = Utils.to_integer(offset)
length = length ? Utils.to_integer(length) : 1
begin
if input.is_a?(Array)
input.slice(offset, length) || []
else
Utils.to_s(input).slice(offset, length) || ''
end
rescue RangeError
if I64_RANGE.cover?(length) && I64_RANGE.cover?(offset)
raise # unexpected error
end
offset = offset.clamp(I64_RANGE)
length = length.clamp(I64_RANGE)
retry
end
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Truncates a string down to a given number of characters.
# @liquid_description
# If the specified number of characters is less than the length of the string, then an ellipsis (`...`) is appended to
# the truncated string. The ellipsis is included in the character count of the truncated string.
# @liquid_syntax string | truncate: number
# @liquid_return [string]
def truncate(input, length = 50, truncate_string = "...")
return if input.nil?
input_str = Utils.to_s(input)
length = Utils.to_integer(length)
truncate_string_str = Utils.to_s(truncate_string)
l = length - truncate_string_str.length
l = 0 if l < 0
input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Truncates a string down to a given number of words.
# @liquid_description
# If the specified number of words is less than the number of words in the string, then an ellipsis (`...`) is appended to
# the truncated string.
#
# > Caution:
# > HTML tags are treated as words, so you should strip any HTML from truncated content. If you don't strip HTML, then
# > closing HTML tags can be removed, which can result in unexpected behavior.
# @liquid_syntax string | truncatewords: number
# @liquid_return [string]
def truncatewords(input, words = 15, truncate_string = "...")
return if input.nil?
input = Utils.to_s(input)
words = Utils.to_integer(words)
words = 1 if words <= 0
wordlist = begin
input.split(" ", words + 1)
rescue RangeError
# integer too big for String#split, but we can semantically assume no truncation is needed
return input if words + 1 > MAX_I32
raise # unexpected error
end
return input if wordlist.length <= words
wordlist.pop
truncate_string = Utils.to_s(truncate_string)
wordlist.join(" ").concat(truncate_string)
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Splits a string into an array of substrings based on a given separator.
# @liquid_syntax string | split: string
# @liquid_return [array[string]]
def split(input, pattern)
pattern = Utils.to_s(pattern)
input = Utils.to_s(input)
input.split(pattern)
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Removes leading and trailing whitespace and collapses consecutive whitespace to a single space.
# @liquid_syntax string | squish
# @liquid_return [string]
def squish(input)
return if input.nil?
Utils.to_s(input).strip.gsub(/\s+/, ' ')
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Strips all whitespace from the left and right of a string.
# @liquid_syntax string | strip
# @liquid_return [string]
def strip(input)
input = Utils.to_s(input)
input.strip
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Strips all whitespace from the left of a string.
# @liquid_syntax string | lstrip
# @liquid_return [string]
def lstrip(input)
input = Utils.to_s(input)
input.lstrip
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Strips all whitespace from the right of a string.
# @liquid_syntax string | rstrip
# @liquid_return [string]
def rstrip(input)
input = Utils.to_s(input)
input.rstrip
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Strips all HTML tags from a string.
# @liquid_syntax string | strip_html
# @liquid_return [string]
def strip_html(input)
input = Utils.to_s(input)
empty = ''
result = input.gsub(STRIP_HTML_BLOCKS, empty)
result.gsub!(STRIP_HTML_TAGS, empty)
result
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Strips all newline characters (line breaks) from a string.
# @liquid_syntax string | strip_newlines
# @liquid_return [string]
def strip_newlines(input)
input = Utils.to_s(input)
input.gsub(/\r?\n/, '')
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Combines all of the items in an array into a single string, separated by a space.
# @liquid_syntax array | join
# @liquid_return [string]
def join(input, glue = ' ')
glue = Utils.to_s(glue)
InputIterator.new(input, context).join(glue)
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Sorts the items in an array in case-sensitive alphabetical, or numerical, order.
# @liquid_syntax array | sort
# @liquid_return [array[untyped]]
def sort(input, property = nil)
ary = InputIterator.new(input, context)
return [] if ary.empty?
if property.nil?
ary.sort do |a, b|
nil_safe_compare(a, b)
end
elsif ary.all? { |el| el.respond_to?(:[]) }
begin
ary.sort { |a, b| nil_safe_compare(a[property], b[property]) }
rescue TypeError
raise_property_error(property)
end
end
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Sorts the items in an array in case-insensitive alphabetical order.
# @liquid_description
# > Caution:
# > You shouldn't use the `sort_natural` filter to sort numerical values. When comparing items an array, each item is converted to a
# > string, so sorting on numerical values can lead to unexpected results.
# @liquid_syntax array | sort_natural
# @liquid_return [array[untyped]]
def sort_natural(input, property = nil)
ary = InputIterator.new(input, context)
return [] if ary.empty?
if property.nil?
ary.sort do |a, b|
nil_safe_casecmp(a, b)
end
elsif ary.all? { |el| el.respond_to?(:[]) }
begin
ary.sort { |a, b| nil_safe_casecmp(a[property], b[property]) }
rescue TypeError
raise_property_error(property)
end
end
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Filters an array to include only items with a specific property value.
# @liquid_description
# This requires you to provide both the property name and the associated value.
# @liquid_syntax array | where: string, string
# @liquid_return [array[untyped]]
def where(input, property, target_value = nil)
filter_array(input, property, target_value) { |ary, &block| ary.select(&block) }
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Filters an array to exclude items with a specific property value.
# @liquid_description
# This requires you to provide both the property name and the associated value.
# @liquid_syntax array | reject: string, string
# @liquid_return [array[untyped]]
def reject(input, property, target_value = nil)
filter_array(input, property, target_value) { |ary, &block| ary.reject(&block) }
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Tests if any item in an array has a specific property value.
# @liquid_description
# This requires you to provide both the property name and the associated value.
# @liquid_syntax array | has: string, string
# @liquid_return [boolean]
def has(input, property, target_value = nil)
filter_array(input, property, target_value, false) { |ary, &block| ary.any?(&block) }
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Returns the first item in an array with a specific property value.
# @liquid_description
# This requires you to provide both the property name and the associated value.
# @liquid_syntax array | find: string, string
# @liquid_return [untyped]
def find(input, property, target_value = nil)
filter_array(input, property, target_value, nil) { |ary, &block| ary.find(&block) }
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Returns the index of the first item in an array with a specific property value.
# @liquid_description
# This requires you to provide both the property name and the associated value.
# @liquid_syntax array | find_index: string, string
# @liquid_return [number]
def find_index(input, property, target_value = nil)
filter_array(input, property, target_value, nil) { |ary, &block| ary.find_index(&block) }
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Removes any duplicate items in an array.
# @liquid_syntax array | uniq
# @liquid_return [array[untyped]]
def uniq(input, property = nil)
ary = InputIterator.new(input, context)
if property.nil?
ary.uniq
elsif ary.empty? # The next two cases assume a non-empty array.
[]
else
ary.uniq do |item|
item[property]
rescue TypeError
raise_property_error(property)
rescue NoMethodError
return nil unless item.respond_to?(:[])
raise
end
end
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Reverses the order of the items in an array.
# @liquid_syntax array | reverse
# @liquid_return [array[untyped]]
def reverse(input)
ary = InputIterator.new(input, context)
ary.reverse
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Creates an array of values from a specific property of the items in an array.
# @liquid_syntax array | map: string
# @liquid_return [array[untyped]]
def map(input, property)
InputIterator.new(input, context).map do |e|
e = e.call if e.is_a?(Proc)
if property == "to_liquid"
e
elsif e.respond_to?(:[])
r = e[property]
r.is_a?(Proc) ? r.call : r
end
end
rescue TypeError
raise_property_error(property)
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Removes any `nil` items from an array.
# @liquid_syntax array | compact
# @liquid_return [array[untyped]]
def compact(input, property = nil)
ary = InputIterator.new(input, context)
if property.nil?
ary.compact
elsif ary.empty? # The next two cases assume a non-empty array.
[]
else
ary.reject do |item|
item[property].nil?
rescue TypeError
raise_property_error(property)
rescue NoMethodError
return nil unless item.respond_to?(:[])
raise
end
end
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Replaces any instance of a substring inside a string with a given string.
# @liquid_syntax string | replace: string, string
# @liquid_return [string]
def replace(input, string, replacement = '')
string = Utils.to_s(string)
replacement = Utils.to_s(replacement)
input = Utils.to_s(input)
input.gsub(string, replacement)
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Replaces the first instance of a substring inside a string with a given string.
# @liquid_syntax string | replace_first: string, string
# @liquid_return [string]
def replace_first(input, string, replacement = '')
string = Utils.to_s(string)
replacement = Utils.to_s(replacement)
input = Utils.to_s(input)
input.sub(string, replacement)
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Replaces the last instance of a substring inside a string with a given string.
# @liquid_syntax string | replace_last: string, string
# @liquid_return [string]
def replace_last(input, string, replacement)
input = Utils.to_s(input)
string = Utils.to_s(string)
replacement = Utils.to_s(replacement)
start_index = input.rindex(string)
return input unless start_index
output = input.dup
output[start_index, string.length] = replacement
output
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Removes any instance of a substring inside a string.
# @liquid_syntax string | remove: string
# @liquid_return [string]
def remove(input, string)
replace(input, string, '')
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Removes the first instance of a substring inside a string.
# @liquid_syntax string | remove_first: string
# @liquid_return [string]
def remove_first(input, string)
replace_first(input, string, '')
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Removes the last instance of a substring inside a string.
# @liquid_syntax string | remove_last: string
# @liquid_return [string]
def remove_last(input, string)
replace_last(input, string, '')
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Adds a given string to the end of a string.
# @liquid_syntax string | append: string
# @liquid_return [string]
def append(input, string)
input = Utils.to_s(input)
string = Utils.to_s(string)
input + string
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Concatenates (combines) two arrays.
# @liquid_description
# > Note:
# > The `concat` filter won't filter out duplicates. If you want to remove duplicates, then you need to use the
# > [`uniq` filter](/docs/api/liquid/filters/uniq).
# @liquid_syntax array | concat: array
# @liquid_return [array[untyped]]
def concat(input, array)
unless array.respond_to?(:to_ary)
raise ArgumentError, "concat filter requires an array argument"
end
InputIterator.new(input, context).concat(array)
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Adds a given string to the beginning of a string.
# @liquid_syntax string | prepend: string
# @liquid_return [string]
def prepend(input, string)
input = Utils.to_s(input)
string = Utils.to_s(string)
string + input
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category string
# @liquid_summary
# Converts newlines (`\n`) in a string to HTML line breaks (`<br>`).
# @liquid_syntax string | newline_to_br
# @liquid_return [string]
def newline_to_br(input)
input = Utils.to_s(input)
input.gsub(/\r?\n/, "<br />\n")
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category date
# @liquid_summary
# Formats a date according to a specified format string.
# @liquid_description
# This filter formats a date using various format specifiers. If the format string is empty,
# the original input is returned. If the input cannot be converted to a date, the original input is returned.
#
# The following format specifiers can be used:
#
# %a - The abbreviated weekday name (``Sun'')
# %A - The full weekday name (``Sunday'')
# %b - The abbreviated month name (``Jan'')
# %B - The full month name (``January'')
# %c - The preferred local date and time representation
# %d - Day of the month (01..31)
# %H - Hour of the day, 24-hour clock (00..23)
# %I - Hour of the day, 12-hour clock (01..12)
# %j - Day of the year (001..366)
# %m - Month of the year (01..12)
# %M - Minute of the hour (00..59)
# %p - Meridian indicator (``AM'' or ``PM'')
# %s - Number of seconds since 1970-01-01 00:00:00 UTC.
# %S - Second of the minute (00..60)
# %U - Week number of the current year,
# starting with the first Sunday as the first
# day of the first week (00..53)
# %W - Week number of the current year,
# starting with the first Monday as the first
# day of the first week (00..53)
# %w - Day of the week (Sunday is 0, 0..6)
# %x - Preferred representation for the date alone, no time
# %X - Preferred representation for the time alone, no date
# %y - Year without a century (00..99)
# %Y - Year with century
# %Z - Time zone name
# %% - Literal ``%'' character
# @liquid_syntax date | date: string
# @liquid_return [string]
def date(input, format)
str_format = Utils.to_s(format)
return input if str_format.empty?
return input unless (date = Utils.to_date(input))
date.strftime(str_format)
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Returns the first item in an array.
# @liquid_syntax array | first
# @liquid_return [untyped]
def first(array)
# ActiveSupport returns "" for empty strings, not nil
return array[0] || "" if array.is_a?(String)
array.first if array.respond_to?(:first)
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Returns the last item in an array.
# @liquid_syntax array | last
# @liquid_return [untyped]
def last(array)
# ActiveSupport returns "" for empty strings, not nil
return array[-1] || "" if array.is_a?(String)
array.last if array.respond_to?(:last)
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category math
# @liquid_summary
# Returns the absolute value of a number.
# @liquid_syntax number | abs
# @liquid_return [number]
def abs(input)
result = Utils.to_number(input).abs
result.is_a?(BigDecimal) ? result.to_f : result
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category math
# @liquid_summary
# Adds two numbers.
# @liquid_syntax number | plus: number
# @liquid_return [number]
def plus(input, operand)
apply_operation(input, operand, :+)
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category math
# @liquid_summary
# Subtracts a given number from another number.
# @liquid_syntax number | minus: number
# @liquid_return [number]
def minus(input, operand)
apply_operation(input, operand, :-)
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category math
# @liquid_summary
# Multiplies a number by a given number.
# @liquid_syntax number | times: number
# @liquid_return [number]
def times(input, operand)
apply_operation(input, operand, :*)
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category math
# @liquid_summary
# 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.
# @liquid_syntax number | divided_by: number
# @liquid_return [number]
def divided_by(input, operand)
apply_operation(input, operand, :/)
rescue ::ZeroDivisionError => e
raise Liquid::ZeroDivisionError, e.message
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category math
# @liquid_summary
# Returns the remainder of dividing a number by a given number.
# @liquid_syntax number | modulo: number
# @liquid_return [number]
def modulo(input, operand)
apply_operation(input, operand, :%)
rescue ::ZeroDivisionError => e
raise Liquid::ZeroDivisionError, e.message
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category math
# @liquid_summary
# Rounds a number to the nearest integer.
# @liquid_syntax number | round
# @liquid_return [number]
def round(input, n = 0)
result = Utils.to_number(input).round(Utils.to_number(n))
result = result.to_f if result.is_a?(BigDecimal)
result = result.to_i if n == 0
result
rescue ::FloatDomainError => e
raise Liquid::FloatDomainError, e.message
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category math
# @liquid_summary
# Rounds a number up to the nearest integer.
# @liquid_syntax number | ceil
# @liquid_return [number]
def ceil(input)
Utils.to_number(input).ceil.to_i
rescue ::FloatDomainError => e
raise Liquid::FloatDomainError, e.message
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category math
# @liquid_summary
# Rounds a number down to the nearest integer.
# @liquid_syntax number | floor
# @liquid_return [number]
def floor(input)
Utils.to_number(input).floor.to_i
rescue ::FloatDomainError => e
raise Liquid::FloatDomainError, e.message
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category math
# @liquid_summary
# Limits a number to a minimum value.
# @liquid_syntax number | at_least
# @liquid_return [number]
def at_least(input, n)
min_value = Utils.to_number(n)
result = Utils.to_number(input)
result = min_value if min_value > result
result.is_a?(BigDecimal) ? result.to_f : result
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category math
# @liquid_summary
# Limits a number to a maximum value.
# @liquid_syntax number | at_most
# @liquid_return [number]
def at_most(input, n)
max_value = Utils.to_number(n)
result = Utils.to_number(input)
result = max_value if max_value < result
result.is_a?(BigDecimal) ? result.to_f : result
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category default
# @liquid_summary
# Sets a default value for any variable whose value is one of the following:
#
# - [`empty`](/docs/api/liquid/basics#empty)
# - [`false`](/docs/api/liquid/basics#truthy-and-falsy)
# - [`nil`](/docs/api/liquid/basics#nil)
# @liquid_syntax variable | default: variable
# @liquid_return [untyped]
# @liquid_optional_param allow_false: [boolean] Whether to use false values instead of the default.
def default(input, default_value = '', options = {})
options = {} unless options.is_a?(Hash)
false_check = options['allow_false'] ? input.nil? : !Liquid::Utils.to_liquid_value(input)
false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
end
# @liquid_public_docs
# @liquid_type filter
# @liquid_category array
# @liquid_summary
# Returns the sum of all elements in an array.
# @liquid_syntax array | sum
# @liquid_return [number]
def sum(input, property = nil)
ary = InputIterator.new(input, context)
return 0 if ary.empty?
values_for_sum = ary.map do |item|
if property.nil?
item
elsif item.respond_to?(:[])
item[property]
else
0
end
rescue TypeError
raise_property_error(property)
end
result = InputIterator.new(values_for_sum, context).sum do |item|
Utils.to_number(item)
end
result.is_a?(BigDecimal) ? result.to_f : result
end
private
attr_reader :context
def filter_array(input, property, target_value, default_value = [], &block)
ary = InputIterator.new(input, context)
return default_value if ary.empty?
block.call(ary) do |item|
if target_value.nil?
item[property]
else
item[property] == target_value
end
rescue TypeError
raise_property_error(property)
rescue NoMethodError
return nil unless item.respond_to?(:[])
raise
end
end
def raise_property_error(property)
raise Liquid::ArgumentError, "cannot select the property '#{Utils.to_s(property)}'"
end
def apply_operation(input, operand, operation)
result = Utils.to_number(input).send(operation, Utils.to_number(operand))
result.is_a?(BigDecimal) ? result.to_f : result
end
def nil_safe_compare(a, b)
result = a <=> b
if result
result
elsif a.nil?
1
elsif b.nil?
-1
else
raise Liquid::ArgumentError, "cannot sort values of incompatible types"
end
end
def nil_safe_casecmp(a, b)
if !a.nil? && !b.nil?
a.to_s.casecmp(b.to_s)
elsif a.nil? && b.nil?
0
else
a.nil? ? 1 : -1
end
end
class InputIterator
include Enumerable
def initialize(input, context)
@context = context
@input = if input.is_a?(Array)
input.flatten
elsif input.is_a?(Hash)
[input]
elsif input.is_a?(Enumerable)
input
else
Array(input)
end
end
def join(glue)
first = true
output = +""
each do |item|
if first
first = false
else
output << glue
end
output << Liquid::Utils.to_s(item)
end
output
end
def concat(args)
to_a.concat(args)
end
def reverse
reverse_each.to_a
end
def uniq(&block)
to_a.uniq do |item|
item = Utils.to_liquid_value(item)
block ? yield(item) : item
end
end
def compact
to_a.compact
end
def empty?
@input.each { return false }
true
end
def each
@input.each do |e|
e = e.respond_to?(:to_liquid) ? e.to_liquid : e
e.context = @context if e.respond_to?(:context=)
yield(e)
end
end
end
end
end
================================================
FILE: lib/liquid/strainer_template.rb
================================================
# frozen_string_literal: true
require 'set'
module Liquid
# StrainerTemplate is the computed class for the filters system.
# New filters are mixed into the strainer class which is then instantiated for each liquid template render run.
#
# The Strainer only allows method calls defined in filters given to it via StrainerFactory.add_global_filter,
# Context#add_filters or Template.register_filter
class StrainerTemplate
def initialize(context)
@context = context
end
class << self
def add_filter(filter)
return if include?(filter)
invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) }
if invokable_non_public_methods.any?
raise MethodOverrideError, "Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}"
end
include(filter)
filter_methods.merge(filter.public_instance_methods.map(&:to_s))
end
def invokable?(method)
filter_methods.include?(method.to_s)
end
def inherited(subclass)
super
subclass.instance_variable_set(:@filter_methods, @filter_methods.dup)
end
def filter_method_names
filter_methods.map(&:to_s).to_a
end
private
def filter_methods
@filter_methods ||= Set.new
end
end
def invoke(method, *args)
if self.class.invokable?(method)
send(method, *args)
elsif @context.strict_filters
raise Liquid::UndefinedFilter, "undefined filter #{method}"
else
args.first
end
rescue ::ArgumentError => e
raise Liquid::ArgumentError, e.message, e.backtrace
end
end
end
================================================
FILE: lib/liquid/tablerowloop_drop.rb
================================================
# frozen_string_literal: true
module Liquid
# @liquid_public_docs
# @liquid_type object
# @liquid_name tablerowloop
# @liquid_summary
# Information about a parent [`tablerow` loop](/docs/api/liquid/tags/tablerow).
class TablerowloopDrop < Drop
def initialize(length, cols)
@length = length
@row = 1
@col = 1
@cols = cols
@index = 0
end
# @liquid_public_docs
# @liquid_summary
# The total number of iterations in the loop.
# @liquid_return [number]
attr_reader :length
# @liquid_public_docs
# @liquid_summary
# The 1-based index of the current column.
# @liquid_return [number]
attr_reader :col
# @liquid_public_docs
# @liquid_summary
# The 1-based index of current row.
# @liquid_return [number]
attr_reader :row
# @liquid_public_docs
# @liquid_summary
# The 1-based index of the current iteration.
# @liquid_return [number]
def index
@index + 1
end
# @liquid_public_docs
# @liquid_summary
# The 0-based index of the current iteration.
# @liquid_return [number]
def index0
@index
end
# @liquid_public_docs
# @liquid_summary
# The 0-based index of the current column.
# @liquid_return [number]
def col0
@col - 1
end
# @liquid_public_docs
# @liquid_summary
# The 1-based index of the current iteration, in reverse order.
# @liquid_return [number]
def rindex
@length - @index
end
# @liquid_public_docs
# @liquid_summary
# The 0-based index of the current iteration, in reverse order.
# @liquid_return [number]
def rindex0
@length - @index - 1
end
# @liquid_public_docs
# @liquid_summary
# Returns `true` if the current iteration is the first. Returns `false` if not.
# @liquid_return [boolean]
def first
@index == 0
end
# @liquid_public_docs
# @liquid_summary
# Returns `true` if the current iteration is the last. Returns `false` if not.
# @liquid_return [boolean]
def last
@index == @length - 1
end
# @liquid_public_docs
# @liquid_summary
# Returns `true` if the current column is the first in the row. Returns `false` if not.
# @liquid_return [boolean]
def col_first
@col == 1
end
# @liquid_public_docs
# @liquid_summary
# Returns `true` if the current column is the last in the row. Returns `false` if not.
# @liquid_return [boolean]
def col_last
@col == @cols
end
protected
def increment!
@index += 1
if @col == @cols
@col = 1
@row += 1
else
@col += 1
end
end
end
end
================================================
FILE: lib/liquid/tag/disableable.rb
================================================
# frozen_string_literal: true
module Liquid
class Tag
module Disableable
def render_to_output_buffer(context, output)
if context.tag_disabled?(tag_name)
output << disabled_error(context)
return
end
super
end
def disabled_error(context)
# raise then rescue the exception so that the Context#exception_renderer can re-raise it
raise DisabledError, "#{tag_name} #{parse_context[:locale].t('errors.disabled.tag')}"
rescue DisabledError => exc
context.handle_error(exc, line_number)
end
end
end
end
================================================
FILE: lib/liquid/tag/disabler.rb
================================================
# frozen_string_literal: true
module Liquid
class Tag
module Disabler
def render_to_output_buffer(context, output)
context.with_disabled_tags(self.class.disabled_tags) do
super
end
end
end
end
end
================================================
FILE: lib/liquid/tag.rb
================================================
# frozen_string_literal: true
require 'liquid/tag/disabler'
require 'liquid/tag/disableable'
module Liquid
class Tag
attr_reader :nodelist, :tag_name, :line_number, :parse_context
alias_method :options, :parse_context
include ParserSwitching
class << self
def parse(tag_name, markup, tokenizer, parse_context)
tag = new(tag_name, markup, parse_context)
tag.parse(tokenizer)
tag
end
def disable_tags(*tag_names)
tag_names += disabled_tags
define_singleton_method(:disabled_tags) { tag_names }
prepend(Disabler)
end
private :new
protected
def disabled_tags
[]
end
end
def initialize(tag_name, markup, parse_context)
@tag_name = tag_name
@markup = markup
@parse_context = parse_context
@line_number = parse_context.line_number
end
def parse(_tokens)
end
def raw
"#{@tag_name} #{@markup}"
end
def name
self.class.name.downcase
end
def render(_context)
''
end
# For backwards compatibility with custom tags. In a future release, the semantics
# of the `render_to_output_buffer` method will become the default and the `render`
# method will be removed.
def render_to_output_buffer(context, output)
render_result = render(context)
output << render_result if render_result
output
end
def blank?
false
end
private
def safe_parse_expression(parser)
parse_context.safe_parse_expression(parser)
end
def parse_expression(markup, safe: false)
parse_context.parse_expression(markup, safe: safe)
end
end
end
================================================
FILE: lib/liquid/tags/assign.rb
================================================
# frozen_string_literal: true
module Liquid
# @liquid_public_docs
# @liquid_type tag
# @liquid_category variable
# @liquid_name assign
# @liquid_summary
# Creates a new variable.
# @liquid_description
# You can create variables of any [basic type](/docs/api/liquid/basics#types), [object](/docs/api/liquid/objects), or object property.
#
# > Caution:
# > Predefined Liquid objects can be overridden by variables with the same name.
# > To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.
# @liquid_syntax
# {% assign variable_name = value %}
# @liquid_syntax_keyword variable_name The name of the variable being created.
# @liquid_syntax_keyword value The value you want to assign to the variable.
class Assign < Tag
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
# @api private
def self.raise_syntax_error(parse_context)
raise Liquid::SyntaxError, parse_context.locale.t('errors.syntax.assign')
end
attr_reader :to, :from
def initialize(tag_name, markup, parse_context)
super
if markup =~ Syntax
@to = Regexp.last_match(1)
@from = Variable.new(Regexp.last_match(2), parse_context)
else
self.class.raise_syntax_error(parse_context)
end
end
def render_to_output_buffer(context, output)
val = @from.render(context)
context.scopes.last[@to] = val
context.resource_limits.increment_assign_score(assign_score_of(val))
output
end
def blank?
true
end
private
def assign_score_of(val)
if val.instance_of?(String)
val.bytesize
elsif val.instance_of?(Array)
sum = 1
# Uses #each to avoid extra allocations.
val.each { |child| sum += assign_score_of(child) }
sum
elsif val.instance_of?(Hash)
sum = 1
val.each do |key, entry_value|
sum += assign_score_of(key)
sum += assign_score_of(entry_value)
end
sum
else
1
end
end
class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children
[@node.from]
end
end
end
end
================================================
FILE: lib/liquid/tags/break.rb
================================================
# frozen_string_literal: true
module Liquid
# Break tag to be used to break out of a for loop.
#
# == Basic Usage:
# {% for item in collection %}
# {% if item.condition %}
# {% break %}
# {% endif %}
# {% endfor %}
#
# @liquid_public_docs
# @liquid_type tag
# @liquid_category iteration
# @liquid_name break
# @liquid_summary
# Stops a [`for` loop](/docs/api/liquid/tags/for) from iterating.
# @liquid_syntax
# {% break %}
class Break < Tag
INTERRUPT = BreakInterrupt.new.freeze
def render_to_output_buffer(context, output)
context.push_interrupt(INTERRUPT)
output
end
end
end
================================================
FILE: lib/liquid/tags/capture.rb
================================================
# frozen_string_literal: true
module Liquid
# @liquid_public_docs
# @liquid_type tag
# @liquid_category variable
# @liquid_name capture
# @liquid_summary
# Creates a new variable with a string value.
# @liquid_description
# You can create complex strings with Liquid logic and variables.
#
# > Caution:
# > Predefined Liquid objects can be overridden by variables with the same name.
# > To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.
# @liquid_syntax
# {% capture variable %}
# value
# {% endcapture %}
# @liquid_syntax_keyword variable The name of the variable being created.
# @liquid_syntax_keyword value The value you want to assign to the variable.
class Capture < Block
Syntax = /(#{VariableSignature}+)/o
def initialize(tag_name, markup, options)
super
if markup =~ Syntax
@to = Regexp.last_match(1)
else
raise SyntaxError, options[:locale].t("errors.syntax.capture")
end
end
def render_to_output_buffer(context, output)
context.resource_limits.with_capture do
capture_output = render(context)
context.scopes.last[@to] = capture_output
end
output
end
def blank?
true
end
end
end
================================================
FILE: lib/liquid/tags/case.rb
================================================
# frozen_string_literal: true
module Liquid
# @liquid_public_docs
# @liquid_type tag
# @liquid_category conditional
# @liquid_name case
# @liquid_summary
# Renders a specific expression depending on the value of a specific variable.
# @liquid_syntax
# {% case variable %}
# {% when first_value %}
# first_expression
# {% when second_value %}
# second_expression
# {% else %}
# third_expression
# {% endcase %}
# @liquid_syntax_keyword variable The name of the variable you want to base your case statement on.
# @liquid_syntax_keyword first_value A specific value to check for.
# @liquid_syntax_keyword second_value A specific value to check for.
# @liquid_syntax_keyword first_expression An expression to be rendered when the variable's value matches `first_value`.
# @liquid_syntax_keyword second_expression An expression to be rendered when the variable's value matches `second_value`.
# @liquid_syntax_keyword third_expression An expression to be rendered when the variable's value has no match.
class Case < Block
Syntax = /(#{QuotedFragment})/o
WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/om
attr_reader :blocks, :left
def initialize(tag_name, markup, options)
super
@blocks = []
parse_with_selected_parser(markup)
end
def parse(tokens)
body = case_body = new_body
body = @blocks.last.attachment while parse_body(body, tokens)
@blocks.reverse_each do |condition|
body = condition.attachment
unless body.frozen?
body.remove_blank_strings if blank?
body.freeze
end
end
case_body.freeze
end
def nodelist
@blocks.map(&:attachment)
end
def unknown_tag(tag, markup, tokens)
case tag
when 'when'
record_when_condition(markup)
when 'else'
record_else_condition(markup)
else
super
end
end
def render_to_output_buffer(context, output)
execute_else_block = true
@blocks.each do |block|
if block.else?
block.attachment.render_to_output_buffer(context, output) if execute_else_block
next
end
result = Liquid::Utils.to_liquid_value(
block.evaluate(context),
)
if result
execute_else_block = false
block.attachment.render_to_output_buffer(context, output)
end
end
output
end
private
def strict2_parse(markup)
parser = @parse_context.new_parser(markup)
@left = safe_parse_expression(parser)
parser.consume(:end_of_string)
end
def strict_parse(markup)
lax_parse(markup)
end
def lax_parse(markup)
if markup =~ Syntax
@left = parse_expression(Regexp.last_match(1))
else
raise SyntaxError, options[:locale].t("errors.syntax.case")
end
end
def record_when_condition(markup)
body = new_body
if strict2_mode?
parse_strict2_when(markup, body)
else
parse_lax_when(markup, body)
end
end
def parse_strict2_when(markup, body)
parser = @parse_context.new_parser(markup)
loop do
expr = Condition.parse_expression(parse_context, parser.expression, safe: true)
block = Condition.new(@left, '==', expr)
block.attach(body)
@blocks << block
break unless parser.id?('or') || parser.consume?(:comma)
end
parser.consume(:end_of_string)
end
def parse_lax_when(markup, body)
while markup
unless markup =~ WhenSyntax
raise SyntaxError, options[:locale].t("errors.syntax.case_invalid_when")
end
markup = Regexp.last_match(2)
block = Condition.new(@left, '==', Condition.parse_expression(parse_context, Regexp.last_match(1)))
block.attach(body)
@blocks << block
end
end
def record_else_condition(markup)
unless markup.strip.empty?
raise SyntaxError, options[:locale].t("errors.syntax.case_invalid_else")
end
block = ElseCondition.new
block.attach(new_body)
@blocks << block
end
class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children
[@node.left] + @node.blocks
end
end
end
end
================================================
FILE: lib/liquid/tags/comment.rb
================================================
# frozen_string_literal: true
module Liquid
# @liquid_public_docs
# @liquid_type tag
# @liquid_category syntax
# @liquid_name comment
# @liquid_summary
# Prevents an expression from being rendered or output.
# @liquid_description
# Any text inside `comment` tags won't be output, and any Liquid code will be parsed, but not executed.
# @liquid_syntax
# {% comment %}
# content
# {% endcomment %}
# @liquid_syntax_keyword content The content of the comment.
class Comment < Block
def render_to_output_buffer(_context, output)
output
end
def unknown_tag(_tag, _markup, _tokens)
end
def blank?
true
end
private
def parse_body(body, tokenizer)
if parse_context.depth >= MAX_DEPTH
raise StackLevelError, "Nesting too deep"
end
parse_context.depth += 1
comment_tag_depth = 1
begin
# Consume tokens without creating child nodes.
# The children tag doesn't require to be a valid Liquid except the comment and raw tag.
# The child comment and raw tag must be closed.
while (token = tokenizer.send(:shift))
tag_name = if tokenizer.for_liquid_tag
next if token.empty? || token.match?(BlockBody::WhitespaceOrNothing)
tag_name_match = BlockBody::LiquidTagToken.match(token)
next if tag_name_match.nil?
tag_name_match[1]
else
token =~ BlockBody::FullToken
Regexp.last_match(2)
end
case tag_name
when "raw"
parse_raw_tag_body(tokenizer)
when "comment"
comment_tag_depth += 1
when "endcomment"
comment_tag_depth -= 1
end
if comment_tag_depth.zero?
parse_context.trim_whitespace = (token[-3] == WhitespaceControl) unless tokenizer.for_liquid_tag
return false
end
end
raise_tag_never_closed(block_name)
ensure
parse_context.depth -= 1
end
false
end
def parse_raw_tag_body(tokenizer)
while (token = tokenizer.send(:shift))
return if token =~ BlockBody::FullTokenPossiblyInvalid && "endraw" == Regexp.last_match(2)
end
raise_tag_never_closed("raw")
end
end
end
================================================
FILE: lib/liquid/tags/continue.rb
================================================
# frozen_string_literal: true
module Liquid
# @liquid_public_docs
# @liquid_type tag
# @liquid_category iteration
# @liquid_name continue
# @liquid_summary
# Causes a [`for` loop](/docs/api/liquid/tags/for) to skip to the next iteration.
# @liquid_syntax
# {% continue %}
class Continue < Tag
INTERRUPT = ContinueInterrupt.new.freeze
def render_to_output_buffer(context, output)
context.push_interrupt(INTERRUPT)
output
end
end
end
================================================
FILE: lib/liquid/tags/cycle.rb
================================================
# frozen_string_literal: true
module Liquid
# @liquid_public_docs
# @liquid_type tag
# @liquid_category iteration
# @liquid_name cycle
# @liquid_summary
# 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).
# @liquid_description
# The `cycle` tag must be used inside a `for` loop.
#
# > Tip:
# > Use the `cycle` tag to output text in a predictable pattern. For example, to apply odd/even classes to rows in a table.
# @liquid_syntax
# {% cycle string, string, ... %}
class Cycle < Tag
SimpleSyntax = /\A#{QuotedFragment}+/o
NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
UNNAMED_CYCLE_PATTERN = /\w+:0x\h{8}/
attr_reader :variables
def initialize(tag_name, markup, options)
super
parse_with_selected_parser(markup)
end
def named?
@is_named
end
def render_to_output_buffer(context, output)
context.registers[:cycle] ||= {}
key = context.evaluate(@name)
iteration = context.registers[:cycle][key].to_i
val = context.evaluate(@variables[iteration])
if val.is_a?(Array)
val = val.join
elsif !val.is_a?(String)
val = val.to_s
end
output << val
iteration += 1
iteration = 0 if iteration >= @variables.size
context.registers[:cycle][key] = iteration
output
end
private
# cycle [name:] expression(, expression)*
def strict2_parse(markup)
p = @parse_context.new_parser(markup)
@variables = []
raise SyntaxError, options[:locale].t("errors.syntax.cycle") if p.look(:end_of_string)
first_expression = safe_parse_expression(p)
if p.look(:colon)
# cycle name: expr1, expr2, ...
@name = first_expression
@is_named = true
p.consume(:colon)
# After the colon, parse the first variable (required for named cycles)
@variables << maybe_dup_lookup(safe_parse_expression(p))
else
# cycle expr1, expr2, ...
@variables << maybe_dup_lookup(first_expression)
end
# Parse remaining comma-separated expressions
while p.consume?(:comma)
break if p.look(:end_of_string)
@variables << maybe_dup_lookup(safe_parse_expression(p))
end
p.consume(:end_of_string)
unless @is_named
@name = @variables.to_s
@is_named = !@name.match?(UNNAMED_CYCLE_PATTERN)
end
end
def strict_parse(markup)
lax_parse(markup)
end
def lax_parse(markup)
case markup
when NamedSyntax
@variables = variables_from_string(Regexp.last_match(2))
@name = parse_expression(Regexp.last_match(1))
@is_named = true
when SimpleSyntax
@variables = variables_from_string(markup)
@name = @variables.to_s
@is_named = !@name.match?(UNNAMED_CYCLE_PATTERN)
else
raise SyntaxError, options[:locale].t("errors.syntax.cycle")
end
end
def variables_from_string(markup)
markup.split(',').collect do |var|
var =~ /\s*(#{QuotedFragment})\s*/o
next unless Regexp.last_match(1)
var = parse_expression(Regexp.last_match(1))
maybe_dup_lookup(var)
end.compact
end
# For backwards compatibility, whenever a lookup is used in an unnamed cycle,
# we make it so that the @variables.to_s produces different strings for cycles
# called with the same arguments (since @variables.to_s is used as the cycle counter key)
# This makes it so {% cycle a, b %} and {% cycle a, b %} have independent counters even if a and b share value.
# This is not true for literal values, {% cycle "a", "b" %} and {% cycle "a", "b" %} share the same counter.
# I was really scratching my head about this one, but migrating away from this would be more headache
# than it's worth. So we're keeping this quirk for now.
def maybe_dup_lookup(var)
var.is_a?(VariableLookup) ? var.dup : var
end
class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children
Array(@node.variables)
end
end
end
end
================================================
FILE: lib/liquid/tags/decrement.rb
================================================
# frozen_string_literal: true
module Liquid
# @liquid_public_docs
# @liquid_type tag
# @liquid_category variable
# @liquid_name decrement
# @liquid_summary
# Creates a new variable, with a default value of -1, that's decreased by 1 with each subsequent call.
#
# > Caution:
# > Predefined Liquid objects can be overridden by variables with the same name.
# > To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.
# @liquid_description
# Variables that are declared with `decrement` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates),
# or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
# [snippets](/themes/architecture/snippets) included in the file.
#
# Similarly, variables that are created with `decrement` are independent from those created with [`assign`](/docs/api/liquid/tags/assign)
# and [`capture`](/docs/api/liquid/tags/capture). However, `decrement` and [`increment`](/docs/api/liquid/tags/increment) share
# variables.
# @liquid_syntax
# {% decrement variable_name %}
# @liquid_syntax_keyword variable_name The name of the variable being decremented.
class Decrement < Tag
attr_reader :variable_name
def initialize(tag_name, markup, options)
super
@variable_name = markup.strip
end
def render_to_output_buffer(context, output)
counter_environment = context.environments.first
value = counter_environment[@variable_name] || 0
value -= 1
counter_environment[@variable_name] = value
output << value.to_s
output
end
end
end
================================================
FILE: lib/liquid/tags/doc.rb
================================================
# frozen_string_literal: true
module Liquid
# @liquid_public_docs
# @liquid_type tag
# @liquid_category syntax
# @liquid_name doc
# @liquid_summary
# Documents template elements with annotations.
# @liquid_description
# The `doc` tag allows developers to include documentation within Liquid
# templates. Any content inside `doc` tags is not rendered or outputted.
# Liquid code inside will be parsed but not executed. This facilitates
# tooling support for features like code completion, linting, and inline
# documentation.
#
# For detailed documentation syntax and examples, see the
# [`LiquidDoc` reference](/docs/storefronts/themes/tools/liquid-doc).
#
# @liquid_syntax
# {% doc %}
# Renders a message.
#
# @param {string} foo - A string value.
# @param {string} [bar] - An optional string value.
#
# @example
# {% render 'message', foo: 'Hello', bar: 'World' %}
# {% enddoc %}
class Doc < Block
NO_UNEXPECTED_ARGS = /\A\s*\z/
def initialize(tag_name, markup, parse_context)
super
ensure_valid_markup(tag_name, markup, parse_context)
end
def parse(tokens)
@body = +""
while (token = tokens.shift)
tag_name = token =~ BlockBody::FullTokenPossiblyInvalid && Regexp.last_match(2)
raise_nested_doc_error if tag_name == @tag_name
if tag_name == block_delimiter
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
@body << Regexp.last_match(1) if Regexp.last_match(1) != ""
return
end
@body << token unless token.empty?
end
raise_tag_never_closed(block_name)
end
def render_to_output_buffer(_context, output)
output
end
def blank?
@body.empty?
end
def nodelist
[@body]
end
private
def ensure_valid_markup(tag_name, markup, parse_context)
unless NO_UNEXPECTED_ARGS.match?(markup)
raise SyntaxError, parse_context.locale.t("errors.syntax.block_tag_unexpected_args", tag: tag_name)
end
end
def raise_nested_doc_error
raise SyntaxError, parse_context.locale.t("errors.syntax.doc_invalid_nested")
end
end
end
================================================
FILE: lib/liquid/tags/echo.rb
================================================
# frozen_string_literal: true
module Liquid
# @liquid_public_docs
# @liquid_type tag
# @liquid_category syntax
# @liquid_name echo
# @liquid_summary
# Outputs an expression.
# @liquid_description
# Using the `echo` tag is the same as wrapping an expression in curly brackets (`{{` and `}}`). However, unlike the curly
# bracket method, you can use the `echo` tag inside [`liquid` tags](/docs/api/liquid/tags/liquid).
#
# > Tip:
# > You can use [filters](/docs/api/liquid/filters) on expressions inside `echo` tags.
# @liquid_syntax
# {% liquid
# echo expression
# %}
# @liquid_syntax_keyword expression The expression to be output.
class Echo < Tag
attr_reader :variable
def initialize(tag_name, markup, parse_context)
super
@variable = Variable.new(markup, parse_context)
end
def render(context)
@variable.render_to_output_buffer(context, +'')
end
class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children
[@node.variable]
end
end
end
end
================================================
FILE: lib/liquid/tags/for.rb
================================================
# frozen_string_literal: true
module Liquid
# @liquid_public_docs
# @liquid_type tag
# @liquid_category iteration
# @liquid_name for
# @liquid_summary
# Renders an expression for every item in an array.
# @liquid_description
# 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
# [`paginate` tag](/docs/api/liquid/tags/paginate) to split the items over multiple pages.
#
# > Tip:
# > Every `for` loop has an associated [`forloop` object](/docs/api/liquid/objects/forloop) with information about the loop.
# @liquid_syntax
# {% for variable in array %}
# expression
# {% endfor %}
# @liquid_syntax_keyword variable The current item in the array.
# @liquid_syntax_keyword array The array to iterate over.
# @liquid_syntax_keyword expression The expression to render for each iteration.
# @liquid_optional_param limit: [number] The number of iterations to perform.
# @liquid_optional_param offset: [number] The 1-based index to start iterating at.
# @liquid_optional_param range [untyped] A custom numeric range to iterate over.
# @liquid_optional_param reversed [untyped] Iterate in reverse order.
class For < Block
Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{
gitextract_1on2h7q1/
├── .github/
│ ├── dependabot.yaml
│ └── workflows/
│ ├── cla.yml
│ └── liquid.yml
├── .gitignore
├── .rubocop.yml
├── .rubocop_todo.yml
├── .ruby-version
├── CONTRIBUTING.md
├── Gemfile
├── History.md
├── LICENSE
├── README.md
├── Rakefile
├── bin/
│ └── render
├── example/
│ └── server/
│ ├── example_servlet.rb
│ ├── liquid_servlet.rb
│ ├── server.rb
│ └── templates/
│ ├── index.liquid
│ └── products.liquid
├── lib/
│ ├── liquid/
│ │ ├── block.rb
│ │ ├── block_body.rb
│ │ ├── condition.rb
│ │ ├── const.rb
│ │ ├── context.rb
│ │ ├── deprecations.rb
│ │ ├── document.rb
│ │ ├── drop.rb
│ │ ├── environment.rb
│ │ ├── errors.rb
│ │ ├── expression.rb
│ │ ├── extensions.rb
│ │ ├── file_system.rb
│ │ ├── forloop_drop.rb
│ │ ├── i18n.rb
│ │ ├── interrupts.rb
│ │ ├── lexer.rb
│ │ ├── locales/
│ │ │ └── en.yml
│ │ ├── parse_context.rb
│ │ ├── parse_tree_visitor.rb
│ │ ├── parser.rb
│ │ ├── parser_switching.rb
│ │ ├── partial_cache.rb
│ │ ├── profiler/
│ │ │ └── hooks.rb
│ │ ├── profiler.rb
│ │ ├── range_lookup.rb
│ │ ├── registers.rb
│ │ ├── resource_limits.rb
│ │ ├── standardfilters.rb
│ │ ├── strainer_template.rb
│ │ ├── tablerowloop_drop.rb
│ │ ├── tag/
│ │ │ ├── disableable.rb
│ │ │ └── disabler.rb
│ │ ├── tag.rb
│ │ ├── tags/
│ │ │ ├── assign.rb
│ │ │ ├── break.rb
│ │ │ ├── capture.rb
│ │ │ ├── case.rb
│ │ │ ├── comment.rb
│ │ │ ├── continue.rb
│ │ │ ├── cycle.rb
│ │ │ ├── decrement.rb
│ │ │ ├── doc.rb
│ │ │ ├── echo.rb
│ │ │ ├── for.rb
│ │ │ ├── if.rb
│ │ │ ├── ifchanged.rb
│ │ │ ├── include.rb
│ │ │ ├── increment.rb
│ │ │ ├── inline_comment.rb
│ │ │ ├── raw.rb
│ │ │ ├── render.rb
│ │ │ ├── table_row.rb
│ │ │ └── unless.rb
│ │ ├── tags.rb
│ │ ├── template.rb
│ │ ├── template_factory.rb
│ │ ├── tokenizer.rb
│ │ ├── usage.rb
│ │ ├── utils.rb
│ │ ├── variable.rb
│ │ ├── variable_lookup.rb
│ │ └── version.rb
│ └── liquid.rb
├── liquid.gemspec
├── performance/
│ ├── benchmark.rb
│ ├── memory_profile.rb
│ ├── profile.rb
│ ├── shopify/
│ │ ├── comment_form.rb
│ │ ├── database.rb
│ │ ├── json_filter.rb
│ │ ├── liquid.rb
│ │ ├── money_filter.rb
│ │ ├── paginate.rb
│ │ ├── shop_filter.rb
│ │ ├── tag_filter.rb
│ │ ├── vision.database.yml
│ │ └── weight_filter.rb
│ ├── tests/
│ │ ├── dropify/
│ │ │ ├── article.liquid
│ │ │ ├── blog.liquid
│ │ │ ├── cart.liquid
│ │ │ ├── collection.liquid
│ │ │ ├── index.liquid
│ │ │ ├── page.liquid
│ │ │ ├── product.liquid
│ │ │ └── theme.liquid
│ │ ├── ripen/
│ │ │ ├── article.liquid
│ │ │ ├── blog.liquid
│ │ │ ├── cart.liquid
│ │ │ ├── collection.liquid
│ │ │ ├── index.liquid
│ │ │ ├── page.liquid
│ │ │ ├── product.liquid
│ │ │ └── theme.liquid
│ │ ├── tribble/
│ │ │ ├── 404.liquid
│ │ │ ├── article.liquid
│ │ │ ├── blog.liquid
│ │ │ ├── cart.liquid
│ │ │ ├── collection.liquid
│ │ │ ├── index.liquid
│ │ │ ├── page.liquid
│ │ │ ├── product.liquid
│ │ │ ├── search.liquid
│ │ │ └── theme.liquid
│ │ └── vogue/
│ │ ├── article.liquid
│ │ ├── blog.liquid
│ │ ├── cart.liquid
│ │ ├── collection.liquid
│ │ ├── index.liquid
│ │ ├── page.liquid
│ │ ├── product.liquid
│ │ └── theme.liquid
│ ├── theme_runner.rb
│ └── unit/
│ ├── expression_benchmark.rb
│ └── lexer_benchmark.rb
├── spec/
│ ├── ruby_liquid.rb
│ ├── ruby_liquid_lax.rb
│ ├── ruby_liquid_with_active_support.rb
│ └── ruby_liquid_yjit.rb
└── test/
├── fixtures/
│ └── en_locale.yml
├── integration/
│ ├── assign_test.rb
│ ├── blank_test.rb
│ ├── block_test.rb
│ ├── capture_test.rb
│ ├── context_test.rb
│ ├── document_test.rb
│ ├── drop_test.rb
│ ├── error_handling_test.rb
│ ├── expression_test.rb
│ ├── filter_kwarg_test.rb
│ ├── filter_test.rb
│ ├── hash_ordering_test.rb
│ ├── hash_rendering_test.rb
│ ├── output_test.rb
│ ├── parsing_quirks_test.rb
│ ├── profiler_test.rb
│ ├── security_test.rb
│ ├── standard_filter_test.rb
│ ├── tag/
│ │ └── disableable_test.rb
│ ├── tag_test.rb
│ ├── tags/
│ │ ├── break_tag_test.rb
│ │ ├── continue_tag_test.rb
│ │ ├── cycle_tag_test.rb
│ │ ├── echo_test.rb
│ │ ├── for_tag_test.rb
│ │ ├── if_else_tag_test.rb
│ │ ├── include_tag_test.rb
│ │ ├── increment_tag_test.rb
│ │ ├── inline_comment_test.rb
│ │ ├── liquid_tag_test.rb
│ │ ├── raw_tag_test.rb
│ │ ├── render_tag_test.rb
│ │ ├── standard_tag_test.rb
│ │ ├── statements_test.rb
│ │ ├── table_row_test.rb
│ │ └── unless_else_tag_test.rb
│ ├── template_test.rb
│ ├── trim_mode_test.rb
│ └── variable_test.rb
├── test_helper.rb
└── unit/
├── block_unit_test.rb
├── condition_unit_test.rb
├── environment_filter_test.rb
├── environment_test.rb
├── file_system_unit_test.rb
├── i18n_unit_test.rb
├── lexer_unit_test.rb
├── parse_context_unit_test.rb
├── parse_tree_visitor_test.rb
├── parser_unit_test.rb
├── partial_cache_unit_test.rb
├── regexp_unit_test.rb
├── registers_unit_test.rb
├── resource_limits_unit_test.rb
├── strainer_template_unit_test.rb
├── tag_unit_test.rb
├── tags/
│ ├── case_tag_unit_test.rb
│ ├── comment_tag_unit_test.rb
│ ├── doc_tag_unit_test.rb
│ ├── for_tag_unit_test.rb
│ └── if_tag_unit_test.rb
├── template_factory_unit_test.rb
├── template_unit_test.rb
├── tokenizer_unit_test.rb
└── variable_unit_test.rb
SYMBOL INDEX (2031 symbols across 140 files)
FILE: example/server/example_servlet.rb
type ProductsFilter (line 3) | module ProductsFilter
function price (line 4) | def price(integer)
function prettyprint (line 8) | def prettyprint(text)
function count (line 12) | def count(array)
function paragraph (line 16) | def paragraph(p)
class Servlet (line 21) | class Servlet < LiquidServlet
method index (line 22) | def index
method products (line 26) | def products
method products_list (line 32) | def products_list
method more_products_list (line 40) | def more_products_list
method description (line 47) | def description
FILE: example/server/liquid_servlet.rb
class LiquidServlet (line 3) | class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet
method do_GET (line 4) | def do_GET(req, res)
method do_POST (line 8) | def do_POST(req, res)
method handle (line 14) | def handle(_type, req, res)
method read_template (line 27) | def read_template(filename = @action)
FILE: lib/liquid.rb
type Liquid (line 26) | module Liquid
FILE: lib/liquid/block.rb
type Liquid (line 3) | module Liquid
class Block (line 4) | class Block < Tag
method initialize (line 7) | def initialize(tag_name, markup, options)
method parse (line 12) | def parse(tokens)
method render (line 20) | def render(context)
method blank? (line 24) | def blank?
method nodelist (line 28) | def nodelist
method unknown_tag (line 32) | def unknown_tag(tag_name, _markup, _tokenizer)
method raise_unknown_tag (line 37) | def self.raise_unknown_tag(tag, block_name, block_delimiter, parse_c...
method raise_tag_never_closed (line 55) | def raise_tag_never_closed(block_name)
method block_name (line 59) | def block_name
method block_delimiter (line 63) | def block_delimiter
method new_body (line 70) | def new_body
method parse_body (line 75) | def parse_body(body, tokens)
FILE: lib/liquid/block_body.rb
type Liquid (line 5) | module Liquid
class BlockBody (line 6) | class BlockBody
method initialize (line 17) | def initialize
method parse (line 22) | def parse(tokenizer, parse_context, &block)
method freeze (line 34) | def freeze
method parse_for_liquid_tag (line 39) | def parse_for_liquid_tag(tokenizer, parse_context)
method unknown_tag_in_liquid_tag (line 71) | def self.unknown_tag_in_liquid_tag(tag, parse_context)
method raise_missing_tag_terminator (line 76) | def self.raise_missing_tag_terminator(token, parse_context)
method raise_missing_variable_terminator (line 81) | def self.raise_missing_variable_terminator(token, parse_context)
method render_node (line 86) | def self.render_node(context, output, node)
method rescue_render_node (line 94) | def self.rescue_render_node(context, output, line_number, exc, blank...
method parse_liquid_tag (line 108) | def parse_liquid_tag(markup, parse_context)
method handle_invalid_tag_token (line 119) | def handle_invalid_tag_token(token, parse_context)
method parse_for_document (line 127) | def parse_for_document(tokenizer, parse_context, &block)
method whitespace_handler (line 176) | def whitespace_handler(token, parse_context)
method blank? (line 190) | def blank?
method remove_blank_strings (line 209) | def remove_blank_strings
method render (line 214) | def render(context)
method render_to_output_buffer (line 218) | def render_to_output_buffer(context, output)
method render_node (line 244) | def render_node(context, output, node)
method create_variable (line 248) | def create_variable(token, parse_context)
method raise_missing_tag_terminator (line 264) | def raise_missing_tag_terminator(token, parse_context)
method raise_missing_variable_terminator (line 269) | def raise_missing_variable_terminator(token, parse_context)
FILE: lib/liquid/condition.rb
type Liquid (line 3) | module Liquid
class Condition (line 11) | class Condition # :nodoc:
class MethodLiteral (line 33) | class MethodLiteral
method initialize (line 36) | def initialize(method_name, to_s)
method operators (line 47) | def self.operators
method parse_expression (line 51) | def self.parse_expression(parse_context, markup, safe: false)
method initialize (line 58) | def initialize(left = nil, operator = nil, right = nil)
method evaluate (line 67) | def evaluate(context = deprecated_default_context)
method or (line 86) | def or(condition)
method and (line 91) | def and(condition)
method attach (line 96) | def attach(attachment)
method else? (line 100) | def else?
method inspect (line 104) | def inspect
method equal_variables (line 114) | def equal_variables(left, right)
method call_method_literal (line 126) | def call_method_literal(literal, value)
method liquid_blank? (line 149) | def liquid_blank?(value)
method liquid_empty? (line 168) | def liquid_empty?(value)
method interpret_condition (line 177) | def interpret_condition(left, right, op, context)
method deprecated_default_context (line 199) | def deprecated_default_context
class ParseTreeVisitor (line 205) | class ParseTreeVisitor < Liquid::ParseTreeVisitor
method children (line 206) | def children
class ElseCondition (line 217) | class ElseCondition < Condition
method else? (line 218) | def else?
method evaluate (line 222) | def evaluate(_context)
FILE: lib/liquid/const.rb
type Liquid (line 3) | module Liquid
type Const (line 4) | module Const
FILE: lib/liquid/context.rb
type Liquid (line 3) | module Liquid
class Context (line 16) | class Context
method build (line 21) | def self.build(environment: Environment.default, environments: {}, o...
method initialize (line 25) | def initialize(environments = {}, outer_scope = {}, registers = {}, ...
method warnings (line 63) | def warnings
method strainer (line 67) | def strainer
method add_filters (line 75) | def add_filters(filters)
method apply_global_filter (line 81) | def apply_global_filter(obj)
method interrupt? (line 86) | def interrupt?
method push_interrupt (line 91) | def push_interrupt(e)
method pop_interrupt (line 96) | def pop_interrupt
method handle_error (line 100) | def handle_error(e, line_number = nil)
method invoke (line 108) | def invoke(method, *args)
method push (line 113) | def push(new_scope = {})
method merge (line 119) | def merge(new_scopes)
method pop (line 124) | def pop
method stack (line 137) | def stack(new_scope = {})
method new_isolated_subcontext (line 146) | def new_isolated_subcontext
method clear_instance_assigns (line 165) | def clear_instance_assigns
method []= (line 170) | def []=(key, value)
method [] (line 182) | def [](expression)
method key? (line 186) | def key?(key)
method evaluate (line 190) | def evaluate(object)
method find_variable (line 195) | def find_variable(key, raise_on_not_found: true)
method lookup_and_evaluate (line 216) | def lookup_and_evaluate(obj, key, raise_on_not_found: true)
method with_disabled_tags (line 230) | def with_disabled_tags(tag_names)
method tag_disabled? (line 241) | def tag_disabled?(tag_name)
method try_variable_find_in_environments (line 253) | def try_variable_find_in_environments(key, raise_on_not_found:)
method check_overflow (line 269) | def check_overflow
method overflow? (line 273) | def overflow?
method internal_error (line 277) | def internal_error
method squash_instance_assigns_with_environments (line 284) | def squash_instance_assigns_with_environments
FILE: lib/liquid/deprecations.rb
type Liquid (line 5) | module Liquid
class Deprecations (line 6) | class Deprecations
method warn (line 12) | def warn(name, alternative)
FILE: lib/liquid/document.rb
type Liquid (line 3) | module Liquid
class Document (line 4) | class Document
method parse (line 5) | def self.parse(tokens, parse_context)
method initialize (line 13) | def initialize(parse_context)
method nodelist (line 18) | def nodelist
method parse (line 22) | def parse(tokenizer, parse_context)
method unknown_tag (line 31) | def unknown_tag(tag, _markup, _tokenizer)
method render_to_output_buffer (line 40) | def render_to_output_buffer(context, output)
method render (line 44) | def render(context)
method new_body (line 50) | def new_body
method parse_body (line 54) | def parse_body(tokenizer)
FILE: lib/liquid/drop.rb
type Liquid (line 5) | module Liquid
class Drop (line 25) | class Drop
method initialize (line 28) | def initialize
method liquid_method_missing (line 33) | def liquid_method_missing(method)
method invoke_drop (line 39) | def invoke_drop(method_or_key)
method key? (line 47) | def key?(_name)
method inspect (line 51) | def inspect
method to_liquid (line 55) | def to_liquid
method to_s (line 59) | def to_s
method invokable? (line 66) | def self.invokable?(method_name)
method invokable_methods (line 70) | def self.invokable_methods
FILE: lib/liquid/environment.rb
type Liquid (line 3) | module Liquid
class Environment (line 6) | class Environment
method build (line 42) | def build(tags: nil, file_system: nil, error_mode: nil, exception_re...
method default (line 55) | def default
method dangerously_override (line 65) | def dangerously_override(environment)
method initialize (line 76) | def initialize
method register_tag (line 93) | def register_tag(name, klass)
method register_filter (line 101) | def register_filter(filter)
method register_filters (line 110) | def register_filters(filters)
method create_strainer (line 124) | def create_strainer(context, filters = Const::EMPTY_ARRAY)
method filter_method_names (line 140) | def filter_method_names
method tag_for_name (line 148) | def tag_for_name(name)
method freeze (line 152) | def freeze
FILE: lib/liquid/errors.rb
type Liquid (line 3) | module Liquid
class Error (line 4) | class Error < ::StandardError
method to_s (line 9) | def to_s(with_prefix = true)
method message_prefix (line 24) | def message_prefix
FILE: lib/liquid/expression.rb
type Liquid (line 3) | module Liquid
class Expression (line 4) | class Expression
method safe_parse (line 31) | def safe_parse(parser, ss = StringScanner.new(""), cache = nil)
method parse (line 35) | def parse(markup, ss = StringScanner.new(""), cache = nil)
method inner_parse (line 57) | def inner_parse(markup, ss, cache)
method parse_number (line 74) | def parse_number(markup, ss)
FILE: lib/liquid/extensions.rb
class String (line 6) | class String # :nodoc:
method to_liquid (line 7) | def to_liquid
class Symbol (line 12) | class Symbol # :nodoc:
method to_liquid (line 13) | def to_liquid
class Array (line 18) | class Array # :nodoc:
method to_liquid (line 19) | def to_liquid
class Hash (line 24) | class Hash # :nodoc:
method to_liquid (line 25) | def to_liquid
class Numeric (line 30) | class Numeric # :nodoc:
method to_liquid (line 31) | def to_liquid
class Range (line 36) | class Range # :nodoc:
method to_liquid (line 37) | def to_liquid
class Time (line 42) | class Time # :nodoc:
method to_liquid (line 43) | def to_liquid
class DateTime (line 48) | class DateTime < Date # :nodoc:
method to_liquid (line 49) | def to_liquid
class Date (line 54) | class Date # :nodoc:
method to_liquid (line 55) | def to_liquid
class TrueClass (line 60) | class TrueClass
method to_liquid (line 61) | def to_liquid # :nodoc:
class FalseClass (line 66) | class FalseClass
method to_liquid (line 67) | def to_liquid # :nodoc:
class NilClass (line 72) | class NilClass
method to_liquid (line 73) | def to_liquid # :nodoc:
FILE: lib/liquid/file_system.rb
type Liquid (line 3) | module Liquid
class BlankFileSystem (line 17) | class BlankFileSystem
method read_template_file (line 19) | def read_template_file(_template_path)
class LocalFileSystem (line 46) | class LocalFileSystem
method initialize (line 49) | def initialize(root, pattern = "_%s.liquid")
method read_template_file (line 54) | def read_template_file(template_path)
method full_path (line 61) | def full_path(template_path)
FILE: lib/liquid/forloop_drop.rb
type Liquid (line 3) | module Liquid
class ForloopDrop (line 9) | class ForloopDrop < Drop
method initialize (line 10) | def initialize(name, length, parentloop)
method index (line 39) | def index
method index0 (line 47) | def index0
method rindex (line 55) | def rindex
method rindex0 (line 63) | def rindex0
method first (line 71) | def first
method last (line 79) | def last
method increment! (line 85) | def increment!
FILE: lib/liquid/i18n.rb
type Liquid (line 5) | module Liquid
class I18n (line 6) | class I18n
method initialize (line 13) | def initialize(path = DEFAULT_LOCALE)
method translate (line 17) | def translate(name, vars = {})
method locale (line 22) | def locale
method interpolate (line 28) | def interpolate(name, vars)
method deep_fetch_translation (line 35) | def deep_fetch_translation(name)
FILE: lib/liquid/interrupts.rb
type Liquid (line 3) | module Liquid
class Interrupt (line 5) | class Interrupt
method initialize (line 8) | def initialize(message = nil)
class BreakInterrupt (line 14) | class BreakInterrupt < Interrupt; end
class ContinueInterrupt (line 17) | class ContinueInterrupt < Interrupt; end
FILE: lib/liquid/lexer.rb
type Liquid (line 3) | module Liquid
class Lexer (line 4) | class Lexer
method tokenize (line 103) | def tokenize(ss)
method raise_syntax_error (line 172) | def raise_syntax_error(start_pos, ss)
FILE: lib/liquid/parse_context.rb
type Liquid (line 3) | module Liquid
class ParseContext (line 4) | class ParseContext
method initialize (line 8) | def initialize(options = Const::EMPTY_HASH)
method [] (line 31) | def [](option_key)
method new_block_body (line 35) | def new_block_body
method new_parser (line 39) | def new_parser(input)
method new_tokenizer (line 44) | def new_tokenizer(source, start_line_number: nil, for_liquid_tag: fa...
method safe_parse_expression (line 53) | def safe_parse_expression(parser)
method parse_expression (line 57) | def parse_expression(markup, safe: false)
method partial= (line 72) | def partial=(value)
method partial_options (line 79) | def partial_options
FILE: lib/liquid/parse_tree_visitor.rb
type Liquid (line 3) | module Liquid
class ParseTreeVisitor (line 4) | class ParseTreeVisitor
method for (line 5) | def self.for(node, callbacks = Hash.new(proc {}))
method initialize (line 13) | def initialize(node, callbacks)
method add_callback_for (line 18) | def add_callback_for(*classes, &block)
method visit (line 26) | def visit(context = nil)
method children (line 38) | def children
FILE: lib/liquid/parser.rb
type Liquid (line 3) | module Liquid
class Parser (line 4) | class Parser
method initialize (line 5) | def initialize(input)
method jump (line 11) | def jump(point)
method consume (line 15) | def consume(type = nil)
method consume? (line 27) | def consume?(type)
method id? (line 35) | def id?(str)
method look (line 43) | def look(type, ahead = 0)
method expression (line 49) | def expression
method argument (line 74) | def argument
method variable_lookups (line 85) | def variable_lookups
FILE: lib/liquid/parser_switching.rb
type Liquid (line 3) | module Liquid
type ParserSwitching (line 4) | module ParserSwitching
function strict_parse_with_error_mode_fallback (line 13) | def strict_parse_with_error_mode_fallback(markup)
function parse_with_selected_parser (line 32) | def parse_with_selected_parser(markup)
function strict2_mode? (line 48) | def strict2_mode?
function rigid_warn (line 54) | def rigid_warn
function strict2_parse_with_error_context (line 58) | def strict2_parse_with_error_context(markup)
function strict_parse_with_error_context (line 66) | def strict_parse_with_error_context(markup)
function markup_context (line 74) | def markup_context(markup)
FILE: lib/liquid/partial_cache.rb
type Liquid (line 3) | module Liquid
class PartialCache (line 4) | class PartialCache
method load (line 5) | def self.load(template_name, context:, parse_context:)
FILE: lib/liquid/profiler.rb
type Liquid (line 5) | module Liquid
class Profiler (line 45) | class Profiler
class Timing (line 48) | class Timing
method initialize (line 54) | def initialize(code: nil, template_name: nil, line_number: nil)
method self_time (line 61) | def self_time
method initialize (line 75) | def initialize
method profile (line 81) | def profile(template_name, &block)
method children (line 98) | def children
method each (line 107) | def each(&block)
method [] (line 111) | def [](idx)
method length (line 115) | def length
method profile_node (line 119) | def profile_node(template_name, code: nil, line_number: nil)
method monotonic_time (line 135) | def monotonic_time
FILE: lib/liquid/profiler/hooks.rb
type Liquid (line 3) | module Liquid
type BlockBodyProfilingHook (line 4) | module BlockBodyProfilingHook
function render_node (line 5) | def render_node(context, output, node)
type DocumentProfilingHook (line 17) | module DocumentProfilingHook
function render_to_output_buffer (line 18) | def render_to_output_buffer(context, output)
type ContextProfilingHook (line 25) | module ContextProfilingHook
function new_isolated_subcontext (line 28) | def new_isolated_subcontext
FILE: lib/liquid/range_lookup.rb
type Liquid (line 3) | module Liquid
class RangeLookup (line 4) | class RangeLookup
method parse (line 5) | def self.parse(start_markup, end_markup, string_scanner, cache = nil)
method initialize (line 27) | def initialize(start_obj, end_obj)
method evaluate (line 32) | def evaluate(context)
method to_integer (line 40) | def to_integer(input)
class ParseTreeVisitor (line 51) | class ParseTreeVisitor < Liquid::ParseTreeVisitor
method children (line 52) | def children
FILE: lib/liquid/registers.rb
type Liquid (line 3) | module Liquid
class Registers (line 4) | class Registers
method initialize (line 7) | def initialize(registers = {})
method []= (line 12) | def []=(key, value)
method [] (line 16) | def [](key)
method delete (line 24) | def delete(key)
method fetch (line 30) | def fetch(key, default = UNDEFINED, &block)
method key? (line 44) | def key?(key)
FILE: lib/liquid/resource_limits.rb
type Liquid (line 3) | module Liquid
class ResourceLimits (line 4) | class ResourceLimits
method initialize (line 15) | def initialize(limits)
method increment_render_score (line 26) | def increment_render_score(amount)
method increment_assign_score (line 33) | def increment_assign_score(amount)
method increment_write_score (line 41) | def increment_write_score(output)
method raise_limits_reached (line 52) | def raise_limits_reached
method reached? (line 57) | def reached?
method reset (line 61) | def reset
method with_capture (line 69) | def with_capture
FILE: lib/liquid/standardfilters.rb
type Liquid (line 6) | module Liquid
type StandardFilters (line 7) | module StandardFilters
function try_coerce_encoding (line 32) | def try_coerce_encoding(input, encoding:)
function size (line 54) | def size(input)
function downcase (line 65) | def downcase(input)
function upcase (line 76) | def upcase(input)
function capitalize (line 87) | def capitalize(input)
function escape (line 98) | def escape(input)
function escape_once (line 110) | def escape_once(input)
function url_encode (line 125) | def url_encode(input)
function url_decode (line 137) | def url_decode(input)
function base64_encode (line 153) | def base64_encode(input)
function base64_decode (line 164) | def base64_decode(input)
function base64_url_safe_encode (line 178) | def base64_url_safe_encode(input)
function base64_url_safe_decode (line 189) | def base64_url_safe_decode(input)
function slice (line 206) | def slice(input, offset, length = nil)
function truncate (line 236) | def truncate(input, length = 50, truncate_string = "...")
function truncatewords (line 263) | def truncatewords(input, words = 15, truncate_string = "...")
function split (line 290) | def split(input, pattern)
function squish (line 303) | def squish(input)
function strip (line 316) | def strip(input)
function lstrip (line 328) | def lstrip(input)
function rstrip (line 340) | def rstrip(input)
function strip_html (line 352) | def strip_html(input)
function strip_newlines (line 367) | def strip_newlines(input)
function join (line 379) | def join(input, glue = ' ')
function sort (line 391) | def sort(input, property = nil)
function sort_natural (line 420) | def sort_natural(input, property = nil)
function where (line 447) | def where(input, property, target_value = nil)
function reject (line 460) | def reject(input, property, target_value = nil)
function has (line 473) | def has(input, property, target_value = nil)
function find (line 486) | def find(input, property, target_value = nil)
function find_index (line 499) | def find_index(input, property, target_value = nil)
function uniq (line 510) | def uniq(input, property = nil)
function reverse (line 536) | def reverse(input)
function map (line 548) | def map(input, property)
function compact (line 570) | def compact(input, property = nil)
function replace (line 596) | def replace(input, string, replacement = '')
function replace_first (line 610) | def replace_first(input, string, replacement = '')
function replace_last (line 624) | def replace_last(input, string, replacement)
function remove (line 645) | def remove(input, string)
function remove_first (line 656) | def remove_first(input, string)
function remove_last (line 667) | def remove_last(input, string)
function append (line 678) | def append(input, string)
function concat (line 695) | def concat(input, array)
function prepend (line 709) | def prepend(input, string)
function newline_to_br (line 722) | def newline_to_br(input)
function date (line 767) | def date(input, format)
function first (line 783) | def first(array)
function last (line 796) | def last(array)
function abs (line 809) | def abs(input)
function plus (line 821) | def plus(input, operand)
function minus (line 832) | def minus(input, operand)
function times (line 843) | def times(input, operand)
function divided_by (line 854) | def divided_by(input, operand)
function modulo (line 867) | def modulo(input, operand)
function round (line 880) | def round(input, n = 0)
function ceil (line 896) | def ceil(input)
function floor (line 909) | def floor(input)
function at_least (line 922) | def at_least(input, n)
function at_most (line 937) | def at_most(input, n)
function default (line 957) | def default(input, default_value = '', options = {})
function sum (line 970) | def sum(input, property = nil)
function filter_array (line 997) | def filter_array(input, property, target_value, default_value = [], ...
function raise_property_error (line 1016) | def raise_property_error(property)
function apply_operation (line 1020) | def apply_operation(input, operand, operation)
function nil_safe_compare (line 1025) | def nil_safe_compare(a, b)
function nil_safe_casecmp (line 1039) | def nil_safe_casecmp(a, b)
class InputIterator (line 1049) | class InputIterator
method initialize (line 1052) | def initialize(input, context)
method join (line 1065) | def join(glue)
method concat (line 1080) | def concat(args)
method reverse (line 1084) | def reverse
method uniq (line 1088) | def uniq(&block)
method compact (line 1095) | def compact
method empty? (line 1099) | def empty?
method each (line 1104) | def each
FILE: lib/liquid/strainer_template.rb
type Liquid (line 5) | module Liquid
class StrainerTemplate (line 11) | class StrainerTemplate
method initialize (line 12) | def initialize(context)
method add_filter (line 17) | def add_filter(filter)
method invokable? (line 30) | def invokable?(method)
method inherited (line 34) | def inherited(subclass)
method filter_method_names (line 39) | def filter_method_names
method filter_methods (line 45) | def filter_methods
method invoke (line 50) | def invoke(method, *args)
FILE: lib/liquid/tablerowloop_drop.rb
type Liquid (line 3) | module Liquid
class TablerowloopDrop (line 9) | class TablerowloopDrop < Drop
method initialize (line 10) | def initialize(length, cols)
method index (line 40) | def index
method index0 (line 48) | def index0
method col0 (line 56) | def col0
method rindex (line 64) | def rindex
method rindex0 (line 72) | def rindex0
method first (line 80) | def first
method last (line 88) | def last
method col_first (line 96) | def col_first
method col_last (line 104) | def col_last
method increment! (line 110) | def increment!
FILE: lib/liquid/tag.rb
type Liquid (line 6) | module Liquid
class Tag (line 7) | class Tag
method parse (line 13) | def parse(tag_name, markup, tokenizer, parse_context)
method disable_tags (line 19) | def disable_tags(*tag_names)
method disabled_tags (line 29) | def disabled_tags
method initialize (line 34) | def initialize(tag_name, markup, parse_context)
method parse (line 41) | def parse(_tokens)
method raw (line 44) | def raw
method name (line 48) | def name
method render (line 52) | def render(_context)
method render_to_output_buffer (line 59) | def render_to_output_buffer(context, output)
method blank? (line 65) | def blank?
method safe_parse_expression (line 71) | def safe_parse_expression(parser)
method parse_expression (line 75) | def parse_expression(markup, safe: false)
FILE: lib/liquid/tag/disableable.rb
type Liquid (line 3) | module Liquid
class Tag (line 4) | class Tag
type Disableable (line 5) | module Disableable
function render_to_output_buffer (line 6) | def render_to_output_buffer(context, output)
function disabled_error (line 14) | def disabled_error(context)
FILE: lib/liquid/tag/disabler.rb
type Liquid (line 3) | module Liquid
class Tag (line 4) | class Tag
type Disabler (line 5) | module Disabler
function render_to_output_buffer (line 6) | def render_to_output_buffer(context, output)
FILE: lib/liquid/tags.rb
type Liquid (line 24) | module Liquid
type Tags (line 25) | module Tags
FILE: lib/liquid/tags/assign.rb
type Liquid (line 3) | module Liquid
class Assign (line 20) | class Assign < Tag
method raise_syntax_error (line 24) | def self.raise_syntax_error(parse_context)
method initialize (line 30) | def initialize(tag_name, markup, parse_context)
method render_to_output_buffer (line 40) | def render_to_output_buffer(context, output)
method blank? (line 47) | def blank?
method assign_score_of (line 53) | def assign_score_of(val)
class ParseTreeVisitor (line 73) | class ParseTreeVisitor < Liquid::ParseTreeVisitor
method children (line 74) | def children
FILE: lib/liquid/tags/break.rb
type Liquid (line 3) | module Liquid
class Break (line 21) | class Break < Tag
method render_to_output_buffer (line 24) | def render_to_output_buffer(context, output)
FILE: lib/liquid/tags/capture.rb
type Liquid (line 3) | module Liquid
class Capture (line 22) | class Capture < Block
method initialize (line 25) | def initialize(tag_name, markup, options)
method render_to_output_buffer (line 34) | def render_to_output_buffer(context, output)
method blank? (line 42) | def blank?
FILE: lib/liquid/tags/case.rb
type Liquid (line 3) | module Liquid
class Case (line 25) | class Case < Block
method initialize (line 31) | def initialize(tag_name, markup, options)
method parse (line 37) | def parse(tokens)
method nodelist (line 50) | def nodelist
method unknown_tag (line 54) | def unknown_tag(tag, markup, tokens)
method render_to_output_buffer (line 65) | def render_to_output_buffer(context, output)
method strict2_parse (line 89) | def strict2_parse(markup)
method strict_parse (line 95) | def strict_parse(markup)
method lax_parse (line 99) | def lax_parse(markup)
method record_when_condition (line 107) | def record_when_condition(markup)
method parse_strict2_when (line 117) | def parse_strict2_when(markup, body)
method parse_lax_when (line 132) | def parse_lax_when(markup, body)
method record_else_condition (line 146) | def record_else_condition(markup)
class ParseTreeVisitor (line 156) | class ParseTreeVisitor < Liquid::ParseTreeVisitor
method children (line 157) | def children
FILE: lib/liquid/tags/comment.rb
type Liquid (line 3) | module Liquid
class Comment (line 17) | class Comment < Block
method render_to_output_buffer (line 18) | def render_to_output_buffer(_context, output)
method unknown_tag (line 22) | def unknown_tag(_tag, _markup, _tokens)
method blank? (line 25) | def blank?
method parse_body (line 31) | def parse_body(body, tokenizer)
method parse_raw_tag_body (line 80) | def parse_raw_tag_body(tokenizer)
FILE: lib/liquid/tags/continue.rb
type Liquid (line 3) | module Liquid
class Continue (line 12) | class Continue < Tag
method render_to_output_buffer (line 15) | def render_to_output_buffer(context, output)
FILE: lib/liquid/tags/cycle.rb
type Liquid (line 3) | module Liquid
class Cycle (line 17) | class Cycle < Tag
method initialize (line 24) | def initialize(tag_name, markup, options)
method named? (line 29) | def named?
method render_to_output_buffer (line 33) | def render_to_output_buffer(context, output)
method strict2_parse (line 59) | def strict2_parse(markup)
method strict_parse (line 94) | def strict_parse(markup)
method lax_parse (line 98) | def lax_parse(markup)
method variables_from_string (line 113) | def variables_from_string(markup)
method maybe_dup_lookup (line 130) | def maybe_dup_lookup(var)
class ParseTreeVisitor (line 134) | class ParseTreeVisitor < Liquid::ParseTreeVisitor
method children (line 135) | def children
FILE: lib/liquid/tags/decrement.rb
type Liquid (line 3) | module Liquid
class Decrement (line 25) | class Decrement < Tag
method initialize (line 28) | def initialize(tag_name, markup, options)
method render_to_output_buffer (line 33) | def render_to_output_buffer(context, output)
FILE: lib/liquid/tags/doc.rb
type Liquid (line 3) | module Liquid
class Doc (line 30) | class Doc < Block
method initialize (line 33) | def initialize(tag_name, markup, parse_context)
method parse (line 38) | def parse(tokens)
method render_to_output_buffer (line 57) | def render_to_output_buffer(_context, output)
method blank? (line 61) | def blank?
method nodelist (line 65) | def nodelist
method ensure_valid_markup (line 71) | def ensure_valid_markup(tag_name, markup, parse_context)
method raise_nested_doc_error (line 77) | def raise_nested_doc_error
FILE: lib/liquid/tags/echo.rb
type Liquid (line 3) | module Liquid
class Echo (line 21) | class Echo < Tag
method initialize (line 24) | def initialize(tag_name, markup, parse_context)
method render (line 29) | def render(context)
class ParseTreeVisitor (line 33) | class ParseTreeVisitor < Liquid::ParseTreeVisitor
method children (line 34) | def children
FILE: lib/liquid/tags/for.rb
type Liquid (line 3) | module Liquid
class For (line 27) | class For < Block
method initialize (line 32) | def initialize(tag_name, markup, options)
method parse (line 40) | def parse(tokens)
method nodelist (line 52) | def nodelist
method unknown_tag (line 56) | def unknown_tag(tag, markup, tokens)
method render_to_output_buffer (line 61) | def render_to_output_buffer(context, output)
method lax_parse (line 75) | def lax_parse(markup)
method strict_parse (line 90) | def strict_parse(markup)
method strict2_parse (line 114) | def strict2_parse(markup)
method collection_segment (line 118) | def collection_segment(context)
method render_segment (line 150) | def render_segment(context, output, segment)
method set_attribute (line 181) | def set_attribute(key, expr, safe: false)
method render_else (line 194) | def render_else(context, output)
class ParseTreeVisitor (line 202) | class ParseTreeVisitor < Liquid::ParseTreeVisitor
method children (line 203) | def children
FILE: lib/liquid/tags/if.rb
type Liquid (line 3) | module Liquid
class If (line 16) | class If < Block
method initialize (line 23) | def initialize(tag_name, markup, options)
method nodelist (line 29) | def nodelist
method parse (line 33) | def parse(tokens)
method unknown_tag (line 45) | def unknown_tag(tag, markup, tokens)
method render_to_output_buffer (line 53) | def render_to_output_buffer(context, output)
method strict2_parse (line 69) | def strict2_parse(markup)
method push_block (line 73) | def push_block(tag, markup)
method parse_expression (line 84) | def parse_expression(markup, safe: false)
method lax_parse (line 88) | def lax_parse(markup)
method strict_parse (line 108) | def strict_parse(markup)
method parse_binary_comparisons (line 115) | def parse_binary_comparisons(p)
method parse_comparison (line 126) | def parse_comparison(p)
class ParseTreeVisitor (line 136) | class ParseTreeVisitor < Liquid::ParseTreeVisitor
method children (line 137) | def children
FILE: lib/liquid/tags/ifchanged.rb
type Liquid (line 3) | module Liquid
class Ifchanged (line 4) | class Ifchanged < Block
method render_to_output_buffer (line 5) | def render_to_output_buffer(context, output)
FILE: lib/liquid/tags/include.rb
type Liquid (line 3) | module Liquid
class Include (line 20) | class Include < Tag
method initialize (line 28) | def initialize(tag_name, markup, options)
method parse (line 33) | def parse(_tokens)
method render_to_output_buffer (line 36) | def render_to_output_buffer(context, output)
method strict2_parse (line 87) | def strict2_parse(markup)
method strict_parse (line 107) | def strict_parse(markup)
method lax_parse (line 111) | def lax_parse(markup)
class ParseTreeVisitor (line 130) | class ParseTreeVisitor < Liquid::ParseTreeVisitor
method children (line 131) | def children
FILE: lib/liquid/tags/increment.rb
type Liquid (line 3) | module Liquid
class Increment (line 25) | class Increment < Tag
method initialize (line 28) | def initialize(tag_name, markup, options)
method render_to_output_buffer (line 33) | def render_to_output_buffer(context, output)
FILE: lib/liquid/tags/inline_comment.rb
type Liquid (line 3) | module Liquid
class InlineComment (line 4) | class InlineComment < Tag
method initialize (line 5) | def initialize(tag_name, markup, options)
method render_to_output_buffer (line 20) | def render_to_output_buffer(_context, output)
method blank? (line 24) | def blank?
FILE: lib/liquid/tags/raw.rb
type Liquid (line 3) | module Liquid
class Raw (line 15) | class Raw < Block
method initialize (line 18) | def initialize(tag_name, markup, parse_context)
method parse (line 24) | def parse(tokens)
method render_to_output_buffer (line 38) | def render_to_output_buffer(_context, output)
method nodelist (line 43) | def nodelist
method blank? (line 47) | def blank?
method ensure_valid_markup (line 53) | def ensure_valid_markup(tag_name, markup, parse_context)
FILE: lib/liquid/tags/render.rb
type Liquid (line 3) | module Liquid
class Render (line 28) | class Render < Tag
method initialize (line 36) | def initialize(tag_name, markup, options)
method for_loop? (line 41) | def for_loop?
method render_to_output_buffer (line 45) | def render_to_output_buffer(context, output)
method render_tag (line 49) | def render_tag(context, output)
method strict2_parse (line 88) | def strict2_parse(markup)
method strict2_template_name (line 110) | def strict2_template_name(p)
method strict_parse (line 114) | def strict_parse(markup)
method lax_parse (line 118) | def lax_parse(markup)
class ParseTreeVisitor (line 136) | class ParseTreeVisitor < Liquid::ParseTreeVisitor
method children (line 137) | def children
FILE: lib/liquid/tags/table_row.rb
type Liquid (line 3) | module Liquid
class TableRow (line 26) | class TableRow < Block
method initialize (line 32) | def initialize(tag_name, markup, options)
method strict2_parse (line 37) | def strict2_parse(markup)
method strict_parse (line 65) | def strict_parse(markup)
method lax_parse (line 69) | def lax_parse(markup)
method render_to_output_buffer (line 82) | def render_to_output_buffer(context, output)
class ParseTreeVisitor (line 123) | class ParseTreeVisitor < Liquid::ParseTreeVisitor
method children (line 124) | def children
method to_integer (line 131) | def to_integer(value)
FILE: lib/liquid/tags/unless.rb
type Liquid (line 5) | module Liquid
class Unless (line 21) | class Unless < If
method render_to_output_buffer (line 22) | def render_to_output_buffer(context, output)
FILE: lib/liquid/template.rb
type Liquid (line 3) | module Liquid
class Template (line 17) | class Template
method error_mode= (line 29) | def error_mode=(mode)
method error_mode (line 34) | def error_mode
method default_exception_renderer= (line 38) | def default_exception_renderer=(renderer)
method default_exception_renderer (line 43) | def default_exception_renderer
method file_system= (line 47) | def file_system=(file_system)
method file_system (line 52) | def file_system
method tags (line 56) | def tags
method register_tag (line 60) | def register_tag(name, klass)
method register_filter (line 67) | def register_filter(mod)
method default_resource_limits= (line 72) | def default_resource_limits=(limits)
method default_resource_limits (line 77) | def default_resource_limits
method parse (line 84) | def parse(source, options = {})
method initialize (line 90) | def initialize(environment: Environment.default)
method parse (line 98) | def parse(source, options = {})
method registers (line 111) | def registers
method assigns (line 115) | def assigns
method instance_assigns (line 119) | def instance_assigns
method errors (line 123) | def errors
method render (line 141) | def render(*args)
method render! (line 200) | def render!(*args)
method render_to_output_buffer (line 205) | def render_to_output_buffer(context, output)
method configure_options (line 211) | def configure_options(options)
method apply_options_to_context (line 230) | def apply_options_to_context(context, options)
FILE: lib/liquid/template_factory.rb
type Liquid (line 3) | module Liquid
class TemplateFactory (line 4) | class TemplateFactory
method for (line 5) | def for(_template_name)
FILE: lib/liquid/tokenizer.rb
type Liquid (line 5) | module Liquid
class Tokenizer (line 6) | class Tokenizer
method initialize (line 17) | def initialize(
method shift (line 37) | def shift
method tokenize (line 53) | def tokenize
method shift_normal (line 64) | def shift_normal
method next_token (line 72) | def next_token
method next_text_token (line 95) | def next_text_token
method next_variable_token (line 114) | def next_variable_token
method next_tag_token (line 147) | def next_tag_token
method next_tag_token_with_start (line 156) | def next_tag_token_with_start(start)
FILE: lib/liquid/usage.rb
type Liquid (line 3) | module Liquid
type Usage (line 4) | module Usage
function increment (line 5) | def self.increment(name)
FILE: lib/liquid/utils.rb
type Liquid (line 3) | module Liquid
type Utils (line 4) | module Utils
function slice_collection (line 8) | def self.slice_collection(collection, from, to)
function slice_collection_using_each (line 16) | def self.slice_collection_using_each(collection, from, to)
function to_integer (line 41) | def self.to_integer(num)
function to_number (line 51) | def self.to_number(obj)
function to_date (line 68) | def self.to_date(obj)
function to_liquid_value (line 88) | def self.to_liquid_value(obj)
function to_s (line 96) | def self.to_s(obj, seen = {})
function inspect (line 116) | def self.inspect(obj, seen = {})
function array_inspect (line 134) | def self.array_inspect(arr, seen = {})
function hash_inspect (line 160) | def self.hash_inspect(hash, seen = {})
FILE: lib/liquid/variable.rb
type Liquid (line 3) | module Liquid
class Variable (line 14) | class Variable
method initialize (line 27) | def initialize(markup, parse_context)
method raw (line 36) | def raw
method markup_context (line 40) | def markup_context(markup)
method lax_parse (line 44) | def lax_parse(markup)
method strict_parse (line 62) | def strict_parse(markup)
method strict2_parse (line 77) | def strict2_parse(markup)
method parse_filterargs (line 88) | def parse_filterargs(p)
method render (line 96) | def render(context)
method render_to_output_buffer (line 107) | def render_to_output_buffer(context, output)
method render_obj_to_output (line 113) | def render_obj_to_output(obj, output)
method disabled? (line 126) | def disabled?(_context)
method disabled_tags (line 130) | def disabled_tags
method lax_parse_filter_expressions (line 136) | def lax_parse_filter_expressions(filter_name, unparsed_args)
method strict2_parse_filter_expressions (line 159) | def strict2_parse_filter_expressions(p)
method argument (line 177) | def argument(p, positional_arguments, keyword_arguments)
method end_of_arguments? (line 188) | def end_of_arguments?(p)
method evaluate_filter_expressions (line 192) | def evaluate_filter_expressions(context, filter_args, filter_kwargs)
class ParseTreeVisitor (line 204) | class ParseTreeVisitor < Liquid::ParseTreeVisitor
method children (line 205) | def children
FILE: lib/liquid/variable_lookup.rb
type Liquid (line 3) | module Liquid
class VariableLookup (line 4) | class VariableLookup
method parse (line 9) | def self.parse(markup, string_scanner = StringScanner.new(""), cache...
method initialize (line 13) | def initialize(markup, string_scanner = StringScanner.new(""), cache...
method lookup_command? (line 43) | def lookup_command?(lookup_index)
method evaluate (line 47) | def evaluate(context)
method == (line 93) | def ==(other)
method state (line 99) | def state
class ParseTreeVisitor (line 103) | class ParseTreeVisitor < Liquid::ParseTreeVisitor
method children (line 104) | def children
FILE: lib/liquid/version.rb
type Liquid (line 4) | module Liquid
FILE: performance/memory_profile.rb
class Profiler (line 8) | class Profiler
method run (line 12) | def self.run
method initialize (line 17) | def initialize
method profile (line 23) | def profile(phase, &block)
method tabulate (line 40) | def tabulate
method sanitize (line 51) | def sanitize(string)
FILE: performance/shopify/comment_form.rb
class CommentForm (line 3) | class CommentForm < Liquid::Block
method initialize (line 6) | def initialize(tag_name, markup, options)
method render_to_output_buffer (line 17) | def render_to_output_buffer(context, output)
method wrap_in_form (line 34) | def wrap_in_form(article, input)
FILE: performance/shopify/database.rb
type Database (line 5) | module Database
function tables (line 10) | def self.tables
FILE: performance/shopify/json_filter.rb
type JsonFilter (line 5) | module JsonFilter
function json (line 6) | def json(object)
FILE: performance/shopify/money_filter.rb
type MoneyFilter (line 3) | module MoneyFilter
function money_with_currency (line 4) | def money_with_currency(money)
function money (line 9) | def money(money)
function currency (line 16) | def currency
FILE: performance/shopify/paginate.rb
class Paginate (line 3) | class Paginate < Liquid::Block
method initialize (line 6) | def initialize(tag_name, markup, options)
method render_to_output_buffer (line 26) | def render_to_output_buffer(context, output)
method no_link (line 81) | def no_link(title)
method link (line 85) | def link(title, page)
method current_url (line 89) | def current_url
FILE: performance/shopify/shop_filter.rb
type ShopFilter (line 3) | module ShopFilter
function asset_url (line 4) | def asset_url(input)
function global_asset_url (line 8) | def global_asset_url(input)
function shopify_asset_url (line 12) | def shopify_asset_url(input)
function script_tag (line 16) | def script_tag(url)
function stylesheet_tag (line 20) | def stylesheet_tag(url, media = "all")
function link_to (line 24) | def link_to(link, url, title = "")
function img_tag (line 28) | def img_tag(url, alt = "")
function link_to_vendor (line 32) | def link_to_vendor(vendor)
function link_to_type (line 40) | def link_to_type(type)
function url_for_vendor (line 48) | def url_for_vendor(vendor_title)
function url_for_type (line 52) | def url_for_type(type_title)
function product_img_url (line 56) | def product_img_url(url, style = 'small')
function default_pagination (line 71) | def default_pagination(paginate)
function pluralize (line 91) | def pluralize(input, singular, plural)
function to_handle (line 97) | def to_handle(str)
FILE: performance/shopify/tag_filter.rb
type TagFilter (line 3) | module TagFilter
function link_to_tag (line 4) | def link_to_tag(label, tag)
function highlight_active_tag (line 8) | def highlight_active_tag(tag, css_class = 'active')
function link_to_add_tag (line 16) | def link_to_add_tag(label, tag)
function link_to_remove_tag (line 21) | def link_to_remove_tag(label, tag)
FILE: performance/shopify/weight_filter.rb
type WeightFilter (line 3) | module WeightFilter
function weight (line 4) | def weight(grams)
function weight_with_unit (line 8) | def weight_with_unit(grams)
FILE: performance/theme_runner.rb
class ThemeRunner (line 14) | class ThemeRunner
class FileSystem (line 15) | class FileSystem
method initialize (line 16) | def initialize(path)
method read_template_file (line 21) | def read_template_file(template_path)
method initialize (line 28) | def initialize
method compile (line 44) | def compile
method tokenize (line 52) | def tokenize
method run (line 65) | def run
method render (line 72) | def render
method render_layout (line 89) | def render_layout(template, layout, assigns)
method compile_and_render (line 94) | def compile_and_render(template, layout, assigns, page_template, templ...
method compile_all_tests (line 99) | def compile_all_tests
method compile_test (line 107) | def compile_test(template, layout, assigns, page_template, template_file)
method each_test (line 120) | def each_test
method init_template (line 132) | def init_template(page_template, template_file)
FILE: test/integration/assign_test.rb
class AssignTest (line 5) | class AssignTest < Minitest::Test
method test_assign_with_hyphen_in_variable_name (line 8) | def test_assign_with_hyphen_in_variable_name
method test_assigned_variable (line 16) | def test_assigned_variable
method test_assign_with_filter (line 30) | def test_assign_with_filter
method test_assign_syntax_error (line 38) | def test_assign_syntax_error
method test_assign_uses_error_mode (line 42) | def test_assign_uses_error_mode
method test_expression_with_whitespace_in_square_brackets (line 51) | def test_expression_with_whitespace_in_square_brackets
method test_assign_score_exceeding_resource_limit (line 56) | def test_assign_score_exceeding_resource_limit
method test_assign_score_exceeding_limit_from_composite_object (line 67) | def test_assign_score_exceeding_limit_from_composite_object
method test_assign_score_of_int (line 78) | def test_assign_score_of_int
method test_assign_score_of_string_counts_bytes (line 82) | def test_assign_score_of_string_counts_bytes
method test_assign_score_of_array (line 88) | def test_assign_score_of_array
method test_assign_score_of_hash (line 94) | def test_assign_score_of_hash
class ObjectWrapperDrop (line 102) | class ObjectWrapperDrop < Liquid::Drop
method initialize (line 103) | def initialize(obj)
method value (line 107) | def value
method assign_score_of (line 112) | def assign_score_of(obj)
FILE: test/integration/blank_test.rb
class FoobarTag (line 5) | class FoobarTag < Liquid::Tag
method render_to_output_buffer (line 6) | def render_to_output_buffer(_context, output)
class BlankTest (line 12) | class BlankTest < Minitest::Test
method wrap_in_for (line 16) | def wrap_in_for(body)
method wrap_in_if (line 20) | def wrap_in_if(body)
method wrap (line 24) | def wrap(body)
method test_new_tags_are_not_blank_by_default (line 28) | def test_new_tags_are_not_blank_by_default
method test_loops_are_blank (line 34) | def test_loops_are_blank
method test_if_else_are_blank (line 38) | def test_if_else_are_blank
method test_unless_is_blank (line 42) | def test_unless_is_blank
method test_mark_as_blank_only_during_parsing (line 46) | def test_mark_as_blank_only_during_parsing
method test_comments_are_blank (line 50) | def test_comments_are_blank
method test_captures_are_blank (line 54) | def test_captures_are_blank
method test_nested_blocks_are_blank_but_only_if_all_children_are (line 58) | def test_nested_blocks_are_blank_but_only_if_all_children_are
method test_assigns_are_blank (line 67) | def test_assigns_are_blank
method test_whitespace_is_blank (line 71) | def test_whitespace_is_blank
method test_whitespace_is_not_blank_if_other_stuff_is_present (line 76) | def test_whitespace_is_not_blank_if_other_stuff_is_present
method test_increment_is_not_blank (line 81) | def test_increment_is_not_blank
method test_cycle_is_not_blank (line 85) | def test_cycle_is_not_blank
method test_raw_is_not_blank (line 89) | def test_raw_is_not_blank
method test_include_is_blank (line 93) | def test_include_is_blank
method test_case_is_blank (line 111) | def test_case_is_blank
FILE: test/integration/block_test.rb
class BlockTest (line 5) | class BlockTest < Minitest::Test
method test_unexpected_end_tag (line 8) | def test_unexpected_end_tag
method test_with_custom_tag (line 13) | def test_with_custom_tag
method test_custom_block_tags_have_a_default_render_to_output_buffer_method_for_backwards_compatibility (line 19) | def test_custom_block_tags_have_a_default_render_to_output_buffer_meth...
FILE: test/integration/capture_test.rb
class CaptureTest (line 5) | class CaptureTest < Minitest::Test
method test_captures_block_content_in_variable (line 8) | def test_captures_block_content_in_variable
method test_capture_with_hyphen_in_variable_name (line 12) | def test_capture_with_hyphen_in_variable_name
method test_capture_to_variable_from_outer_scope_if_existing (line 20) | def test_capture_to_variable_from_outer_scope_if_existing
method test_assigning_from_capture (line 34) | def test_assigning_from_capture
method test_increment_assign_score_by_bytes_not_characters (line 47) | def test_increment_assign_score_by_bytes_not_characters
FILE: test/integration/context_test.rb
class HundredCentes (line 5) | class HundredCentes
method to_liquid (line 6) | def to_liquid
class CentsDrop (line 11) | class CentsDrop < Liquid::Drop
method amount (line 12) | def amount
method non_zero? (line 16) | def non_zero?
class ContextSensitiveDrop (line 21) | class ContextSensitiveDrop < Liquid::Drop
method test (line 22) | def test
class Category (line 27) | class Category
method initialize (line 30) | def initialize(name)
method to_liquid (line 34) | def to_liquid
class ProductsDrop (line 39) | class ProductsDrop < Liquid::Drop
method initialize (line 40) | def initialize(products)
method size (line 44) | def size
method to_liquid (line 48) | def to_liquid
class CategoryDrop (line 57) | class CategoryDrop < Liquid::Drop
method initialize (line 60) | def initialize(category)
class CounterDrop (line 65) | class CounterDrop < Liquid::Drop
method count (line 66) | def count
class ArrayLike (line 72) | class ArrayLike
method fetch (line 73) | def fetch(index)
method [] (line 76) | def [](index)
method to_liquid (line 82) | def to_liquid
class ContextTest (line 87) | class ContextTest < Minitest::Test
method setup (line 90) | def setup
method test_variables (line 94) | def test_variables
method test_variables_not_existing (line 122) | def test_variables_not_existing
method test_scoping (line 126) | def test_scoping
method test_length_query (line 141) | def test_length_query
method test_hyphenated_variable (line 161) | def test_hyphenated_variable
method test_add_filter (line 165) | def test_add_filter
method test_only_intended_filters_make_it_there (line 183) | def test_only_intended_filters_make_it_there
method test_add_item_in_outer_scope (line 197) | def test_add_item_in_outer_scope
method test_add_item_in_inner_scope (line 205) | def test_add_item_in_inner_scope
method test_hierachical_data (line 213) | def test_hierachical_data
method test_keywords (line 219) | def test_keywords
method test_digits (line 224) | def test_digits
method test_strings (line 229) | def test_strings
method test_merge (line 234) | def test_merge
method test_array_notation (line 242) | def test_array_notation
method test_recoursive_array_notation (line 249) | def test_recoursive_array_notation
method test_hash_to_array_transition (line 257) | def test_hash_to_array_transition
method test_try_first (line 271) | def test_try_first
method test_access_hashes_with_hash_notation (line 285) | def test_access_hashes_with_hash_notation
method test_access_variable_with_hash_notation (line 298) | def test_access_variable_with_hash_notation
method test_access_hashes_with_hash_access_variables (line 303) | def test_access_hashes_with_hash_access_variables
method test_hash_notation_only_for_hash_access (line 314) | def test_hash_notation_only_for_hash_access
method test_first_can_appear_in_middle_of_callchain (line 322) | def test_first_can_appear_in_middle_of_callchain
method test_cents (line 331) | def test_cents
method test_nested_cents (line 336) | def test_nested_cents
method test_cents_through_drop (line 344) | def test_cents_through_drop
method test_nested_cents_through_drop (line 349) | def test_nested_cents_through_drop
method test_drop_methods_with_question_marks (line 354) | def test_drop_methods_with_question_marks
method test_context_from_within_drop (line 359) | def test_context_from_within_drop
method test_nested_context_from_within_drop (line 364) | def test_nested_context_from_within_drop
method test_ranges (line 369) | def test_ranges
method test_cents_through_drop_nestedly (line 378) | def test_cents_through_drop_nestedly
method test_drop_with_variable_called_only_once (line 386) | def test_drop_with_variable_called_only_once
method test_drop_with_key_called_only_once (line 394) | def test_drop_with_key_called_only_once
method test_proc_as_variable (line 402) | def test_proc_as_variable
method test_lambda_as_variable (line 408) | def test_lambda_as_variable
method test_nested_lambda_as_variable (line 414) | def test_nested_lambda_as_variable
method test_array_containing_lambda_as_variable (line 420) | def test_array_containing_lambda_as_variable
method test_lambda_is_called_once (line 426) | def test_lambda_is_called_once
method test_nested_lambda_is_called_once (line 439) | def test_nested_lambda_is_called_once
method test_lambda_in_array_is_called_once (line 454) | def test_lambda_in_array_is_called_once
method test_access_to_context_from_proc (line 468) | def test_access_to_context_from_proc
method test_to_liquid_and_context_at_first_level (line 476) | def test_to_liquid_and_context_at_first_level
method test_interrupt_avoids_object_allocations (line 482) | def test_interrupt_avoids_object_allocations
method test_context_initialization_with_a_proc_in_environment (line 489) | def test_context_initialization_with_a_proc_in_environment
method test_apply_global_filter (line 496) | def test_apply_global_filter
method test_static_environments_are_read_with_lower_priority_than_environments (line 505) | def test_static_environments_are_read_with_lower_priority_than_environ...
method test_apply_global_filter_when_no_global_filter_exist (line 515) | def test_apply_global_filter_when_no_global_filter_exist
method test_new_isolated_subcontext_does_not_inherit_variables (line 520) | def test_new_isolated_subcontext_does_not_inherit_variables
method test_new_isolated_subcontext_inherits_static_environment (line 528) | def test_new_isolated_subcontext_inherits_static_environment
method test_new_isolated_subcontext_inherits_resource_limits (line 535) | def test_new_isolated_subcontext_inherits_resource_limits
method test_new_isolated_subcontext_inherits_exception_renderer (line 542) | def test_new_isolated_subcontext_inherits_exception_renderer
method test_new_isolated_subcontext_does_not_inherit_non_static_registers (line 549) | def test_new_isolated_subcontext_does_not_inherit_non_static_registers
method test_new_isolated_subcontext_inherits_static_registers (line 559) | def test_new_isolated_subcontext_inherits_static_registers
method test_new_isolated_subcontext_registers_do_not_pollute_context (line 565) | def test_new_isolated_subcontext_registers_do_not_pollute_context
method test_new_isolated_subcontext_inherits_filters (line 572) | def test_new_isolated_subcontext_inherits_filters
method test_disables_tag_specified (line 586) | def test_disables_tag_specified
method test_disables_nested_tags (line 595) | def test_disables_nested_tags
method test_override_global_filter (line 615) | def test_override_global_filter
method test_has_key_will_not_add_an_error_for_missing_keys (line 634) | def test_has_key_will_not_add_an_error_for_missing_keys
method test_key_lookup_will_raise_for_missing_keys_when_strict_variables_is_enabled (line 642) | def test_key_lookup_will_raise_for_missing_keys_when_strict_variables_...
method test_has_key_will_not_raise_for_missing_keys_when_strict_variables_is_enabled (line 650) | def test_has_key_will_not_raise_for_missing_keys_when_strict_variables...
method test_context_always_uses_static_registers (line 657) | def test_context_always_uses_static_registers
method test_variable_to_liquid_returns_contextual_drop (line 671) | def test_variable_to_liquid_returns_contextual_drop
method test_new_isolated_context_inherits_parent_environment (line 690) | def test_new_isolated_context_inherits_parent_environment
method test_newly_built_context_inherits_parent_environment (line 698) | def test_newly_built_context_inherits_parent_environment
method assert_no_object_allocations (line 707) | def assert_no_object_allocations
FILE: test/integration/document_test.rb
class DocumentTest (line 5) | class DocumentTest < Minitest::Test
method test_unexpected_outer_tag (line 8) | def test_unexpected_outer_tag
method test_unknown_tag (line 13) | def test_unknown_tag
FILE: test/integration/drop_test.rb
class ContextDrop (line 5) | class ContextDrop < Liquid::Drop
method scopes (line 6) | def scopes
method scopes_as_array (line 10) | def scopes_as_array
method loop_pos (line 14) | def loop_pos
method liquid_method_missing (line 18) | def liquid_method_missing(method)
class ProductDrop (line 23) | class ProductDrop < Liquid::Drop
class TextDrop (line 24) | class TextDrop < Liquid::Drop
method array (line 25) | def array
method text (line 29) | def text
class CatchallDrop (line 34) | class CatchallDrop < Liquid::Drop
method liquid_method_missing (line 35) | def liquid_method_missing(method)
method texts (line 40) | def texts
method catchall (line 44) | def catchall
method context (line 48) | def context
method callmenot (line 54) | def callmenot
class EnumerableDrop (line 59) | class EnumerableDrop < Liquid::Drop
method liquid_method_missing (line 60) | def liquid_method_missing(method)
method size (line 64) | def size
method first (line 68) | def first
method count (line 72) | def count
method min (line 76) | def min
method max (line 80) | def max
method each (line 84) | def each
class RealEnumerableDrop (line 91) | class RealEnumerableDrop < Liquid::Drop
method liquid_method_missing (line 94) | def liquid_method_missing(method)
method each (line 98) | def each
class DropsTest (line 105) | class DropsTest < Minitest::Test
method test_product_drop (line 108) | def test_product_drop
method test_drop_does_only_respond_to_whitelisted_methods (line 113) | def test_drop_does_only_respond_to_whitelisted_methods
method test_drops_respond_to_to_liquid (line 122) | def test_drops_respond_to_to_liquid
method test_text_drop (line 127) | def test_text_drop
method test_catchall_unknown_method (line 132) | def test_catchall_unknown_method
method test_catchall_integer_argument_drop (line 137) | def test_catchall_integer_argument_drop
method test_text_array_drop (line 142) | def test_text_array_drop
method test_context_drop (line 147) | def test_context_drop
method test_context_drop_array_with_map (line 152) | def test_context_drop_array_with_map
method test_nested_context_drop (line 157) | def test_nested_context_drop
method test_protected (line 162) | def test_protected
method test_object_methods_not_allowed (line 167) | def test_object_methods_not_allowed
method test_scope (line 174) | def test_scope
method test_scope_though_proc (line 180) | def test_scope_though_proc
method test_scope_with_assigns (line 186) | def test_scope_with_assigns
method test_scope_from_tags (line 193) | def test_scope_from_tags
method test_access_context_from_drop (line 199) | def test_access_context_from_drop
method test_enumerable_drop (line 203) | def test_enumerable_drop
method test_enumerable_drop_size (line 207) | def test_enumerable_drop_size
method test_enumerable_drop_will_invoke_liquid_method_missing_for_clashing_method_names (line 211) | def test_enumerable_drop_will_invoke_liquid_method_missing_for_clashin...
method test_some_enumerable_methods_still_get_invoked (line 220) | def test_some_enumerable_methods_still_get_invoked
method test_empty_string_value_access (line 238) | def test_empty_string_value_access
method test_nil_value_access (line 242) | def test_nil_value_access
method test_default_to_s_on_drops (line 246) | def test_default_to_s_on_drops
method test_invokable_methods (line 251) | def test_invokable_methods
FILE: test/integration/error_handling_test.rb
class ErrorHandlingTest (line 5) | class ErrorHandlingTest < Minitest::Test
method test_templates_parsed_with_line_numbers_renders_them_in_errors (line 8) | def test_templates_parsed_with_line_numbers_renders_them_in_errors
method test_standard_error (line 41) | def test_standard_error
method test_syntax (line 49) | def test_syntax
method test_argument (line 57) | def test_argument
method test_missing_endtag_parse_time_error (line 65) | def test_missing_endtag_parse_time_error
method test_unrecognized_operator (line 69) | def test_unrecognized_operator
method test_lax_unrecognized_operator (line 77) | def test_lax_unrecognized_operator
method test_with_line_numbers_adds_numbers_to_parser_errors (line 84) | def test_with_line_numbers_adds_numbers_to_parser_errors
method test_with_line_numbers_adds_numbers_to_parser_errors_with_whitespace_trim (line 95) | def test_with_line_numbers_adds_numbers_to_parser_errors_with_whitespa...
method test_parsing_warn_with_line_numbers_adds_numbers_to_lexer_errors (line 107) | def test_parsing_warn_with_line_numbers_adds_numbers_to_lexer_errors
method test_parsing_strict_with_line_numbers_adds_numbers_to_lexer_errors (line 126) | def test_parsing_strict_with_line_numbers_adds_numbers_to_lexer_errors
method test_syntax_errors_in_nested_blocks_have_correct_line_number (line 144) | def test_syntax_errors_in_nested_blocks_have_correct_line_number
method test_strict_error_messages (line 158) | def test_strict_error_messages
method test_warnings (line 170) | def test_warnings
method test_warning_line_numbers (line 179) | def test_warning_line_numbers
method test_exceptions_propagate (line 189) | def test_exceptions_propagate
method test_default_exception_renderer_with_internal_error (line 196) | def test_default_exception_renderer_with_internal_error
method test_setting_default_exception_renderer (line 205) | def test_setting_default_exception_renderer
method test_setting_exception_renderer_on_environment (line 221) | def test_setting_exception_renderer_on_environment
method test_exception_renderer_exposing_non_liquid_error (line 236) | def test_exception_renderer_exposing_non_liquid_error
class TestFileSystem (line 252) | class TestFileSystem
method read_template_file (line 253) | def read_template_file(_template_path)
method test_included_template_name_with_line_numbers (line 258) | def test_included_template_name_with_line_numbers
method test_bug_compatible_silencing_of_errors_in_blank_nodes (line 267) | def test_bug_compatible_silencing_of_errors_in_blank_nodes
method test_syntax_error_is_raised_with_template_name (line 275) | def test_syntax_error_is_raised_with_template_name
method test_syntax_error_is_raised_with_template_name_from_template_factory (line 294) | def test_syntax_error_is_raised_with_template_name_from_template_factory
method test_error_is_raised_during_parse_with_template_name (line 316) | def test_error_is_raised_during_parse_with_template_name
method test_internal_error_is_raised_with_template_name (line 332) | def test_internal_error_is_raised_with_template_name
FILE: test/integration/expression_test.rb
class ExpressionTest (line 6) | class ExpressionTest < Minitest::Test
method test_keyword_literals (line 7) | def test_keyword_literals
method test_string (line 12) | def test_string
method test_int (line 20) | def test_int
method test_float (line 26) | def test_float
method test_range (line 45) | def test_range
method test_quirky_negative_sign_expression_markup (line 59) | def test_quirky_negative_sign_expression_markup
method test_expression_cache (line 72) | def test_expression_cache
method test_expression_cache_with_true_boolean (line 93) | def test_expression_cache_with_true_boolean
method test_expression_cache_with_lru_redux (line 117) | def test_expression_cache_with_lru_redux
method test_disable_expression_cache (line 138) | def test_disable_expression_cache
method test_safe_parse_with_variable_lookup (line 155) | def test_safe_parse_with_variable_lookup
method test_safe_parse_with_number (line 165) | def test_safe_parse_with_number
method test_safe_parse_raises_syntax_error_for_invalid_expression (line 173) | def test_safe_parse_raises_syntax_error_for_invalid_expression
method assert_expression_result (line 186) | def assert_expression_result(expect, markup, **assigns)
FILE: test/integration/filter_kwarg_test.rb
class FilterKwargTest (line 5) | class FilterKwargTest < Minitest::Test
type KwargFilter (line 6) | module KwargFilter
function html_tag (line 7) | def html_tag(_tag, attributes)
method test_can_parse_data_kwargs (line 16) | def test_can_parse_data_kwargs
FILE: test/integration/filter_test.rb
type MoneyFilter (line 5) | module MoneyFilter
function money (line 6) | def money(input)
function money_with_underscore (line 10) | def money_with_underscore(input)
type CanadianMoneyFilter (line 15) | module CanadianMoneyFilter
function money (line 16) | def money(input)
type SubstituteFilter (line 21) | module SubstituteFilter
function substitute (line 22) | def substitute(input, params = {})
class FiltersTest (line 27) | class FiltersTest < Minitest::Test
type OverrideObjectMethodFilter (line 30) | module OverrideObjectMethodFilter
function tap (line 31) | def tap(_input)
method setup (line 36) | def setup
method test_local_filter (line 40) | def test_local_filter
method test_underscore_in_filter_name (line 47) | def test_underscore_in_filter_name
method test_second_filter_overwrites_first (line 53) | def test_second_filter_overwrites_first
method test_size (line 61) | def test_size
method test_join (line 65) | def test_join
method test_sort (line 69) | def test_sort
method test_sort_natural (line 85) | def test_sort_natural
method test_compact (line 105) | def test_compact
method test_strip_html (line 125) | def test_strip_html
method test_strip_html_ignore_comments_with_html (line 129) | def test_strip_html_ignore_comments_with_html
method test_capitalize (line 137) | def test_capitalize
method test_nonexistent_filter_is_ignored (line 141) | def test_nonexistent_filter_is_ignored
method test_filter_with_keyword_arguments (line 145) | def test_filter_with_keyword_arguments
method test_override_object_method_in_filter (line 153) | def test_override_object_method_in_filter
method test_liquid_argument_error (line 160) | def test_liquid_argument_error
class FiltersInTemplate (line 170) | class FiltersInTemplate < Minitest::Test
method test_local_global (line 173) | def test_local_global
method test_local_filter_with_deprecated_syntax (line 181) | def test_local_filter_with_deprecated_syntax
class TestObject (line 187) | class TestObject < Liquid::Drop
method initialize (line 189) | def initialize(a)
FILE: test/integration/hash_ordering_test.rb
class HashOrderingTest (line 5) | class HashOrderingTest < Minitest::Test
type MoneyFilter (line 6) | module MoneyFilter
function money (line 7) | def money(input)
type CanadianMoneyFilter (line 12) | module CanadianMoneyFilter
function money (line 13) | def money(input)
method test_global_register_order (line 20) | def test_global_register_order
FILE: test/integration/hash_rendering_test.rb
class HashRenderingTest (line 5) | class HashRenderingTest < Minitest::Test
method test_render_empty_hash (line 6) | def test_render_empty_hash
method test_render_hash_with_string_keys_and_values (line 10) | def test_render_hash_with_string_keys_and_values
method test_render_hash_with_symbol_keys_and_integer_values (line 14) | def test_render_hash_with_symbol_keys_and_integer_values
method test_render_nested_hash (line 18) | def test_render_nested_hash
method test_render_hash_with_array_values (line 22) | def test_render_hash_with_array_values
method test_render_recursive_hash (line 26) | def test_render_recursive_hash
method test_hash_with_downcase_filter (line 32) | def test_hash_with_downcase_filter
method test_hash_with_upcase_filter (line 36) | def test_hash_with_upcase_filter
method test_hash_with_strip_filter (line 40) | def test_hash_with_strip_filter
method test_hash_with_escape_filter (line 44) | def test_hash_with_escape_filter
method test_hash_with_url_encode_filter (line 48) | def test_hash_with_url_encode_filter
method test_hash_with_strip_html_filter (line 52) | def test_hash_with_strip_html_filter
method test_hash_with_truncate__20_filter (line 56) | def test_hash_with_truncate__20_filter
method test_hash_with_replace___key____replaced_key__filter (line 60) | def test_hash_with_replace___key____replaced_key__filter
method test_hash_with_append____appended_text__filter (line 64) | def test_hash_with_append____appended_text__filter
method test_hash_with_prepend___prepended_text___filter (line 68) | def test_hash_with_prepend___prepended_text___filter
method test_render_hash_with_array_values_empty (line 72) | def test_render_hash_with_array_values_empty
method test_render_hash_with_array_values_hash (line 76) | def test_render_hash_with_array_values_hash
method test_join_filter_with_hash (line 80) | def test_join_filter_with_hash
method test_render_hash_with_hash_key (line 86) | def test_render_hash_with_hash_key
method test_rendering_hash_with_custom_to_s_method_uses_custom_to_s (line 90) | def test_rendering_hash_with_custom_to_s_method_uses_custom_to_s
method test_rendering_hash_without_custom_to_s_uses_default_inspect (line 94) | def test_rendering_hash_without_custom_to_s_uses_default_inspect
FILE: test/integration/output_test.rb
type FunnyFilter (line 5) | module FunnyFilter
function make_funny (line 6) | def make_funny(_input)
function cite_funny (line 10) | def cite_funny(input)
function add_smiley (line 14) | def add_smiley(input, smiley = ":-)")
function add_tag (line 18) | def add_tag(input, tag = "p", id = "foo")
function paragraph (line 22) | def paragraph(input)
function link_to (line 26) | def link_to(name, url)
class OutputTest (line 31) | class OutputTest < Minitest::Test
method setup (line 34) | def setup
method test_variable (line 40) | def test_variable
method test_variable_traversing_with_two_brackets (line 44) | def test_variable_traversing_with_two_brackets
method test_variable_traversing (line 52) | def test_variable_traversing
method test_variable_piping (line 57) | def test_variable_piping
method test_variable_piping_with_input (line 64) | def test_variable_piping_with_input
method test_variable_piping_with_args (line 71) | def test_variable_piping_with_args
method test_variable_piping_with_no_args (line 78) | def test_variable_piping_with_no_args
method test_multiple_variable_piping_with_args (line 85) | def test_multiple_variable_piping_with_args
method test_variable_piping_with_multiple_args (line 92) | def test_variable_piping_with_multiple_args
method test_variable_piping_with_variable_args (line 99) | def test_variable_piping_with_variable_args
method test_multiple_pipings (line 106) | def test_multiple_pipings
method test_link_to (line 114) | def test_link_to
FILE: test/integration/parsing_quirks_test.rb
class ParsingQuirksTest (line 5) | class ParsingQuirksTest < Minitest::Test
method test_parsing_css (line 8) | def test_parsing_css
method test_raise_on_single_close_bracet (line 13) | def test_raise_on_single_close_bracet
method test_raise_on_label_and_no_close_bracets (line 19) | def test_raise_on_label_and_no_close_bracets
method test_raise_on_label_and_no_close_bracets_percent (line 25) | def test_raise_on_label_and_no_close_bracets_percent
method test_error_on_empty_filter (line 31) | def test_error_on_empty_filter
method test_meaningless_parens_error (line 44) | def test_meaningless_parens_error
method test_unexpected_characters_syntax_error (line 53) | def test_unexpected_characters_syntax_error
method test_no_error_on_lax_empty_filter (line 66) | def test_no_error_on_lax_empty_filter
method test_meaningless_parens_lax (line 72) | def test_meaningless_parens_lax
method test_unexpected_characters_silently_eat_logic_lax (line 80) | def test_unexpected_characters_silently_eat_logic_lax
method test_raise_on_invalid_tag_delimiter (line 89) | def test_raise_on_invalid_tag_delimiter
method test_unanchored_filter_arguments (line 95) | def test_unanchored_filter_arguments
method test_invalid_variables_work (line 108) | def test_invalid_variables_work
method test_extra_dots_in_ranges (line 115) | def test_extra_dots_in_ranges
method test_blank_variable_markup (line 121) | def test_blank_variable_markup
method test_lookup_on_var_with_literal_name (line 125) | def test_lookup_on_var_with_literal_name
method test_contains_in_id (line 131) | def test_contains_in_id
method test_incomplete_expression (line 135) | def test_incomplete_expression
FILE: test/integration/profiler_test.rb
class ProfilerTest (line 5) | class ProfilerTest < Minitest::Test
class TestDrop (line 6) | class TestDrop < Liquid::Drop
method initialize (line 7) | def initialize(value)
method to_s (line 12) | def to_s
method artificial_execution_time (line 22) | def artificial_execution_time
class ProfilingFileSystem (line 29) | class ProfilingFileSystem
method read_template_file (line 30) | def read_template_file(template_path)
method setup (line 35) | def setup
method test_template_allows_flagging_profiling (line 39) | def test_template_allows_flagging_profiling
method test_parse_makes_available_simple_profiling (line 46) | def test_parse_makes_available_simple_profiling
method test_render_ignores_raw_strings_when_profiling (line 56) | def test_render_ignores_raw_strings_when_profiling
method test_profiling_includes_line_numbers_of_liquid_nodes (line 63) | def test_profiling_includes_line_numbers_of_liquid_nodes
method test_profiling_includes_line_numbers_of_included_partials (line 74) | def test_profiling_includes_line_numbers_of_included_partials
method test_profiling_render_tag (line 86) | def test_profiling_render_tag
method test_profiling_times_the_rendering_of_tokens (line 97) | def test_profiling_times_the_rendering_of_tokens
method test_profiling_times_the_entire_render (line 105) | def test_profiling_times_the_entire_render
class SleepTag (line 112) | class SleepTag < Liquid::Tag
method initialize (line 113) | def initialize(tag_name, markup, parse_context)
method render_to_output_buffer (line 118) | def render_to_output_buffer(_context, _output)
method test_profiling_multiple_renders (line 123) | def test_profiling_multiple_renders
method test_profiling_uses_include_to_mark_children (line 143) | def test_profiling_uses_include_to_mark_children
method test_profiling_marks_children_with_the_name_of_included_partial (line 151) | def test_profiling_marks_children_with_the_name_of_included_partial
method test_profiling_supports_multiple_templates (line 161) | def test_profiling_supports_multiple_templates
method test_profiling_supports_rendering_the_same_partial_multiple_times (line 176) | def test_profiling_supports_rendering_the_same_partial_multiple_times
method test_can_iterate_over_each_profiling_entry (line 191) | def test_can_iterate_over_each_profiling_entry
method test_profiling_marks_children_of_if_blocks (line 203) | def test_profiling_marks_children_of_if_blocks
method test_profiling_marks_children_of_for_blocks (line 211) | def test_profiling_marks_children_of_for_blocks
method test_profiling_supports_self_time (line 220) | def test_profiling_supports_self_time
method test_profiling_supports_total_time (line 233) | def test_profiling_supports_total_time
FILE: test/integration/security_test.rb
type SecurityFilter (line 5) | module SecurityFilter
function add_one (line 6) | def add_one(input)
class SecurityTest (line 11) | class SecurityTest < Minitest::Test
method setup (line 14) | def setup
method test_no_instance_eval (line 18) | def test_no_instance_eval
method test_no_existing_instance_eval (line 25) | def test_no_existing_instance_eval
method test_no_instance_eval_after_mixing_in_new_filter (line 32) | def test_no_instance_eval_after_mixing_in_new_filter
method test_no_instance_eval_later_in_chain (line 39) | def test_no_instance_eval_later_in_chain
method test_does_not_permanently_add_filters_to_symbol_table (line 46) | def test_does_not_permanently_add_filters_to_symbol_table
method test_does_not_add_drop_methods_to_symbol_table (line 65) | def test_does_not_add_drop_methods_to_symbol_table
method test_max_depth_nested_blocks_does_not_raise_exception (line 76) | def test_max_depth_nested_blocks_does_not_raise_exception
method test_more_than_max_depth_nested_blocks_raises_exception (line 82) | def test_more_than_max_depth_nested_blocks_raises_exception
FILE: test/integration/standard_filter_test.rb
class TestThing (line 6) | class TestThing
method initialize (line 9) | def initialize
method to_s (line 13) | def to_s
method [] (line 17) | def [](_whatever)
method to_liquid (line 21) | def to_liquid
class TestDrop (line 27) | class TestDrop < Liquid::Drop
method initialize (line 28) | def initialize(value:)
method registers (line 34) | def registers
class TestModel (line 39) | class TestModel
method initialize (line 40) | def initialize(value:)
method to_liquid (line 44) | def to_liquid
class TestEnumerable (line 49) | class TestEnumerable < Liquid::Drop
method each (line 52) | def each(&block)
class NumberLikeThing (line 57) | class NumberLikeThing < Liquid::Drop
method initialize (line 58) | def initialize(amount)
method to_number (line 62) | def to_number
class StandardFiltersTest (line 67) | class StandardFiltersTest < Minitest::Test
method setup (line 73) | def setup
method test_size (line 77) | def test_size
method test_downcase (line 83) | def test_downcase
method test_upcase (line 88) | def test_upcase
method test_slice (line 93) | def test_slice
method test_slice_on_arrays (line 118) | def test_slice_on_arrays
method test_find_on_empty_array (line 136) | def test_find_on_empty_array
method test_find_index_on_empty_array (line 140) | def test_find_index_on_empty_array
method test_has_on_empty_array (line 144) | def test_has_on_empty_array
method test_truncate (line 148) | def test_truncate
method test_split (line 159) | def test_split
method test_squish_filter (line 167) | def test_squish_filter
method test_escape (line 174) | def test_escape
method test_h (line 181) | def test_h
method test_escape_once (line 188) | def test_escape_once
method test_base64_encode (line 192) | def test_base64_encode
method test_base64_decode (line 197) | def test_base64_decode
method test_base64_url_safe_encode (line 217) | def test_base64_url_safe_encode
method test_base64_url_safe_decode (line 225) | def test_base64_url_safe_decode
method test_url_encode (line 247) | def test_url_encode
method test_url_decode (line 254) | def test_url_decode
method test_truncatewords (line 267) | def test_truncatewords
method test_strip_html (line 284) | def test_strip_html
method test_join (line 297) | def test_join
method test_join_calls_to_liquid_on_each_element (line 303) | def test_join_calls_to_liquid_on_each_element
method test_sort (line 307) | def test_sort
method test_sort_with_nils (line 312) | def test_sort_with_nils
method test_sort_when_property_is_sometimes_missing_puts_nils_last (line 317) | def test_sort_when_property_is_sometimes_missing_puts_nils_last
method test_sort_natural (line 335) | def test_sort_natural
method test_sort_natural_with_nils (line 340) | def test_sort_natural_with_nils
method test_sort_natural_when_property_is_sometimes_missing_puts_nils_last (line 345) | def test_sort_natural_when_property_is_sometimes_missing_puts_nils_last
method test_sort_natural_case_check (line 363) | def test_sort_natural_case_check
method test_sort_empty_array (line 386) | def test_sort_empty_array
method test_sort_invalid_property (line 390) | def test_sort_invalid_property
method test_sort_natural_empty_array (line 402) | def test_sort_natural_empty_array
method test_sort_natural_invalid_property (line 406) | def test_sort_natural_invalid_property
method test_legacy_sort_hash (line 418) | def test_legacy_sort_hash
method test_numerical_vs_lexicographical_sort (line 422) | def test_numerical_vs_lexicographical_sort
method test_uniq (line 429) | def test_uniq
method test_uniq_empty_array (line 438) | def test_uniq_empty_array
method test_uniq_invalid_property (line 442) | def test_uniq_invalid_property
method test_compact_empty_array (line 454) | def test_compact_empty_array
method test_compact_invalid_property (line 458) | def test_compact_invalid_property
method test_reverse (line 470) | def test_reverse
method test_legacy_reverse_hash (line 474) | def test_legacy_reverse_hash
method test_map (line 478) | def test_map
method test_map_doesnt_call_arbitrary_stuff (line 487) | def test_map_doesnt_call_arbitrary_stuff
method test_map_calls_to_liquid (line 492) | def test_map_calls_to_liquid
method test_map_calls_context= (line 497) | def test_map_calls_context=
method test_map_on_hashes (line 507) | def test_map_on_hashes
method test_legacy_map_on_hashes_with_dynamic_key (line 515) | def test_legacy_map_on_hashes_with_dynamic_key
method test_sort_calls_to_liquid (line 521) | def test_sort_calls_to_liquid
method test_map_over_proc (line 527) | def test_map_over_proc
method test_map_over_drops_returning_procs (line 534) | def test_map_over_drops_returning_procs
method test_map_works_on_enumerables (line 547) | def test_map_works_on_enumerables
method test_map_returns_empty_on_2d_input_array (line 552) | def test_map_returns_empty_on_2d_input_array
method test_map_with_value_property (line 564) | def test_map_with_value_property
method test_map_returns_input_with_no_property (line 574) | def test_map_returns_input_with_no_property
method test_sort_works_on_enumerables (line 586) | def test_sort_works_on_enumerables
method test_first_and_last_call_to_liquid (line 590) | def test_first_and_last_call_to_liquid
method test_truncate_calls_to_liquid (line 595) | def test_truncate_calls_to_liquid
method test_date (line 599) | def test_date
method test_first_last (line 630) | def test_first_last
method test_first_last_on_strings (line 637) | def test_first_last_on_strings
method test_first_last_on_unicode_strings (line 653) | def test_first_last_on_unicode_strings
method test_first_last_on_strings_via_template (line 662) | def test_first_last_on_strings_via_template
method test_replace (line 671) | def test_replace
method test_remove (line 688) | def test_remove
method test_pipes_in_string_arguments (line 699) | def test_pipes_in_string_arguments
method test_strip (line 703) | def test_strip
method test_lstrip (line 708) | def test_lstrip
method test_rstrip (line 713) | def test_rstrip
method test_strip_newlines (line 718) | def test_strip_newlines
method test_newlines_to_br (line 723) | def test_newlines_to_br
method test_plus (line 728) | def test_plus
method test_minus (line 735) | def test_minus
method test_abs (line 742) | def test_abs
method test_times (line 755) | def test_times
method test_divided_by (line 765) | def test_divided_by
method test_modulo (line 780) | def test_modulo
method test_round (line 789) | def test_round
method test_ceil (line 801) | def test_ceil
method test_floor (line 811) | def test_floor
method test_at_most (line 821) | def test_at_most
method test_at_least (line 832) | def test_at_least
method test_append (line 843) | def test_append
method test_concat (line 849) | def test_concat
method test_prepend (line 859) | def test_prepend
method test_default (line 865) | def test_default
method test_default_handle_false (line 877) | def test_default_handle_false
method test_cannot_access_private_methods (line 889) | def test_cannot_access_private_methods
method test_date_raises_nothing (line 893) | def test_date_raises_nothing
method test_reject (line 898) | def test_reject
method test_reject_with_value (line 912) | def test_reject_with_value
method test_reject_with_false_value (line 926) | def test_reject_with_false_value
method test_has (line 940) | def test_has
method test_has_when_does_not_have_it (line 954) | def test_has_when_does_not_have_it
method test_has_with_empty_arrays (line 968) | def test_has_with_empty_arrays
method test_has_with_false_value (line 980) | def test_has_with_false_value
method test_has_with_false_value_when_does_not_have_it (line 994) | def test_has_with_false_value_when_does_not_have_it
method test_find_with_value (line 1008) | def test_find_with_value
method test_find_with_empty_arrays (line 1026) | def test_find_with_empty_arrays
method test_find_index_with_value (line 1038) | def test_find_index_with_value
method test_find_index_with_empty_arrays (line 1056) | def test_find_index_with_empty_arrays
method test_where (line 1068) | def test_where
method test_where_with_empty_string_is_a_no_op (line 1082) | def test_where_with_empty_string_is_a_no_op
method test_where_with_nil_is_a_no_op (line 1090) | def test_where_with_nil_is_a_no_op
method test_where_with_value (line 1099) | def test_where_with_value
method test_where_with_false_value (line 1113) | def test_where_with_false_value
method test_where_string_keys (line 1127) | def test_where_string_keys
method test_where_no_key_set (line 1139) | def test_where_no_key_set
method test_where_non_array_map_input (line 1156) | def test_where_non_array_map_input
method test_where_indexable_but_non_map_value (line 1161) | def test_where_indexable_but_non_map_value
method test_where_non_boolean_value (line 1166) | def test_where_non_boolean_value
method test_where_array_of_only_unindexable_values (line 1178) | def test_where_array_of_only_unindexable_values
method test_all_filters_never_raise_non_liquid_exception (line 1183) | def test_all_filters_never_raise_non_liquid_exception
method test_where_no_target_value (line 1221) | def test_where_no_target_value
method test_sum_with_all_numbers (line 1232) | def test_sum_with_all_numbers
method test_sum_with_numeric_strings (line 1241) | def test_sum_with_numeric_strings
method test_sum_with_nested_arrays (line 1250) | def test_sum_with_nested_arrays
method test_sum_with_indexable_map_values (line 1259) | def test_sum_with_indexable_map_values
method test_sum_with_indexable_non_map_values (line 1268) | def test_sum_with_indexable_non_map_values
method test_sum_with_unindexable_values (line 1277) | def test_sum_with_unindexable_values
method test_sum_without_property_calls_to_liquid (line 1286) | def test_sum_without_property_calls_to_liquid
method test_sum_with_property_calls_to_liquid_on_property_values (line 1292) | def test_sum_with_property_calls_to_liquid_on_property_values
method test_sum_of_floats (line 1298) | def test_sum_of_floats
method test_sum_of_negative_floats (line 1304) | def test_sum_of_negative_floats
method test_sum_with_float_strings (line 1310) | def test_sum_with_float_strings
method test_sum_resulting_in_negative_float (line 1316) | def test_sum_resulting_in_negative_float
method test_sum_with_floats_and_indexable_map_values (line 1322) | def test_sum_with_floats_and_indexable_map_values
method test_sum_with_non_string_property (line 1334) | def test_sum_with_non_string_property
method test_uniq_with_to_liquid_value (line 1345) | def test_uniq_with_to_liquid_value
method test_uniq_with_to_liquid_value_pick_correct_classes (line 1353) | def test_uniq_with_to_liquid_value_pick_correct_classes
method with_timezone (line 1363) | def with_timezone(tz)
FILE: test/integration/tag/disableable_test.rb
class TagDisableableTest (line 5) | class TagDisableableTest < Minitest::Test
type RenderTagName (line 8) | module RenderTagName
function render (line 9) | def render(_context)
class Custom (line 14) | class Custom < Tag
class Custom2 (line 19) | class Custom2 < Tag
class DisableCustom (line 24) | class DisableCustom < Block
class DisableBoth (line 28) | class DisableBoth < Block
method test_block_tag_disabling_nested_tag (line 32) | def test_block_tag_disabling_nested_tag
method test_block_tag_disabling_multiple_nested_tags (line 41) | def test_block_tag_disabling_multiple_nested_tags
method with_disableable_tags (line 52) | def with_disableable_tags
FILE: test/integration/tag_test.rb
class TagTest (line 5) | class TagTest < Minitest::Test
method test_custom_tags_have_a_default_render_to_output_buffer_method_for_backwards_compatibility (line 8) | def test_custom_tags_have_a_default_render_to_output_buffer_method_for...
FILE: test/integration/tags/break_tag_test.rb
class BreakTagTest (line 5) | class BreakTagTest < Minitest::Test
method test_break_with_no_block (line 10) | def test_break_with_no_block
FILE: test/integration/tags/continue_tag_test.rb
class ContinueTagTest (line 5) | class ContinueTagTest < Minitest::Test
method test_continue_with_no_block (line 10) | def test_continue_with_no_block
FILE: test/integration/tags/cycle_tag_test.rb
class CycleTagTest (line 5) | class CycleTagTest < Minitest::Test
method test_simple_cycle_inside_for_loop (line 6) | def test_simple_cycle_inside_for_loop
method test_cycle_with_variables_inside_for_loop (line 16) | def test_cycle_with_variables_inside_for_loop
method test_cycle_named_groups_string (line 29) | def test_cycle_named_groups_string
method test_cycle_named_groups_vlookup (line 40) | def test_cycle_named_groups_vlookup
method test_unnamed_cycle_have_independent_counters_when_used_with_lookups (line 53) | def test_unnamed_cycle_have_independent_counters_when_used_with_lookups
method test_unnamed_cycle_dependent_counter_when_used_with_literal_values (line 65) | def test_unnamed_cycle_dependent_counter_when_used_with_literal_values
method test_optional_trailing_comma (line 75) | def test_optional_trailing_comma
method test_cycle_tag_without_arguments (line 86) | def test_cycle_tag_without_arguments
method test_cycle_tag_with_error_mode (line 94) | def test_cycle_tag_with_error_mode
method test_cycle_with_trailing_elements (line 115) | def test_cycle_with_trailing_elements
method test_cycle_name_with_invalid_expression (line 149) | def test_cycle_name_with_invalid_expression
method test_cycle_variable_with_invalid_expression (line 166) | def test_cycle_variable_with_invalid_expression
FILE: test/integration/tags/echo_test.rb
class EchoTest (line 5) | class EchoTest < Minitest::Test
method test_echo_outputs_its_input (line 8) | def test_echo_outputs_its_input
FILE: test/integration/tags/for_tag_test.rb
class ThingWithValue (line 5) | class ThingWithValue < Liquid::Drop
method value (line 6) | def value
class ForTagTest (line 11) | class ForTagTest < Minitest::Test
method test_for (line 14) | def test_for
method test_for_reversed (line 36) | def test_for_reversed
method test_for_with_range (line 41) | def test_for_with_range
method test_for_with_variable_range (line 51) | def test_for_with_variable_range
method test_for_with_hash_value_range (line 55) | def test_for_with_hash_value_range
method test_for_with_drop_value_range (line 60) | def test_for_with_drop_value_range
method test_for_with_variable (line 65) | def test_for_with_variable
method test_for_helpers (line 74) | def test_for_helpers
method test_for_and_if (line 89) | def test_for_and_if
method test_for_else (line 98) | def test_for_else
method test_limiting (line 104) | def test_limiting
method test_limiting_with_invalid_limit (line 113) | def test_limiting_with_invalid_limit
method test_limiting_with_invalid_offset (line 127) | def test_limiting_with_invalid_offset
method test_dynamic_variable_limiting (line 141) | def test_dynamic_variable_limiting
method test_nested_for (line 149) | def test_nested_for
method test_offset_only (line 154) | def test_offset_only
method test_pause_resume (line 159) | def test_pause_resume
method test_pause_resume_limit (line 178) | def test_pause_resume_limit
method test_pause_resume_big_limit (line 197) | def test_pause_resume_big_limit
method test_pause_resume_big_offset (line 216) | def test_pause_resume_big_offset
method test_for_with_break (line 231) | def test_for_with_break
method test_for_with_break_after_nested_loop (line 271) | def test_for_with_break_after_nested_loop
method test_for_with_continue (line 284) | def test_for_with_continue
method test_for_tag_string (line 327) | def test_for_tag_string
method test_for_parentloop_references_parent_loop (line 360) | def test_for_parentloop_references_parent_loop
method test_for_parentloop_nil_when_not_present (line 370) | def test_for_parentloop_nil_when_not_present
method test_inner_for_over_empty_input (line 380) | def test_inner_for_over_empty_input
method test_blank_string_not_iterable (line 384) | def test_blank_string_not_iterable
method test_bad_variable_naming_in_for_loop (line 388) | def test_bad_variable_naming_in_for_loop
method test_spacing_with_variable_naming_in_for_loop (line 394) | def test_spacing_with_variable_naming_in_for_loop
class LoaderDrop (line 401) | class LoaderDrop < Liquid::Drop
method initialize (line 404) | def initialize(data)
method each (line 408) | def each
method load_slice (line 413) | def load_slice(from, to)
method test_iterate_with_each_when_no_limit_applied (line 419) | def test_iterate_with_each_when_no_limit_applied
method test_iterate_with_load_slice_when_limit_applied (line 429) | def test_iterate_with_load_slice_when_limit_applied
method test_iterate_with_load_slice_when_limit_and_offset_applied (line 439) | def test_iterate_with_load_slice_when_limit_and_offset_applied
method test_iterate_with_load_slice_returns_same_results_as_without (line 449) | def test_iterate_with_load_slice_returns_same_results_as_without
method test_for_cleans_up_registers (line 459) | def test_for_cleans_up_registers
FILE: test/integration/tags/if_else_tag_test.rb
class IfElseTagTest (line 5) | class IfElseTagTest < Minitest::Test
method test_if (line 8) | def test_if
method test_literal_comparisons (line 17) | def test_literal_comparisons
method test_if_else (line 22) | def test_if_else
method test_if_boolean (line 28) | def test_if_boolean
method test_if_or (line 32) | def test_if_or
method test_if_or_with_operators (line 42) | def test_if_or_with_operators
method test_comparison_of_strings_containing_and_or_or (line 48) | def test_comparison_of_strings_containing_and_or_or
method test_comparison_of_expressions_starting_with_and_or_or (line 54) | def test_comparison_of_expressions_starting_with_and_or_or
method test_if_and (line 68) | def test_if_and
method test_hash_miss_generates_false (line 74) | def test_hash_miss_generates_false
method test_if_from_variable (line 78) | def test_if_from_variable
method test_nested_if (line 111) | def test_nested_if
method test_comparisons_on_null (line 122) | def test_comparisons_on_null
method test_else_if (line 134) | def test_else_if
method test_syntax_error_no_variable (line 142) | def test_syntax_error_no_variable
method test_syntax_error_no_expression (line 146) | def test_syntax_error_no_expression
method test_if_with_custom_condition (line 150) | def test_if_with_custom_condition
method test_operators_are_ignored_unless_isolated (line 160) | def test_operators_are_ignored_unless_isolated
method test_operators_are_whitelisted (line 172) | def test_operators_are_whitelisted
method test_multiple_conditions (line 178) | def test_multiple_conditions
FILE: test/integration/tags/include_tag_test.rb
class TestFileSystem (line 5) | class TestFileSystem
method read_template_file (line 11) | def read_template_file(template_path)
class OtherFileSystem (line 16) | class OtherFileSystem
method read_template_file (line 17) | def read_template_file(_template_path)
class CountingFileSystem (line 22) | class CountingFileSystem
method read_template_file (line 24) | def read_template_file(_template_path)
class CustomInclude (line 31) | class CustomInclude < Liquid::Tag
method initialize (line 34) | def initialize(tag_name, markup, tokens)
method parse (line 40) | def parse(tokens)
method render_to_output_buffer (line 43) | def render_to_output_buffer(_context, output)
class IncludeTagTest (line 49) | class IncludeTagTest < Minitest::Test
method test_include_tag_looks_for_file_system_in_registers_first (line 52) | def test_include_tag_looks_for_file_system_in_registers_first
method test_include_tag_with (line 59) | def test_include_tag_with
method test_include_tag_with_alias (line 68) | def test_include_tag_with_alias
method test_include_tag_for_alias (line 77) | def test_include_tag_for_alias
method test_include_tag_with_default_name (line 86) | def test_include_tag_with_default_name
method test_include_tag_for (line 95) | def test_include_tag_for
method test_include_tag_with_local_variables (line 104) | def test_include_tag_with_local_variables
method test_include_tag_with_multiple_local_variables (line 112) | def test_include_tag_with_multiple_local_variables
method test_include_tag_with_multiple_local_variables_from_context (line 120) | def test_include_tag_with_multiple_local_variables_from_context
method test_included_templates_assigns_variables (line 129) | def test_included_templates_assigns_variables
method test_nested_include_tag (line 137) | def test_nested_include_tag
method test_nested_include_with_variable (line 149) | def test_nested_include_with_variable
method test_recursively_included_template_does_not_produce_endless_loop (line 170) | def test_recursively_included_template_does_not_produce_endless_loop
method test_dynamically_choosen_template (line 184) | def test_dynamically_choosen_template
method test_strict2_parsing_errors (line 207) | def test_strict2_parsing_errors
method test_optional_commas (line 226) | def test_optional_commas
method test_include_tag_caches_second_read_of_same_partial (line 233) | def test_include_tag_caches_second_read_of_same_partial
method test_include_tag_doesnt_cache_partials_across_renders (line 243) | def test_include_tag_doesnt_cache_partials_across_renders
method test_include_tag_within_if_statement (line 258) | def test_include_tag_within_if_statement
method test_custom_include_tag (line 266) | def test_custom_include_tag
method test_custom_include_tag_within_if_statement (line 279) | def test_custom_include_tag_within_if_statement
method test_does_not_add_error_in_strict_mode_for_missing_variable (line 292) | def test_does_not_add_error_in_strict_mode_for_missing_variable
method test_passing_options_to_included_templates (line 300) | def test_passing_options_to_included_templates
method test_render_raise_argument_error_when_template_is_undefined (line 317) | def test_render_raise_argument_error_when_template_is_undefined
method test_render_raise_argument_error_when_template_is_not_a_string (line 331) | def test_render_raise_argument_error_when_template_is_not_a_string
method test_including_via_variable_value (line 339) | def test_including_via_variable_value
method test_including_with_strict_variables (line 363) | def test_including_with_strict_variables
method test_break_through_include (line 374) | def test_break_through_include
method test_render_tag_renders_error_with_template_name (line 383) | def test_render_tag_renders_error_with_template_name
method test_render_tag_renders_error_with_template_name_from_template_factory (line 393) | def test_render_tag_renders_error_with_template_name_from_template_fac...
method test_include_template_with_invalid_expression (line 404) | def test_include_template_with_invalid_expression
method test_include_with_invalid_expression (line 417) | def test_include_with_invalid_expression
method test_include_attribute_with_invalid_expression (line 430) | def test_include_attribute_with_invalid_expression
FILE: test/integration/tags/increment_tag_test.rb
class IncrementTagTest (line 5) | class IncrementTagTest < Minitest::Test
method test_inc (line 8) | def test_inc
method test_dec (line 19) | def test_dec
FILE: test/integration/tags/inline_comment_test.rb
class InlineCommentTest (line 5) | class InlineCommentTest < Minitest::Test
method test_inline_comment_returns_nothing (line 8) | def test_inline_comment_returns_nothing
method test_inline_comment_does_not_require_a_space_after_the_pound_sign (line 15) | def test_inline_comment_does_not_require_a_space_after_the_pound_sign
method test_liquid_inline_comment_returns_nothing (line 19) | def test_liquid_inline_comment_returns_nothing
method test_inline_comment_can_be_written_on_multiple_lines (line 35) | def test_inline_comment_can_be_written_on_multiple_lines
method test_inline_comment_multiple_pound_signs (line 47) | def test_inline_comment_multiple_pound_signs
method test_inline_comments_require_the_pound_sign_on_every_new_line (line 57) | def test_inline_comments_require_the_pound_sign_on_every_new_line
method test_inline_comment_does_not_support_nested_tags (line 66) | def test_inline_comment_does_not_support_nested_tags
FILE: test/integration/tags/liquid_tag_test.rb
class LiquidTagTest (line 5) | class LiquidTagTest < Minitest::Test
method test_liquid_tag (line 8) | def test_liquid_tag
method test_liquid_tag_errors (line 48) | def test_liquid_tag_errors
method test_line_number_is_correct_after_a_blank_token (line 79) | def test_line_number_is_correct_after_a_blank_token
method test_nested_liquid_tag (line 84) | def test_nested_liquid_tag
method test_cannot_open_blocks_living_past_a_liquid_tag (line 94) | def test_cannot_open_blocks_living_past_a_liquid_tag
method test_cannot_close_blocks_created_before_a_liquid_tag (line 103) | def test_cannot_close_blocks_created_before_a_liquid_tag
method test_liquid_tag_in_raw (line 111) | def test_liquid_tag_in_raw
method test_nested_liquid_tags (line 117) | def test_nested_liquid_tags
method test_nested_liquid_tags_on_same_line (line 128) | def test_nested_liquid_tags_on_same_line
method test_nested_liquid_liquid_is_not_skipped_if_used_in_non_tag_position (line 134) | def test_nested_liquid_liquid_is_not_skipped_if_used_in_non_tag_position
method test_next_liquid_with_unclosed_if_tag (line 140) | def test_next_liquid_with_unclosed_if_tag
FILE: test/integration/tags/raw_tag_test.rb
class RawTagTest (line 5) | class RawTagTest < Minitest::Test
method test_tag_in_raw (line 8) | def test_tag_in_raw
method test_output_in_raw (line 15) | def test_output_in_raw
method test_open_tag_in_raw (line 22) | def test_open_tag_in_raw
method test_invalid_raw (line 34) | def test_invalid_raw
FILE: test/integration/tags/render_tag_test.rb
class RenderTagTest (line 5) | class RenderTagTest < Minitest::Test
method test_render_with_no_arguments (line 8) | def test_render_with_no_arguments
method test_render_tag_looks_for_file_system_in_registers_first (line 16) | def test_render_tag_looks_for_file_system_in_registers_first
method test_render_passes_named_arguments_into_inner_scope (line 24) | def test_render_passes_named_arguments_into_inner_scope
method test_render_accepts_literals_as_arguments (line 33) | def test_render_accepts_literals_as_arguments
method test_render_accepts_multiple_named_arguments (line 41) | def test_render_accepts_multiple_named_arguments
method test_render_does_not_inherit_parent_scope_variables (line 49) | def test_render_does_not_inherit_parent_scope_variables
method test_render_does_not_inherit_variable_with_same_name_as_snippet (line 57) | def test_render_does_not_inherit_variable_with_same_name_as_snippet
method test_render_does_not_mutate_parent_scope (line 65) | def test_render_does_not_mutate_parent_scope
method test_nested_render_tag (line 73) | def test_nested_render_tag
method test_recursively_rendered_template_does_not_produce_endless_loop (line 84) | def test_recursively_rendered_template_does_not_produce_endless_loop
method test_sub_contexts_count_towards_the_same_recursion_limit (line 94) | def test_sub_contexts_count_towards_the_same_recursion_limit
method test_dynamically_choosen_templates_are_not_allowed (line 104) | def test_dynamically_choosen_templates_are_not_allowed
method test_strict2_parsing_errors (line 108) | def test_strict2_parsing_errors
method test_optional_commas (line 127) | def test_optional_commas
method test_render_tag_caches_second_read_of_same_partial (line 134) | def test_render_tag_caches_second_read_of_same_partial
method test_render_tag_doesnt_cache_partials_across_renders (line 144) | def test_render_tag_doesnt_cache_partials_across_renders
method test_render_tag_within_if_statement (line 160) | def test_render_tag_within_if_statement
method test_break_through_render (line 168) | def test_break_through_render
method test_increment_is_isolated_between_renders (line 174) | def test_increment_is_isolated_between_renders
method test_decrement_is_isolated_between_renders (line 182) | def test_decrement_is_isolated_between_renders
method test_includes_will_not_render_inside_render_tag (line 190) | def test_includes_will_not_render_inside_render_tag
method test_includes_will_not_render_inside_nested_sibling_tags (line 202) | def test_includes_will_not_render_inside_nested_sibling_tags
method test_render_tag_with (line 216) | def test_render_tag_with
method test_render_tag_with_alias (line 228) | def test_render_tag_with_alias
method test_render_tag_for_alias (line 240) | def test_render_tag_for_alias
method test_render_tag_for (line 252) | def test_render_tag_for
method test_render_tag_forloop (line 264) | def test_render_tag_forloop
method test_render_tag_for_drop (line 275) | def test_render_tag_for_drop
method test_render_tag_with_drop (line 286) | def test_render_tag_with_drop
method test_render_tag_renders_error_with_template_name (line 297) | def test_render_tag_renders_error_with_template_name
method test_render_tag_renders_error_with_template_name_from_template_factory (line 307) | def test_render_tag_renders_error_with_template_name_from_template_fac...
method test_render_with_invalid_expression (line 318) | def test_render_with_invalid_expression
method test_render_attribute_with_invalid_expression (line 331) | def test_render_attribute_with_invalid_expression
FILE: test/integration/tags/standard_tag_test.rb
class StandardTagTest (line 5) | class StandardTagTest < Minitest::Test
method test_no_transform (line 8) | def test_no_transform
method test_has_a_block_which_does_nothing (line 24) | def test_has_a_block_which_does_nothing
method test_hyphenated_assign (line 59) | def test_hyphenated_assign
method test_assign_with_colon_and_spaces (line 64) | def test_assign_with_colon_and_spaces
method test_capture (line 69) | def test_capture
method test_capture_detects_bad_syntax (line 78) | def test_capture_detects_bad_syntax
method test_case (line 88) | def test_case
method test_case_with_else (line 125) | def test_case_with_else
method test_case_on_size (line 148) | def test_case_on_size
method test_case_on_size_with_else (line 157) | def test_case_on_size_with_else
method test_case_on_length_with_else (line 195) | def test_case_on_length_with_else
method test_assign_from_case (line 221) | def test_assign_from_case
method test_case_when_or (line 231) | def test_case_when_or
method test_case_when_comma (line 246) | def test_case_when_comma
method test_case_when_comma_and_blank_body (line 261) | def test_case_when_comma_and_blank_body
method test_assign (line 266) | def test_assign
method test_assign_unassigned (line 270) | def test_assign_unassigned
method test_assign_an_empty_string (line 275) | def test_assign_an_empty_string
method test_assign_is_global (line 279) | def test_assign_is_global
method test_case_detects_bad_syntax (line 283) | def test_case_detects_bad_syntax
method test_cycle (line 293) | def test_cycle
method test_multiple_cycles (line 306) | def test_multiple_cycles
method test_multiple_named_cycles (line 313) | def test_multiple_named_cycles
method test_multiple_named_cycles_with_names_from_context (line 320) | def test_multiple_named_cycles_with_names_from_context
method test_size_of_array (line 329) | def test_size_of_array
method test_size_of_hash (line 334) | def test_size_of_hash
method test_illegal_symbols (line 339) | def test_illegal_symbols
method test_ifchanged (line 346) | def test_ifchanged
method test_multiline_tag (line 354) | def test_multiline_tag
FILE: test/integration/tags/statements_test.rb
class StatementsTest (line 5) | class StatementsTest < Minitest::Test
method test_true_eql_true (line 8) | def test_true_eql_true
method test_true_not_eql_true (line 13) | def test_true_not_eql_true
method test_true_lq_true (line 18) | def test_true_lq_true
method test_one_lq_zero (line 23) | def test_one_lq_zero
method test_zero_lq_one (line 28) | def test_zero_lq_one
method test_zero_lq_or_equal_one (line 33) | def test_zero_lq_or_equal_one
method test_zero_lq_or_equal_one_involving_nil (line 38) | def test_zero_lq_or_equal_one_involving_nil
method test_zero_lqq_or_equal_one (line 46) | def test_zero_lqq_or_equal_one
method test_strings (line 51) | def test_strings
method test_strings_not_equal (line 56) | def test_strings_not_equal
method test_var_strings_equal (line 61) | def test_var_strings_equal
method test_var_strings_are_not_equal (line 66) | def test_var_strings_are_not_equal
method test_var_and_long_string_are_equal (line 71) | def test_var_and_long_string_are_equal
method test_var_and_long_string_are_equal_backwards (line 76) | def test_var_and_long_string_are_equal_backwards
method test_is_collection_empty (line 88) | def test_is_collection_empty
method test_is_not_collection_empty (line 93) | def test_is_not_collection_empty
method test_nil (line 98) | def test_nil
method test_not_nil (line 106) | def test_not_nil
FILE: test/integration/tags/table_row_test.rb
class TableRowTest (line 5) | class TableRowTest < Minitest::Test
class ArrayDrop (line 8) | class ArrayDrop < Liquid::Drop
method initialize (line 11) | def initialize(array)
method each (line 15) | def each(&block)
method test_table_row (line 20) | def test_table_row
method test_table_row_with_different_cols (line 34) | def test_table_row_with_different_cols
method test_table_col_counter (line 42) | def test_table_col_counter
method test_quoted_fragment (line 50) | def test_quoted_fragment
method test_enumerable_drop (line 63) | def test_enumerable_drop
method test_offset_and_limit (line 71) | def test_offset_and_limit
method test_blank_string_not_iterable (line 79) | def test_blank_string_not_iterable
method test_cols_nil_constant_same_as_evaluated_nil_expression (line 87) | def test_cols_nil_constant_same_as_evaluated_nil_expression
method test_nil_limit_is_treated_as_zero (line 105) | def test_nil_limit_is_treated_as_zero
method test_nil_offset_is_treated_as_zero (line 121) | def test_nil_offset_is_treated_as_zero
method test_tablerow_loop_drop_attributes (line 139) | def test_tablerow_loop_drop_attributes
method test_table_row_renders_correct_error_message_for_invalid_parameters (line 191) | def test_table_row_renders_correct_error_message_for_invalid_parameters
method test_table_row_handles_interrupts (line 214) | def test_table_row_handles_interrupts
method test_table_row_does_not_leak_interrupts (line 226) | def test_table_row_does_not_leak_interrupts
method test_tablerow_with_cols_attribute_in_strict2_mode (line 262) | def test_tablerow_with_cols_attribute_in_strict2_mode
method test_tablerow_with_limit_attribute_in_strict2_mode (line 278) | def test_tablerow_with_limit_attribute_in_strict2_mode
method test_tablerow_with_offset_attribute_in_strict2_mode (line 293) | def test_tablerow_with_offset_attribute_in_strict2_mode
method test_tablerow_with_range_attribute_in_strict2_mode (line 308) | def test_tablerow_with_range_attribute_in_strict2_mode
method test_tablerow_with_multiple_attributes_in_strict2_mode (line 323) | def test_tablerow_with_multiple_attributes_in_strict2_mode
method test_tablerow_with_variable_collection_in_strict2_mode (line 339) | def test_tablerow_with_variable_collection_in_strict2_mode
method test_tablerow_with_dotted_access_in_strict2_mode (line 355) | def test_tablerow_with_dotted_access_in_strict2_mode
method test_tablerow_with_bracketed_access_in_strict2_mode (line 371) | def test_tablerow_with_bracketed_access_in_strict2_mode
method test_tablerow_without_attributes_in_strict2_mode (line 386) | def test_tablerow_without_attributes_in_strict2_mode
method test_tablerow_without_in_keyword_in_strict2_mode (line 401) | def test_tablerow_without_in_keyword_in_strict2_mode
method test_tablerow_with_multiple_invalid_attributes_reports_first_in_strict2_mode (line 410) | def test_tablerow_with_multiple_invalid_attributes_reports_first_in_st...
method test_tablerow_with_empty_collection_in_strict2_mode (line 419) | def test_tablerow_with_empty_collection_in_strict2_mode
method test_tablerow_with_invalid_attribute_strict_vs_strict2 (line 434) | def test_tablerow_with_invalid_attribute_strict_vs_strict2
method test_tablerow_with_invalid_expression_strict_vs_strict2 (line 452) | def test_tablerow_with_invalid_expression_strict_vs_strict2
FILE: test/integration/tags/unless_else_tag_test.rb
class UnlessElseTagTest (line 5) | class UnlessElseTagTest < Minitest::Test
method test_unless (line 8) | def test_unless
method test_unless_else (line 17) | def test_unless_else
method test_unless_in_loop (line 23) | def test_unless_in_loop
method test_unless_else_in_loop (line 27) | def test_unless_else_in_loop
FILE: test/integration/template_test.rb
class TemplateContextDrop (line 6) | class TemplateContextDrop < Liquid::Drop
method liquid_method_missing (line 7) | def liquid_method_missing(method)
method foo (line 11) | def foo
method baz (line 15) | def baz
class SomethingWithLength (line 20) | class SomethingWithLength < Liquid::Drop
method length (line 21) | def length
class ErroneousDrop (line 26) | class ErroneousDrop < Liquid::Drop
method bad_method (line 27) | def bad_method
class DropWithUndefinedMethod (line 32) | class DropWithUndefinedMethod < Liquid::Drop
method foo (line 33) | def foo
class TemplateTest (line 38) | class TemplateTest < Minitest::Test
method test_instance_assigns_persist_on_same_template_object_between_parses (line 41) | def test_instance_assigns_persist_on_same_template_object_between_parses
method test_warnings_is_not_exponential_time (line 47) | def test_warnings_is_not_exponential_time
method test_instance_assigns_persist_on_same_template_parsing_between_renders (line 57) | def test_instance_assigns_persist_on_same_template_parsing_between_ren...
method test_custom_assigns_do_not_persist_on_same_template (line 63) | def test_custom_assigns_do_not_persist_on_same_template
method test_custom_assigns_squash_instance_assigns (line 69) | def test_custom_assigns_squash_instance_assigns
method test_persistent_assigns_squash_instance_assigns (line 75) | def test_persistent_assigns_squash_instance_assigns
method test_lambda_is_called_once_from_persistent_assigns_over_multiple_parses_and_renders (line 82) | def test_lambda_is_called_once_from_persistent_assigns_over_multiple_p...
method test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders (line 94) | def test_lambda_is_called_once_from_custom_assigns_over_multiple_parse...
method test_resource_limits_works_with_custom_length_method (line 108) | def test_resource_limits_works_with_custom_length_method
method test_resource_limits_render_length (line 114) | def test_resource_limits_render_length
method test_resource_limits_render_score (line 124) | def test_resource_limits_render_score
method test_resource_limits_aborts_rendering_after_first_error (line 140) | def test_resource_limits_aborts_rendering_after_first_error
method test_resource_limits_hash_in_template_gets_updated_even_if_no_limits_are_set (line 147) | def test_resource_limits_hash_in_template_gets_updated_even_if_no_limi...
method test_render_length_persists_between_blocks (line 154) | def test_render_length_persists_between_blocks
method test_render_length_uses_number_of_bytes_not_characters (line 174) | def test_render_length_uses_number_of_bytes_not_characters
method test_cumulative_render_score_limit_across_render_tags (line 182) | def test_cumulative_render_score_limit_across_render_tags
method test_cumulative_render_score_limit_raises_on_render_bang (line 207) | def test_cumulative_render_score_limit_raises_on_render_bang
method test_cumulative_assign_score_limit_across_include_tags (line 222) | def test_cumulative_assign_score_limit_across_include_tags
method test_cumulative_render_score_tracks_across_partials_without_limit (line 246) | def test_cumulative_render_score_tracks_across_partials_without_limit
method test_default_resource_limits_unaffected_by_render_with_context (line 262) | def test_default_resource_limits_unaffected_by_render_with_context
method test_can_use_drop_as_context (line 270) | def test_can_use_drop_as_context
method test_render_bang_force_rethrow_errors_on_passed_context (line 279) | def test_render_bang_force_rethrow_errors_on_passed_context
method test_exception_renderer_that_returns_string (line 289) | def test_exception_renderer_that_returns_string
method test_exception_renderer_that_raises (line 302) | def test_exception_renderer_that_raises
method test_global_filter_option_on_render (line 313) | def test_global_filter_option_on_render
method test_global_filter_option_when_native_filters_exist (line 320) | def test_global_filter_option_when_native_filters_exist
method test_undefined_variables (line 327) | def test_undefined_variables
method test_nil_value_does_not_raise (line 341) | def test_nil_value_does_not_raise
method test_undefined_variables_raise (line 349) | def test_undefined_variables_raise
method test_undefined_drop_methods (line 357) | def test_undefined_drop_methods
method test_undefined_drop_methods_raise (line 367) | def test_undefined_drop_methods_raise
method test_undefined_filters (line 376) | def test_undefined_filters
method test_undefined_filters_raise (line 391) | def test_undefined_filters_raise
method test_using_range_literal_works_as_expected (line 399) | def test_using_range_literal_works_as_expected
method test_source_string_subclass (line 407) | def test_source_string_subclass
method test_raises_error_with_invalid_utf8 (line 421) | def test_raises_error_with_invalid_utf8
method test_allows_non_string_values_as_source (line 433) | def test_allows_non_string_values_as_source
FILE: test/integration/trim_mode_test.rb
class TrimModeTest (line 5) | class TrimModeTest < Minitest::Test
method test_standard_output (line 9) | def test_standard_output
method test_variable_output_with_multiple_blank_lines (line 27) | def test_variable_output_with_multiple_blank_lines
method test_tag_output_with_multiple_blank_lines (line 47) | def test_tag_output_with_multiple_blank_lines
method test_standard_tags (line 70) | def test_standard_tags
method test_no_trim_output (line 112) | def test_no_trim_output
method test_no_trim_tags (line 119) | def test_no_trim_tags
method test_single_line_outer_tag (line 129) | def test_single_line_outer_tag
method test_single_line_inner_tag (line 139) | def test_single_line_inner_tag
method test_single_line_post_tag (line 149) | def test_single_line_post_tag
method test_single_line_pre_tag (line 159) | def test_single_line_pre_tag
method test_pre_trim_output (line 169) | def test_pre_trim_output
method test_pre_trim_tags (line 186) | def test_pre_trim_tags
method test_post_trim_output (line 223) | def test_post_trim_output
method test_post_trim_tags (line 240) | def test_post_trim_tags
method test_pre_and_post_trim_tags (line 277) | def test_pre_and_post_trim_tags
method test_post_and_pre_trim_tags (line 313) | def test_post_and_pre_trim_tags
method test_trim_output (line 352) | def test_trim_output
method test_trim_tags (line 368) | def test_trim_tags
method test_whitespace_trim_output (line 402) | def test_whitespace_trim_output
method test_whitespace_trim_tags (line 419) | def test_whitespace_trim_tags
method test_complex_trim_output (line 453) | def test_complex_trim_output
method test_complex_trim (line 483) | def test_complex_trim
method test_right_trim_followed_by_tag (line 501) | def test_right_trim_followed_by_tag
method test_raw_output (line 505) | def test_raw_output
method test_pre_trim_blank_preceding_text (line 532) | def test_pre_trim_blank_preceding_text
method test_bug_compatible_pre_trim (line 538) | def test_bug_compatible_pre_trim
method test_trim_blank (line 555) | def test_trim_blank
FILE: test/integration/variable_test.rb
class VariableTest (line 6) | class VariableTest < Minitest::Test
method test_simple_variable (line 9) | def test_simple_variable
method test_variable_render_calls_to_liquid (line 14) | def test_variable_render_calls_to_liquid
method test_variable_lookup_calls_to_liquid_value (line 18) | def test_variable_lookup_calls_to_liquid_value
method test_if_tag_calls_to_liquid_value (line 26) | def test_if_tag_calls_to_liquid_value
method test_unless_tag_calls_to_liquid_value (line 42) | def test_unless_tag_calls_to_liquid_value
method test_case_tag_calls_to_liquid_value (line 47) | def test_case_tag_calls_to_liquid_value
method test_simple_with_whitespaces (line 51) | def test_simple_with_whitespaces
method test_expression_with_whitespace_in_square_brackets (line 56) | def test_expression_with_whitespace_in_square_brackets
method test_ignore_unknown (line 61) | def test_ignore_unknown
method test_using_blank_as_variable_name (line 65) | def test_using_blank_as_variable_name
method test_using_empty_as_variable_name (line 69) | def test_using_empty_as_variable_name
method test_hash_scoping (line 73) | def test_hash_scoping
method test_false_renders_as_false (line 78) | def test_false_renders_as_false
method test_nil_renders_as_empty_string (line 83) | def test_nil_renders_as_empty_string
method test_preset_assigns (line 88) | def test_preset_assigns
method test_reuse_parsed_template (line 94) | def test_reuse_parsed_template
method test_assigns_not_polluted_from_template (line 104) | def test_assigns_not_polluted_from_template
method test_hash_with_default_proc (line 113) | def test_hash_with_default_proc
method test_multiline_variable (line 125) | def test_multiline_variable
method test_render_symbol (line 129) | def test_render_symbol
method test_nested_array (line 133) | def test_nested_array
method test_dynamic_find_var (line 137) | def test_dynamic_find_var
method test_raw_value_variable (line 141) | def test_raw_value_variable
method test_dynamic_find_var_with_drop (line 145) | def test_dynamic_find_var_with_drop
method test_double_nested_variable_lookup (line 167) | def test_double_nested_variable_lookup
method test_variable_lookup_should_not_hang_with_invalid_syntax (line 179) | def test_variable_lookup_should_not_hang_with_invalid_syntax
method test_filter_with_single_trailing_comma (line 213) | def test_filter_with_single_trailing_comma
method test_multiple_filters_with_trailing_commas (line 226) | def test_multiple_filters_with_trailing_commas
method test_filter_with_colon_but_no_arguments (line 239) | def test_filter_with_colon_but_no_arguments
method test_filter_chain_with_colon_no_args (line 252) | def test_filter_chain_with_colon_no_args
method test_combining_trailing_comma_and_empty_args (line 265) | def test_combining_trailing_comma_and_empty_args
FILE: test/test_helper.rb
type Minitest (line 25) | module Minitest
class Test (line 26) | class Test
method fixture (line 27) | def fixture(name)
type Assertions (line 32) | module Assertions
function assert_template_result (line 35) | def assert_template_result(
function assert_match_syntax_error (line 49) | def assert_match_syntax_error(match, template, error_mode: nil)
function assert_syntax_error (line 56) | def assert_syntax_error(template, error_mode: nil)
function assert_usage_increment (line 60) | def assert_usage_increment(name, times: 1)
function with_global_filter (line 77) | def with_global_filter(*globals, &blk)
function with_error_modes (line 85) | def with_error_modes(*modes)
function with_custom_tag (line 95) | def with_custom_tag(tag_name, tag_class, &block)
class ThingWithToLiquid (line 104) | class ThingWithToLiquid
method to_liquid (line 105) | def to_liquid
class SettingsDrop (line 110) | class SettingsDrop < Liquid::Drop
method initialize (line 111) | def initialize(settings)
method liquid_method_missing (line 116) | def liquid_method_missing(key)
class IntegerDrop (line 121) | class IntegerDrop < Liquid::Drop
method initialize (line 122) | def initialize(value)
method to_s (line 127) | def to_s
method to_liquid_value (line 131) | def to_liquid_value
class BooleanDrop (line 136) | class BooleanDrop < Liquid::Drop
method initialize (line 137) | def initialize(value)
method to_liquid_value (line 142) | def to_liquid_value
method to_s (line 146) | def to_s
class StringDrop (line 151) | class StringDrop < Liquid::Drop
method initialize (line 154) | def initialize(value)
method to_liquid_value (line 159) | def to_liquid_value
method to_s (line 163) | def to_s
method to_str (line 167) | def to_str
method inspect (line 171) | def inspect
method <=> (line 175) | def <=>(other)
class ErrorDrop (line 180) | class ErrorDrop < Liquid::Drop
method standard_error (line 181) | def standard_error
method argument_error (line 185) | def argument_error
method syntax_error (line 189) | def syntax_error
method runtime_error (line 193) | def runtime_error
method exception (line 197) | def exception
class CustomToLiquidDrop (line 202) | class CustomToLiquidDrop < Liquid::Drop
method initialize (line 203) | def initialize(value)
method to_liquid (line 208) | def to_liquid
class HashWithCustomToS (line 213) | class HashWithCustomToS < Hash
method to_s (line 214) | def to_s
class HashWithoutCustomToS (line 219) | class HashWithoutCustomToS < Hash
class StubFileSystem (line 222) | class StubFileSystem
method initialize (line 225) | def initialize(values)
method read_template_file (line 230) | def read_template_file(template_path)
class StubTemplateFactory (line 236) | class StubTemplateFactory
method initialize (line 239) | def initialize
method for (line 243) | def for(template_name)
FILE: test/unit/block_unit_test.rb
class BlockUnitTest (line 5) | class BlockUnitTest < Minitest::Test
method test_blankspace (line 8) | def test_blankspace
method test_variable_beginning (line 13) | def test_variable_beginning
method test_variable_end (line 20) | def test_variable_end
method test_variable_middle (line 27) | def test_variable_middle
method test_variable_with_multibyte_character (line 35) | def test_variable_with_multibyte_character
method test_variable_many_embedded_fragments (line 41) | def test_variable_many_embedded_fragments
method test_comment_tag_with_block (line 50) | def test_comment_tag_with_block
method test_doc_tag_with_block (line 56) | def test_doc_tag_with_block
method block_types (line 64) | def block_types(nodelist)
FILE: test/unit/condition_unit_test.rb
class ConditionUnitTest (line 5) | class ConditionUnitTest < Minitest::Test
method setup (line 8) | def setup
method test_basic_condition (line 12) | def test_basic_condition
method test_default_operators_evalute_true (line 17) | def test_default_operators_evalute_true
method test_default_operators_evalute_false (line 34) | def test_default_operators_evalute_false
method test_contains_works_on_strings (line 46) | def test_contains_works_on_strings
method test_contains_binary_encoding_compatibility_with_utf8 (line 58) | def test_contains_binary_encoding_compatibility_with_utf8
method test_invalid_comparation_operator (line 63) | def test_invalid_comparation_operator
method test_comparation_of_int_and_str (line 67) | def test_comparation_of_int_and_str
method test_hash_compare_backwards_compatibility (line 74) | def test_hash_compare_backwards_compatibility
method test_contains_works_on_arrays (line 82) | def test_contains_works_on_arrays
method test_contains_returns_false_for_nil_operands (line 97) | def test_contains_returns_false_for_nil_operands
method test_contains_return_false_on_wrong_data_type (line 103) | def test_contains_return_false_on_wrong_data_type
method test_contains_with_string_left_operand_coerces_right_operand_to_string (line 107) | def test_contains_with_string_left_operand_coerces_right_operand_to_st...
method test_or_condition (line 112) | def test_or_condition
method test_and_condition (line 125) | def test_and_condition
method test_should_allow_custom_proc_operator (line 139) | def test_should_allow_custom_proc_operator
method test_left_or_right_may_contain_operators (line 148) | def test_left_or_right_may_contain_operators
method test_default_context_is_deprecated (line 155) | def test_default_context_is_deprecated
method test_parse_expression_in_strict_mode (line 169) | def test_parse_expression_in_strict_mode
method test_parse_expression_in_strict2_mode_raises_internal_error (line 179) | def test_parse_expression_in_strict2_mode_raises_internal_error
method test_parse_expression_with_safe_true_in_strict2_mode (line 190) | def test_parse_expression_with_safe_true_in_strict2_mode
method test_blank_with_whitespace_string (line 213) | def test_blank_with_whitespace_string
method test_blank_with_empty_string (line 222) | def test_blank_with_empty_string
method test_blank_with_empty_array (line 231) | def test_blank_with_empty_array
method test_blank_with_empty_hash (line 240) | def test_blank_with_empty_hash
method test_blank_with_nil (line 249) | def test_blank_with_nil
method test_blank_with_false (line 258) | def test_blank_with_false
method test_not_blank_with_true (line 267) | def test_not_blank_with_true
method test_not_blank_with_number (line 276) | def test_not_blank_with_number
method test_not_blank_with_string_content (line 285) | def test_not_blank_with_string_content
method test_not_blank_with_non_empty_array (line 294) | def test_not_blank_with_non_empty_array
method test_not_blank_with_non_empty_hash (line 303) | def test_not_blank_with_non_empty_hash
method test_empty_with_empty_string (line 319) | def test_empty_with_empty_string
method test_empty_with_whitespace_string_not_empty (line 328) | def test_empty_with_whitespace_string_not_empty
method test_empty_with_empty_array (line 338) | def test_empty_with_empty_array
method test_empty_with_empty_hash (line 347) | def test_empty_with_empty_hash
method test_nil_is_not_empty (line 356) | def test_nil_is_not_empty
method assert_evaluates_true (line 368) | def assert_evaluates_true(left, op, right)
method assert_evaluates_false (line 375) | def assert_evaluates_false(left, op, right)
method assert_evaluates_argument_error (line 382) | def assert_evaluates_argument_error(left, op, right)
FILE: test/unit/environment_filter_test.rb
class EnvironmentFilterTest (line 5) | class EnvironmentFilterTest < Minitest::Test
type AccessScopeFilters (line 8) | module AccessScopeFilters
function public_filter (line 9) | def public_filter
function private_filter (line 13) | def private_filter
type LateAddedFilter (line 19) | module LateAddedFilter
function late_added_filter (line 20) | def late_added_filter(_input)
method setup (line 25) | def setup
method test_strainer (line 33) | def test_strainer
method test_strainer_raises_argument_error (line 39) | def test_strainer_raises_argument_error
method test_strainer_argument_error_contains_backtrace (line 46) | def test_strainer_argument_error_contains_backtrace
method test_strainer_only_invokes_public_filter_methods (line 62) | def test_strainer_only_invokes_public_filter_methods
method test_strainer_returns_nil_if_no_filter_method_found (line 71) | def test_strainer_returns_nil_if_no_filter_method_found
method test_strainer_returns_first_argument_if_no_method_and_arguments_given (line 77) | def test_strainer_returns_first_argument_if_no_method_and_arguments_given
method test_strainer_only_allows_methods_defined_in_filters (line 82) | def test_strainer_only_allows_methods_defined_in_filters
method test_strainer_uses_a_class_cache_to_avoid_method_cache_invalidation (line 89) | def test_strainer_uses_a_class_cache_to_avoid_method_cache_invalidation
method test_add_global_filter_clears_cache (line 101) | def test_add_global_filter_clears_cache
FILE: test/unit/environment_test.rb
class EnvironmentTest (line 5) | class EnvironmentTest < Minitest::Test
class UnsubscribeFooter (line 8) | class UnsubscribeFooter < Liquid::Tag
method render (line 9) | def render(_context)
method test_custom_tag (line 14) | def test_custom_tag
FILE: test/unit/file_system_unit_test.rb
class FileSystemUnitTest (line 5) | class FileSystemUnitTest < Minitest::Test
method test_default (line 8) | def test_default
method test_local (line 14) | def test_local
method test_custom_template_filename_patterns (line 32) | def test_custom_template_filename_patterns
FILE: test/unit/i18n_unit_test.rb
class I18nUnitTest (line 5) | class I18nUnitTest < Minitest::Test
method setup (line 8) | def setup
method test_simple_translate_string (line 12) | def test_simple_translate_string
method test_nested_translate_string (line 16) | def test_nested_translate_string
method test_single_string_interpolation (line 20) | def test_single_string_interpolation
method test_raises_unknown_translation (line 30) | def test_raises_unknown_translation
method test_sets_default_path_to_en (line 36) | def test_sets_default_path_to_en
FILE: test/unit/lexer_unit_test.rb
class LexerUnitTest (line 5) | class LexerUnitTest < Minitest::Test
method test_strings (line 8) | def test_strings
method test_integer (line 15) | def test_integer
method test_float (line 22) | def test_float
method test_comparison (line 29) | def test_comparison
method test_comparison_without_whitespace (line 36) | def test_comparison_without_whitespace
method test_comparison_with_negative_number (line 43) | def test_comparison_with_negative_number
method test_raise_for_invalid_comparison (line 50) | def test_raise_for_invalid_comparison
method test_specials (line 64) | def test_specials
method test_fancy_identifiers (line 76) | def test_fancy_identifiers
method test_whitespace (line 82) | def test_whitespace
method test_unexpected_character (line 89) | def test_unexpected_character
method test_negative_numbers (line 95) | def test_negative_numbers
method test_greater_than_two_digits (line 102) | def test_greater_than_two_digits
method test_error_with_utf8_character (line 109) | def test_error_with_utf8_character
method test_contains_as_attribute_name (line 120) | def test_contains_as_attribute_name
method test_tokenize_incomplete_expression (line 127) | def test_tokenize_incomplete_expression
method test_error_with_invalid_utf8 (line 134) | def test_error_with_invalid_utf8
method tokenize (line 146) | def tokenize(input)
FILE: test/unit/parse_context_unit_test.rb
class ParseContextUnitTest (line 5) | class ParseContextUnitTest < Minitest::Test
method test_safe_parse_expression_with_variable_lookup (line 8) | def test_safe_parse_expression_with_variable_lookup
method test_safe_parse_expression_raises_syntax_error_for_invalid_expression (line 24) | def test_safe_parse_expression_raises_syntax_error_for_invalid_expression
method test_parse_expression_with_variable_lookup (line 40) | def test_parse_expression_with_variable_lookup
method test_parse_expression_with_safe_true (line 54) | def test_parse_expression_with_safe_true
method test_parse_expression_with_empty_string (line 68) | def test_parse_expression_with_empty_string
method test_parse_expression_with_empty_string_and_safe_true (line 79) | def test_parse_expression_with_empty_string_and_safe_true
method test_safe_parse_expression_advances_parser_pointer (line 87) | def test_safe_parse_expression_advances_parser_pointer
method test_parse_expression_with_whitespace_in_strict2_mode (line 105) | def test_parse_expression_with_whitespace_in_strict2_mode
method strict_parse_context (line 112) | def strict_parse_context
method strict2_parse_context (line 118) | def strict2_parse_context
FILE: test/unit/parse_tree_visitor_test.rb
class ParseTreeVisitorTest (line 5) | class ParseTreeVisitorTest < Minitest::Test
method test_variable (line 8) | def test_variable
method test_varible_with_filter (line 15) | def test_varible_with_filter
method test_dynamic_variable (line 22) | def test_dynamic_variable
method test_echo (line 29) | def test_echo
method test_if_condition (line 36) | def test_if_condition
method test_complex_if_condition (line 43) | def test_complex_if_condition
method test_if_body (line 50) | def test_if_body
method test_unless_condition (line 57) | def test_unless_condition
method test_complex_unless_condition (line 64) | def test_complex_unless_condition
method test_unless_body (line 71) | def test_unless_body
method test_elsif_condition (line 78) | def test_elsif_condition
method test_complex_elsif_condition (line 85) | def test_complex_elsif_condition
method test_elsif_body (line 92) | def test_elsif_body
method test_else_body (line 99) | def test_else_body
method test_case_left (line 106) | def test_case_left
method test_case_condition (line 113) | def test_case_condition
method test_case_when_body (line 120) | def test_case_when_body
method test_case_else_body (line 127) | def test_case_else_body
method test_for_in (line 134) | def test_for_in
method test_for_limit (line 141) | def test_for_limit
method test_for_offset (line 148) | def test_for_offset
method test_for_body (line 155) | def test_for_body
method test_for_range (line 162) | def test_for_range
method test_tablerow_in (line 169) | def test_tablerow_in
method test_tablerow_limit (line 176) | def test_tablerow_limit
method test_tablerow_offset (line 183) | def test_tablerow_offset
method test_tablerow_body (line 190) | def test_tablerow_body
method test_cycle (line 197) | def test_cycle
method test_assign (line 204) | def test_assign
method test_capture (line 211) | def test_capture
method test_include (line 218) | def test_include
method test_include_with (line 225) | def test_include_with
method test_include_for (line 232) | def test_include_for
method test_render_with (line 239) | def test_render_with
method test_render_for (line 246) | def test_render_for
method test_preserve_tree_structure (line 253) | def test_preserve_tree_structure
method traversal (line 266) | def traversal(template)
method visit (line 272) | def visit(template)
FILE: test/unit/parser_unit_test.rb
class ParserUnitTest (line 5) | class ParserUnitTest < Minitest::Test
method test_consume (line 8) | def test_consume
method test_jump (line 15) | def test_jump
method test_consume? (line 21) | def test_consume?
method test_id? (line 29) | def test_id?
method test_look (line 38) | def test_look
method test_expressions (line 48) | def test_expressions
method test_ranges (line 61) | def test_ranges
method test_arguments (line 69) | def test_arguments
method test_invalid_expression (line 78) | def test_invalid_expression
method new_parser (line 87) | def new_parser(str)
FILE: test/unit/partial_cache_unit_test.rb
class PartialCacheUnitTest (line 5) | class PartialCacheUnitTest < Minitest::Test
method test_uses_the_file_system_register_if_present (line 6) | def test_uses_the_file_system_register_if_present
method test_reads_from_the_file_system_only_once_per_file (line 22) | def test_reads_from_the_file_system_only_once_per_file
method test_cache_state_is_stored_per_context (line 39) | def test_cache_state_is_stored_per_context
method test_cache_is_not_broken_when_a_different_parse_context_is_used (line 72) | def test_cache_is_not_broken_when_a_different_parse_context_is_used
method test_uses_default_template_factory_when_no_template_factory_found_in_register (line 94) | def test_uses_default_template_factory_when_no_template_factory_found_...
method test_uses_template_factory_register_if_present (line 110) | def test_uses_template_factory_register_if_present
method test_cache_state_is_shared_for_subcontexts (line 129) | def test_cache_state_is_shared_for_subcontexts
method test_uses_template_name_from_template_factory (line 160) | def test_uses_template_name_from_template_factory
method test_includes_error_mode_into_template_cache (line 178) | def test_includes_error_mode_into_template_cache
FILE: test/unit/regexp_unit_test.rb
class RegexpUnitTest (line 6) | class RegexpUnitTest < Minitest::Test
method test_empty (line 9) | def test_empty
method test_quote (line 13) | def test_quote
method test_words (line 17) | def test_words
method test_tags (line 21) | def test_tags
method test_double_quoted_words (line 27) | def test_double_quoted_words
method test_single_quoted_words (line 31) | def test_single_quoted_words
method test_quoted_words_in_the_middle (line 35) | def test_quoted_words_in_the_middle
method test_variable_parser (line 39) | def test_variable_parser
method test_variable_parser_with_large_input (line 49) | def test_variable_parser_with_large_input
FILE: test/unit/registers_unit_test.rb
class RegistersUnitTest (line 5) | class RegistersUnitTest < Minitest::Test
method test_set (line 8) | def test_set
method test_get_missing_key (line 18) | def test_get_missing_key
method test_delete (line 24) | def test_delete
method test_fetch (line 39) | def test_fetch
method test_key (line 63) | def test_key
method test_static_register_can_be_frozen (line 74) | def test_static_register_can_be_frozen
method test_new_static_retains_static (line 96) | def test_new_static_retains_static
method test_multiple_instances_are_unique (line 120) | def test_multiple_instances_are_unique
method test_initialization_reused_static_same_memory_object (line 140) | def test_initialization_reused_static_same_memory_object
FILE: test/unit/resource_limits_unit_test.rb
class ResourceLimitsUnitTest (line 5) | class ResourceLimitsUnitTest < Minitest::Test
method test_cumulative_scores_initialize_to_zero (line 6) | def test_cumulative_scores_initialize_to_zero
method test_cumulative_limits_default_to_nil (line 12) | def test_cumulative_limits_default_to_nil
method test_cumulative_limits_configurable_via_hash (line 18) | def test_cumulative_limits_configurable_via_hash
method test_cumulative_limits_configurable_via_accessor (line 27) | def test_cumulative_limits_configurable_via_accessor
method test_cumulative_scores_survive_reset (line 33) | def test_cumulative_scores_survive_reset
method test_cumulative_scores_accumulate_across_resets (line 46) | def test_cumulative_scores_accumulate_across_resets
method test_cumulative_render_score_limit_raises (line 58) | def test_cumulative_render_score_limit_raises
method test_cumulative_assign_score_limit_raises (line 71) | def test_cumulative_assign_score_limit_raises
method test_per_template_limits_still_work_with_cumulative (line 82) | def test_per_template_limits_still_work_with_cumulative
FILE: test/unit/strainer_template_unit_test.rb
class StrainerTemplateUnitTest (line 5) | class StrainerTemplateUnitTest < Minitest::Test
method test_add_filter_when_wrong_filter_class (line 8) | def test_add_filter_when_wrong_filter_class
type PrivateMethodOverrideFilter (line 19) | module PrivateMethodOverrideFilter
function public_filter (line 22) | def public_filter
method test_add_filter_raises_when_module_privately_overrides_registered_public_methods (line 27) | def test_add_filter_raises_when_module_privately_overrides_registered_...
type ProtectedMethodOverrideFilter (line 38) | module ProtectedMethodOverrideFilter
function public_filter (line 41) | def public_filter
method test_add_filter_raises_when_module_overrides_registered_public_method_as_protected (line 46) | def test_add_filter_raises_when_module_overrides_registered_public_met...
type PublicMethodOverrideFilter (line 57) | module PublicMethodOverrideFilter
function public_filter (line 58) | def public_filter
method test_add_filter_does_not_raise_when_module_overrides_previously_registered_method (line 63) | def test_add_filter_does_not_raise_when_module_overrides_previously_re...
method test_add_filter_does_not_include_already_included_module (line 72) | def test_add_filter_does_not_include_already_included_module
FILE: test/unit/tag_unit_test.rb
class TagUnitTest (line 5) | class TagUnitTest < Minitest::Test
method test_tag (line 8) | def test_tag
method test_return_raw_text_of_tag (line 14) | def test_return_raw_text_of_tag
method test_tag_name_should_return_name_of_the_tag (line 19) | def test_tag_name_should_return_name_of_the_tag
class CustomTag (line 24) | class CustomTag < Liquid::Tag
method render (line 25) | def render(_context); end
method test_tag_render_to_output_buffer_nil_value (line 28) | def test_tag_render_to_output_buffer_nil_value
method new_tokenizer (line 35) | def new_tokenizer
FILE: test/unit/tags/case_tag_unit_test.rb
class CaseTagUnitTest (line 5) | class CaseTagUnitTest < Minitest::Test
method test_case_nodelist (line 8) | def test_case_nodelist
method test_case_with_trailing_element (line 13) | def test_case_with_trailing_element
method test_case_when_with_trailing_element (line 34) | def test_case_when_with_trailing_element
method test_case_when_with_comma (line 55) | def test_case_when_with_comma
method test_case_when_with_or (line 70) | def test_case_when_with_or
method test_case_when_empty (line 85) | def test_case_when_empty
method test_case_with_invalid_expression (line 105) | def test_case_with_invalid_expression
method test_case_when_with_invalid_expression (line 127) | def test_case_when_with_invalid_expression
FILE: test/unit/tags/comment_tag_unit_test.rb
class CommentTagUnitTest (line 5) | class CommentTagUnitTest < Minitest::Test
method test_comment_inside_liquid_tag (line 6) | def test_comment_inside_liquid_tag
method test_does_not_parse_nodes_inside_a_comment (line 19) | def test_does_not_parse_nodes_inside_a_comment
method test_allows_unclosed_tags (line 34) | def test_allows_unclosed_tags
method test_open_tags_in_comment (line 42) | def test_open_tags_in_comment
method test_child_comment_tags_need_to_be_closed (line 75) | def test_child_comment_tags_need_to_be_closed
method test_child_raw_tags_need_to_be_closed (line 95) | def test_child_raw_tags_need_to_be_closed
method test_error_line_number_is_correct (line 114) | def test_error_line_number_is_correct
method test_comment_tag_delimiter_with_extra_strings (line 131) | def test_comment_tag_delimiter_with_extra_strings
method test_nested_comment_tag_with_extra_strings (line 145) | def test_nested_comment_tag_with_extra_strings
method test_ignores_delimiter_with_extra_strings (line 159) | def test_ignores_delimiter_with_extra_strings
method test_delimiter_can_have_extra_strings (line 172) | def test_delimiter_can_have_extra_strings
method test_with_whitespace_control (line 180) | def test_with_whitespace_control
method test_dont_override_liquid_tag_whitespace_control (line 191) | def test_dont_override_liquid_tag_whitespace_control
FILE: test/unit/tags/doc_tag_unit_test.rb
class DocTagUnitTest (line 5) | class DocTagUnitTest < Minitest::Test
method test_doc_tag (line 6) | def test_doc_tag
method test_doc_tag_body_content (line 23) | def test_doc_tag_body_content
method test_doc_tag_does_not_support_extra_arguments (line 38) | def test_doc_tag_does_not_support_extra_arguments
method test_doc_tag_must_support_valid_tags (line 54) | def test_doc_tag_must_support_valid_tags
method test_doc_tag_ignores_liquid_nodes (line 60) | def test_doc_tag_ignores_liquid_nodes
method test_doc_tag_ignores_unclosed_liquid_tags (line 77) | def test_doc_tag_ignores_unclosed_liquid_tags
method test_doc_tag_does_not_allow_nested_docs (line 87) | def test_doc_tag_does_not_allow_nested_docs
method test_doc_tag_ignores_nested_raw_tags (line 105) | def test_doc_tag_ignores_nested_raw_tags
method test_doc_tag_ignores_unclosed_assign (line 115) | def test_doc_tag_ignores_unclosed_assign
method test_doc_tag_ignores_malformed_syntax (line 125) | def test_doc_tag_ignores_malformed_syntax
method test_doc_tag_captures_token_before_enddoc (line 134) | def test_doc_tag_captures_token_before_enddoc
method test_doc_tag_preserves_error_line_numbers (line 148) | def test_doc_tag_preserves_error_line_numbers
method test_doc_tag_whitespace_control (line 164) | def test_doc_tag_whitespace_control
method test_doc_tag_delimiter_handling (line 175) | def test_doc_tag_delimiter_handling
method test_doc_tag_visitor (line 190) | def test_doc_tag_visitor
method test_doc_tag_blank_with_empty_content (line 199) | def test_doc_tag_blank_with_empty_content
method test_doc_tag_blank_with_content (line 213) | def test_doc_tag_blank_with_content
method test_doc_tag_blank_with_whitespace_only (line 227) | def test_doc_tag_blank_with_whitespace_only
method test_doc_tag_nodelist_returns_array_with_body (line 241) | def test_doc_tag_nodelist_returns_array_with_body
method test_doc_tag_nodelist_with_empty_content (line 258) | def test_doc_tag_nodelist_with_empty_content
method traversal (line 275) | def traversal(template)
method visit (line 284) | def visit(template)
FILE: test/unit/tags/for_tag_unit_test.rb
class ForTagUnitTest (line 5) | class ForTagUnitTest < Minitest::Test
method test_for_nodelist (line 6) | def test_for_nodelist
method test_for_else_nodelist (line 11) | def test_for_else_nodelist
FILE: test/unit/tags/if_tag_unit_test.rb
class IfTagUnitTest (line 5) | class IfTagUnitTest < Minitest::Test
method test_if_nodelist (line 6) | def test_if_nodelist
FILE: test/unit/template_factory_unit_test.rb
class TemplateFactoryUnitTest (line 5) | class TemplateFactoryUnitTest < Minitest::Test
method test_for_returns_liquid_template_instance (line 8) | def test_for_returns_liquid_template_instance
FILE: test/unit/template_unit_test.rb
class TemplateUnitTest (line 5) | class TemplateUnitTest < Minitest::Test
method test_sets_default_localization_in_document (line 8) | def test_sets_default_localization_in_document
method test_sets_default_localization_in_context_with_quick_initialization (line 14) | def test_sets_default_localization_in_context_with_quick_initialization
class FakeTag (line 23) | class FakeTag; end
method test_tags_can_be_looped_over (line 25) | def test_tags_can_be_looped_over
class TemplateSubclass (line 32) | class TemplateSubclass < Liquid::Template
method test_template_inheritance (line 35) | def test_template_inheritance
method test_invalid_utf8 (line 39) | def test_invalid_utf8
FILE: test/unit/tokenizer_unit_test.rb
class TokenizerTest (line 5) | class TokenizerTest < Minitest::Test
method test_tokenize_strings (line 6) | def test_tokenize_strings
method test_tokenize_variables (line 12) | def test_tokenize_variables
method test_tokenize_blocks (line 19) | def test_tokenize_blocks
method test_calculate_line_numbers_per_token_with_profiling (line 27) | def test_calculate_line_numbers_per_token_with_profiling
method test_tokenize_with_nil_source_returns_empty_array (line 34) | def test_tokenize_with_nil_source_returns_empty_array
method test_incomplete_curly_braces (line 38) | def test_incomplete_curly_braces
method test_unmatching_start_and_end (line 44) | def test_unmatching_start_and_end
method new_tokenizer (line 53) | def new_tokenizer(source, parse_context: Liquid::ParseContext.new, sta...
method tokenize (line 57) | def tokenize(source)
method tokenize_line_numbers (line 67) | def tokenize_line_numbers(source)
FILE: test/unit/variable_unit_test.rb
class VariableUnitTest (line 5) | class VariableUnitTest < Minitest::Test
method test_variable (line 8) | def test_variable
method test_filters (line 13) | def test_filters
method test_filter_with_date_parameter (line 55) | def test_filter_with_date_parameter
method test_filters_without_whitespace (line 61) | def test_filters_without_whitespace
method test_symbol (line 75) | def test_symbol
method test_string_to_filter (line 81) | def test_string_to_filter
method test_string_single_quoted (line 87) | def test_string_single_quoted
method test_string_double_quoted (line 92) | def test_string_double_quoted
method test_integer (line 97) | def test_integer
method test_float (line 102) | def test_float
method test_dashes (line 107) | def test_dashes
method test_string_with_special_chars (line 118) | def test_string_with_special_chars
method test_string_dot (line 123) | def test_string_dot
method test_filter_with_keyword_arguments (line 128) | def test_filter_with_keyword_arguments
method test_lax_filter_argument_parsing (line 134) | def test_lax_filter_argument_parsing
method test_strict_filter_argument_parsing (line 156) | def test_strict_filter_argument_parsing
method test_strict2_filter_argument_parsing (line 164) | def test_strict2_filter_argument_parsing
method test_output_raw_source_of_variable (line 200) | def test_output_raw_source_of_variable
method test_variable_lookup_interface (line 205) | def test_variable_lookup_interface
method create_variable (line 213) | def create_variable(markup, options = {})
Condensed preview — 204 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (784K chars).
[
{
"path": ".github/dependabot.yaml",
"chars": 114,
"preview": "version: 2\nupdates:\n - package-ecosystem: github-actions\n directory: \"/\"\n schedule:\n interval: weekly\n"
},
{
"path": ".github/workflows/cla.yml",
"chars": 593,
"preview": "name: Contributor License Agreement (CLA)\n\non:\n pull_request_target:\n types: [opened, synchronize]\n issue_comment:\n"
},
{
"path": ".github/workflows/liquid.yml",
"chars": 2509,
"preview": "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:"
},
{
"path": ".gitignore",
"chars": 69,
"preview": "*~\n*.gem\n*.swp\npkg\n*.rbc\n.rvmrc\n.bundle\n.byebug_history\nGemfile.lock\n"
},
{
"path": ".rubocop.yml",
"chars": 508,
"preview": "inherit_gem:\n rubocop-shopify: rubocop.yml\n\ninherit_from:\n - .rubocop_todo.yml\n\nrequire: rubocop-performance\n\nPerforma"
},
{
"path": ".rubocop_todo.yml",
"chars": 6337,
"preview": "# This configuration was generated by\n# `rubocop --auto-gen-config`\n# on 2022-05-18 19:25:47 UTC using RuboCop version 1"
},
{
"path": ".ruby-version",
"chars": 6,
"preview": "3.4.1\n"
},
{
"path": "CONTRIBUTING.md",
"chars": 1556,
"preview": "# How to contribute\n\n## Things we will merge\n\n* Bugfixes\n* Performance improvements\n* Features that are likely to be use"
},
{
"path": "Gemfile",
"chars": 732,
"preview": "# frozen_string_literal: true\n\nsource 'https://rubygems.org'\ngit_source(:github) do |repo_name|\n \"https://github.com/#{"
},
{
"path": "History.md",
"chars": 21535,
"preview": "# Liquid Change Log\n\n## 5.11.0\n* Revert the Inline Snippets tag (#2001), treat its inclusion in the latest Liquid releas"
},
{
"path": "LICENSE",
"chars": 1063,
"preview": "Copyright (c) 2005, 2006 Tobias Luetke\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of "
},
{
"path": "README.md",
"chars": 7783,
"preview": "[](https://github.com/Shopify/l"
},
{
"path": "Rakefile",
"chars": 3765,
"preview": "# frozen_string_literal: true\n\nrequire 'rake'\nrequire 'rake/testtask'\n$LOAD_PATH.unshift(File.expand_path(\"../lib\", __FI"
},
{
"path": "bin/render",
"chars": 909,
"preview": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nrequire 'bundler/setup'\nrequire 'liquid'\n\nclass VirtualFileSystem\n d"
},
{
"path": "example/server/example_servlet.rb",
"chars": 1493,
"preview": "# frozen_string_literal: true\n\nmodule ProductsFilter\n def price(integer)\n format(\"$%.2d USD\", integer / 100.0)\n end"
},
{
"path": "example/server/liquid_servlet.rb",
"chars": 716,
"preview": "# frozen_string_literal: true\n\nclass LiquidServlet < WEBrick::HTTPServlet::AbstractServlet\n def do_GET(req, res)\n ha"
},
{
"path": "example/server/server.rb",
"chars": 325,
"preview": "# frozen_string_literal: true\n\nrequire 'webrick'\nrequire 'rexml/document'\n\nrequire_relative '../../lib/liquid'\nrequire_r"
},
{
"path": "example/server/templates/index.liquid",
"chars": 107,
"preview": "<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",
"chars": 1431,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n \"http://www.w3.org/TR/"
},
{
"path": "lib/liquid/block.rb",
"chars": 2332,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n class Block < Tag\n MAX_DEPTH = 100\n\n def initialize(tag_name, marku"
},
{
"path": "lib/liquid/block_body.rb",
"chars": 9172,
"preview": "# frozen_string_literal: true\n\nrequire 'English'\n\nmodule Liquid\n class BlockBody\n LiquidTagToken = /\\A\\s*(#{Tag"
},
{
"path": "lib/liquid/condition.rb",
"chars": 6014,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # Container for liquid nodes which conveniently wraps decision making log"
},
{
"path": "lib/liquid/const.rb",
"chars": 125,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n module Const\n EMPTY_HASH = {}.freeze\n EMPTY_ARRAY = [].freeze\n end"
},
{
"path": "lib/liquid/context.rb",
"chars": 9478,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # Context keeps the variable stack and resolves variables, as well as key"
},
{
"path": "lib/liquid/deprecations.rb",
"chars": 469,
"preview": "# frozen_string_literal: true\n\nrequire \"set\"\n\nmodule Liquid\n class Deprecations\n class << self\n attr_accessor :"
},
{
"path": "lib/liquid/document.rb",
"chars": 1448,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n class Document\n def self.parse(tokens, parse_context)\n doc = new("
},
{
"path": "lib/liquid/drop.rb",
"chars": 2260,
"preview": "# frozen_string_literal: true\n\nrequire 'set'\n\nmodule Liquid\n # A drop in liquid is a class which allows you to export D"
},
{
"path": "lib/liquid/environment.rb",
"chars": 5354,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # The Environment is the container for all configuration options of Liqui"
},
{
"path": "lib/liquid/errors.rb",
"chars": 1445,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n class Error < ::StandardError\n attr_accessor :line_number\n attr_acc"
},
{
"path": "lib/liquid/expression.rb",
"chars": 3598,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n class Expression\n LITERALS = {\n nil => nil,\n 'nil' => nil,\n "
},
{
"path": "lib/liquid/extensions.rb",
"chars": 780,
"preview": "# frozen_string_literal: true\n\nrequire 'time'\nrequire 'date'\n\nclass String # :nodoc:\n def to_liquid\n self\n end\nend\n"
},
{
"path": "lib/liquid/file_system.rb",
"chars": 2806,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # A Liquid file system is a way to let your templates retrieve other temp"
},
{
"path": "lib/liquid/forloop_drop.rb",
"chars": 2123,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # @liquid_public_docs\n # @liquid_type object\n # @liquid_name forloop\n "
},
{
"path": "lib/liquid/i18n.rb",
"chars": 979,
"preview": "# frozen_string_literal: true\n\nrequire 'yaml'\n\nmodule Liquid\n class I18n\n DEFAULT_LOCALE = File.join(File.expand_pat"
},
{
"path": "lib/liquid/interrupts.rb",
"chars": 479,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # An interrupt is any command that breaks processing of a block (ex: a fo"
},
{
"path": "lib/liquid/lexer.rb",
"chars": 5702,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n class Lexer\n CLOSE_ROUND = [:close_round, \")\"].freeze\n CLOSE_SQUARE"
},
{
"path": "lib/liquid/locales/en.yml",
"chars": 2451,
"preview": "---\n errors:\n syntax:\n tag_unexpected_args: \"Syntax Error in '%{tag}' - Valid syntax: %{tag}\"\n block_tag_u"
},
{
"path": "lib/liquid/parse_context.rb",
"chars": 2819,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n class ParseContext\n attr_accessor :locale, :line_number, :trim_whitesp"
},
{
"path": "lib/liquid/parse_tree_visitor.rb",
"chars": 1036,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n class ParseTreeVisitor\n def self.for(node, callbacks = Hash.new(proc {"
},
{
"path": "lib/liquid/parser.rb",
"chars": 2299,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n class Parser\n def initialize(input)\n ss = input.is_a?(StringScann"
},
{
"path": "lib/liquid/parser_switching.rb",
"chars": 2155,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n module ParserSwitching\n # Do not use this.\n #\n # It's basically "
},
{
"path": "lib/liquid/partial_cache.rb",
"chars": 925,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n class PartialCache\n def self.load(template_name, context:, parse_conte"
},
{
"path": "lib/liquid/profiler/hooks.rb",
"chars": 859,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n module BlockBodyProfilingHook\n def render_node(context, output, node)\n"
},
{
"path": "lib/liquid/profiler.rb",
"chars": 4060,
"preview": "# frozen_string_literal: true\n\nrequire 'liquid/profiler/hooks'\n\nmodule Liquid\n # Profiler enables support for profiling"
},
{
"path": "lib/liquid/range_lookup.rb",
"chars": 1477,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n class RangeLookup\n def self.parse(start_markup, end_markup, string_sca"
},
{
"path": "lib/liquid/registers.rb",
"chars": 965,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n class Registers\n attr_reader :static\n\n def initialize(registers = {"
},
{
"path": "lib/liquid/resource_limits.rb",
"chars": 2709,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n class ResourceLimits\n attr_accessor :render_length_limit,\n :rende"
},
{
"path": "lib/liquid/standardfilters.rb",
"chars": 34720,
"preview": "# frozen_string_literal: true\n\nrequire 'cgi'\nrequire 'base64'\nrequire 'bigdecimal'\nmodule Liquid\n module StandardFilter"
},
{
"path": "lib/liquid/strainer_template.rb",
"chars": 1767,
"preview": "# frozen_string_literal: true\n\nrequire 'set'\n\nmodule Liquid\n # StrainerTemplate is the computed class for the filters s"
},
{
"path": "lib/liquid/tablerowloop_drop.rb",
"chars": 2763,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # @liquid_public_docs\n # @liquid_type object\n # @liquid_name tablerowlo"
},
{
"path": "lib/liquid/tag/disableable.rb",
"chars": 606,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n class Tag\n module Disableable\n def render_to_output_buffer(contex"
},
{
"path": "lib/liquid/tag/disabler.rb",
"chars": 248,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n class Tag\n module Disabler\n def render_to_output_buffer(context, "
},
{
"path": "lib/liquid/tag.rb",
"chars": 1717,
"preview": "# frozen_string_literal: true\n\nrequire 'liquid/tag/disabler'\nrequire 'liquid/tag/disableable'\n\nmodule Liquid\n class Tag"
},
{
"path": "lib/liquid/tags/assign.rb",
"chars": 2237,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # @liquid_public_docs\n # @liquid_type tag\n # @liquid_category variable\n"
},
{
"path": "lib/liquid/tags/break.rb",
"chars": 668,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # Break tag to be used to break out of a for loop.\n #\n # == Basic Usage"
},
{
"path": "lib/liquid/tags/capture.rb",
"chars": 1339,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # @liquid_public_docs\n # @liquid_type tag\n # @liquid_category variable\n"
},
{
"path": "lib/liquid/tags/case.rb",
"chars": 4382,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # @liquid_public_docs\n # @liquid_type tag\n # @liquid_category condition"
},
{
"path": "lib/liquid/tags/comment.rb",
"chars": 2325,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # @liquid_public_docs\n # @liquid_type tag\n # @liquid_category syntax\n "
},
{
"path": "lib/liquid/tags/continue.rb",
"chars": 482,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # @liquid_public_docs\n # @liquid_type tag\n # @liquid_category iteration"
},
{
"path": "lib/liquid/tags/cycle.rb",
"chars": 4205,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # @liquid_public_docs\n # @liquid_type tag\n # @liquid_category iteration"
},
{
"path": "lib/liquid/tags/decrement.rb",
"chars": 1761,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # @liquid_public_docs\n # @liquid_type tag\n # @liquid_category variable\n"
},
{
"path": "lib/liquid/tags/doc.rb",
"chars": 2238,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # @liquid_public_docs\n # @liquid_type tag\n # @liquid_category syntax\n "
},
{
"path": "lib/liquid/tags/echo.rb",
"chars": 1071,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # @liquid_public_docs\n # @liquid_type tag\n # @liquid_category syntax\n "
},
{
"path": "lib/liquid/tags/for.rb",
"chars": 5958,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # @liquid_public_docs\n # @liquid_type tag\n # @liquid_category iteration"
},
{
"path": "lib/liquid/tags/if.rb",
"chars": 3904,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # @liquid_public_docs\n # @liquid_type tag\n # @liquid_category condition"
},
{
"path": "lib/liquid/tags/ifchanged.rb",
"chars": 362,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n class Ifchanged < Block\n def render_to_output_buffer(context, output)\n"
},
{
"path": "lib/liquid/tags/include.rb",
"chars": 4071,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # @liquid_public_docs\n # @liquid_type tag\n # @liquid_category theme\n #"
},
{
"path": "lib/liquid/tags/increment.rb",
"chars": 1748,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # @liquid_public_docs\n # @liquid_type tag\n # @liquid_category variable\n"
},
{
"path": "lib/liquid/tags/inline_comment.rb",
"chars": 841,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n class InlineComment < Tag\n def initialize(tag_name, markup, options)\n "
},
{
"path": "lib/liquid/tags/raw.rb",
"chars": 1432,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # @liquid_public_docs\n # @liquid_type tag\n # @liquid_category syntax\n "
},
{
"path": "lib/liquid/tags/render.rb",
"chars": 5186,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # @liquid_public_docs\n # @liquid_type tag\n # @liquid_category theme\n #"
},
{
"path": "lib/liquid/tags/table_row.rb",
"chars": 4265,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # @liquid_public_docs\n # @liquid_type tag\n # @liquid_category iteration"
},
{
"path": "lib/liquid/tags/unless.rb",
"chars": 1368,
"preview": "# frozen_string_literal: true\n\nrequire_relative 'if'\n\nmodule Liquid\n # @liquid_public_docs\n # @liquid_type tag\n # @li"
},
{
"path": "lib/liquid/tags.rb",
"chars": 1247,
"preview": "# frozen_string_literal: true\n\nrequire_relative \"tags/table_row\"\nrequire_relative \"tags/echo\"\nrequire_relative \"tags/if\""
},
{
"path": "lib/liquid/template.rb",
"chars": 7659,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # Templates are central to liquid.\n # Interpreting templates is a two st"
},
{
"path": "lib/liquid/template_factory.rb",
"chars": 142,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n class TemplateFactory\n def for(_template_name)\n Liquid::Template."
},
{
"path": "lib/liquid/tokenizer.rb",
"chars": 3296,
"preview": "# frozen_string_literal: true\n\nrequire \"strscan\"\n\nmodule Liquid\n class Tokenizer\n attr_reader :line_number, :for_liq"
},
{
"path": "lib/liquid/usage.rb",
"chars": 107,
"preview": "# 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",
"chars": 4321,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n module Utils\n DECIMAL_REGEX = /\\A-?\\d+\\.\\d+\\z/\n UNIX_TIMESTAMP_REGE"
},
{
"path": "lib/liquid/variable.rb",
"chars": 5994,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n # Holds variables. Variables are only loaded \"just in time\"\n # and are n"
},
{
"path": "lib/liquid/variable_lookup.rb",
"chars": 3406,
"preview": "# frozen_string_literal: true\n\nmodule Liquid\n class VariableLookup\n COMMAND_METHODS = ['size', 'first', 'last'].free"
},
{
"path": "lib/liquid/version.rb",
"chars": 88,
"preview": "# encoding: utf-8\n# frozen_string_literal: true\n\nmodule Liquid\n VERSION = \"5.12.0\"\nend\n"
},
{
"path": "lib/liquid.rb",
"chars": 3445,
"preview": "# frozen_string_literal: true\n\n# Copyright (c) 2005 Tobias Luetke\n#\n# Permission is hereby granted, free of charge, to a"
},
{
"path": "liquid.gemspec",
"chars": 1099,
"preview": "# encoding: utf-8\n# frozen_string_literal: true\n\nlib = File.expand_path('../lib/', __FILE__)\n$LOAD_PATH.unshift(lib) unl"
},
{
"path": "performance/benchmark.rb",
"chars": 757,
"preview": "# frozen_string_literal: true\n\nrequire 'benchmark/ips'\nrequire_relative 'theme_runner'\n\nRubyVM::YJIT.enable if defined?("
},
{
"path": "performance/memory_profile.rb",
"chars": 1606,
"preview": "# frozen_string_literal: true\n\nrequire 'benchmark/ips'\nrequire 'memory_profiler'\nrequire 'terminal-table'\nrequire_relati"
},
{
"path": "performance/profile.rb",
"chars": 716,
"preview": "# frozen_string_literal: true\n\nrequire 'stackprof'\nrequire_relative 'theme_runner'\n\nLiquid::Template.error_mode = ARGV.f"
},
{
"path": "performance/shopify/comment_form.rb",
"chars": 1049,
"preview": "# frozen_string_literal: true\n\nclass CommentForm < Liquid::Block\n Syntax = /(#{Liquid::VariableSignature}+)/\n\n def ini"
},
{
"path": "performance/shopify/database.rb",
"chars": 1745,
"preview": "# frozen_string_literal: true\n\nrequire 'yaml'\n\nmodule Database\n DATABASE_FILE_PATH = \"#{__dir__}/vision.database.yml\"\n\n"
},
{
"path": "performance/shopify/json_filter.rb",
"chars": 154,
"preview": "# frozen_string_literal: true\n\nrequire 'json'\n\nmodule JsonFilter\n def json(object)\n JSON.dump(object.reject { |k, _v"
},
{
"path": "performance/shopify/liquid.rb",
"chars": 731,
"preview": "# frozen_string_literal: true\n\n$LOAD_PATH.unshift(__dir__ + '/../../lib')\nrequire_relative '../../lib/liquid'\n\nrequire_r"
},
{
"path": "performance/shopify/money_filter.rb",
"chars": 310,
"preview": "# frozen_string_literal: true\n\nmodule MoneyFilter\n def money_with_currency(money)\n return '' if money.nil?\n forma"
},
{
"path": "performance/shopify/paginate.rb",
"chars": 2557,
"preview": "# frozen_string_literal: true\n\nclass Paginate < Liquid::Block\n Syntax = /(#{Liquid::QuotedFragment})\\s*(by\\s*(\\d+))?/\n\n"
},
{
"path": "performance/shopify/shop_filter.rb",
"chars": 2854,
"preview": "# frozen_string_literal: true\n\nmodule ShopFilter\n def asset_url(input)\n \"/files/1/[shop_id]/[shop_id]/assets/#{input"
},
{
"path": "performance/shopify/tag_filter.rb",
"chars": 779,
"preview": "# frozen_string_literal: true\n\nmodule TagFilter\n def link_to_tag(label, tag)\n \"<a title=\\\"Show tag #{tag}\\\" href=\\\"/"
},
{
"path": "performance/shopify/vision.database.yml",
"chars": 29695,
"preview": "# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n# Variants\n# =-=-=-"
},
{
"path": "performance/shopify/weight_filter.rb",
"chars": 177,
"preview": "# frozen_string_literal: true\n\nmodule WeightFilter\n def weight(grams)\n format(\"%.2f\", grams / 1000)\n end\n\n def wei"
},
{
"path": "performance/tests/dropify/article.liquid",
"chars": 2757,
"preview": "<div class=\"article\">\n <h2 class=\"article-title\">{{ article.title }}</h2>\n <p class=\"article-details\">posted <span cla"
},
{
"path": "performance/tests/dropify/blog.liquid",
"chars": 821,
"preview": "<div id=\"page\">\n <h2>{{page.title}}</h2>\n\n {% paginate blog.articles by 20 %}\n\n {% for article in blog.articles %}"
},
{
"path": "performance/tests/dropify/cart.liquid",
"chars": 2362,
"preview": "<script type=\"text/javascript\">\n function remove_item(id) {\n document.getElementById('updates_'+id).value = 0;\n "
},
{
"path": "performance/tests/dropify/collection.liquid",
"chars": 768,
"preview": "{% paginate collection.products by 20 %}\n\n<ul id=\"product-collection\">\n {% for product in collection.products %}\n "
},
{
"path": "performance/tests/dropify/index.liquid",
"chars": 1784,
"preview": "<div id=\"frontproducts\"><div id=\"frontproducts-top\"><div id=\"frontproducts-bottom\">\n<h2 style=\"display: none;\">Featured "
},
{
"path": "performance/tests/dropify/page.liquid",
"chars": 113,
"preview": "<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",
"chars": 2707,
"preview": "<div id=\"productpage\">\n\n <div id=\"productimages\"><div id=\"productimages-top\"><div id=\"productimages-bottom\">\n {% for"
},
{
"path": "performance/tests/dropify/theme.liquid",
"chars": 3903,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dt"
},
{
"path": "performance/tests/ripen/article.liquid",
"chars": 2691,
"preview": "<div class=\"article\">\n <h2 class=\"article-title\">{{ article.title }}</h2>\n <p class=\"article-details\">posted <span cla"
},
{
"path": "performance/tests/ripen/blog.liquid",
"chars": 437,
"preview": "<div id=\"blog-page\">\n <h2 class=\"heading-shaded\">{{page.title}}</h2>\n {% for article in blog.articles %}\n <h4>\n"
},
{
"path": "performance/tests/ripen/cart.liquid",
"chars": 2077,
"preview": "<script type=\"text/javascript\">\n function remove_item(id) {\n document.getElementById('updates_'+id).value = 0;\n "
},
{
"path": "performance/tests/ripen/collection.liquid",
"chars": 985,
"preview": "<div id=\"collection-page\">\n\n{% if collection.description %}\n <div id=\"collection-description\" class=\"textile\">{{ collec"
},
{
"path": "performance/tests/ripen/index.liquid",
"chars": 1347,
"preview": "<div id=\"home-page\">\n <h3 class=\"heading-shaded\">Featured products...</h3>\n <div class=\"featured-prod-row clearfix\">\n "
},
{
"path": "performance/tests/ripen/page.liquid",
"chars": 98,
"preview": "<div id=\"single-page\">\n<h2 class=\"heading-shaded\">{{page.title}}</h2>\n {{ page.content }}\n</div>\n"
},
{
"path": "performance/tests/ripen/product.liquid",
"chars": 2815,
"preview": "<div id=\"product-page\">\n <h2 class=\"heading-shaded\">{{ product.title }}</h2>\n <div id=\"product-details\">\n <div id=\"pr"
},
{
"path": "performance/tests/ripen/theme.liquid",
"chars": 2551,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<html xmln"
},
{
"path": "performance/tests/tribble/404.liquid",
"chars": 2126,
"preview": " <div id=\"page\" class=\"innerpage clearfix\">\n\n <div id=\"text-page\">\n <div class=\"entry\">\n <h1>Oh no!</h1>"
},
{
"path": "performance/tests/tribble/article.liquid",
"chars": 3259,
"preview": "\n <div id=\"page\" class=\"innerpage clearfix\">\n <div id=\"text-page\">\n\n <div class=\"entry\">\n <h1><s"
},
{
"path": "performance/tests/tribble/blog.liquid",
"chars": 1133,
"preview": " <div id=\"page\" class=\"innerpage clearfix\">\n <div id=\"text-page\">\n <h1>Post from our blog...</h1>\n {% pagi"
},
{
"path": "performance/tests/tribble/cart.liquid",
"chars": 4986,
"preview": "<script type=\"text/javascript\">\n function remove_item(id) {\n document.getElementById('updates_'+id).value = 0;\n "
},
{
"path": "performance/tests/tribble/collection.liquid",
"chars": 2507,
"preview": " <div id=\"page\" class=\"innerpage clearfix\">\n <h1>{{ collection.title }}</h1>\n {% if collection.description.size >"
},
{
"path": "performance/tests/tribble/index.liquid",
"chars": 3130,
"preview": " <div id=\"gwrap\">\n <div id=\"gbox\">\n <h1>Three Great Reasons You Should Shop With Us...</h1>\n <ul>\n "
},
{
"path": "performance/tests/tribble/page.liquid",
"chars": 1980,
"preview": " <div id=\"page\" class=\"innerpage clearfix\">\n\n <div id=\"text-page\">\n <div class=\"entry\">\n <h1>{{page.titl"
},
{
"path": "performance/tests/tribble/product.liquid",
"chars": 3636,
"preview": "<div id=\"page\" class=\"innerpage clearfix\">\n <h1>{{ collection.title }} {{ product.title }}</h1>\n\n\n <p class=\"latest-ne"
},
{
"path": "performance/tests/tribble/search.liquid",
"chars": 1290,
"preview": "\n\n\n\n <div id=\"page\" class=\"innerpage clearfix\">\n <h1>Search Results</h1>\n {% if search.performed %}\n\n {% pa"
},
{
"path": "performance/tests/tribble/theme.liquid",
"chars": 2960,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<html xmln"
},
{
"path": "performance/tests/vogue/article.liquid",
"chars": 2459,
"preview": "<div class=\"article\">\n <h3 class=\"article-head-title\">{{ article.title }}</h3>\n <p> posted {{ article.created_at | dat"
},
{
"path": "performance/tests/vogue/blog.liquid",
"chars": 782,
"preview": "<div id=\"shop-id-label_about\">\n<h3 class=\"article-head-title\">{{page.title}}</h3>\n\n{% paginate blog.articles by 20 %}\n\n "
},
{
"path": "performance/tests/vogue/cart.liquid",
"chars": 3061,
"preview": "<h1>Shopping Cart</h1>\n{% if cart.item_count == 0 %}\n <p><strong>Your shopping basket is empty.</strong> Perhaps a feat"
},
{
"path": "performance/tests/vogue/collection.liquid",
"chars": 1081,
"preview": "{% paginate collection.products by 12 %}{% if collection.products.size == 0 %}\n <strong>No products found in this colle"
},
{
"path": "performance/tests/vogue/index.liquid",
"chars": 1217,
"preview": " <div id=\"about-excerpt\">\n {% assign article = pages.frontpage %}\n {% if article.content != \"\" %}\n <h2>{{ ar"
},
{
"path": "performance/tests/vogue/page.liquid",
"chars": 50,
"preview": " <h1>{{ page.title }}</h1>\n {{ page.content }}\n\n"
},
{
"path": "performance/tests/vogue/product.liquid",
"chars": 2595,
"preview": "<div id=\"product-left\">\n {% for image in product.images %}{% if forloop.first %}<div id=\"product-image\">\n <a href=\"{"
},
{
"path": "performance/tests/vogue/theme.liquid",
"chars": 4876,
"preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
},
{
"path": "performance/theme_runner.rb",
"chars": 4713,
"preview": "# frozen_string_literal: true\n\n# This profiler run simulates Shopify.\n# We are looking in the tests directory for liquid"
},
{
"path": "performance/unit/expression_benchmark.rb",
"chars": 1551,
"preview": "# frozen_string_literal: true\n\nrequire \"benchmark/ips\"\n\n# benchmark liquid lexing\n\nrequire 'liquid'\n\nRubyVM::YJIT.enable"
},
{
"path": "performance/unit/lexer_benchmark.rb",
"chars": 654,
"preview": "# frozen_string_literal: true\n\nrequire \"benchmark/ips\"\n\n# benchmark liquid lexing\n\nrequire 'liquid'\n\nRubyVM::YJIT.enable"
},
{
"path": "spec/ruby_liquid.rb",
"chars": 1107,
"preview": "# frozen_string_literal: true\n\n# Liquid Spec Adapter for Shopify/liquid (Ruby reference implementation)\n#\n# Run with: bu"
},
{
"path": "spec/ruby_liquid_lax.rb",
"chars": 977,
"preview": "# frozen_string_literal: true\n\n# Liquid Spec Adapter for Shopify/liquid with lax parsing mode\n#\n# Run with: bundle exec "
},
{
"path": "spec/ruby_liquid_with_active_support.rb",
"chars": 1202,
"preview": "# frozen_string_literal: true\n\n# Liquid Spec Adapter for Shopify/liquid with ActiveSupport loaded\n#\n# Run with: bundle e"
},
{
"path": "spec/ruby_liquid_yjit.rb",
"chars": 1155,
"preview": "# frozen_string_literal: true\n\n# Liquid Spec Adapter for Shopify/liquid with YJIT + strict mode + ActiveSupport\n#\n# Run "
},
{
"path": "test/fixtures/en_locale.yml",
"chars": 253,
"preview": "---\n simple: \"less is more\"\n whatever: \"something %{something}\"\n errors:\n i18n:\n undefined_interpolation: \"un"
},
{
"path": "test/integration/assign_test.rb",
"chars": 3208,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass AssignTest < Minitest::Test\n include Liquid\n\n def test_ass"
},
{
"path": "test/integration/blank_test.rb",
"chars": 3416,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass FoobarTag < Liquid::Tag\n def render_to_output_buffer(_conte"
},
{
"path": "test/integration/block_test.rb",
"chars": 1514,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass BlockTest < Minitest::Test\n include Liquid\n\n def test_unex"
},
{
"path": "test/integration/capture_test.rb",
"chars": 1530,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass CaptureTest < Minitest::Test\n include Liquid\n\n def test_ca"
},
{
"path": "test/integration/context_test.rb",
"chars": 20908,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass HundredCentes\n def to_liquid\n 100\n end\nend\n\nclass Cents"
},
{
"path": "test/integration/document_test.rb",
"chars": 419,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass DocumentTest < Minitest::Test\n include Liquid\n\n def test_u"
},
{
"path": "test/integration/drop_test.rb",
"chars": 10112,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ContextDrop < Liquid::Drop\n def scopes\n @context.scopes."
},
{
"path": "test/integration/error_handling_test.rb",
"chars": 11167,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ErrorHandlingTest < Minitest::Test\n include Liquid\n\n def t"
},
{
"path": "test/integration/expression_test.rb",
"chars": 5186,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\nrequire 'lru_redux'\n\nclass ExpressionTest < Minitest::Test\n def te"
},
{
"path": "test/integration/filter_kwarg_test.rb",
"chars": 544,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass FilterKwargTest < Minitest::Test\n module KwargFilter\n de"
},
{
"path": "test/integration/filter_test.rb",
"chars": 5675,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nmodule MoneyFilter\n def money(input)\n format(' %d$ ', input)\n "
},
{
"path": "test/integration/hash_ordering_test.rb",
"chars": 494,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass HashOrderingTest < Minitest::Test\n module MoneyFilter\n d"
},
{
"path": "test/integration/hash_rendering_test.rb",
"chars": 4809,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass HashRenderingTest < Minitest::Test\n def test_render_empty_h"
},
{
"path": "test/integration/output_test.rb",
"chars": 3307,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nmodule FunnyFilter\n def make_funny(_input)\n 'LOL'\n end\n\n def"
},
{
"path": "test/integration/parsing_quirks_test.rb",
"chars": 5064,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ParsingQuirksTest < Minitest::Test\n include Liquid\n\n def t"
},
{
"path": "test/integration/profiler_test.rb",
"chars": 6962,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ProfilerTest < Minitest::Test\n class TestDrop < Liquid::Dro"
},
{
"path": "test/integration/security_test.rb",
"chars": 2669,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nmodule SecurityFilter\n def add_one(input)\n \"#{input} + 1\"\n en"
},
{
"path": "test/integration/standard_filter_test.rb",
"chars": 47070,
"preview": "# encoding: utf-8\n# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass TestThing\n attr_reader :foo\n\n def initia"
},
{
"path": "test/integration/tag/disableable_test.rb",
"chars": 1422,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass TagDisableableTest < Minitest::Test\n include Liquid\n\n modu"
},
{
"path": "test/integration/tag_test.rb",
"chars": 1098,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass TagTest < Minitest::Test\n include Liquid\n\n def test_custom"
},
{
"path": "test/integration/tags/break_tag_test.rb",
"chars": 380,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass BreakTagTest < Minitest::Test\n include Liquid\n\n # tests th"
},
{
"path": "test/integration/tags/continue_tag_test.rb",
"chars": 365,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ContinueTagTest < Minitest::Test\n include Liquid\n\n # tests"
},
{
"path": "test/integration/tags/cycle_tag_test.rb",
"chars": 5381,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass CycleTagTest < Minitest::Test\n def test_simple_cycle_inside"
},
{
"path": "test/integration/tags/echo_test.rb",
"chars": 276,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass EchoTest < Minitest::Test\n include Liquid\n\n def test_echo_"
},
{
"path": "test/integration/tags/for_tag_test.rb",
"chars": 15967,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ThingWithValue < Liquid::Drop\n def value\n 3\n end\nend\n\nc"
},
{
"path": "test/integration/tags/if_else_tag_test.rb",
"chars": 9528,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass IfElseTagTest < Minitest::Test\n include Liquid\n\n def test_"
},
{
"path": "test/integration/tags/include_tag_test.rb",
"chars": 14323,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass TestFileSystem\n PARTIALS = {\n \"nested_template\" => \"{% i"
},
{
"path": "test/integration/tags/increment_tag_test.rb",
"chars": 986,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass IncrementTagTest < Minitest::Test\n include Liquid\n\n def te"
},
{
"path": "test/integration/tags/inline_comment_test.rb",
"chars": 2110,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass InlineCommentTest < Minitest::Test\n include Liquid\n\n def t"
},
{
"path": "test/integration/tags/liquid_tag_test.rb",
"chars": 3731,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass LiquidTagTest < Minitest::Test\n include Liquid\n\n def test_"
},
{
"path": "test/integration/tags/raw_tag_test.rb",
"chars": 1843,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass RawTagTest < Minitest::Test\n include Liquid\n\n def test_tag"
},
{
"path": "test/integration/tags/render_tag_test.rb",
"chars": 10703,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass RenderTagTest < Minitest::Test\n include Liquid\n\n def test_"
},
{
"path": "test/integration/tags/standard_tag_test.rb",
"chars": 13558,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass StandardTagTest < Minitest::Test\n include Liquid\n\n def tes"
},
{
"path": "test/integration/tags/statements_test.rb",
"chars": 3702,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass StatementsTest < Minitest::Test\n include Liquid\n\n def test"
},
{
"path": "test/integration/tags/table_row_test.rb",
"chars": 14541,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass TableRowTest < Minitest::Test\n include Liquid\n\n class Arra"
},
{
"path": "test/integration/tags/unless_else_tag_test.rb",
"chars": 1279,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass UnlessElseTagTest < Minitest::Test\n include Liquid\n\n def t"
},
{
"path": "test/integration/template_test.rb",
"chars": 15321,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\nrequire 'timeout'\n\nclass TemplateContextDrop < Liquid::Drop\n def l"
},
{
"path": "test/integration/trim_mode_test.rb",
"chars": 11558,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass TrimModeTest < Minitest::Test\n include Liquid\n\n # Make sur"
},
{
"path": "test/integration/variable_test.rb",
"chars": 9530,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\nrequire 'timeout'\n\nclass VariableTest < Minitest::Test\n include Li"
},
{
"path": "test/test_helper.rb",
"chars": 5403,
"preview": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nENV[\"MT_NO_EXPECTATIONS\"] = \"1\"\nrequire 'minitest/autorun'\n\n$LOAD_PAT"
},
{
"path": "test/unit/block_unit_test.rb",
"chars": 2127,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass BlockUnitTest < Minitest::Test\n include Liquid\n\n def test_"
},
{
"path": "test/unit/condition_unit_test.rb",
"chars": 14230,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ConditionUnitTest < Minitest::Test\n include Liquid\n\n def s"
},
{
"path": "test/unit/environment_filter_test.rb",
"chars": 3354,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass EnvironmentFilterTest < Minitest::Test\n include Liquid\n\n m"
},
{
"path": "test/unit/environment_test.rb",
"chars": 675,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass EnvironmentTest < Minitest::Test\n include Liquid\n\n class U"
},
{
"path": "test/unit/file_system_unit_test.rb",
"chars": 1105,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass FileSystemUnitTest < Minitest::Test\n include Liquid\n\n def "
},
{
"path": "test/unit/i18n_unit_test.rb",
"chars": 965,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass I18nUnitTest < Minitest::Test\n include Liquid\n\n def setup\n"
},
{
"path": "test/unit/lexer_unit_test.rb",
"chars": 3597,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass LexerUnitTest < Minitest::Test\n include Liquid\n\n def test_"
},
{
"path": "test/unit/parse_context_unit_test.rb",
"chars": 4131,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ParseContextUnitTest < Minitest::Test\n include Liquid\n\n de"
},
{
"path": "test/unit/parse_tree_visitor_test.rb",
"chars": 5052,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ParseTreeVisitorTest < Minitest::Test\n include Liquid\n\n de"
},
{
"path": "test/unit/parser_unit_test.rb",
"chars": 2404,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ParserUnitTest < Minitest::Test\n include Liquid\n\n def test"
},
{
"path": "test/unit/partial_cache_unit_test.rb",
"chars": 5401,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass PartialCacheUnitTest < Minitest::Test\n def test_uses_the_fi"
},
{
"path": "test/unit/regexp_unit_test.rb",
"chars": 2226,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\nrequire 'timeout'\n\nclass RegexpUnitTest < Minitest::Test\n include "
},
{
"path": "test/unit/registers_unit_test.rb",
"chars": 4335,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass RegistersUnitTest < Minitest::Test\n include Liquid\n\n def t"
},
{
"path": "test/unit/resource_limits_unit_test.rb",
"chars": 2689,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ResourceLimitsUnitTest < Minitest::Test\n def test_cumulativ"
},
{
"path": "test/unit/strainer_template_unit_test.rb",
"chars": 2388,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass StrainerTemplateUnitTest < Minitest::Test\n include Liquid\n\n"
},
{
"path": "test/unit/tag_unit_test.rb",
"chars": 1075,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass TagUnitTest < Minitest::Test\n include Liquid\n\n def test_ta"
},
{
"path": "test/unit/tags/case_tag_unit_test.rb",
"chars": 3701,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass CaseTagUnitTest < Minitest::Test\n include Liquid\n\n def tes"
},
{
"path": "test/unit/tags/comment_tag_unit_test.rb",
"chars": 4898,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass CommentTagUnitTest < Minitest::Test\n def test_comment_insid"
},
{
"path": "test/unit/tags/doc_tag_unit_test.rb",
"chars": 7302,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass DocTagUnitTest < Minitest::Test\n def test_doc_tag\n templ"
},
{
"path": "test/unit/tags/for_tag_unit_test.rb",
"chars": 516,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass ForTagUnitTest < Minitest::Test\n def test_for_nodelist\n "
},
{
"path": "test/unit/tags/if_tag_unit_test.rb",
"chars": 299,
"preview": "# frozen_string_literal: true\n\nrequire 'test_helper'\n\nclass IfTagUnitTest < Minitest::Test\n def test_if_nodelist\n te"
}
]
// ... and 4 more files (download for full content)
About this extraction
This page contains the full source code of the Shopify/liquid GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 204 files (720.9 KB), approximately 203.7k tokens, and a symbol index with 2031 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.