mirror of
https://github.com/kemko/liquid.git
synced 2026-01-04 09:15:41 +03:00
Compare commits
30 Commits
c-tokenize
...
test-gem-i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19a60ccee2 | ||
|
|
114a37d9ba | ||
|
|
30bd9ad957 | ||
|
|
2239921804 | ||
|
|
1ea178e7a8 | ||
|
|
5650c7eea1 | ||
|
|
553b0926ae | ||
|
|
2bac6267f9 | ||
|
|
628ab3dc6a | ||
|
|
2eb552c65d | ||
|
|
6e40746ce4 | ||
|
|
75068e8fa4 | ||
|
|
ad1152853a | ||
|
|
73098ac5bc | ||
|
|
8bc3792c0e | ||
|
|
3ef29c624c | ||
|
|
a85fb38769 | ||
|
|
6a1c3cff1a | ||
|
|
bde32018dd | ||
|
|
2a12f253bf | ||
|
|
f15d24509d | ||
|
|
09e4378cfb | ||
|
|
af0e26fb16 | ||
|
|
f5502e8152 | ||
|
|
c098235baa | ||
|
|
0e2bf768ba | ||
|
|
4c477c2087 | ||
|
|
cd7fc050b1 | ||
|
|
8291c5e72c | ||
|
|
7e45155aa9 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -6,7 +6,3 @@ pkg
|
|||||||
.rvmrc
|
.rvmrc
|
||||||
.ruby-version
|
.ruby-version
|
||||||
Gemfile.lock
|
Gemfile.lock
|
||||||
/ext/liquid/Makefile
|
|
||||||
*.o
|
|
||||||
*.bundle
|
|
||||||
/tmp
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
rvm:
|
rvm:
|
||||||
- 1.9.3
|
- 1.9.3
|
||||||
- 2.0.0
|
- 2.0.0
|
||||||
- 2.1.0
|
- 2.1
|
||||||
|
- 2.1.1
|
||||||
- jruby-19mode
|
- jruby-19mode
|
||||||
- jruby-head
|
- jruby-head
|
||||||
- rbx-19mode
|
- rbx-19mode
|
||||||
@@ -10,7 +11,7 @@ matrix:
|
|||||||
- rvm: rbx-19mode
|
- rvm: rbx-19mode
|
||||||
- rvm: jruby-head
|
- rvm: jruby-head
|
||||||
|
|
||||||
script: "rake test"
|
script: "gem build liquid.gemspec && gem install liquid-3.0.0.gem"
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
disable: true
|
disable: true
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
## 3.0.0 / not yet released / branch "master"
|
## 3.0.0 / not yet released / branch "master"
|
||||||
|
|
||||||
* ...
|
* ...
|
||||||
|
* Add error messages for missing variables when :strict, see #352 [Daniel Gaiottino]
|
||||||
|
* Properly set context rethrow_errors on render! #349 [Thierry Joyal, tjoyal]
|
||||||
|
* Fix broken rendering of variables which are equal to false, see #345 [Florian Weingarten, fw42]
|
||||||
|
* Remove ActionView template handler [Dylan Thacker-Smith, dylanahsmith]
|
||||||
* Freeze lots of string literals for new Ruby 2.1 optimization, see #297 [Florian Weingarten, fw42]
|
* Freeze lots of string literals for new Ruby 2.1 optimization, see #297 [Florian Weingarten, fw42]
|
||||||
* Allow newlines in tags and variables, see #324 [Dylan Thacker-Smith, dylanahsmith]
|
* Allow newlines in tags and variables, see #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]
|
* Tag#parse is called after initialize, which now takes options instead of tokens as the 3rd argument. See #321 [Dylan Thacker-Smith, dylanahsmith]
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
[](http://travis-ci.org/Shopify/liquid)
|
[](http://travis-ci.org/Shopify/liquid)
|
||||||
|
[](http://inch-ci.org/github/Shopify/liquid)
|
||||||
|
|
||||||
# Liquid template engine
|
# Liquid template engine
|
||||||
|
|
||||||
* [Contributing guidelines](CONTRIBUTING.md)
|
* [Contributing guidelines](CONTRIBUTING.md)
|
||||||
|
|||||||
10
Rakefile
10
Rakefile
@@ -8,7 +8,7 @@ task :default => 'test'
|
|||||||
desc 'run test suite with default parser'
|
desc 'run test suite with default parser'
|
||||||
Rake::TestTask.new(:base_test) do |t|
|
Rake::TestTask.new(:base_test) do |t|
|
||||||
t.libs << '.' << 'lib' << 'test'
|
t.libs << '.' << 'lib' << 'test'
|
||||||
t.test_files = FileList['test/liquid/**/*_test.rb']
|
t.test_files = FileList['test/{integration,unit}/**/*_test.rb']
|
||||||
t.verbose = false
|
t.verbose = false
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -75,11 +75,3 @@ desc "Run example"
|
|||||||
task :example do
|
task :example do
|
||||||
ruby "-w -d -Ilib example/server/server.rb"
|
ruby "-w -d -Ilib example/server/server.rb"
|
||||||
end
|
end
|
||||||
|
|
||||||
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
|
|
||||||
require 'rake/extensiontask'
|
|
||||||
Rake::ExtensionTask.new "liquid" do |ext|
|
|
||||||
ext.lib_dir = "lib/liquid"
|
|
||||||
end
|
|
||||||
Rake::Task[:test].prerequisites << :compile
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
require 'mkmf'
|
|
||||||
$CFLAGS << ' -Wall -Werror'
|
|
||||||
$warnflags.gsub!(/-Wdeclaration-after-statement/, "")
|
|
||||||
create_makefile("liquid/liquid")
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
#include "liquid.h"
|
|
||||||
|
|
||||||
VALUE mLiquid;
|
|
||||||
|
|
||||||
void Init_liquid(void)
|
|
||||||
{
|
|
||||||
mLiquid = rb_define_module("Liquid");
|
|
||||||
init_liquid_tokenizer();
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#ifndef LIQUID_H
|
|
||||||
#define LIQUID_H
|
|
||||||
|
|
||||||
#include <ruby.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include "tokenizer.h"
|
|
||||||
|
|
||||||
extern VALUE mLiquid;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
#include "liquid.h"
|
|
||||||
|
|
||||||
VALUE cLiquidTokenizer;
|
|
||||||
|
|
||||||
static void tokenizer_mark(void *ptr) {
|
|
||||||
tokenizer_t *tokenizer = ptr;
|
|
||||||
rb_gc_mark(tokenizer->source);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tokenizer_free(void *ptr)
|
|
||||||
{
|
|
||||||
tokenizer_t *tokenizer = ptr;
|
|
||||||
xfree(tokenizer);
|
|
||||||
}
|
|
||||||
|
|
||||||
static size_t tokenizer_memsize(const void *ptr)
|
|
||||||
{
|
|
||||||
return ptr ? sizeof(tokenizer_t) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rb_data_type_t tokenizer_data_type = {
|
|
||||||
"liquid_tokenizer",
|
|
||||||
{tokenizer_mark, tokenizer_free, tokenizer_memsize,},
|
|
||||||
#ifdef RUBY_TYPED_FREE_IMMEDIATELY
|
|
||||||
NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
static VALUE tokenizer_allocate(VALUE klass)
|
|
||||||
{
|
|
||||||
VALUE obj;
|
|
||||||
tokenizer_t *tokenizer;
|
|
||||||
|
|
||||||
obj = TypedData_Make_Struct(klass, tokenizer_t, &tokenizer_data_type, tokenizer);
|
|
||||||
tokenizer->source = Qnil;
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
static VALUE tokenizer_initialize_method(VALUE self, VALUE source)
|
|
||||||
{
|
|
||||||
tokenizer_t *tokenizer;
|
|
||||||
|
|
||||||
Check_Type(source, T_STRING);
|
|
||||||
Tokenizer_Get_Struct(self, tokenizer);
|
|
||||||
source = rb_str_dup_frozen(source);
|
|
||||||
tokenizer->source = source;
|
|
||||||
tokenizer->cursor = RSTRING_PTR(source);
|
|
||||||
tokenizer->length = RSTRING_LEN(source);
|
|
||||||
return Qnil;
|
|
||||||
}
|
|
||||||
|
|
||||||
void tokenizer_next(tokenizer_t *tokenizer, token_t *token)
|
|
||||||
{
|
|
||||||
if (tokenizer->length <= 0) {
|
|
||||||
memset(token, 0, sizeof(*token));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *cursor = tokenizer->cursor;
|
|
||||||
const char *last = cursor + tokenizer->length - 1;
|
|
||||||
|
|
||||||
token->str = cursor;
|
|
||||||
token->type = TOKEN_STRING;
|
|
||||||
|
|
||||||
while (cursor < last) {
|
|
||||||
if (*cursor++ != '{')
|
|
||||||
continue;
|
|
||||||
|
|
||||||
char c = *cursor++;
|
|
||||||
if (c != '%' && c != '{')
|
|
||||||
continue;
|
|
||||||
if (cursor - tokenizer->cursor > 2) {
|
|
||||||
token->type = TOKEN_STRING;
|
|
||||||
cursor -= 2;
|
|
||||||
goto found;
|
|
||||||
}
|
|
||||||
token->type = TOKEN_INVALID;
|
|
||||||
if (c == '%') {
|
|
||||||
while (cursor < last) {
|
|
||||||
if (*cursor++ != '%')
|
|
||||||
continue;
|
|
||||||
c = *cursor++;
|
|
||||||
while (c == '%' && cursor <= last)
|
|
||||||
c = *cursor++;
|
|
||||||
if (c != '}')
|
|
||||||
continue;
|
|
||||||
token->type = TOKEN_TAG;
|
|
||||||
goto found;
|
|
||||||
}
|
|
||||||
// unterminated tag
|
|
||||||
cursor = tokenizer->cursor + 2;
|
|
||||||
goto found;
|
|
||||||
} else {
|
|
||||||
while (cursor < last) {
|
|
||||||
if (*cursor++ != '}')
|
|
||||||
continue;
|
|
||||||
if (*cursor++ != '}') {
|
|
||||||
// variable incomplete end, used to end raw tags
|
|
||||||
cursor--;
|
|
||||||
goto found;
|
|
||||||
}
|
|
||||||
token->type = TOKEN_VARIABLE;
|
|
||||||
goto found;
|
|
||||||
}
|
|
||||||
// unterminated variable
|
|
||||||
cursor = tokenizer->cursor + 2;
|
|
||||||
goto found;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cursor = last + 1;
|
|
||||||
found:
|
|
||||||
token->length = cursor - tokenizer->cursor;
|
|
||||||
tokenizer->cursor += token->length;
|
|
||||||
tokenizer->length -= token->length;
|
|
||||||
}
|
|
||||||
|
|
||||||
static VALUE tokenizer_next_method(VALUE self)
|
|
||||||
{
|
|
||||||
tokenizer_t *tokenizer;
|
|
||||||
Tokenizer_Get_Struct(self, tokenizer);
|
|
||||||
|
|
||||||
token_t token;
|
|
||||||
tokenizer_next(tokenizer, &token);
|
|
||||||
if (token.type == TOKEN_NONE)
|
|
||||||
return Qnil;
|
|
||||||
|
|
||||||
return rb_str_new(token.str, token.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
void init_liquid_tokenizer()
|
|
||||||
{
|
|
||||||
cLiquidTokenizer = rb_define_class_under(mLiquid, "Tokenizer", rb_cObject);
|
|
||||||
rb_define_alloc_func(cLiquidTokenizer, tokenizer_allocate);
|
|
||||||
rb_define_method(cLiquidTokenizer, "initialize", tokenizer_initialize_method, 1);
|
|
||||||
rb_define_method(cLiquidTokenizer, "next", tokenizer_next_method, 0);
|
|
||||||
rb_define_alias(cLiquidTokenizer, "shift", "next");
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
#ifndef LIQUID_TOKENIZER_H
|
|
||||||
#define LIQUID_TOKENIZER_H
|
|
||||||
|
|
||||||
enum token_type {
|
|
||||||
TOKEN_NONE,
|
|
||||||
TOKEN_INVALID,
|
|
||||||
TOKEN_STRING,
|
|
||||||
TOKEN_TAG,
|
|
||||||
TOKEN_VARIABLE
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct token {
|
|
||||||
enum token_type type;
|
|
||||||
const char *str;
|
|
||||||
long length;
|
|
||||||
} token_t;
|
|
||||||
|
|
||||||
typedef struct tokenizer {
|
|
||||||
VALUE source;
|
|
||||||
const char *cursor;
|
|
||||||
long length;
|
|
||||||
} tokenizer_t;
|
|
||||||
|
|
||||||
extern VALUE cLiquidTokenizer;
|
|
||||||
extern const rb_data_type_t tokenizer_data_type;
|
|
||||||
#define Tokenizer_Get_Struct(obj, sval) TypedData_Get_Struct(obj, tokenizer_t, &tokenizer_data_type, sval)
|
|
||||||
|
|
||||||
void init_liquid_tokenizer();
|
|
||||||
void tokenizer_next(tokenizer_t *tokenizer, token_t *token);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
8
init.rb
8
init.rb
@@ -1,8 +0,0 @@
|
|||||||
require 'liquid'
|
|
||||||
require 'extras/liquid_view'
|
|
||||||
|
|
||||||
if defined? ActionView::Template and ActionView::Template.respond_to? :register_template_handler
|
|
||||||
ActionView::Template
|
|
||||||
else
|
|
||||||
ActionView::Base
|
|
||||||
end.register_template_handler(:liquid, LiquidView)
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
# LiquidView is a action view extension class. You can register it with rails
|
|
||||||
# and use liquid as an template system for .liquid files
|
|
||||||
#
|
|
||||||
# Example
|
|
||||||
#
|
|
||||||
# ActionView::Base::register_template_handler :liquid, LiquidView
|
|
||||||
class LiquidView
|
|
||||||
PROTECTED_ASSIGNS = %w( template_root response _session template_class action_name request_origin session template
|
|
||||||
_response url _request _cookies variables_added _flash params _headers request cookies
|
|
||||||
ignore_missing_templates flash _params logger before_filter_chain_aborted headers )
|
|
||||||
PROTECTED_INSTANCE_VARIABLES = %w( @_request @controller @_first_render @_memoized__pick_template @view_paths
|
|
||||||
@helpers @assigns_added @template @_render_stack @template_format @assigns )
|
|
||||||
|
|
||||||
def self.call(template)
|
|
||||||
"LiquidView.new(self).render(template, local_assigns)"
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(view)
|
|
||||||
@view = view
|
|
||||||
end
|
|
||||||
|
|
||||||
def render(template, local_assigns = nil)
|
|
||||||
@view.controller.headers["Content-Type"] ||= 'text/html; charset=utf-8'
|
|
||||||
|
|
||||||
# Rails 2.2 Template has source, but not locals
|
|
||||||
if template.respond_to?(:source) && !template.respond_to?(:locals)
|
|
||||||
assigns = (@view.instance_variables - PROTECTED_INSTANCE_VARIABLES).inject({}) do |hash, ivar|
|
|
||||||
hash[ivar[1..-1]] = @view.instance_variable_get(ivar)
|
|
||||||
hash
|
|
||||||
end
|
|
||||||
else
|
|
||||||
assigns = @view.assigns.reject{ |k,v| PROTECTED_ASSIGNS.include?(k) }
|
|
||||||
end
|
|
||||||
|
|
||||||
source = template.respond_to?(:source) ? template.source : template
|
|
||||||
local_assigns = (template.respond_to?(:locals) ? template.locals : local_assigns) || {}
|
|
||||||
|
|
||||||
if content_for_layout = @view.instance_variable_get("@content_for_layout")
|
|
||||||
assigns['content_for_layout'] = content_for_layout
|
|
||||||
end
|
|
||||||
assigns.merge!(local_assigns.stringify_keys)
|
|
||||||
|
|
||||||
liquid = Liquid::Template.parse(source)
|
|
||||||
liquid.render(assigns, :filters => [@view.controller.master_helper_module], :registers => {:action_view => @view, :controller => @view.controller})
|
|
||||||
end
|
|
||||||
|
|
||||||
def compilable?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@@ -30,9 +30,13 @@ module Liquid
|
|||||||
VariableSegment = /[\w\-]/
|
VariableSegment = /[\w\-]/
|
||||||
VariableStart = /\{\{/
|
VariableStart = /\{\{/
|
||||||
VariableEnd = /\}\}/
|
VariableEnd = /\}\}/
|
||||||
|
VariableIncompleteEnd = /\}\}?/
|
||||||
QuotedString = /"[^"]*"|'[^']*'/
|
QuotedString = /"[^"]*"|'[^']*'/
|
||||||
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
|
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
|
||||||
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
|
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
|
||||||
|
AnyStartingTag = /\{\{|\{\%/
|
||||||
|
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
|
||||||
|
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
|
||||||
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
|
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -60,9 +64,3 @@ require 'liquid/utils'
|
|||||||
# Load all the tags of the standard library
|
# Load all the tags of the standard library
|
||||||
#
|
#
|
||||||
Dir[File.dirname(__FILE__) + '/liquid/tags/*.rb'].each { |f| require f }
|
Dir[File.dirname(__FILE__) + '/liquid/tags/*.rb'].each { |f| require f }
|
||||||
|
|
||||||
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
|
|
||||||
require 'liquid/liquid'
|
|
||||||
else
|
|
||||||
require 'liquid/tokenizer'
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ module Liquid
|
|||||||
class Context
|
class Context
|
||||||
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
|
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
|
||||||
|
|
||||||
|
attr_accessor :rethrow_errors
|
||||||
|
|
||||||
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = {})
|
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = {})
|
||||||
@environments = [environments].flatten
|
@environments = [environments].flatten
|
||||||
@scopes = [(outer_scope || {})]
|
@scopes = [(outer_scope || {})]
|
||||||
@@ -194,7 +196,8 @@ module Liquid
|
|||||||
|
|
||||||
if scope.nil?
|
if scope.nil?
|
||||||
@environments.each do |e|
|
@environments.each do |e|
|
||||||
if variable = lookup_and_evaluate(e, key)
|
variable = lookup_and_evaluate(e, key)
|
||||||
|
unless variable.nil?
|
||||||
scope = e
|
scope = e
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@@ -202,6 +205,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
scope ||= @environments.last || @scopes.last
|
scope ||= @environments.last || @scopes.last
|
||||||
|
handle_not_found(key) unless scope.has_key?(key)
|
||||||
variable ||= lookup_and_evaluate(scope, key)
|
variable ||= lookup_and_evaluate(scope, key)
|
||||||
|
|
||||||
variable = variable.to_liquid
|
variable = variable.to_liquid
|
||||||
@@ -251,6 +255,7 @@ module Liquid
|
|||||||
# No key was present with the desired value and it wasn't one of the directly supported
|
# No key was present with the desired value and it wasn't one of the directly supported
|
||||||
# keywords either. The only thing we got left is to return nil
|
# keywords either. The only thing we got left is to return nil
|
||||||
else
|
else
|
||||||
|
handle_not_found(markup)
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -280,6 +285,10 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end # squash_instance_assigns_with_environments
|
end # squash_instance_assigns_with_environments
|
||||||
|
|
||||||
|
def handle_not_found(variable)
|
||||||
|
@errors << "Variable {{#{variable}}} not found" if Template.error_mode == :strict
|
||||||
|
end
|
||||||
end # Context
|
end # Context
|
||||||
|
|
||||||
end # Liquid
|
end # Liquid
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ module Liquid
|
|||||||
input.to_s.gsub(/\n/, "<br />\n".freeze)
|
input.to_s.gsub(/\n/, "<br />\n".freeze)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Reformat a date
|
# Reformat a date using Ruby's core Time#strftime( string ) -> string
|
||||||
#
|
#
|
||||||
# %a - The abbreviated weekday name (``Sun'')
|
# %a - The abbreviated weekday name (``Sun'')
|
||||||
# %A - The full weekday name (``Sunday'')
|
# %A - The full weekday name (``Sunday'')
|
||||||
@@ -176,6 +176,7 @@ module Liquid
|
|||||||
# %m - Month of the year (01..12)
|
# %m - Month of the year (01..12)
|
||||||
# %M - Minute of the hour (00..59)
|
# %M - Minute of the hour (00..59)
|
||||||
# %p - Meridian indicator (``AM'' or ``PM'')
|
# %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)
|
# %S - Second of the minute (00..60)
|
||||||
# %U - Week number of the current year,
|
# %U - Week number of the current year,
|
||||||
# starting with the first Sunday as the first
|
# starting with the first Sunday as the first
|
||||||
@@ -190,34 +191,14 @@ module Liquid
|
|||||||
# %Y - Year with century
|
# %Y - Year with century
|
||||||
# %Z - Time zone name
|
# %Z - Time zone name
|
||||||
# %% - Literal ``%'' character
|
# %% - Literal ``%'' character
|
||||||
|
#
|
||||||
|
# See also: http://www.ruby-doc.org/core/Time.html#method-i-strftime
|
||||||
def date(input, format)
|
def date(input, format)
|
||||||
|
return input if format.to_s.empty?
|
||||||
|
|
||||||
if format.to_s.empty?
|
return input unless date = to_date(input)
|
||||||
return input.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
if ((input.is_a?(String) && !/\A\d+\z/.match(input.to_s).nil?) || input.is_a?(Integer)) && input.to_i > 0
|
date.strftime(format.to_s)
|
||||||
input = Time.at(input.to_i)
|
|
||||||
end
|
|
||||||
|
|
||||||
date = if input.is_a?(String)
|
|
||||||
case input.downcase
|
|
||||||
when 'now'.freeze, 'today'.freeze
|
|
||||||
Time.now
|
|
||||||
else
|
|
||||||
Time.parse(input)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
input
|
|
||||||
end
|
|
||||||
|
|
||||||
if date.respond_to?(:strftime)
|
|
||||||
date.strftime(format.to_s)
|
|
||||||
else
|
|
||||||
input
|
|
||||||
end
|
|
||||||
rescue
|
|
||||||
input
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Get the first element of the passed in array
|
# Get the first element of the passed in array
|
||||||
@@ -262,6 +243,21 @@ module Liquid
|
|||||||
apply_operation(input, operand, :%)
|
apply_operation(input, operand, :%)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def round(input, n = 0)
|
||||||
|
result = to_number(input).round(to_number(n))
|
||||||
|
result = result.to_f if result.is_a?(BigDecimal)
|
||||||
|
result = result.to_i if n == 0
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
def ceil(input)
|
||||||
|
to_number(input).ceil.to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
def floor(input)
|
||||||
|
to_number(input).floor.to_i
|
||||||
|
end
|
||||||
|
|
||||||
def default(input, default_value = "".freeze)
|
def default(input, default_value = "".freeze)
|
||||||
is_blank = input.respond_to?(:empty?) ? input.empty? : !input
|
is_blank = input.respond_to?(:empty?) ? input.empty? : !input
|
||||||
is_blank ? default_value : input
|
is_blank ? default_value : input
|
||||||
@@ -293,6 +289,23 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_date(obj)
|
||||||
|
return obj if obj.respond_to?(:strftime)
|
||||||
|
|
||||||
|
case obj
|
||||||
|
when 'now'.freeze, 'today'.freeze
|
||||||
|
Time.now
|
||||||
|
when /\A\d+\z/, Integer
|
||||||
|
Time.at(obj.to_i)
|
||||||
|
when String
|
||||||
|
Time.parse(obj)
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
rescue ArgumentError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
def apply_operation(input, operand, operation)
|
def apply_operation(input, operand, operation)
|
||||||
result = to_number(input).send(operation, to_number(operand))
|
result = to_number(input).send(operation, to_number(operand))
|
||||||
result.is_a?(BigDecimal) ? result.to_f : result
|
result.is_a?(BigDecimal) ? result.to_f : result
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ module Liquid
|
|||||||
@@file_system = BlankFileSystem.new
|
@@file_system = BlankFileSystem.new
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
|
# Sets how strict the parser should be.
|
||||||
|
# :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
|
||||||
|
# :warn is the default and will give deprecation warnings when invalid syntax is used.
|
||||||
|
# :strict will enforce correct syntax.
|
||||||
|
attr_writer :error_mode
|
||||||
|
|
||||||
def file_system
|
def file_system
|
||||||
@@file_system
|
@@file_system
|
||||||
end
|
end
|
||||||
@@ -38,14 +44,6 @@ module Liquid
|
|||||||
@tags ||= {}
|
@tags ||= {}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sets how strict the parser should be.
|
|
||||||
# :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
|
|
||||||
# :warn is the default and will give deprecation warnings when invalid syntax is used.
|
|
||||||
# :strict will enforce correct syntax.
|
|
||||||
def error_mode=(mode)
|
|
||||||
@error_mode = mode
|
|
||||||
end
|
|
||||||
|
|
||||||
def error_mode
|
def error_mode
|
||||||
@error_mode || :lax
|
@error_mode || :lax
|
||||||
end
|
end
|
||||||
@@ -60,7 +58,6 @@ module Liquid
|
|||||||
def parse(source, options = {})
|
def parse(source, options = {})
|
||||||
template = Template.new
|
template = Template.new
|
||||||
template.parse(source, options)
|
template.parse(source, options)
|
||||||
template
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -114,7 +111,9 @@ module Liquid
|
|||||||
|
|
||||||
context = case args.first
|
context = case args.first
|
||||||
when Liquid::Context
|
when Liquid::Context
|
||||||
args.shift
|
c = args.shift
|
||||||
|
c.rethrow_errors = true if @rethrow_errors
|
||||||
|
c
|
||||||
when Liquid::Drop
|
when Liquid::Drop
|
||||||
drop = args.shift
|
drop = args.shift
|
||||||
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
|
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
|
||||||
@@ -157,14 +156,22 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def render!(*args)
|
def render!(*args)
|
||||||
@rethrow_errors = true; render(*args)
|
@rethrow_errors = true
|
||||||
|
render(*args)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# Uses the <tt>Liquid::TemplateParser</tt> regexp to tokenize the passed source
|
||||||
def tokenize(source)
|
def tokenize(source)
|
||||||
source = source.source if source.respond_to?(:source)
|
source = source.source if source.respond_to?(:source)
|
||||||
Tokenizer.new(source.to_s)
|
return [] if source.to_s.empty?
|
||||||
|
tokens = source.split(TemplateParser)
|
||||||
|
|
||||||
|
# removes the rogue empty element at the beginning of the array
|
||||||
|
tokens.shift if tokens[0] and tokens[0].empty?
|
||||||
|
|
||||||
|
tokens
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
module Liquid
|
|
||||||
class Tokenizer
|
|
||||||
VariableIncompleteEnd = /\}\}?/
|
|
||||||
AnyStartingTag = /\{\{|\{\%/
|
|
||||||
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
|
|
||||||
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
|
|
||||||
|
|
||||||
def initialize(source)
|
|
||||||
@tokens = source.split(TemplateParser)
|
|
||||||
|
|
||||||
# removes the rogue empty element at the beginning of the array
|
|
||||||
@tokens.shift if @tokens[0] && @tokens[0].empty?
|
|
||||||
end
|
|
||||||
|
|
||||||
def next
|
|
||||||
@tokens.shift
|
|
||||||
end
|
|
||||||
alias_method :shift, :next
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -18,17 +18,12 @@ Gem::Specification.new do |s|
|
|||||||
s.required_rubygems_version = ">= 1.3.7"
|
s.required_rubygems_version = ">= 1.3.7"
|
||||||
|
|
||||||
s.test_files = Dir.glob("{test}/**/*")
|
s.test_files = Dir.glob("{test}/**/*")
|
||||||
s.files = Dir.glob("{lib,ext}/**/*") + %w(MIT-LICENSE README.md)
|
s.files = Dir.glob("{lib}/**/*") + %w(MIT-LICENSE README.md)
|
||||||
|
|
||||||
s.extra_rdoc_files = ["History.md", "README.md"]
|
s.extra_rdoc_files = ["History.md", "README.md"]
|
||||||
|
|
||||||
s.require_path = "lib"
|
s.require_path = "lib"
|
||||||
|
|
||||||
|
s.add_development_dependency 'stackprof' if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.1.0")
|
||||||
s.add_development_dependency 'rake'
|
s.add_development_dependency 'rake'
|
||||||
s.add_development_dependency 'activesupport'
|
|
||||||
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
|
|
||||||
s.extensions = ['ext/liquid/extconf.rb']
|
|
||||||
s.add_development_dependency 'rake-compiler'
|
|
||||||
s.add_development_dependency 'stackprof' if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.1.0")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
require 'rubygems'
|
|
||||||
require 'benchmark'
|
require 'benchmark'
|
||||||
require File.dirname(__FILE__) + '/theme_runner'
|
require File.dirname(__FILE__) + '/theme_runner'
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'yaml'
|
require 'yaml'
|
||||||
module Database
|
|
||||||
|
|
||||||
|
module Database
|
||||||
# Load the standard vision toolkit database and re-arrage it to be simply exportable
|
# Load the standard vision toolkit database and re-arrage it to be simply exportable
|
||||||
# to liquid as assigns. All this is based on Shopify
|
# to liquid as assigns. All this is based on Shopify
|
||||||
def self.tables
|
def self.tables
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
require 'json'
|
||||||
|
|
||||||
module JsonFilter
|
module JsonFilter
|
||||||
|
|
||||||
def json(object)
|
def json(object)
|
||||||
object.reject {|k,v| k == "collections" }.to_json
|
JSON.dump(object.reject {|k,v| k == "collections" })
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,11 +6,6 @@
|
|||||||
# Shopify which is likely the biggest user of liquid in the world which something to the tune of several
|
# Shopify which is likely the biggest user of liquid in the world which something to the tune of several
|
||||||
# million Template#render calls a day.
|
# million Template#render calls a day.
|
||||||
|
|
||||||
require 'rubygems'
|
|
||||||
require 'active_support'
|
|
||||||
require 'active_support/json'
|
|
||||||
require 'yaml'
|
|
||||||
require 'digest/md5'
|
|
||||||
require File.dirname(__FILE__) + '/shopify/liquid'
|
require File.dirname(__FILE__) + '/shopify/liquid'
|
||||||
require File.dirname(__FILE__) + '/shopify/database.rb'
|
require File.dirname(__FILE__) + '/shopify/database.rb'
|
||||||
|
|
||||||
@@ -81,6 +76,3 @@ class ThemeRunner
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
23
test/integration/context_test.rb
Normal file
23
test/integration/context_test.rb
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class ContextTest < Test::Unit::TestCase
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
def test_override_global_filter
|
||||||
|
global = Module.new do
|
||||||
|
def notice(output)
|
||||||
|
"Global #{output}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local = Module.new do
|
||||||
|
def notice(output)
|
||||||
|
"Local #{output}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Template.register_filter(global)
|
||||||
|
assert_equal 'Global test', Template.parse("{{'test' | notice }}").render!
|
||||||
|
assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, :filters => [local])
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -3,12 +3,9 @@ require 'test_helper'
|
|||||||
class ParsingQuirksTest < Test::Unit::TestCase
|
class ParsingQuirksTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_error_with_css
|
def test_parsing_css
|
||||||
text = %| div { font-weight: bold; } |
|
text = " div { font-weight: bold; } "
|
||||||
template = Template.parse(text)
|
assert_equal text, Template.parse(text).render!
|
||||||
|
|
||||||
assert_equal text, template.render!
|
|
||||||
assert_equal [String], template.root.nodelist.collect {|i| i.class}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_raise_on_single_close_bracet
|
def test_raise_on_single_close_bracet
|
||||||
@@ -54,10 +54,10 @@ class SecurityTest < Test::Unit::TestCase
|
|||||||
def test_does_not_add_drop_methods_to_symbol_table
|
def test_does_not_add_drop_methods_to_symbol_table
|
||||||
current_symbols = Symbol.all_symbols
|
current_symbols = Symbol.all_symbols
|
||||||
|
|
||||||
drop = Drop.new
|
assigns = { 'drop' => Drop.new }
|
||||||
drop.invoke_drop("custom_method_1")
|
assert_equal "", Template.parse("{{ drop.custom_method_1 }}", assigns).render!
|
||||||
drop.invoke_drop("custom_method_2")
|
assert_equal "", Template.parse("{{ drop.custom_method_2 }}", assigns).render!
|
||||||
drop.invoke_drop("custom_method_3")
|
assert_equal "", Template.parse("{{ drop.custom_method_3 }}", assigns).render!
|
||||||
|
|
||||||
assert_equal [], (Symbol.all_symbols - current_symbols)
|
assert_equal [], (Symbol.all_symbols - current_symbols)
|
||||||
end
|
end
|
||||||
@@ -115,6 +115,13 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
assert_equal [{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], @filters.sort([{"a" => 4}, {"a" => 3}, {"a" => 1}, {"a" => 2}], "a")
|
assert_equal [{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], @filters.sort([{"a" => 4}, {"a" => 3}, {"a" => 1}, {"a" => 2}], "a")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_numerical_vs_lexicographical_sort
|
||||||
|
assert_equal [2, 10], @filters.sort([10, 2])
|
||||||
|
assert_equal [{"a" => 2}, {"a" => 10}], @filters.sort([{"a" => 10}, {"a" => 2}], "a")
|
||||||
|
assert_equal ["10", "2"], @filters.sort(["10", "2"])
|
||||||
|
assert_equal [{"a" => "10"}, {"a" => "2"}], @filters.sort([{"a" => "10"}, {"a" => "2"}], "a")
|
||||||
|
end
|
||||||
|
|
||||||
def test_reverse
|
def test_reverse
|
||||||
assert_equal [4,3,2,1], @filters.reverse([1,2,3,4])
|
assert_equal [4,3,2,1], @filters.reverse([1,2,3,4])
|
||||||
end
|
end
|
||||||
@@ -186,7 +193,6 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
assert_equal "07/05/2006", @filters.date("1152098955", "%m/%d/%Y")
|
assert_equal "07/05/2006", @filters.date("1152098955", "%m/%d/%Y")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def test_first_last
|
def test_first_last
|
||||||
assert_equal 1, @filters.first([1,2,3])
|
assert_equal 1, @filters.first([1,2,3])
|
||||||
assert_equal 3, @filters.last([1,2,3])
|
assert_equal 3, @filters.last([1,2,3])
|
||||||
@@ -267,6 +273,22 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
assert_template_result "1", "{{ 3 | modulo:2 }}"
|
assert_template_result "1", "{{ 3 | modulo:2 }}"
|
||||||
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
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ceil
|
||||||
|
assert_template_result "5", "{{ input | ceil }}", 'input' => 4.6
|
||||||
|
assert_template_result "5", "{{ '4.3' | ceil }}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_floor
|
||||||
|
assert_template_result "4", "{{ input | floor }}", 'input' => 4.6
|
||||||
|
assert_template_result "4", "{{ '4.3' | floor }}"
|
||||||
|
end
|
||||||
|
|
||||||
def test_append
|
def test_append
|
||||||
assigns = {'a' => 'bc', 'b' => 'd' }
|
assigns = {'a' => 'bc', 'b' => 'd' }
|
||||||
assert_template_result('bcd',"{{ a | append: 'd'}}",assigns)
|
assert_template_result('bcd',"{{ a | append: 'd'}}",assigns)
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
|
class ThingWithValue < Liquid::Drop
|
||||||
|
def value
|
||||||
|
3
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class ForTagTest < Test::Unit::TestCase
|
class ForTagTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
@@ -34,6 +40,20 @@ HERE
|
|||||||
assert_template_result(' 1 2 3 ','{%for item in (1..3) %} {{item}} {%endfor%}')
|
assert_template_result(' 1 2 3 ','{%for item in (1..3) %} {{item}} {%endfor%}')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_for_with_variable_range
|
||||||
|
assert_template_result(' 1 2 3 ','{%for item in (1..foobar) %} {{item}} {%endfor%}', "foobar" => 3)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_for_with_hash_value_range
|
||||||
|
foobar = { "value" => 3 }
|
||||||
|
assert_template_result(' 1 2 3 ','{%for item in (1..foobar.value) %} {{item}} {%endfor%}', "foobar" => foobar)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_for_with_drop_value_range
|
||||||
|
foobar = ThingWithValue.new
|
||||||
|
assert_template_result(' 1 2 3 ','{%for item in (1..foobar.value) %} {{item}} {%endfor%}', "foobar" => foobar)
|
||||||
|
end
|
||||||
|
|
||||||
def test_for_with_variable
|
def test_for_with_variable
|
||||||
assert_template_result(' 1 2 3 ','{%for item in array%} {{item}} {%endfor%}','array' => [1,2,3])
|
assert_template_result(' 1 2 3 ','{%for item in array%} {{item}} {%endfor%}','array' => [1,2,3])
|
||||||
assert_template_result('123','{%for item in array%}{{item}}{%endfor%}','array' => [1,2,3])
|
assert_template_result('123','{%for item in array%}{{item}}{%endfor%}','array' => [1,2,3])
|
||||||
@@ -295,16 +315,6 @@ HERE
|
|||||||
assert_template_result(expected, template, assigns)
|
assert_template_result(expected, template, assigns)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_for_nodelist
|
|
||||||
template = Liquid::Template.parse('{% for item in items %}FOR{% endfor %}')
|
|
||||||
assert_equal ['FOR'], template.root.nodelist[0].nodelist
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_for_else_nodelist
|
|
||||||
template = Liquid::Template.parse('{% for item in items %}FOR{% else %}ELSE{% endfor %}')
|
|
||||||
assert_equal ['FOR', 'ELSE'], template.root.nodelist[0].nodelist
|
|
||||||
end
|
|
||||||
|
|
||||||
class LoaderDrop < Liquid::Drop
|
class LoaderDrop < Liquid::Drop
|
||||||
attr_accessor :each_called, :load_slice_called
|
attr_accessor :each_called, :load_slice_called
|
||||||
|
|
||||||
@@ -158,14 +158,9 @@ class IfElseTagTest < Test::Unit::TestCase
|
|||||||
%({% if 'gnomeslab-and-or-liquid' contains 'gnomeslab-and-or-liquid' %}yes{% endif %}))
|
%({% if 'gnomeslab-and-or-liquid' contains 'gnomeslab-and-or-liquid' %}yes{% endif %}))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_if_nodelist
|
|
||||||
template = Liquid::Template.parse('{% if true %}IF{% else %}ELSE{% endif %}')
|
|
||||||
assert_equal ['IF', 'ELSE'], template.root.nodelist[0].nodelist
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_operators_are_whitelisted
|
def test_operators_are_whitelisted
|
||||||
assert_raise(SyntaxError) do
|
assert_raise(SyntaxError) do
|
||||||
assert_template_result('', %({% if 1 or throw or or 1 %}yes{% endif %}))
|
assert_template_result('', %({% if 1 or throw or or 1 %}yes{% endif %}))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end # IfElseTest
|
end
|
||||||
@@ -3,12 +3,6 @@ require 'test_helper'
|
|||||||
class StandardTagTest < Test::Unit::TestCase
|
class StandardTagTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_tag
|
|
||||||
tag = Tag.parse('tag', [], [], {})
|
|
||||||
assert_equal 'liquid::tag', tag.name
|
|
||||||
assert_equal '', tag.render(Context.new)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_no_transform
|
def test_no_transform
|
||||||
assert_template_result('this text should come out of the template without change...',
|
assert_template_result('this text should come out of the template without change...',
|
||||||
'this text should come out of the template without change...')
|
'this text should come out of the template without change...')
|
||||||
@@ -22,6 +22,12 @@ class SomethingWithLength
|
|||||||
liquid_methods :length
|
liquid_methods :length
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class ErroneousDrop < Liquid::Drop
|
||||||
|
def bad_method
|
||||||
|
raise 'ruby error in drop'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class TemplateTest < Test::Unit::TestCase
|
class TemplateTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
@@ -138,17 +144,13 @@ class TemplateTest < Test::Unit::TestCase
|
|||||||
assert_equal 'haha', t.parse("{{baz}}").render!(drop)
|
assert_equal 'haha', t.parse("{{baz}}").render!(drop)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_sets_default_localization_in_document
|
def test_render_bang_force_rethrow_errors_on_passed_context
|
||||||
t = Template.new
|
context = Context.new({'drop' => ErroneousDrop.new})
|
||||||
t.parse('')
|
t = Template.new.parse('{{ drop.bad_method }}')
|
||||||
assert_instance_of I18n, t.root.options[:locale]
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_sets_default_localization_in_context_with_quick_initialization
|
e = assert_raises RuntimeError do
|
||||||
t = Template.new
|
t.render!(context)
|
||||||
t.parse('{{foo}}', :locale => I18n.new(fixture("en_locale.yml")))
|
end
|
||||||
|
assert_equal 'ruby error in drop', e.message
|
||||||
assert_instance_of I18n, t.root.options[:locale]
|
|
||||||
assert_equal fixture("en_locale.yml"), t.root.options[:locale].path
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
72
test/integration/variable_test.rb
Normal file
72
test/integration/variable_test.rb
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class VariableTest < Test::Unit::TestCase
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
def test_simple_variable
|
||||||
|
template = Template.parse(%|{{test}}|)
|
||||||
|
assert_equal 'worked', template.render!('test' => 'worked')
|
||||||
|
assert_equal 'worked wonderfully', template.render!('test' => 'worked wonderfully')
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_simple_with_whitespaces
|
||||||
|
template = Template.parse(%| {{ test }} |)
|
||||||
|
assert_equal ' worked ', template.render!('test' => 'worked')
|
||||||
|
assert_equal ' worked wonderfully ', template.render!('test' => 'worked wonderfully')
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ignore_unknown
|
||||||
|
template = Template.parse(%|{{ test }}|)
|
||||||
|
assert_equal '', template.render!
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_hash_scoping
|
||||||
|
template = Template.parse(%|{{ test.test }}|)
|
||||||
|
assert_equal 'worked', template.render!('test' => {'test' => 'worked'})
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_false_renders_as_false
|
||||||
|
assert_equal 'false', Template.parse("{{ foo }}").render!('foo' => false)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preset_assigns
|
||||||
|
template = Template.parse(%|{{ test }}|)
|
||||||
|
template.assigns['test'] = 'worked'
|
||||||
|
assert_equal 'worked', template.render!
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reuse_parsed_template
|
||||||
|
template = Template.parse(%|{{ greeting }} {{ name }}|)
|
||||||
|
template.assigns['greeting'] = 'Goodbye'
|
||||||
|
assert_equal 'Hello Tobi', template.render!('greeting' => 'Hello', 'name' => 'Tobi')
|
||||||
|
assert_equal 'Hello ', template.render!('greeting' => 'Hello', 'unknown' => 'Tobi')
|
||||||
|
assert_equal 'Hello Brian', template.render!('greeting' => 'Hello', 'name' => 'Brian')
|
||||||
|
assert_equal 'Goodbye Brian', template.render!('name' => 'Brian')
|
||||||
|
assert_equal({'greeting'=>'Goodbye'}, template.assigns)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_assigns_not_polluted_from_template
|
||||||
|
template = Template.parse(%|{{ test }}{% assign test = 'bar' %}{{ test }}|)
|
||||||
|
template.assigns['test'] = 'baz'
|
||||||
|
assert_equal 'bazbar', template.render!
|
||||||
|
assert_equal 'bazbar', template.render!
|
||||||
|
assert_equal 'foobar', template.render!('test' => 'foo')
|
||||||
|
assert_equal 'bazbar', template.render!
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_hash_with_default_proc
|
||||||
|
template = Template.parse(%|Hello {{ test }}|)
|
||||||
|
assigns = Hash.new { |h,k| raise "Unknown variable '#{k}'" }
|
||||||
|
assigns['test'] = 'Tobi'
|
||||||
|
assert_equal 'Hello Tobi', template.render!(assigns)
|
||||||
|
assigns.delete('test')
|
||||||
|
e = assert_raises(RuntimeError) {
|
||||||
|
template.render!(assigns)
|
||||||
|
}
|
||||||
|
assert_equal "Unknown variable 'test'", e.message
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_multiline_variable
|
||||||
|
assert_equal 'worked', Template.parse("{{\ntest\n}}").render!('test' => 'worked')
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class BlockTest < Test::Unit::TestCase
|
class BlockUnitTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_blankspace
|
def test_blankspace
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class ConditionTest < Test::Unit::TestCase
|
class ConditionUnitTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_basic_condition
|
def test_basic_condition
|
||||||
@@ -63,7 +63,7 @@ class ArrayLike
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class ContextTest < Test::Unit::TestCase
|
class ContextUnitTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
@@ -162,24 +162,6 @@ class ContextTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_override_global_filter
|
|
||||||
global = Module.new do
|
|
||||||
def notice(output)
|
|
||||||
"Global #{output}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local = Module.new do
|
|
||||||
def notice(output)
|
|
||||||
"Local #{output}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Template.register_filter(global)
|
|
||||||
assert_equal 'Global test', Template.parse("{{'test' | notice }}").render!
|
|
||||||
assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, :filters => [local])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_only_intended_filters_make_it_there
|
def test_only_intended_filters_make_it_there
|
||||||
|
|
||||||
filter = Module.new do
|
filter = Module.new do
|
||||||
@@ -475,4 +457,22 @@ class ContextTest < Test::Unit::TestCase
|
|||||||
assert_kind_of CategoryDrop, @context['category']
|
assert_kind_of CategoryDrop, @context['category']
|
||||||
assert_equal @context, @context['category'].context
|
assert_equal @context, @context['category'].context
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_strict_variables_not_found
|
||||||
|
with_error_mode(:strict) do
|
||||||
|
@context['does_not_exist']
|
||||||
|
assert(@context.errors.length == 1)
|
||||||
|
assert_equal(@context.errors[0], 'Variable {{does_not_exist}} not found')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_strict_nested_variables_not_found
|
||||||
|
with_error_mode(:strict) do
|
||||||
|
@context['hash'] = {'this' => 'exists'}
|
||||||
|
@context['hash.does_not_exist']
|
||||||
|
assert(@context.errors.length == 1)
|
||||||
|
assert_equal(@context.errors[0], 'Variable {{hash.does_not_exist}} not found')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end # ContextTest
|
end # ContextTest
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class FileSystemTest < Test::Unit::TestCase
|
class FileSystemUnitTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_default
|
def test_default
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class I18nTest < Test::Unit::TestCase
|
class I18nUnitTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class LexerTest < Test::Unit::TestCase
|
class LexerUnitTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_strings
|
def test_strings
|
||||||
@@ -36,7 +36,7 @@ class TestClassC::LiquidDropClass
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class ModuleExTest < Test::Unit::TestCase
|
class ModuleExUnitTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class ParserTest < Test::Unit::TestCase
|
class ParserUnitTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_consume
|
def test_consume
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class RegexpTest < Test::Unit::TestCase
|
class RegexpUnitTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_empty
|
def test_empty
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class StrainerTest < Test::Unit::TestCase
|
class StrainerUnitTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
module AccessScopeFilters
|
module AccessScopeFilters
|
||||||
11
test/unit/tag_unit_test.rb
Normal file
11
test/unit/tag_unit_test.rb
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class TagUnitTest < Test::Unit::TestCase
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
def test_tag
|
||||||
|
tag = Tag.parse('tag', [], [], {})
|
||||||
|
assert_equal 'liquid::tag', tag.name
|
||||||
|
assert_equal '', tag.render(Context.new)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class CaseTagTest < Test::Unit::TestCase
|
class CaseTagUnitTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_case_nodelist
|
def test_case_nodelist
|
||||||
template = Liquid::Template.parse('{% case var %}{% when true %}WHEN{% else %}ELSE{% endcase %}')
|
template = Liquid::Template.parse('{% case var %}{% when true %}WHEN{% else %}ELSE{% endcase %}')
|
||||||
assert_equal ['WHEN', 'ELSE'], template.root.nodelist[0].nodelist
|
assert_equal ['WHEN', 'ELSE'], template.root.nodelist[0].nodelist
|
||||||
end
|
end
|
||||||
end # CaseTest
|
end
|
||||||
13
test/unit/tags/for_tag_unit_test.rb
Normal file
13
test/unit/tags/for_tag_unit_test.rb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class ForTagUnitTest < Test::Unit::TestCase
|
||||||
|
def test_for_nodelist
|
||||||
|
template = Liquid::Template.parse('{% for item in items %}FOR{% endfor %}')
|
||||||
|
assert_equal ['FOR'], template.root.nodelist[0].nodelist
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_for_else_nodelist
|
||||||
|
template = Liquid::Template.parse('{% for item in items %}FOR{% else %}ELSE{% endfor %}')
|
||||||
|
assert_equal ['FOR', 'ELSE'], template.root.nodelist[0].nodelist
|
||||||
|
end
|
||||||
|
end
|
||||||
8
test/unit/tags/if_tag_unit_test.rb
Normal file
8
test/unit/tags/if_tag_unit_test.rb
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class IfTagUnitTest < Test::Unit::TestCase
|
||||||
|
def test_if_nodelist
|
||||||
|
template = Liquid::Template.parse('{% if true %}IF{% else %}ELSE{% endif %}')
|
||||||
|
assert_equal ['IF', 'ELSE'], template.root.nodelist[0].nodelist
|
||||||
|
end
|
||||||
|
end
|
||||||
19
test/unit/template_unit_test.rb
Normal file
19
test/unit/template_unit_test.rb
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class TemplateUnitTest < Test::Unit::TestCase
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
def test_sets_default_localization_in_document
|
||||||
|
t = Template.new
|
||||||
|
t.parse('')
|
||||||
|
assert_instance_of I18n, t.root.options[:locale]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_sets_default_localization_in_context_with_quick_initialization
|
||||||
|
t = Template.new
|
||||||
|
t.parse('{{foo}}', :locale => I18n.new(fixture("en_locale.yml")))
|
||||||
|
|
||||||
|
assert_instance_of I18n, t.root.options[:locale]
|
||||||
|
assert_equal fixture("en_locale.yml"), t.root.options[:locale].path
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -24,11 +24,6 @@ class TokenizerTest < Test::Unit::TestCase
|
|||||||
private
|
private
|
||||||
|
|
||||||
def tokenize(source)
|
def tokenize(source)
|
||||||
tokenizer = Liquid::Tokenizer.new(source)
|
Liquid::Template.new.send(:tokenize, source)
|
||||||
tokens = []
|
|
||||||
while token = tokenizer.next
|
|
||||||
tokens << token
|
|
||||||
end
|
|
||||||
tokens
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class VariableTest < Test::Unit::TestCase
|
class VariableUnitTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_variable
|
def test_variable
|
||||||
@@ -134,71 +134,3 @@ class VariableTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
class VariableResolutionTest < Test::Unit::TestCase
|
|
||||||
include Liquid
|
|
||||||
|
|
||||||
def test_simple_variable
|
|
||||||
template = Template.parse(%|{{test}}|)
|
|
||||||
assert_equal 'worked', template.render!('test' => 'worked')
|
|
||||||
assert_equal 'worked wonderfully', template.render!('test' => 'worked wonderfully')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_simple_with_whitespaces
|
|
||||||
template = Template.parse(%| {{ test }} |)
|
|
||||||
assert_equal ' worked ', template.render!('test' => 'worked')
|
|
||||||
assert_equal ' worked wonderfully ', template.render!('test' => 'worked wonderfully')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_ignore_unknown
|
|
||||||
template = Template.parse(%|{{ test }}|)
|
|
||||||
assert_equal '', template.render!
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_hash_scoping
|
|
||||||
template = Template.parse(%|{{ test.test }}|)
|
|
||||||
assert_equal 'worked', template.render!('test' => {'test' => 'worked'})
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_preset_assigns
|
|
||||||
template = Template.parse(%|{{ test }}|)
|
|
||||||
template.assigns['test'] = 'worked'
|
|
||||||
assert_equal 'worked', template.render!
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_reuse_parsed_template
|
|
||||||
template = Template.parse(%|{{ greeting }} {{ name }}|)
|
|
||||||
template.assigns['greeting'] = 'Goodbye'
|
|
||||||
assert_equal 'Hello Tobi', template.render!('greeting' => 'Hello', 'name' => 'Tobi')
|
|
||||||
assert_equal 'Hello ', template.render!('greeting' => 'Hello', 'unknown' => 'Tobi')
|
|
||||||
assert_equal 'Hello Brian', template.render!('greeting' => 'Hello', 'name' => 'Brian')
|
|
||||||
assert_equal 'Goodbye Brian', template.render!('name' => 'Brian')
|
|
||||||
assert_equal({'greeting'=>'Goodbye'}, template.assigns)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_assigns_not_polluted_from_template
|
|
||||||
template = Template.parse(%|{{ test }}{% assign test = 'bar' %}{{ test }}|)
|
|
||||||
template.assigns['test'] = 'baz'
|
|
||||||
assert_equal 'bazbar', template.render!
|
|
||||||
assert_equal 'bazbar', template.render!
|
|
||||||
assert_equal 'foobar', template.render!('test' => 'foo')
|
|
||||||
assert_equal 'bazbar', template.render!
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_hash_with_default_proc
|
|
||||||
template = Template.parse(%|Hello {{ test }}|)
|
|
||||||
assigns = Hash.new { |h,k| raise "Unknown variable '#{k}'" }
|
|
||||||
assigns['test'] = 'Tobi'
|
|
||||||
assert_equal 'Hello Tobi', template.render!(assigns)
|
|
||||||
assigns.delete('test')
|
|
||||||
e = assert_raises(RuntimeError) {
|
|
||||||
template.render!(assigns)
|
|
||||||
}
|
|
||||||
assert_equal "Unknown variable 'test'", e.message
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_multiline_variable
|
|
||||||
assert_equal 'worked', Template.parse("{{\ntest\n}}").render!('test' => 'worked')
|
|
||||||
end
|
|
||||||
end # VariableTest
|
|
||||||
Reference in New Issue
Block a user