Compare commits

..

71 Commits

Author SHA1 Message Date
Justin Li
74f9bad513 Add spaceless tag 2015-06-26 10:00:34 -07:00
Justin Li
8a8de46c6a Merge pull request #603 from Shopify/format-history
Format changelog attribution to include one name only
2015-06-23 07:40:05 -07:00
Justin Li
58c7f226cc Format changelog attribution to include one name only 2015-06-19 11:45:37 -04:00
Justin Li
adfcd0ab13 Update history to reflect merge of #600
[ci skip]
2015-06-19 11:38:59 -04:00
Justin Li
30ef7d14b0 Merge pull request #600 from carsonreinke/filter-compact
Merge pull request 600
2015-06-19 11:38:14 -04:00
Florian Weingarten
4920ec50e4 update changelog 2015-06-19 07:41:39 -04:00
David Cornu
e395229283 Merge pull request #601 from Shopify/safe-to-integer
Use to_integer instead of to_i on arguments
2015-06-16 11:31:20 -04:00
David Cornu
9470fba0c8 Exclude lib/liquid/standardfilters.rb from ModuleLength 2015-06-16 15:19:06 +00:00
David Cornu
ac180e8402 Use to_integer instead of to_i on arguments 2015-06-16 15:08:29 +00:00
Carson Reinke
7c5d54aced Ignore Rubocop Metrics/ModuleLength for now 2015-06-15 15:07:25 -04:00
Carson Reinke
5fbb312a67 "Trailing whitespace detected." 2015-06-15 14:27:48 -04:00
Carson Reinke
8385099960 Added "compact" filter 2015-06-15 14:14:28 -04:00
Florian Weingarten
504b6fb3c7 Merge pull request #596 from Shopify/liquid_c_tests
Run tests with latest liquid/c gem
2015-06-08 22:52:57 +02:00
Florian Weingarten
01420e8014 fix gem platforms 2015-06-08 18:38:40 +00:00
Florian Weingarten
dde35a2907 shut up rubocop 2015-06-08 18:38:40 +00:00
Florian Weingarten
e2323332cd Run tests with latest liquid/c gem 2015-06-08 18:38:35 +00:00
Florian Weingarten
7b4398d0c4 Merge pull request #595 from Shopify/uniq_on_strings
Fix uniq filter with string input
2015-06-05 16:27:05 +02:00
Florian Weingarten
1e23036b2d Fix uniq filter with string input 2015-06-04 22:55:03 -04:00
Florian Weingarten
13716fa68b Merge pull request #594 from boobooninja/rake_console
add rake console
2015-06-05 04:21:01 +02:00
Loren Hale
232e8bb4cd add rake console
add Rake console task to load irb with liquid
2015-06-05 10:17:55 +08:00
Dylan Thacker-Smith
6968def5dd Merge pull request #574 from Shopify/template-name-in-errors
Include template name with line numbers in render errors.
2015-06-04 15:28:12 -04:00
Dylan Thacker-Smith
ad3748af21 Include template name with line numbers in render errors. 2015-06-04 13:44:01 -04:00
Florian Weingarten
c82e04f4e6 Merge pull request #593 from Shopify/fix_predicate_name
Rename 'has_key?' and 'has_interrupt?'
2015-06-04 19:40:14 +02:00
Florian Weingarten
5919626da4 Rename 'has_key?' and 'has_interrupt?' 2015-06-04 13:14:46 -04:00
Florian Weingarten
82269e2509 fix a few more rubocop offenses 2015-06-04 13:09:58 -04:00
Florian Weingarten
b347fac3c0 Merge pull request #592 from Shopify/method_literal
blank and empty as variable names
2015-06-04 19:09:48 +02:00
Florian Weingarten
e761a6864e clean up some rubocop stuff 2015-06-04 12:56:29 -04:00
Florian Weingarten
4c22cef341 blank and empty as variable names 2015-06-04 12:30:50 -04:00
Florian Weingarten
c319240174 run rubocop on CI 2015-06-04 11:57:25 -04:00
Florian Weingarten
6ace095207 Avoid parallel assignments 2015-06-04 11:56:47 -04:00
Florian Weingarten
e36f366c33 gitignore .bundle 2015-06-04 11:56:00 -04:00
Florian Weingarten
02729e89c0 make rubocop happy 2015-06-04 11:56:00 -04:00
Gaurav Chande
6b0f6401d0 Merge pull request #590 from Shopify/allow-template-tags
Local Tags
2015-06-04 11:19:24 -04:00
Gaurav Chande
fc8e6c8d3a Change Tokenizer test to fetch tokens instead of exposing ivar 2015-06-04 15:10:01 +00:00
Gaurav Chande
79d7dd06df Extract tag fetching into a method (which can be overriden then) 2015-06-04 04:39:54 +00:00
Gaurav Chande
3a907a4db7 Move DEFAULT_OPTIONS related logic to Document 2015-06-04 04:39:54 +00:00
Gaurav Chande
8b98f92c7f Extract tokenize logic from Template to a RubyTokenizer 2015-06-04 04:39:30 +00:00
Dylan Thacker-Smith
b79c0c611c Merge pull request #586 from Shopify/string-contains-non-string
Avoid an exception from checking if a string contains a non-string.
2015-06-03 10:58:38 -04:00
Dylan Thacker-Smith
8a2947865b Avoid an exception from checking if a string contains a non-string. 2015-06-03 02:21:51 -04:00
Dylan Thacker-Smith
ea29f8b4b8 Merge pull request #583 from Shopify/slice-nil-offset
Raise a Liquid::ArgumentError in slice filter for invalid integers.
2015-06-03 01:43:56 -04:00
Dylan Thacker-Smith
c84f4520cc Keep input out of error message and add test for slice Integer parsing. 2015-06-03 01:35:01 -04:00
Dylan Thacker-Smith
3dd6433e2f Merge pull request #584 from Shopify/replace-non-string
Convert arguments to replace filters to strings to avoid exceptions.
2015-06-02 16:41:15 -04:00
Dylan Thacker-Smith
ab7109a335 Raise a Liquid::ArgumentError in slice filter for invalid integers. 2015-06-02 16:05:08 -04:00
Dylan Thacker-Smith
94fe050952 Convert arguments to replace filters to strings to avoid exceptions. 2015-06-02 15:59:29 -04:00
Justin Li
9b98c436c4 Merge pull request #582 from Shopify/require-empty-raw-tag
Ensure raw tag has no arguments
2015-06-02 15:58:15 -04:00
Justin Li
889019f53a Keep old test as well 2015-06-02 15:21:51 -04:00
Justin Li
c290375aec Remove unnecessary regex options 2015-06-02 15:17:36 -04:00
Justin Li
719a98a25e Ensure raw tag has no arguments 2015-06-02 14:32:39 -04:00
Justin Li
86d8b552da Merge pull request #581 from Shopify/require-closed-raw-tag
Raise SyntaxError if raw tag is unclosed
2015-06-02 11:38:45 -04:00
Justin Li
b1ee9129e7 Raise SyntaxError if raw tag is unclosed 2015-06-02 10:56:51 -04:00
Justin Li
be2e41e4d5 Merge pull request #579 from Shopify/ast-match
Ensure For@reversed is a boolean
2015-05-28 16:45:09 -04:00
Justin Li
20ca2b9632 Update history to reflect merge of #570
[ci skip]
2015-05-28 16:43:22 -04:00
Justin Li
6c058823ad Merge pull request #570 from Shopify/fix-strict-conditions
Fix condition parse order in strict mode
2015-05-28 16:33:54 -04:00
Dylan Thacker-Smith
27245c9eab Merge pull request #577 from Shopify/table-row-blank-string-collection
Fix exception from using an empty string for the table row collection.
2015-05-28 16:20:42 -04:00
Justin Li
a639a13380 Use cleaner recursive solution 2015-05-28 16:16:30 -04:00
Justin Li
05a0fe56c8 Ensure For@reversed is a boolean 2015-05-28 16:09:26 -04:00
Dylan Thacker-Smith
c1eb694057 Remove the redundant iterable check in the for tag.
Just do it in slice_collection for consistency with the tablerow tag.
2015-05-28 16:04:50 -04:00
Dylan Thacker-Smith
f53b31c867 Merge pull request #578 from Shopify/filter-error-handling
Handle some more standard filter errors.
2015-05-28 15:00:57 -04:00
Dylan Thacker-Smith
363388e92f Handle some more standard filter errors. 2015-05-28 14:18:53 -04:00
Dylan Thacker-Smith
873eddbb85 Split a line and use String#empty? for readability 2015-05-28 12:55:04 -04:00
Dylan Thacker-Smith
e790b60f60 Fix exception from using an empty string for the table row collection. 2015-05-28 12:11:39 -04:00
Dylan Thacker-Smith
3264d60425 Merge pull request #576 from Shopify/flexible-exception-handler
Allow the exception handler to convert exceptions to hide error messges
2015-05-28 11:38:44 -04:00
Dylan Thacker-Smith
8ff1b8e01f Test set_line_number_from_token after exception is converted. 2015-05-28 09:22:02 -04:00
Dylan Thacker-Smith
8d5e71f856 Allow the exception handler to convert exceptions to hide error messages. 2015-05-27 18:59:51 -04:00
Dylan Thacker-Smith
89c6e605f8 Merge pull request #575 from Shopify/zero-division-error
Raise Liquid::ZeroDivisionError instead of ZeroDivisionError.
2015-05-26 10:43:23 -04:00
Dylan Thacker-Smith
6265c36ec9 Raise Liquid::ZeroDivisionError instead of ZeroDivisionError. 2015-05-25 15:40:17 -04:00
Dylan Thacker-Smith
8af99ff918 Merge pull request #573 from Shopify/optional-error-rendering
Make liquid error rendering optional.
2015-05-25 12:11:10 -04:00
Dylan Thacker-Smith
36200ff704 Make liquid error rendering optional.
Although the author of the liquid template wants to see these errors, they
probably don't want the visitor to see the liquid errors.  Probably the
best fallback when rendering the page for visitors is to render the empty
string for tags with errors.
2015-05-25 11:24:53 -04:00
Justin Li
a9c7df931f Strict parse conditions in reverse order 2015-05-19 11:51:01 -04:00
Justin Li
b4e5017c79 Add truth table test for multiple if conditions 2015-05-14 14:11:03 -04:00
Justin Li
f1bc9f27df Include message in assert_template_result 2015-05-14 14:10:45 -04:00
59 changed files with 614 additions and 355 deletions

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ pkg
.rvmrc
.ruby-version
Gemfile.lock
.bundle

View File

@@ -3,22 +3,49 @@ inherit_from: ./.rubocop_todo.yml
AllCops:
Exclude:
- 'performance/shopify/*'
- 'pkg/**'
Metrics/BlockNesting:
Max: 3
Exclude:
- 'lib/liquid/block_body.rb'
Metrics/ModuleLength:
Enabled: false
Lint/AssignmentInCondition:
Enabled: false
Lint/AmbiguousOperator:
Enabled: false
Lint/AmbiguousRegexpLiteral:
Enabled: false
Lint/ParenthesesAsGroupedExpression:
Enabled: false
Lint/UnusedBlockArgument:
Enabled: false
Lint/EndAlignment:
AlignWith: variable
Lint/UnusedMethodArgument:
Enabled: false
Style/SingleLineBlockParams:
Enabled: false
Style/DoubleNegation:
Enabled: false
Style/StringLiteralsInInterpolation:
Enabled: false
Style/AndOr:
Enabled: false
Style/SignalException:
Enabled: false
@@ -90,3 +117,7 @@ Style/PerlBackrefs:
Style/WordArray:
Enabled: false
Style/ModuleLength:
Exclude:
- lib/liquid/standardfilters.rb

View File

@@ -1,62 +1,33 @@
# This configuration was generated by `rubocop --auto-gen-config`
# on 2015-05-13 19:54:01 +0000 using RuboCop version 0.31.0.
# on 2015-06-08 18:16:16 +0000 using RuboCop version 0.32.0.
# 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: 2
Lint/AmbiguousOperator:
# Offense count: 5
Lint/NestedMethodDefinition:
Enabled: false
# Offense count: 2
Lint/AmbiguousRegexpLiteral:
Enabled: false
# Offense count: 1
Lint/Eval:
Enabled: false
# Offense count: 4
Lint/ParenthesesAsGroupedExpression:
Enabled: false
# Offense count: 3
# Cop supports --auto-correct.
Lint/UnusedMethodArgument:
Enabled: false
# Offense count: 1
Lint/UselessAccessModifier:
Enabled: false
# Offense count: 6
Lint/UselessAssignment:
Enabled: false
# Offense count: 51
# Offense count: 53
Metrics/AbcSize:
Max: 59
# Offense count: 2
Metrics/BlockNesting:
Max: 6
Max: 58
# Offense count: 16
# Configuration parameters: CountComments.
Metrics/ClassLength:
Max: 304
Max: 314
# Offense count: 10
# Offense count: 12
Metrics/CyclomaticComplexity:
Max: 15
# Offense count: 547
# Offense count: 553
# Configuration parameters: AllowURI, URISchemes.
Metrics/LineLength:
Max: 294
# Offense count: 76
# Offense count: 77
# Configuration parameters: CountComments.
Metrics/MethodLength:
Max: 46
@@ -64,7 +35,7 @@ Metrics/MethodLength:
# Offense count: 1
# Configuration parameters: CountComments.
Metrics/ModuleLength:
Max: 216
Max: 235
# Offense count: 6
Metrics/PerceivedComplexity:
@@ -74,40 +45,17 @@ Metrics/PerceivedComplexity:
Style/AccessorMethodName:
Enabled: false
# Offense count: 2
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
Style/AndOr:
Enabled: false
# Offense count: 2
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods.
Style/BlockDelimiters:
Enabled: false
# Offense count: 1
Style/DoubleNegation:
Enabled: false
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
Style/EmptyLinesAroundClassBody:
Style/ClosingParenthesisIndentation:
Enabled: false
# Offense count: 2
# Offense count: 3
# Configuration parameters: MinBodyLength.
Style/GuardClause:
Enabled: false
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, UseHashRocketsWithSymbolValues.
Style/HashSyntax:
Enabled: false
# Offense count: 13
# Offense count: 4
# Configuration parameters: EnforcedStyle, SupportedStyles.
Style/MethodName:
Enabled: false
@@ -116,27 +64,19 @@ Style/MethodName:
Style/MultilineBlockChain:
Enabled: false
# Offense count: 3
# Offense count: 2
# Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles.
Style/Next:
Enabled: false
# Offense count: 3
# Configuration parameters: NamePrefix, NamePrefixBlacklist.
Style/PredicateName:
Enabled: false
# Offense count: 1
Style/RescueModifier:
Enabled: false
# Offense count: 7
# Cop supports --auto-correct.
# Configuration parameters: AllowAsExpressionSeparator.
Style/Semicolon:
Enabled: false
# Offense count: 3
# Offense count: 1
# Cop supports --auto-correct.
Style/SpecialGlobalVars:
# Configuration parameters: MaxLineLength.
Style/WhileUntilModifier:
Enabled: false

View File

@@ -14,7 +14,7 @@ matrix:
allow_failures:
- rvm: jruby-head
script: "rake test"
script: "bundle exec rake"
notifications:
disable: true

View File

@@ -7,4 +7,8 @@ group :test do
gem 'spy', '0.4.1'
gem 'benchmark-ips'
gem 'rubocop'
platform :mri do
gem 'liquid-c', github: 'Shopify/liquid-c', ref: '35e9aee48d639ae1d3ac9ba77616aca9800eab7d'
end
end

View File

@@ -3,26 +3,36 @@
## 4.0.0 / not yet released / branch "master"
### Changed
* Add sort_natural filter (#554) [Martin Hanzel, arthanzel]
* Add forloop.parentloop as a reference to the parent loop (#520) [Justin Li, pushrax]
* Block parsing moved to BlockBody class (#458) [Dylan Thacker-Smith, dylanahsmith]
* Add concat filter to concatenate arrays (#429) [Diogo Beato, dvbeato]
* Ruby 1.9 support dropped (#491) [Justin Li, pushrax]
* Liquid::Template.file_system's read_template_file method is no longer passed the context. (#441) [James Reid-Smith, sunblaze]
* 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 support for `liquid_methods`
### Fixed
* Fix naming of the "context variable" when dynamically including a template (#559) [Justin Li, pushrax]
* Gracefully accept empty strings in the date filter (#555) [Loren Hale, boobooninja]
* Fix capturing into variables with a hyphen in the name (#505) [Florian Weingarten, fw42]
* Fix case sensitivity regression in date standard filter (#499) [Kelley Reynolds, kreynolds]
* Disallow filters with no variable in strict mode (#475) [Justin Li, pushrax]
* Disallow variable names in the strict parser that are not valid in the lax parser (#463) [Justin Li, pushrax]
* Fix BlockBody#warnings taking exponential time to compute (#486) [Justin Li, pushrax]
* 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.2 / 2015-04-24 / branch "3-0-stable"
## 3.0.3 / 2015-05-28 / branch "3-0-stable"
* Expose VariableLookup private members (#551) [Justin Li, pushrax]
* 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
@@ -31,44 +41,44 @@
## 3.0.0 / 2014-11-12
* Removed Block#end_tag. Instead, override parse with `super` followed by your code. See #446 [Dylan Thacker-Smith, dylanahsmith]
* 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, djreimer]
* Add uniq to standard filters [Florian Weingarten, fw42]
* Add exception_handler feature (#397) and #254 [Bogdan Gusiev, bogdan and Florian Weingarten, fw42]
* Optimize variable parsing to avoid repeated regex evaluation during template rendering #383 [Jason Hiltz-Laforge, jasonhl]
* Optimize checking for block interrupts to reduce object allocation #380 [Jason Hiltz-Laforge, jasonhl]
* Properly set context rethrow_errors on render! #349 [Thierry Joyal, tjoyal]
* Fix broken rendering of variables which are equal to false (#345) [Florian Weingarten, fw42]
* Remove ActionView template handler [Dylan Thacker-Smith, dylanahsmith]
* Freeze lots of string literals for new Ruby 2.1 optimization (#297) [Florian Weingarten, fw42]
* Allow newlines in tags and variables (#324) [Dylan Thacker-Smith, dylanahsmith]
* Tag#parse is called after initialize, which now takes options instead of tokens as the 3rd argument. See #321 [Dylan Thacker-Smith, dylanahsmith]
* Raise `Liquid::ArgumentError` instead of `::ArgumentError` when filter has wrong number of arguments #309 [Bogdan Gusiev, bogdan]
* Add a to_s default for liquid drops (#306) [Adam Doeler, releod]
* Add strip, lstrip, and rstrip to standard filters [Florian Weingarten, fw42]
* Make if, for & case tags return complete and consistent nodelists (#250) [Nick Jones, dntj]
* Prevent arbitrary method invocation on condition objects (#274) [Dylan Thacker-Smith, dylanahsmith]
* Don't call to_sym when creating conditions for security reasons (#273) [Bouke van der Bijl, bouk]
* Fix resource counting bug with respond_to?(:length) (#263) [Florian Weingarten, fw42]
* Allow specifying custom patterns for template filenames (#284) [Andrei Gladkyi, agladkyi]
* Allow drops to optimize loading a slice of elements (#282) [Tom Burns, boourns]
* Support for passing variables to snippets in subdirs (#271) [Joost Hietbrink, joost]
* Add a class cache to avoid runtime extend calls (#249) [James Tucker, raggi]
* Remove some legacy Ruby 1.8 compatibility code (#276) [Florian Weingarten, fw42]
* Add default filter to standard filters (#267) [Derrick Reimer, djreimer]
* Add optional strict parsing and warn parsing (#235) [Tristan Hume, trishume]
* 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, fw42]
* Fix clashing method names in enumerable drops (#238) [Florian Weingarten, fw42]
* Make map filter work on enumerable drops (#233) [Florian Weingarten, fw42]
* Improved whitespace stripping for blank blocks, related to #216 [Florian Weingarten, fw42]
* 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.1 / 2014-01-10 / branch "2-6-stable"
Security fix, cherry-picked from master (4e14a65):
* Don't call to_sym when creating conditions for security reasons (#273) [Bouke van der Bijl, bouk]
* Prevent arbitrary method invocation on condition objects (#274) [Dylan Thacker-Smith, dylanahsmith]
* 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
@@ -76,37 +86,37 @@ IMPORTANT: Liquid 2.6 is going to be the last version of Liquid which maintains
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, joliss]
* Bugfix for #114: strip_html filter supports style tags [James Allardice, jamesallardice]
* Bugfix for #117: 'now' support for date filter in Ruby 1.9 [Notre Dame Webgroup, ndwebgroup]
* Bugfix for #166: truncate filter on UTF-8 strings with Ruby 1.8 [Florian Weingarten, fw42]
* Bugfix for #204: 'raw' parsing bug [Florian Weingarten, fw42]
* Bugfix for #150: 'for' parsing bug [Peter Schröder, phoet]
* Bugfix for #126: Strip CRLF in strip_newline [Peter Schröder, phoet]
* Bugfix for #174, "can't convert Fixnum into String" for "replace" [wǒ_is神仙, jsw0528]
* Allow a Liquid::Drop to be passed into Template#render [Daniel Huckstep, darkhelmet]
* Resource limits [Florian Weingarten, fw42]
* Add reverse filter [Jay Strybis, unreal]
* 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, astathopoulos]
* Cache tokenized partial templates [Tom Burns, boourns]
* Avoid warnings in Ruby 1.9.3 [Marcus Stollsteimer, stomar]
* Better documentation for 'include' tag (closes #163) [Peter Schröder, phoet]
* Use of BigDecimal on filters to have better precision (closes #155) [Arthur Nogueira Neves, arthurnn]
* 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, bouk]
* Prevent arbitrary method invocation on condition objects (#274) [Dylan Thacker-Smith, dylanahsmith]
* 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), [wǒ_is神仙, jsw0528]
* 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, fw42]
* #232, #234, #237: Fix map filter bugs [Florian Weingarten]
## 2.5.2 / 2013-09-03 / deleted
@@ -114,7 +124,7 @@ Yanked from rubygems, as it contained too many changes that broke compatibility.
## 2.5.1 / 2013-07-24
* #230: Fix security issue with map filter, Use invoke_drop in map filter [Florian Weingarten, fw42]
* #230: Fix security issue with map filter, Use invoke_drop in map filter [Florian Weingarten]
## 2.5.0 / 2013-03-06

View File

@@ -27,9 +27,22 @@ desc 'runs test suite with both strict and lax 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
if RUBY_ENGINE == 'ruby'
ENV['LIQUID-C'] = '1'
ENV['LIQUID_PARSER_MODE'] = 'lax'
Rake::Task['base_test'].reenable
Rake::Task['base_test'].invoke
ENV['LIQUID_PARSER_MODE'] = 'strict'
Rake::Task['base_test'].reenable
Rake::Task['base_test'].invoke
end
end
task gem: :build
@@ -76,3 +89,7 @@ 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

View File

@@ -1,5 +1,4 @@
class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
handle(:get, req, res)
end
@@ -20,7 +19,7 @@ class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet
@response['Content-Type'] = "text/html"
@response.status = 200
@response.body = Liquid::Template.parse(read_template).render(@assigns, :filters => [ProductsFilter])
@response.body = Liquid::Template.parse(read_template).render(@assigns, filters: [ProductsFilter])
end
def read_template(filename = @action)

View File

@@ -50,7 +50,6 @@ require 'liquid/i18n'
require 'liquid/drop'
require 'liquid/extensions'
require 'liquid/errors'
require 'liquid/error_location'
require 'liquid/interrupts'
require 'liquid/strainer'
require 'liquid/expression'
@@ -69,6 +68,7 @@ require 'liquid/template'
require 'liquid/standardfilters'
require 'liquid/condition'
require 'liquid/utils'
require 'liquid/tokenizer'
require 'liquid/token'
# Load all the tags of the standard library

View File

@@ -7,7 +7,7 @@ module Liquid
def parse(tokens)
@body = BlockBody.new
while more = parse_body(@body, tokens)
while parse_body(@body, tokens)
end
end

View File

@@ -22,7 +22,7 @@ module Liquid
tag_name = $1
markup = $2
# fetch the tag from registered blocks
if tag = Template.tags[tag_name]
if tag = registered_tags[tag_name]
markup = token.child(markup) if token.is_a?(Token)
new_tag = tag.parse(tag_name, markup, tokens, options)
new_tag.line_number = token.line_number if token.is_a?(Token)
@@ -73,7 +73,7 @@ module Liquid
@nodelist.each do |token|
# Break out if we have any unhanded interrupts.
break if context.has_interrupt?
break if context.interrupt?
begin
# If we get an Interrupt that means the block must stop processing. An
@@ -127,5 +127,9 @@ module Liquid
def raise_missing_variable_terminator(token, options)
raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination".freeze, token: token, tag_end: VariableEnd.inspect))
end
def registered_tags
Template.tags
end
end
end

View File

@@ -16,7 +16,12 @@ module Liquid
'>='.freeze => :>=,
'<='.freeze => :<=,
'contains'.freeze => lambda do |cond, left, right|
left && right && left.respond_to?(:include?) ? left.include?(right) : false
if left && right && left.respond_to?(:include?)
right = right.to_s if left.is_a?(String)
left.include?(right)
else
false
end
end
}
@@ -73,17 +78,17 @@ module Liquid
private
def equal_variables(left, right)
if left.is_a?(Symbol)
if right.respond_to?(left)
return right.send(left.to_s)
if left.is_a?(Liquid::Expression::MethodLiteral)
if right.respond_to?(left.method_name)
return right.send(left.method_name)
else
return nil
end
end
if right.is_a?(Symbol)
if left.respond_to?(right)
return left.send(right.to_s)
if right.is_a?(Liquid::Expression::MethodLiteral)
if left.respond_to?(right.method_name)
return left.send(right.method_name)
else
return nil
end

View File

@@ -12,23 +12,21 @@ module Liquid
#
# context['bob'] #=> nil class Context
class Context
attr_reader :scopes, :errors, :error_locations, :registers, :environments, :resource_limits
attr_accessor :exception_handler, :render_errors, :template_name
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
attr_accessor :exception_handler, :template_name
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, render_errors = true)
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
@environments = [environments].flatten
@scopes = [(outer_scope || {})]
@registers = registers
@errors = []
@error_locations = []
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
squash_instance_assigns_with_environments
@render_errors = render_errors
@this_stack_used = false
if rethrow_errors
self.exception_handler = ->(e) { true }
self.exception_handler = ->(e) { raise }
end
@interrupts = []
@@ -50,7 +48,7 @@ module Liquid
end
# are there any not handled interrupts?
def has_interrupt?
def interrupt?
!@interrupts.empty?
end
@@ -70,10 +68,22 @@ module Liquid
e.set_line_number_from_token(token)
end
output = nil
if exception_handler
result = exception_handler.call(e)
case result
when Exception
e = result
e.set_line_number_from_token(token) if e.is_a?(Liquid::Error)
when String
output = result
else
raise if result
end
end
errors.push(e)
error_locations.push(ErrorLocation.from_token(template_name, token))
raise if exception_handler && exception_handler.call(e)
render_errors ? Liquid::Error.render(e) : ''
output || Liquid::Error.render(e)
end
def invoke(method, *args)
@@ -145,7 +155,7 @@ module Liquid
evaluate(Expression.parse(expression))
end
def has_key?(key)
def key?(key)
self[key] != nil
end
@@ -157,7 +167,7 @@ module Liquid
def find_variable(key)
# 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.has_key?(key) }
index = @scopes.find_index { |s| s.key?(key) }
scope = @scopes[index] if index
variable = nil
@@ -194,7 +204,7 @@ module Liquid
def squash_instance_assigns_with_environments
@scopes.last.each_key do |k|
@environments.each do |env|
if env.has_key?(k)
if env.key?(k)
scopes.last[k] = lookup_and_evaluate(env, k)
break
end

View File

@@ -1,8 +1,12 @@
module Liquid
class Document < BlockBody
DEFAULT_OPTIONS = {
locale: I18n.new
}
def self.parse(tokens, options)
doc = new
doc.parse(tokens, options)
doc.parse(tokens, DEFAULT_OPTIONS.merge(options))
doc
end

View File

@@ -39,7 +39,7 @@ module Liquid
end
end
def has_key?(_name)
def key?(_name)
true
end
@@ -57,8 +57,6 @@ module Liquid
alias_method :[], :invoke_drop
private
# Check for method existence without invoking respond_to?, which creates symbols
def self.invokable?(method_name)
invokable_methods.include?(method_name.to_s)

View File

@@ -1,11 +0,0 @@
module Liquid
ErrorLocation = Struct.new(:template_name, :line_number) do
def self.line_number_from_token(token)
token.respond_to?(:line_number) ? token.line_number : nil
end
def self.from_token(template_name, token)
new(template_name, line_number_from_token(token))
end
end
end

View File

@@ -18,8 +18,9 @@ module Liquid
end
def set_line_number_from_token(token)
return unless token.respond_to?(:line_number)
return if line_number
self.line_number = ErrorLocation.line_number_from_token(token)
self.line_number = token.line_number
end
def self.render(e)
@@ -59,4 +60,6 @@ module Liquid
StackLevelError = Class.new(Error)
TaintedError = Class.new(Error)
MemoryError = Class.new(Error)
ZeroDivisionError = Class.new(Error)
FloatDomainError = Class.new(Error)
end

View File

@@ -1,11 +1,24 @@
module Liquid
class Expression
class MethodLiteral
attr_reader :method_name, :to_s
def initialize(method_name, to_s)
@method_name = method_name
@to_s = to_s
end
def to_liquid
to_s
end
end
LITERALS = {
nil => nil, 'nil'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil,
'true'.freeze => true,
'false'.freeze => false,
'blank'.freeze => :blank?,
'empty'.freeze => :empty?
'blank'.freeze => MethodLiteral.new(:blank?, '').freeze,
'empty'.freeze => MethodLiteral.new(:empty?, '').freeze
}
def self.parse(markup)

View File

@@ -24,10 +24,10 @@ module Liquid
private
def interpolate(name, vars)
name.gsub(/%\{(\w+)\}/) {
name.gsub(/%\{(\w+)\}/) do
# raise TranslationError, "Undefined key #{$1} for interpolation in translation #{name}" unless vars[$1.to_sym]
"#{vars[$1.to_sym]}"
}
end
end
def deep_fetch_translation(name)

View File

@@ -1,6 +1,7 @@
---
errors:
syntax:
tag_unexpected_args: "Syntax Error in '%{tag}' - Valid syntax: %{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]"

View File

@@ -46,8 +46,8 @@ module Liquid
end
def slice(input, offset, length = nil)
offset = Integer(offset)
length = length ? Integer(length) : 1
offset = to_integer(offset)
length = length ? to_integer(length) : 1
if input.is_a?(Array)
input.slice(offset, length) || []
@@ -59,15 +59,17 @@ module Liquid
# Truncate a string down to x characters
def truncate(input, length = 50, truncate_string = "...".freeze)
return if input.nil?
l = length.to_i - truncate_string.length
length = to_integer(length)
l = length - truncate_string.length
l = 0 if l < 0
input.length > length.to_i ? input[0...l] + truncate_string : input
input.length > length ? input[0...l] + truncate_string : input
end
def truncatewords(input, words = 15, truncate_string = "...".freeze)
return if input.nil?
wordlist = input.to_s.split
l = words.to_i - 1
words = to_integer(words)
l = words - 1
l = 0 if l < 0
wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string : input
end
@@ -103,6 +105,11 @@ module Liquid
input.to_s.gsub(/\r?\n/, ''.freeze)
end
# Remove whitespace between HTML tags
def strip_html_whitespace(input)
Spaceless.strip_html_whitespace(input.to_s)
end
# Join elements of the array with certain character between them
def join(input, glue = ' '.freeze)
InputIterator.new(input).join(glue)
@@ -139,10 +146,11 @@ module Liquid
# provide optional property with which to determine uniqueness
def uniq(input, property = nil)
ary = InputIterator.new(input)
if property.nil?
input.uniq
elsif input.first.respond_to?(:[])
input.uniq{ |a| a[property] }
ary.uniq
elsif ary.first.respond_to?(:[])
ary.uniq{ |a| a[property] }
end
end
@@ -165,24 +173,38 @@ module Liquid
end
end
# Remove nils within an array
# provide optional property with which to check for nil
def compact(input, property = nil)
ary = InputIterator.new(input)
if property.nil?
ary.compact
elsif ary.first.respond_to?(:[])
ary.reject{ |a| a[property].nil? }
elsif ary.first.respond_to?(property)
ary.reject { |a| a.send(property).nil? }
end
end
# Replace occurrences of a string with another
def replace(input, string, replacement = ''.freeze)
input.to_s.gsub(string, replacement.to_s)
input.to_s.gsub(string.to_s, replacement.to_s)
end
# Replace the first occurrences of a string with another
def replace_first(input, string, replacement = ''.freeze)
input.to_s.sub(string, replacement.to_s)
input.to_s.sub(string.to_s, replacement.to_s)
end
# remove a substring
def remove(input, string)
input.to_s.gsub(string, ''.freeze)
input.to_s.gsub(string.to_s, ''.freeze)
end
# remove the first occurrences of a substring
def remove_first(input, string)
input.to_s.sub(string, ''.freeze)
input.to_s.sub(string.to_s, ''.freeze)
end
# add one string to another
@@ -279,10 +301,14 @@ module Liquid
# division
def divided_by(input, operand)
apply_operation(input, operand, :/)
rescue ::ZeroDivisionError => e
raise Liquid::ZeroDivisionError, e.message
end
def modulo(input, operand)
apply_operation(input, operand, :%)
rescue ::ZeroDivisionError => e
raise Liquid::ZeroDivisionError, e.message
end
def round(input, n = 0)
@@ -290,14 +316,20 @@ module Liquid
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
def ceil(input)
to_number(input).ceil.to_i
rescue ::FloatDomainError => e
raise Liquid::FloatDomainError, e.message
end
def floor(input)
to_number(input).floor.to_i
rescue ::FloatDomainError => e
raise Liquid::FloatDomainError, e.message
end
def default(input, default_value = "".freeze)
@@ -307,6 +339,16 @@ module Liquid
private
def to_integer(num)
return num if num.is_a?(Integer)
num = num.to_s
begin
Integer(num)
rescue ::ArgumentError
raise Liquid::ArgumentError, "invalid integer"
end
end
def to_number(obj)
case obj
when Float
@@ -372,6 +414,14 @@ module Liquid
reverse_each.to_a
end
def uniq(&block)
to_a.uniq(&block)
end
def compact
to_a.compact
end
def each
@input.each do |e|
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)

View File

@@ -16,7 +16,7 @@ module Liquid
def parse(tokens)
body = BlockBody.new
while more = parse_body(body, tokens)
while parse_body(body, tokens)
body = @blocks.last.attachment
end
end

View File

@@ -76,9 +76,6 @@ module Liquid
collection = context.evaluate(@collection_name)
collection = collection.to_a if collection.is_a?(Range)
# Maintains Ruby 1.8.7 String#each behaviour on 1.9
return render_else(context) unless iterable?(collection)
from = if @from == :continue
for_offsets[@name].to_i
else
@@ -122,7 +119,7 @@ module Liquid
result << @for_block.render(context)
# Handle any interrupts if they exist.
if context.has_interrupt?
if context.interrupt?
interrupt = context.pop_interrupt
break if interrupt.is_a? BreakInterrupt
next if interrupt.is_a? ContinueInterrupt
@@ -141,7 +138,7 @@ module Liquid
if markup =~ Syntax
@variable_name = $1
collection_name = $2
@reversed = $3
@reversed = !!$3
@name = "#{@variable_name}-#{collection_name}"
@collection_name = Expression.parse(collection_name)
markup.scan(TagAttributes) do |key, value|
@@ -189,10 +186,6 @@ module Liquid
def render_else(context)
@else_block ? @else_block.render(context) : ''.freeze
end
def iterable?(collection)
collection.respond_to?(:each) || Utils.non_blank_string?(collection)
end
end
Template.register_tag('for'.freeze, For)

View File

@@ -83,16 +83,16 @@ module Liquid
def strict_parse(markup)
p = Parser.new(markup)
condition = parse_comparison(p)
while op = (p.id?('and'.freeze) || p.id?('or'.freeze))
new_cond = parse_comparison(p)
new_cond.send(op, condition)
condition = new_cond
end
condition = parse_binary_comparison(p)
p.consume(:end_of_string)
condition
end
def parse_binary_comparison(p)
condition = parse_comparison(p)
if op = (p.id?('and'.freeze) || p.id?('or'.freeze))
condition.send(op, parse_binary_comparison(p))
end
condition
end

View File

@@ -1,7 +1,16 @@
module Liquid
class Raw < Block
Syntax = /\A\s*\z/
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
def initialize(tag_name, markup, options)
super
unless markup =~ Syntax
raise SyntaxError.new(@options[:locale].t("errors.syntax.tag_unexpected_args".freeze, tag: tag_name))
end
end
def parse(tokens)
@body = ''
while token = tokens.shift
@@ -11,6 +20,8 @@ module Liquid
end
@body << token unless token.empty?
end
raise SyntaxError.new(@options[:locale].t("errors.syntax.tag_never_closed".freeze, block_name: block_name))
end
def render(_context)

View File

@@ -0,0 +1,24 @@
module Liquid
# The spaceless tag strips whitespace between HTML tags using a simple regex.
#
# == Usage:
# {% spaceless %}
# <h1>
# Hello
# </h1>
# {% endspaceless %}
#
class Spaceless < Block
HTML_STRIP_SPACE_REGEXP = />\s+</
def render(context)
self.class.strip_html_whitespace(super)
end
def self.strip_html_whitespace(input)
input.gsub(HTML_STRIP_SPACE_REGEXP, '><')
end
end
Template.register_tag('spaceless'.freeze, Spaceless)
end

View File

@@ -13,11 +13,7 @@ module Liquid
# template.render('user_name' => 'bob')
#
class Template
DEFAULT_OPTIONS = {
locale: I18n.new
}
attr_accessor :root, :render_errors
attr_accessor :root
attr_reader :resource_limits
@@file_system = BlankFileSystem.new
@@ -29,7 +25,7 @@ module Liquid
end
def [](tag_name)
return nil unless @tags.has_key?(tag_name)
return nil unless @tags.key?(tag_name)
return @cache[tag_name] if Liquid.cache_classes
lookup_class(@tags[tag_name]).tap { |o| @cache[tag_name] = o }
@@ -112,7 +108,6 @@ module Liquid
def initialize
@resource_limits = ResourceLimits.new(self.class.default_resource_limits)
@render_errors = true
end
# Parse source code.
@@ -121,7 +116,7 @@ module Liquid
@options = options
@profiling = options[:profile]
@line_numbers = options[:line_numbers] || @profiling
@root = Document.parse(tokenize(source), DEFAULT_OPTIONS.merge(options))
@root = Document.parse(tokenize(source), options)
@warnings = nil
self
end
@@ -164,24 +159,22 @@ module Liquid
def render(*args)
return ''.freeze if @root.nil?
render_errors = self.render_errors
context = case args.first
when Liquid::Context
c = args.shift
if @rethrow_errors
c.exception_handler = ->(e) { true }
c.exception_handler = ->(e) { raise }
end
c
when Liquid::Drop
drop = args.shift
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits, render_errors)
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
when Hash
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits, render_errors)
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
when nil
Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits, render_errors)
Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits)
else
raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
end
@@ -231,28 +224,8 @@ module Liquid
private
# Uses the <tt>Liquid::TemplateParser</tt> regexp to tokenize the passed source
def tokenize(source)
source = source.source if source.respond_to?(:source)
return [] if source.to_s.empty?
tokens = calculate_line_numbers(source.split(TemplateParser))
# removes the rogue empty element at the beginning of the array
tokens.shift if tokens[0] && tokens[0].empty?
tokens
end
def calculate_line_numbers(raw_tokens)
return raw_tokens unless @line_numbers
current_line = 1
raw_tokens.map do |token|
Token.new(token, current_line).tap do
current_line += token.count("\n")
end
end
Tokenizer.new(source, @line_numbers)
end
def with_profiling

37
lib/liquid/tokenizer.rb Normal file
View File

@@ -0,0 +1,37 @@
module Liquid
class Tokenizer
def initialize(source, line_numbers = false)
@source = source
@line_numbers = line_numbers
@tokens = tokenize
end
def shift
@tokens.shift
end
private
def tokenize
@source = @source.source if @source.respond_to?(:source)
return [] if @source.to_s.empty?
tokens = @source.split(TemplateParser)
tokens = @line_numbers ? calculate_line_numbers(tokens) : tokens
# removes the rogue empty element at the beginning of the array
tokens.shift if tokens[0] && tokens[0].empty?
tokens
end
def calculate_line_numbers(tokens)
current_line = 1
tokens.map do |token|
Token.new(token, current_line).tap do
current_line += token.count("\n")
end
end
end
end
end

View File

@@ -8,16 +8,15 @@ module Liquid
end
end
def self.non_blank_string?(collection)
collection.is_a?(String) && collection != ''.freeze
end
def self.slice_collection_using_each(collection, from, to)
segments = []
index = 0
# Maintains Ruby 1.8.7 String#each behaviour on 1.9
return [collection] if non_blank_string?(collection)
if collection.is_a?(String)
return collection.empty? ? [] : [collection]
end
return [] unless collection.respond_to?(:each)
collection.each do |item|
if to && to <= index

View File

@@ -41,11 +41,10 @@ module Liquid
if filter_markup =~ /#{FilterSeparator}\s*(.*)/om
filters = $1.scan(FilterParser)
filters.each do |f|
if f =~ /\w+/
filtername = Regexp.last_match(0)
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
@filters << parse_filter_expressions(filtername, filterargs)
end
next unless f =~ /\w+/
filtername = Regexp.last_match(0)
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
@filters << parse_filter_expressions(filtername, filterargs)
end
end
end
@@ -74,7 +73,7 @@ module Liquid
def render(context)
@filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)|
filter_args = evaluate_filter_expressions(context, filter_args, filter_kwargs)
output = context.invoke(filter_name, output, *filter_args)
context.invoke(filter_name, output, *filter_args)
end.tap{ |obj| taint_check(obj) }
end

View File

@@ -41,7 +41,7 @@ module Liquid
# If object is a hash- or array-like object we look for the
# presence of the key and if its available we return it
if object.respond_to?(:[]) &&
((object.respond_to?(:has_key?) && object.has_key?(key)) ||
((object.respond_to?(:key?) && object.key?(key)) ||
(object.respond_to?(:fetch) && key.is_a?(Integer)))
# if its a proc we will replace the entry with the proc

View File

@@ -1,4 +1,4 @@
require 'stackprof' rescue fail("install stackprof extension/gem")
require 'stackprof'
require_relative 'theme_runner'
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first

View File

@@ -18,14 +18,14 @@ class ContextTest < Minitest::Test
with_global_filter(global) do
assert_equal 'Global test', Template.parse("{{'test' | notice }}").render!
assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, :filters => [local])
assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, filters: [local])
end
end
def test_has_key_will_not_add_an_error_for_missing_keys
with_error_mode :strict do
context = Context.new
context.has_key?('unknown')
context.key?('unknown')
assert_empty context.errors
end
end

View File

@@ -19,7 +19,6 @@ class ContextDrop < Liquid::Drop
end
class ProductDrop < Liquid::Drop
class TextDrop < Liquid::Drop
def array
['text1', 'text2']

View File

@@ -75,7 +75,7 @@ class ErrorHandlingTest < Minitest::Test
end
def test_lax_unrecognized_operator
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :lax)
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :lax)
assert_equal ' Liquid error: Unknown operator =! ', template.render
assert_equal 1, template.errors.size
assert_equal Liquid::ArgumentError, template.errors.first.class
@@ -83,15 +83,15 @@ class ErrorHandlingTest < Minitest::Test
def test_with_line_numbers_adds_numbers_to_parser_errors
err = assert_raises(SyntaxError) do
template = Liquid::Template.parse(%q(
Liquid::Template.parse(%q(
foobar
{% "cat" | foobar %}
bla
),
:line_numbers => true
)
),
line_numbers: true
)
end
assert_match /Liquid syntax error \(line 4\)/, err.message
@@ -105,8 +105,8 @@ class ErrorHandlingTest < Minitest::Test
bla
',
:error_mode => :warn,
:line_numbers => true
error_mode: :warn,
line_numbers: true
)
assert_equal ['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'],
@@ -122,8 +122,8 @@ class ErrorHandlingTest < Minitest::Test
bla
',
:error_mode => :strict,
:line_numbers => true
error_mode: :strict,
line_numbers: true
)
end
@@ -141,7 +141,7 @@ class ErrorHandlingTest < Minitest::Test
bla
',
:line_numbers => true
line_numbers: true
)
end
@@ -150,18 +150,18 @@ class ErrorHandlingTest < Minitest::Test
def test_strict_error_messages
err = assert_raises(SyntaxError) do
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :strict)
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :strict)
end
assert_equal 'Liquid syntax error: Unexpected character = in "1 =! 2"', err.message
err = assert_raises(SyntaxError) do
Liquid::Template.parse('{{%%%}}', :error_mode => :strict)
Liquid::Template.parse('{{%%%}}', error_mode: :strict)
end
assert_equal 'Liquid syntax error: Unexpected character % in "{{%%%}}"', err.message
end
def test_warnings
template = Liquid::Template.parse('{% if ~~~ %}{{%%%}}{% else %}{{ hello. }}{% endif %}', :error_mode => :warn)
template = Liquid::Template.parse('{% if ~~~ %}{{%%%}}{% else %}{{ hello. }}{% endif %}', error_mode: :warn)
assert_equal 3, template.warnings.size
assert_equal 'Unexpected character ~ in "~~~"', template.warnings[0].to_s(false)
assert_equal 'Unexpected character % in "{{%%%}}"', template.warnings[1].to_s(false)
@@ -170,7 +170,7 @@ class ErrorHandlingTest < Minitest::Test
end
def test_warning_line_numbers
template = Liquid::Template.parse("{% if ~~~ %}\n{{%%%}}{% else %}\n{{ hello. }}{% endif %}", :error_mode => :warn, :line_numbers => true)
template = Liquid::Template.parse("{% if ~~~ %}\n{{%%%}}{% else %}\n{{ hello. }}{% endif %}", error_mode: :warn, line_numbers: true)
assert_equal 'Liquid syntax error (line 1): Unexpected character ~ in "~~~"', template.warnings[0].message
assert_equal 'Liquid syntax error (line 2): Unexpected character % in "{{%%%}}"', template.warnings[1].message
assert_equal 'Liquid syntax error (line 3): Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].message
@@ -185,4 +185,42 @@ class ErrorHandlingTest < Minitest::Test
template.render('errors' => ErrorDrop.new)
end
end
def test_exception_handler_with_string_result
template = Liquid::Template.parse('This is an argument error: {{ errors.argument_error }}')
output = template.render({ 'errors' => ErrorDrop.new }, exception_handler: ->(e) { '' })
assert_equal 'This is an argument error: ', output
assert_equal [ArgumentError], template.errors.map(&:class)
end
class InternalError < Liquid::Error
end
def test_exception_handler_with_exception_result
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
handler = ->(e) { e.is_a?(Liquid::Error) ? e : InternalError.new('internal') }
output = template.render({ 'errors' => ErrorDrop.new }, exception_handler: handler)
assert_equal 'This is a runtime error: Liquid error (line 1): internal', output
assert_equal [InternalError], template.errors.map(&:class)
end
class TestFileSystem
def read_template_file(template_path)
"{{ errors.argument_error }}"
end
end
def test_included_template_name_with_line_numbers
old_file_system = Liquid::Template.file_system
begin
Liquid::Template.file_system = TestFileSystem.new
template = Liquid::Template.parse("Argument error:\n{% include 'product' %}", line_numbers: true)
page = template.render('errors' => ErrorDrop.new)
ensure
Liquid::Template.file_system = old_file_system
end
assert_equal "Argument error:\nLiquid error (product line 1): argument error", page
assert_equal "product", template.errors.first.template_name
end
end

View File

@@ -104,6 +104,27 @@ class FiltersTest < Minitest::Test
assert_equal sorted[2].a, 'C'
end
def test_compact
@context['words'] = ['a', nil, 'b', nil, 'c']
@context['hashes'] = [{ 'a' => 'A' }, { 'a' => nil }, { 'a' => 'C' }]
@context['objects'] = [TestObject.new('A'), TestObject.new(nil), TestObject.new('C')]
# Test strings
assert_equal ['a', 'b', 'c'], Variable.new("words | compact").render(@context)
# Test hashes
sorted = Variable.new("hashes | compact: 'a'").render(@context)
assert_equal sorted[0]['a'], 'A'
assert_equal sorted[1]['a'], 'C'
assert_nil sorted[2]
# Test objects
sorted = Variable.new("objects | compact: 'a'").render(@context)
assert_equal sorted[0].a, 'A'
assert_equal sorted[1].a, 'C'
assert_nil sorted[2]
end
def test_strip_html
@context['var'] = "<b>bla blub</a>"
@@ -136,7 +157,7 @@ class FiltersTest < Minitest::Test
end
def test_override_object_method_in_filter
assert_equal "tap overridden", Template.parse("{{var | tap}}").render!({ 'var' => 1000 }, :filters => [OverrideObjectMethodFilter])
assert_equal "tap overridden", Template.parse("{{var | tap}}").render!({ 'var' => 1000 }, filters: [OverrideObjectMethodFilter])
# tap still treated as a non-existent filter
assert_equal "1000", Template.parse("{{var | tap}}").render!({ 'var' => 1000 })
@@ -149,8 +170,8 @@ class FiltersInTemplate < Minitest::Test
def test_local_global
with_global_filter(MoneyFilter) do
assert_equal " 1000$ ", Template.parse("{{1000 | money}}").render!(nil, nil)
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, :filters => CanadianMoneyFilter)
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, :filters => [CanadianMoneyFilter])
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, filters: CanadianMoneyFilter)
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, filters: [CanadianMoneyFilter])
end
end

View File

@@ -54,62 +54,62 @@ class OutputTest < Minitest::Test
text = %( {{ car.gm | make_funny }} )
expected = %( LOL )
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end
def test_variable_piping_with_input
text = %( {{ car.gm | cite_funny }} )
expected = %( LOL: bad )
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end
def test_variable_piping_with_args
text = %! {{ car.gm | add_smiley : ':-(' }} !
expected = %| bad :-( |
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end
def test_variable_piping_with_no_args
text = %( {{ car.gm | add_smiley }} )
expected = %| bad :-) |
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end
def test_multiple_variable_piping_with_args
text = %! {{ car.gm | add_smiley : ':-(' | add_smiley : ':-('}} !
expected = %| bad :-( :-( |
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end
def test_variable_piping_with_multiple_args
text = %( {{ car.gm | add_tag : 'span', 'bar'}} )
expected = %( <span id="bar">bad</span> )
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end
def test_variable_piping_with_variable_args
text = %( {{ car.gm | add_tag : 'span', car.bmw}} )
expected = %( <span id="good">bad</span> )
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end
def test_multiple_pipings
text = %( {{ best_cars | cite_funny | paragraph }} )
expected = %( <p>LOL: bmw</p> )
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end
def test_link_to
text = %( {{ 'Typo' | link_to: 'http://typo.leetsoft.com' }} )
expected = %( <a href="http://typo.leetsoft.com">Typo</a> )
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end
end # OutputTest

View File

@@ -62,9 +62,9 @@ class ParsingQuirksTest < Minitest::Test
end
def test_no_error_on_lax_empty_filter
assert Template.parse("{{test |a|b|}}", :error_mode => :lax)
assert Template.parse("{{test}}", :error_mode => :lax)
assert Template.parse("{{|test|}}", :error_mode => :lax)
assert Template.parse("{{test |a|b|}}", error_mode: :lax)
assert Template.parse("{{test}}", error_mode: :lax)
assert Template.parse("{{|test|}}", error_mode: :lax)
end
def test_meaningless_parens_lax
@@ -115,5 +115,4 @@ class ParsingQuirksTest < Minitest::Test
assert_template_result('12345', "{% for i in (1...5) %}{{ i }}{% endfor %}")
end
end
end # ParsingQuirksTest

View File

@@ -21,7 +21,7 @@ class RenderProfilingTest < Minitest::Test
end
def test_parse_makes_available_simple_profiling
t = Template.parse("{{ 'a string' | upcase }}", :profile => true)
t = Template.parse("{{ 'a string' | upcase }}", profile: true)
t.render!
assert_equal 1, t.profiler.length
@@ -31,14 +31,14 @@ class RenderProfilingTest < Minitest::Test
end
def test_render_ignores_raw_strings_when_profiling
t = Template.parse("This is raw string\nstuff\nNewline", :profile => true)
t = Template.parse("This is raw string\nstuff\nNewline", profile: true)
t.render!
assert_equal 0, t.profiler.length
end
def test_profiling_includes_line_numbers_of_liquid_nodes
t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", :profile => true)
t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", profile: true)
t.render!
assert_equal 2, t.profiler.length
@@ -49,7 +49,7 @@ class RenderProfilingTest < Minitest::Test
end
def test_profiling_includes_line_numbers_of_included_partials
t = Template.parse("{% include 'a_template' %}", :profile => true)
t = Template.parse("{% include 'a_template' %}", profile: true)
t.render!
included_children = t.profiler[0].children
@@ -61,7 +61,7 @@ class RenderProfilingTest < Minitest::Test
end
def test_profiling_times_the_rendering_of_tokens
t = Template.parse("{% include 'a_template' %}", :profile => true)
t = Template.parse("{% include 'a_template' %}", profile: true)
t.render!
node = t.profiler[0]
@@ -69,14 +69,14 @@ class RenderProfilingTest < Minitest::Test
end
def test_profiling_times_the_entire_render
t = Template.parse("{% include 'a_template' %}", :profile => true)
t = Template.parse("{% include 'a_template' %}", profile: true)
t.render!
assert t.profiler.total_render_time >= 0, "Total render time was not calculated"
end
def test_profiling_uses_include_to_mark_children
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", :profile => true)
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", profile: true)
t.render!
include_node = t.profiler[1]
@@ -84,7 +84,7 @@ class RenderProfilingTest < Minitest::Test
end
def test_profiling_marks_children_with_the_name_of_included_partial
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", :profile => true)
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", profile: true)
t.render!
include_node = t.profiler[1]
@@ -94,7 +94,7 @@ class RenderProfilingTest < Minitest::Test
end
def test_profiling_supports_multiple_templates
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}\n{% include 'b_template' %}", :profile => true)
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}\n{% include 'b_template' %}", profile: true)
t.render!
a_template = t.profiler[1]
@@ -109,7 +109,7 @@ class RenderProfilingTest < Minitest::Test
end
def test_profiling_supports_rendering_the_same_partial_multiple_times
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}\n{% include 'a_template' %}", :profile => true)
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}\n{% include 'a_template' %}", profile: true)
t.render!
a_template1 = t.profiler[1]
@@ -124,7 +124,7 @@ class RenderProfilingTest < Minitest::Test
end
def test_can_iterate_over_each_profiling_entry
t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", :profile => true)
t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", profile: true)
t.render!
timing_count = 0
@@ -136,7 +136,7 @@ class RenderProfilingTest < Minitest::Test
end
def test_profiling_marks_children_of_if_blocks
t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", :profile => true)
t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", profile: true)
t.render!
assert_equal 1, t.profiler.length
@@ -144,7 +144,7 @@ class RenderProfilingTest < Minitest::Test
end
def test_profiling_marks_children_of_for_blocks
t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", :profile => true)
t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", profile: true)
t.render!({ "collection" => ["one", "two"] })
assert_equal 1, t.profiler.length

View File

@@ -34,7 +34,7 @@ class SecurityTest < Minitest::Test
text = %( {{ '1+1' | add_one | instance_eval }} )
expected = %( 1+1 + 1 )
assert_equal expected, Template.parse(text).render!(@assigns, :filters => SecurityFilter)
assert_equal expected, Template.parse(text).render!(@assigns, filters: SecurityFilter)
end
def test_does_not_add_filters_to_symbol_table

View File

@@ -76,6 +76,13 @@ class StandardFiltersTest < Minitest::Test
assert_equal '', @filters.slice(nil, 0)
assert_equal '', @filters.slice('foobar', 100, 10)
assert_equal '', @filters.slice('foobar', -100, 10)
assert_equal 'oob', @filters.slice('foobar', '1', '3')
assert_raises(Liquid::ArgumentError) do
@filters.slice('foobar', nil)
end
assert_raises(Liquid::ArgumentError) do
@filters.slice('foobar', 0, "")
end
end
def test_slice_on_arrays
@@ -163,6 +170,7 @@ class StandardFiltersTest < Minitest::Test
end
def test_uniq
assert_equal ["foo"], @filters.uniq("foo")
assert_equal [1, 3, 2, 4], @filters.uniq([1, 1, 3, 2, 3, 1, 4, 3, 2, 1])
assert_equal [{ "a" => 1 }, { "a" => 3 }, { "a" => 2 }], @filters.uniq([{ "a" => 1 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a")
testdrop = TestDrop.new
@@ -268,13 +276,17 @@ class StandardFiltersTest < Minitest::Test
def test_replace
assert_equal '2 2 2 2', @filters.replace('1 1 1 1', '1', 2)
assert_equal '2 2 2 2', @filters.replace('1 1 1 1', 1, 2)
assert_equal '2 1 1 1', @filters.replace_first('1 1 1 1', '1', 2)
assert_equal '2 1 1 1', @filters.replace_first('1 1 1 1', 1, 2)
assert_template_result '2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}"
end
def test_remove
assert_equal ' ', @filters.remove("a a a a", 'a')
assert_equal ' ', @filters.remove("1 1 1 1", 1)
assert_equal 'a a a', @filters.remove_first("a a a a", 'a ')
assert_equal ' 1 1 1', @filters.remove_first("1 1 1 1", 1)
assert_template_result 'a a a', "{{ 'a a a a' | remove_first: 'a ' }}"
end
@@ -333,26 +345,41 @@ class StandardFiltersTest < Minitest::Test
assert_equal "Liquid error: divided by 0", Template.parse("{{ 5 | divided_by:0 }}").render
assert_template_result "0.5", "{{ 2.0 | divided_by:4 }}"
assert_raises(Liquid::ZeroDivisionError) do
assert_template_result "4", "{{ 1 | modulo: 0 }}"
end
end
def test_modulo
assert_template_result "1", "{{ 3 | modulo:2 }}"
assert_raises(Liquid::ZeroDivisionError) do
assert_template_result "4", "{{ 1 | modulo: 0 }}"
end
end
def test_round
assert_template_result "5", "{{ input | round }}", 'input' => 4.6
assert_template_result "4", "{{ '4.3' | round }}"
assert_template_result "4.56", "{{ input | round: 2 }}", 'input' => 4.5612
assert_raises(Liquid::FloatDomainError) do
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | round }}"
end
end
def test_ceil
assert_template_result "5", "{{ input | ceil }}", 'input' => 4.6
assert_template_result "5", "{{ '4.3' | ceil }}"
assert_raises(Liquid::FloatDomainError) do
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | ceil }}"
end
end
def test_floor
assert_template_result "4", "{{ input | floor }}", 'input' => 4.6
assert_template_result "4", "{{ '4.3' | floor }}"
assert_raises(Liquid::FloatDomainError) do
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | floor }}"
end
end
def test_append
@@ -390,4 +417,9 @@ class StandardFiltersTest < Minitest::Test
def test_cannot_access_private_methods
assert_template_result('a', "{{ 'a' | to_number }}")
end
def test_strip_html_whitespace
html = "<a>\n\n <b> \n <c>a</c> \r\n </b> </a>"
assert_template_result('<a><b><c>a</c></b></a>', "{{ a | strip_html_whitespace }}", { 'a' => html })
end
end # StandardFiltersTest

View File

@@ -12,5 +12,4 @@ class BreakTagTest < Minitest::Test
assert_template_result(expected, markup, assigns)
end
end

View File

@@ -12,5 +12,4 @@ class ContinueTagTest < Minitest::Test
assert_template_result(expected, markup, assigns)
end
end

View File

@@ -164,4 +164,25 @@ class IfElseTagTest < Minitest::Test
assert_template_result('', %({% if 1 or throw or or 1 %}yes{% endif %}))
end
end
def test_multiple_conditions
tpl = "{% if a or b and c %}true{% else %}false{% endif %}"
tests = {
[true, true, true] => true,
[true, true, false] => true,
[true, false, true] => true,
[true, false, false] => true,
[false, true, true] => true,
[false, true, false] => false,
[false, false, true] => false,
[false, false, false] => false,
}
tests.each do |vals, expected|
a, b, c = vals
assigns = { 'a' => a, 'b' => b, 'c' => c }
assert_template_result expected.to_s, tpl, assigns, assigns.to_s
end
end
end

View File

@@ -77,7 +77,7 @@ class IncludeTagTest < Minitest::Test
def test_include_tag_looks_for_file_system_in_registers_first
assert_equal 'from OtherFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, :registers => { :file_system => OtherFileSystem.new })
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: OtherFileSystem.new })
end
def test_include_tag_with
@@ -153,18 +153,18 @@ class IncludeTagTest < Minitest::Test
def test_include_tag_caches_second_read_of_same_partial
file_system = CountingFileSystem.new
assert_equal 'from CountingFileSystemfrom CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render!({}, :registers => { :file_system => file_system })
Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system })
assert_equal 1, file_system.count
end
def test_include_tag_doesnt_cache_partials_across_renders
file_system = CountingFileSystem.new
assert_equal 'from CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, :registers => { :file_system => file_system })
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system })
assert_equal 1, file_system.count
assert_equal 'from CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, :registers => { :file_system => file_system })
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system })
assert_equal 2, file_system.count
end

View File

@@ -20,5 +20,4 @@ class IncrementTagTest < Minitest::Test
'{%increment port %} {%decrement port%} ' \
'{%decrement starboard %}', { 'port' => 1, 'starboard' => 5 })
end
end

View File

@@ -22,4 +22,10 @@ class RawTagTest < Minitest::Test
assert_template_result ' test {% raw %} {% endraw %}', '{% raw %} test {% raw %} {% {% endraw %}endraw %}'
assert_template_result ' Foobar {{ invalid 1', '{% raw %} Foobar {{ invalid {% endraw %}{{ 1 }}'
end
def test_invalid_raw
assert_match_syntax_error /tag was never closed/, '{% raw %} foo'
assert_match_syntax_error /Valid syntax/, '{% raw } foo {% endraw %}'
assert_match_syntax_error /Valid syntax/, '{% raw } foo %}{% endraw %}'
end
end

View File

@@ -271,7 +271,7 @@ class StandardTagTest < Minitest::Test
end
def test_size_of_hash
assigns = { "hash" => { :a => 1, :b => 2, :c => 3, :d => 4 } }
assigns = { "hash" => { a: 1, b: 2, c: 3, d: 4 } }
assert_template_result('hash has 4 elements', "hash has {{ hash.size }} elements", assigns)
end
@@ -293,4 +293,9 @@ class StandardTagTest < Minitest::Test
def test_multiline_tag
assert_template_result '0 1 2 3', "0{%\nfor i in (1..3)\n%} {{\ni\n}}{%\nendfor\n%}"
end
def test_spaceless
html = "<a>\n\n <b> \n <c>a</c> \r\n </b> </a>"
assert_template_result '<a><b><c>a</c></b></a>', "{% spaceless %}#{html}{% endspaceless %}"
end
end # StandardTagTest

View File

@@ -57,4 +57,8 @@ class TableRowTest < Minitest::Test
'{% tablerow n in numbers cols:3 offset:1 limit:6%} {{n}} {% endtablerow %}',
'numbers' => [0, 1, 2, 3, 4, 5, 6, 7])
end
def test_blank_string_not_iterable
assert_template_result("<tr class=\"row1\">\n</tr>\n", "{% tablerow char in characters cols:3 %}I WILL NOT BE OUTPUT{% endtablerow %}", 'characters' => '')
end
end

View File

@@ -201,14 +201,14 @@ class TemplateTest < Minitest::Test
def test_exception_handler_doesnt_reraise_if_it_returns_false
exception = nil
Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_handler: ->(e) { exception = e; false })
assert exception.is_a?(ZeroDivisionError)
assert exception.is_a?(Liquid::ZeroDivisionError)
end
def test_exception_handler_does_reraise_if_it_returns_true
exception = nil
assert_raises(ZeroDivisionError) do
assert_raises(Liquid::ZeroDivisionError) do
Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_handler: ->(e) { exception = e; true })
end
assert exception.is_a?(ZeroDivisionError)
assert exception.is_a?(Liquid::ZeroDivisionError)
end
end

View File

@@ -24,6 +24,16 @@ class VariableTest < Minitest::Test
assert_equal '', template.render!
end
def test_using_blank_as_variable_name
template = Template.parse("{% assign foo = blank %}{{ foo }}")
assert_equal '', template.render!
end
def test_using_empty_as_variable_name
template = Template.parse("{% assign foo = empty %}{{ foo }}")
assert_equal '', template.render!
end
def test_hash_scoping
template = Template.parse(%({{ test.test }}))
assert_equal 'worked', template.render!('test' => { 'test' => 'worked' })
@@ -70,9 +80,9 @@ class VariableTest < Minitest::Test
assigns['test'] = 'Tobi'
assert_equal 'Hello Tobi', template.render!(assigns)
assigns.delete('test')
e = assert_raises(RuntimeError) {
e = assert_raises(RuntimeError) do
template.render!(assigns)
}
end
assert_equal "Unknown variable 'test'", e.message
end

View File

@@ -4,7 +4,7 @@ ENV["MT_NO_EXPECTATIONS"] = "1"
require 'minitest/autorun'
require 'spy/integration'
$:.unshift(File.join(File.expand_path(__dir__), '..', 'lib'))
$LOAD_PATH.unshift(File.join(File.expand_path(__dir__), '..', 'lib'))
require 'liquid.rb'
require 'liquid/profiler'
@@ -15,6 +15,11 @@ if env_mode = ENV['LIQUID_PARSER_MODE']
end
Liquid::Template.error_mode = mode
if ENV['LIQUID-C'] == '1'
puts "-- LIQUID C"
require 'liquid/c'
end
if Minitest.const_defined?('Test')
# We're on Minitest 5+. Nothing to do here.
else
@@ -33,19 +38,19 @@ module Minitest
include Liquid
def assert_template_result(expected, template, assigns = {}, message = nil)
assert_equal expected, Template.parse(template).render!(assigns)
assert_equal expected, Template.parse(template).render!(assigns), message
end
def assert_template_result_matches(expected, template, assigns = {}, message = nil)
return assert_template_result(expected, template, assigns, message) unless expected.is_a? Regexp
assert_match expected, Template.parse(template).render!(assigns)
assert_match expected, Template.parse(template).render!(assigns), message
end
def assert_match_syntax_error(match, template, registers = {})
exception = assert_raises(Liquid::SyntaxError) {
def assert_match_syntax_error(match, template, assigns = {})
exception = assert_raises(Liquid::SyntaxError) do
Template.parse(template).render(assigns)
}
end
assert_match match, exception.message
end
@@ -102,8 +107,11 @@ class ErrorDrop < Liquid::Drop
raise Liquid::SyntaxError, 'syntax error'
end
def runtime_error
raise 'runtime error'
end
def exception
raise Exception, 'exception'
end
end

View File

@@ -85,6 +85,11 @@ class ConditionUnitTest < Minitest::Test
assert_evalutes_false 1, 'contains', 0
end
def test_contains_with_string_left_operand_coerces_right_operand_to_string
assert_evalutes_true ' 1 ', 'contains', 1
assert_evalutes_false ' 1 ', 'contains', 2
end
def test_or_condition
condition = Condition.new(1, '==', 2)
@@ -146,5 +151,4 @@ class ConditionUnitTest < Minitest::Test
Condition.new(left, op, right).evaluate(@context || Liquid::Context.new)
end
end
end # ConditionTest

View File

@@ -453,19 +453,17 @@ class ContextUnitTest < Minitest::Test
def test_use_empty_instead_of_any_in_interrupt_handling_to_avoid_lots_of_unnecessary_object_allocations
mock_any = Spy.on_instance_method(Array, :any?)
mock_empty = Spy.on_instance_method(Array, :empty?)
mock_has_interrupt = Spy.on(@context, :has_interrupt?).and_call_through
@context.has_interrupt?
@context.interrupt?
refute mock_any.has_been_called?
assert mock_empty.has_been_called?
end
def test_context_initialization_with_a_proc_in_environment
contx = Context.new([:test => ->(c) { c['poutine'] }], { :test => :foo })
contx = Context.new([test: ->(c) { c['poutine'] }], { test: :foo })
assert contx
assert_nil contx['poutine']
end
end # ContextTest

View File

@@ -16,7 +16,7 @@ class I18nUnitTest < Minitest::Test
end
def test_single_string_interpolation
assert_equal "something different", @i18n.translate("whatever", :something => "different")
assert_equal "something different", @i18n.translate("whatever", something: "different")
end
# def test_raises_translation_error_on_undefined_interpolation_key

View File

@@ -65,5 +65,4 @@ class StrainerUnitTest < Minitest::Test
assert_kind_of b, strainer
assert_kind_of Liquid::StandardFilters, strainer
end
end # StrainerTest

View File

@@ -11,7 +11,7 @@ class TemplateUnitTest < Minitest::Test
def test_sets_default_localization_in_context_with_quick_initialization
t = Template.new
t.parse('{%comment%}{%endcomment%}', :locale => I18n.new(fixture("en_locale.yml")))
t.parse('{%comment%}{%endcomment%}', locale: I18n.new(fixture("en_locale.yml")))
locale = t.root.nodelist[0].options[:locale]
assert_instance_of I18n, locale

View File

@@ -22,17 +22,20 @@ class TokenizerTest < Minitest::Test
end
def test_calculate_line_numbers_per_token_with_profiling
template = Liquid::Template.parse("", :profile => true)
assert_equal [1], template.send(:tokenize, "{{funk}}").map(&:line_number)
assert_equal [1, 1, 1], template.send(:tokenize, " {{funk}} ").map(&:line_number)
assert_equal [1, 2, 2], template.send(:tokenize, "\n{{funk}}\n").map(&:line_number)
assert_equal [1, 1, 3], template.send(:tokenize, " {{\n funk \n}} ").map(&:line_number)
assert_equal [1], tokenize("{{funk}}", true).map(&:line_number)
assert_equal [1, 1, 1], tokenize(" {{funk}} ", true).map(&:line_number)
assert_equal [1, 2, 2], tokenize("\n{{funk}}\n", true).map(&:line_number)
assert_equal [1, 1, 3], tokenize(" {{\n funk \n}} ", true).map(&:line_number)
end
private
def tokenize(source)
Liquid::Template.new.send(:tokenize, source)
def tokenize(source, line_numbers = false)
tokenizer = Liquid::Tokenizer.new(source, line_numbers)
tokens = []
while t = tokenizer.shift
tokens << t
end
tokens
end
end

View File

@@ -71,7 +71,7 @@ class VariableUnitTest < Minitest::Test
end
def test_symbol
var = Variable.new("http://disney.com/logo.gif | image: 'med' ", :error_mode => :lax)
var = Variable.new("http://disney.com/logo.gif | image: 'med' ", error_mode: :lax)
assert_equal VariableLookup.new('http://disney.com/logo.gif'), var.name
assert_equal [['image', ['med']]], var.filters
end
@@ -130,7 +130,7 @@ class VariableUnitTest < Minitest::Test
end
def test_lax_filter_argument_parsing
var = Variable.new(%( number_of_comments | pluralize: 'comment': 'comments' ), :error_mode => :lax)
var = Variable.new(%( number_of_comments | pluralize: 'comment': 'comments' ), error_mode: :lax)
assert_equal VariableLookup.new('number_of_comments'), var.name
assert_equal [['pluralize', ['comment', 'comments']]], var.filters
end