Compare commits

..

16 Commits

Author SHA1 Message Date
Tom Burns
cb807e9484 Add extension to gemspec 2013-03-19 18:30:10 -04:00
Tom Burns
e53884ce08 Remove generated C file from repo 2013-03-19 18:30:09 -04:00
Tom Burns
78fe69259d Allow floats > 9 2013-03-19 18:30:09 -04:00
Tom Burns
afe8474a2b Remove extension Makefile 2013-03-19 18:30:09 -04:00
Tobias Lutke
e404f6d8e1 fixed but with single char identifiers 2013-03-19 18:30:09 -04:00
Tobias Lutke
8867d6a65f enable weird method endings 2013-03-19 18:30:09 -04:00
Tobias Lutke
141fa898d0 enabled hard mode 2013-03-19 18:30:09 -04:00
Tobias Lutke
58b04bae74 conquered parser_tests 2013-03-19 18:30:09 -04:00
Tobias Lütke
28737171f7 wip 2013-03-19 18:30:09 -04:00
Tobias Lütke
594b6da8c5 Initial checkin 2013-03-19 18:30:09 -04:00
Tobias Lutke
cf5fa5923d remove binaries 2013-03-19 18:30:09 -04:00
Tobias Lutke
7cc10b9abf various speedups 2013-03-19 18:30:09 -04:00
Tobias Lütke
20beec2de3 Convert parser to C99 2013-03-19 18:30:09 -04:00
Tobias Lutke
06486fd21d Implement naive recusrive descent
Ragel doesn't allow us to recurse so we simply
reinvoke the parser for each step.
2013-03-19 18:30:08 -04:00
Tobias Lütke
7dcf578898 Replace regexpes with Ragel grammer
context parsing was handrolled and pretty ad-hoc
this branch exists to explore parsing the context
through a defined fsm as produced by Ragel
2013-03-19 18:30:08 -04:00
Tobias Lutke
006e7475f4 fix benchmarks 2013-03-19 18:30:08 -04:00
138 changed files with 2763 additions and 4724 deletions

7
.gitignore vendored
View File

@@ -4,7 +4,8 @@
pkg pkg
*.rbc *.rbc
.rvmrc .rvmrc
.ruby-version *.o
*.bundle *.bundle
/tmp ext/liquid/Makefile
Gemfile.lock ext/liquid/liquid_context.c

View File

@@ -1,14 +1,11 @@
rvm: rvm:
- 1.8.7
- 1.9.3 - 1.9.3
- 2.0.0 - ree
- 2.1.0 - jruby-18mode
- jruby-19mode - jruby-19mode
- jruby-head - rbx-18mode
- rbx-19mode - rbx-19mode
matrix:
allow_failures:
- rvm: rbx-19mode
- rvm: jruby-head
script: "rake test" script: "rake test"

View File

@@ -1,26 +0,0 @@
# How to contribute
## Things we will merge
* Bugfixes
* Performance improvements
* Features which are likely to be useful to the majority of Liquid users
## Things we won't merge
* Code which introduces considerable performance degrations
* Code which touches performance critical parts of Liquid and comes without benchmarks
* Features which are not important for most people (we want to keep the core Liquid code small and tidy)
* Features which can easily be implemented on top of Liquid (for example as a custom filter or custom filesystem)
* Code which comes without tests
* Code which breaks existing tests
## Workflow
* Fork the Liquid repository
* Create a new branch in your fork
* If it makes sense, add tests for your code and run a performance benchmark
* Make sure all tests pass
* Create a pull request
* In the description, ping one of [@boourns](https://github.com/boourns), [@fw42](https://github.com/fw42), [@camilo](https://github.com/camilo), [@dylanahsmith](https://github.com/dylanahsmith), or [@arthurnn](https://github.com/arthurnn) and ask for a code review.

View File

@@ -1,3 +0,0 @@
source 'https://rubygems.org'
gemspec

View File

@@ -1,68 +1,5 @@
# Liquid Version History # Liquid Version History
## 3.0.0 / not yet released / branch "master"
* ...
* Add a to_s default for liquid drops, see #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, see #250 [Nick Jones, dntj]
* Prevent arbitrary method invocation on condition objects, see #274 [Dylan Thacker-Smith, dylanahsmith]
* Don't call to_sym when creating conditions for security reasons, see #273 [Bouke van der Bijl, bouk]
* Fix resource counting bug with respond_to?(:length), see #263 [Florian Weingarten, fw42]
* Allow specifying custom patterns for template filenames, see #284 [Andrei Gladkyi, agladkyi]
* Allow drops to optimize loading a slice of elements, see #282 [Tom Burns, boourns]
* Support for passing variables to snippets in subdirs, see #271 [Joost Hietbrink, joost]
* Add a class cache to avoid runtime extend calls, see #249 [James Tucker, raggi]
* Remove some legacy Ruby 1.8 compatibility code, see #276 [Florian Weingarten, fw42]
* Add default filter to standard filters, see #267 [Derrick Reimer, djreimer]
* Add optional strict parsing and warn parsing, see #235 [Tristan Hume, trishume]
* Add I18n syntax error translation, see #241 [Simon Hørup Eskildsen, Sirupsen]
* Make sort filter work on enumerable drops, see #239 [Florian Weingarten, fw42]
* Fix clashing method names in enumerable drops, see #238 [Florian Weingarten, fw42]
* Make map filter work on enumerable drops, see #233 [Florian Weingarten, fw42]
* Improved whitespace stripping for blank blocks, related to #216 [Florian Weingarten, fw42]
* Raise `Liquid::ArgumentError` instead of `::ArgumentError` when filter has wrong number of arguments #309 [Bogdan Gusiev, bogdan]
## 2.6.0 / 2013-11-25 / branch "2.6-stable"
IMPORTANT: Liquid 2.6 is going to be the last version of Liquid which maintains explicit Ruby 1.8 compatability.
The following releases will only be tested against Ruby 1.9 and Ruby 2.0 and are likely to break on Ruby 1.8.
* Bugfix for #106: fix example servlet [gnowoel]
* Bugfix for #97: strip_html filter supports multi-line tags [Jo Liss, 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]
* 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]
## 2.5.4 / 2013-11-11 / branch "2.5-stable"
* Fix "can't convert Fixnum into String" for "replace", see #173, [wǒ_is神仙, jsw0528]
## 2.5.3 / 2013-10-09
* #232, #234, #237: Fix map filter bugs [Florian Weingarten, fw42]
## 2.5.2 / 2013-09-03 / deleted
Yanked from rubygems, as it contained too many changes that broke compatibility. Those changes will be on following major releases.
## 2.5.1 / 2013-07-24
* #230: Fix security issue with map filter, Use invoke_drop in map filter [Florian Weingarten, fw42]
## 2.5.0 / 2013-03-06 ## 2.5.0 / 2013-03-06
* Prevent Object methods from being called on drops * Prevent Object methods from being called on drops
@@ -71,7 +8,6 @@ Yanked from rubygems, as it contained too many changes that broke compatibility.
* Fix filter parser for args without space separators * Fix filter parser for args without space separators
* Add support for filter keyword arguments * Add support for filter keyword arguments
## 2.4.0 / 2012-08-03 ## 2.4.0 / 2012-08-03
* Performance improvements * Performance improvements

View File

@@ -1,12 +1,5 @@
[![Build Status](https://secure.travis-ci.org/Shopify/liquid.png?branch=master)](http://travis-ci.org/Shopify/liquid)
# Liquid template engine # Liquid template engine
* [Contributing guidelines](CONTRIBUTING.md)
* [Version history](History.md)
* [Liquid documentation from Shopify](http://docs.shopify.com/themes/liquid-basics)
* [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki)
* [Website](http://liquidmarkup.org/)
## Introduction ## Introduction
Liquid is a template engine which was written with very specific requirements: Liquid is a template engine which was written with very specific requirements:
@@ -48,26 +41,4 @@ For standard use you can just pass it the content of a file and call render with
@template.render('name' => 'tobi') # => "hi tobi" @template.render('name' => 'tobi') # => "hi tobi"
``` ```
### Error Modes [![Build Status](https://secure.travis-ci.org/Shopify/liquid.png)](http://travis-ci.org/Shopify/liquid)
Setting the error mode of Liquid lets you specify how strictly you want your templates to be interpreted.
Normally the parser is very lax and will accept almost anything without error. Unfortunately this can make
it very hard to debug and can lead to unexpected behaviour.
Liquid also comes with a stricter parser that can be used when editing templates to give better error messages
when templates are invalid. You can enable this new parser like this:
```ruby
Liquid::Template.error_mode = :strict # Raises a SyntaxError when invalid syntax is used
Liquid::Template.error_mode = :warn # Adds errors to template.errors but continues as normal
Liquid::Template.error_mode = :lax # The default mode, accepts almost anything.
```
If you want to set the error mode only on specific templates you can pass `:error_mode` as an option to `parse`:
```ruby
Liquid::Template.parse(source, :error_mode => :strict)
```
This is useful for doing things like enabling strict mode only in the theme editor.
It is recommended that you enable `:strict` or `:warn` mode on new apps to stop invalid templates from being created.
It is also recommended that you use it in the template editors of existing apps to give editors better error messages.

102
Rakefile
View File

@@ -1,60 +1,73 @@
#!/usr/bin/env ruby
require 'rubygems'
require 'rake' require 'rake'
require 'rake/clean'
require 'fileutils'
require 'rake/testtask' require 'rake/testtask'
require 'rake/extensiontask' require 'rubygems/package_task'
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
require "liquid/version"
task :default => 'test' task :default => [:compile, :test]
desc 'run test suite with default parser' task :gen do
Rake::TestTask.new(:base_test) do |t| sh "leg -oext/liquid/liquid_context.c ext/liquid/liquid_context.leg"
end
task :compile => [:gen, :liquid_ext]
extension = "liquid_ext"
ext = "ext/liquid"
ext_so = "#{ext}/#{extension}.#{RbConfig::CONFIG['DLEXT']}"
ext_files = FileList[
"#{ext}/*.c",
"#{ext}/*.h",
"#{ext}/*.leg",
"#{ext}/extconf.rb",
"#{ext}/Makefile",
"lib"
]
task "lib" do
directory "lib"
end
desc "Builds just the #{extension} extension"
task extension.to_sym => [:gen, "#{ext}/Makefile", ext_so ]
file "#{ext}/Makefile" => ["#{ext}/extconf.rb"] do
Dir.chdir(ext) do ruby "extconf.rb" end
end
file ext_so => ext_files do
Dir.chdir(ext) do
sh "make"
end
cp ext_so, "lib"
end
Rake::TestTask.new(:test => [:gen, 'liquid_ext']) do |t|
t.libs << '.' << 'lib' << 'test' t.libs << '.' << 'lib' << 'test'
t.test_files = FileList['test/liquid/**/*_test.rb'] t.test_files = FileList['test/liquid/**/*_test.rb']
t.verbose = false t.verbose = false
end end
desc 'run test suite with warn error mode' gemspec = eval(File.read('liquid.gemspec'))
task :warn_test do Gem::PackageTask.new(gemspec) do |pkg|
ENV['LIQUID_PARSER_MODE'] = 'warn' pkg.gem_spec = gemspec
Rake::Task['base_test'].invoke
end end
desc 'runs test suite with both strict and lax parsers' desc "Build the gem and release it to rubygems.org"
task :test do task :release => :gem do
ENV['LIQUID_PARSER_MODE'] = 'lax' sh "gem push pkg/liquid-#{gemspec.version}.gem"
Rake::Task['base_test'].invoke
ENV['LIQUID_PARSER_MODE'] = 'strict'
Rake::Task['base_test'].reenable
Rake::Task['base_test'].invoke
end
task :gem => :build
task :build do
system "gem build liquid.gemspec"
end
task :install => :build do
system "gem install liquid-#{Liquid::VERSION}.gem"
end
task :release => :build do
system "git tag -a v#{Liquid::VERSION} -m 'Tagging #{Liquid::VERSION}'"
system "git push --tags"
system "gem push liquid-#{Liquid::VERSION}.gem"
system "rm liquid-#{Liquid::VERSION}.gem"
end end
namespace :benchmark do namespace :benchmark do
desc "Run the liquid benchmark with lax parsing" desc "Run the liquid benchmark"
task :run do task :run do
ruby "./performance/benchmark.rb lax" ruby "./performance/benchmark.rb"
end end
desc "Run the liquid benchmark with strict parsing"
task :strict do
ruby "./performance/benchmark.rb strict"
end
end end
@@ -65,13 +78,9 @@ namespace :profile do
ruby "./performance/profile.rb" ruby "./performance/profile.rb"
end end
task :stackprof do
ruby "./performance/stackprof.rb"
end
desc "Run KCacheGrind" desc "Run KCacheGrind"
task :grind => :run do task :grind => :run do
system "qcachegrind /tmp/liquid.rubyprof_calltreeprinter.txt" system "qcachegrind /tmp//callgrind.liquid.txt"
end end
end end
@@ -80,8 +89,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
Rake::ExtensionTask.new "liquid" do |ext|
ext.lib_dir = "lib/liquid"
end
Rake::Task[:test].prerequisites << :compile

View File

@@ -23,7 +23,11 @@ class Servlet < LiquidServlet
end end
def products def products
{ 'products' => products_list, 'description' => description, 'section' => 'Snowboards', 'cool_products' => true} { 'products' => products_list, 'section' => 'Snowboards', 'cool_products' => true}
end
def description
"List of Products ~ This is a list of products with price and description."
end end
private private
@@ -34,8 +38,4 @@ class Servlet < LiquidServlet
{'name' => 'Arbor Diamond', 'price' => 59900, 'description' => 'the *arbor diamond* is a made up product because im obsessed with arbor and have no creativity'}] {'name' => 'Arbor Diamond', 'price' => 59900, 'description' => 'the *arbor diamond* is a made up product because im obsessed with arbor and have no creativity'}]
end end
def description
"List of Products ~ This is a list of products with price and description."
end
end end

View File

@@ -13,7 +13,7 @@ class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet
def handle(type, req, res) def handle(type, req, res)
@request, @response = req, res @request, @response = req, res
@request.path_info =~ /(\w+)\z/ @request.path_info =~ /(\w+)$/
@action = $1 || 'index' @action = $1 || 'index'
@assigns = send(@action) if respond_to?(@action) @assigns = send(@action) if respond_to?(@action)

View File

@@ -1,27 +1,27 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head> <head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" /> <meta http-equiv="Content-Language" content="en-us" />
<title>products</title> <title>products</title>
<meta name="ROBOTS" content="ALL" /> <meta name="ROBOTS" content="ALL" />
<meta http-equiv="imagetoolbar" content="no" /> <meta http-equiv="imagetoolbar" content="no" />
<meta name="MSSmartTagsPreventParsing" content="true" /> <meta name="MSSmartTagsPreventParsing" content="true" />
<meta name="Copyright" content="(c) 2005 Copyright content: Copyright design: Tobias Luetke" /> <meta name="Copyright" content="(c) 2005 Copyright content: Copyright design: Tobias Luetke" />
<!-- (c) Copyright 2005 by Tobias Luetke All Rights Reserved. --> <!-- (c) Copyright 2005 by Tobias Luetke All Rights Reserved. -->
</head> </head>
<body> <body>
<h1>{{ description | split: '~' | first }}</h1> <h1>{{ description | split: '~' | first }}</h1>
<h2>{{ description | split: '~' | last }}</h2> <h2>{{ description | split: '~' | last }}</h2>
<h2>There are currently {{products | count}} products in the {{section}} catalog</h2> <h2>There are currently {{products | count}} products in the {{section}} catalog</h2>
{% if cool_products %} {% if cool_products %}
Cool products :) Cool products :)
@@ -45,5 +45,5 @@
</ul> </ul>
</body> </body>
</html> </html>

View File

@@ -1,167 +0,0 @@
#include "liquid_ext.h"
VALUE cLiquidBlock;
ID intern_assert_missing_delimitation, intern_block_delimiter, intern_is_blank,
intern_new_with_options, intern_tags, intern_unknown_tag, intern_unterminated_tag,
intern_unterminated_variable;
struct liquid_tag
{
char *name, *markup;
long name_length, markup_length;
};
static bool parse_tag(struct liquid_tag *tag, char *token, long token_length)
{
// Strip {{ and }} braces
token += 2;
token_length -= 4;
char *end = token + token_length;
while (token < end && isspace(*token))
token++;
tag->name = token;
char c = *token;
while (token < end && (isalnum(c) || c == '_'))
c = *(++token);
tag->name_length = token - tag->name;
if (!tag->name_length) {
memset(tag, 0, sizeof(*tag));
return false;
}
while (token < end && isspace(*token))
token++;
tag->markup = token;
char *last = end - 1;
while (token < last && isspace(*last))
last--;
end = last + 1;
tag->markup_length = end - token;
return true;
}
static VALUE rb_parse_body(VALUE self, VALUE tokenizerObj)
{
struct liquid_tokenizer *tokenizer = LIQUID_TOKENIZER_GET_STRUCT(tokenizerObj);
bool blank = true;
VALUE nodelist = rb_iv_get(self, "@nodelist");
if (nodelist == Qnil) {
nodelist = rb_ary_new();
rb_iv_set(self, "@nodelist", nodelist);
} else {
rb_ary_clear(nodelist);
}
struct token token;
while (true) {
liquid_tokenizer_next(tokenizer, &token);
switch (token.type) {
case TOKEN_NONE:
/*
* Make sure that it's ok to end parsing in the current block.
* Effectively this method will throw an exception unless the current block is
* of type Document
*/
rb_funcall(self, intern_assert_missing_delimitation, 0);
goto done;
case TOKEN_INVALID:
{
VALUE token_obj = rb_str_new(token.str, token.length);
if (token.str[1] == '%')
rb_funcall(self, intern_unterminated_tag, 1, token_obj);
else
rb_funcall(self, intern_unterminated_variable, 1, token_obj);
break;
}
case TOKEN_TAG:
{
struct liquid_tag tag;
if (!parse_tag(&tag, token.str, token.length)) {
// FIXME: provide more appropriate error message
rb_funcall(self, intern_unterminated_tag, 1, rb_str_new(token.str, token.length));
} else {
if (tag.name_length >= 3 && !memcmp(tag.name, "end", 3)) {
VALUE block_delimiter = rb_funcall(self, intern_block_delimiter, 0);
if (TYPE(block_delimiter) == T_STRING &&
tag.name_length == RSTRING_LEN(block_delimiter) &&
!memcmp(tag.name, RSTRING_PTR(block_delimiter), tag.name_length))
{
goto done;
}
}
VALUE tags = rb_funcall(cLiquidTemplate, intern_tags, 0);
Check_Type(tags, T_HASH);
VALUE tag_name = rb_str_new(tag.name, tag.name_length);
VALUE tag_class = rb_hash_lookup(tags, tag_name);
VALUE markup = rb_str_new(tag.markup, tag.markup_length);
if (tag_class != Qnil) {
VALUE options = rb_iv_get(self, "@options");
if (options == Qnil)
options = rb_hash_new();
VALUE new_tag = rb_funcall(tag_class, intern_new_with_options, 4,
tag_name, markup, tokenizerObj, options);
if (blank) {
VALUE blank_block = rb_funcall(new_tag, intern_is_blank, 0);
if (blank_block == Qnil || blank_block == Qfalse)
blank = false;
}
rb_ary_push(nodelist, new_tag);
} else {
rb_funcall(self, intern_unknown_tag, 3, tag_name, markup, tokenizerObj);
/*
* multi-block tags may store the nodelist in a block array on unknown_tag
* then replace @nodelist with a new array. We need to use the new array
* for the block following the tag token.
*/
nodelist = rb_iv_get(self, "@nodelist");
}
}
break;
}
case TOKEN_VARIABLE:
{
VALUE markup = rb_str_new(token.str + 2, token.length - 4);
VALUE options = rb_iv_get(self, "@options");
VALUE new_var = rb_funcall(cLiquidVariable, intern_new, 2, markup, options);
rb_ary_push(nodelist, new_var);
blank = false;
break;
}
case TOKEN_STRING:
rb_ary_push(nodelist, liquid_string_slice_new(token.str, token.length));
if (blank) {
int i;
for (i = 0; i < token.length; i++) {
if (!isspace(token.str[i])) {
blank = false;
break;
}
}
}
break;
}
}
done:
rb_iv_set(self, "@blank", blank ? Qtrue : Qfalse);
return Qnil;
}
void init_liquid_block()
{
intern_assert_missing_delimitation = rb_intern("assert_missing_delimitation!");
intern_block_delimiter = rb_intern("block_delimiter");
intern_is_blank = rb_intern("blank?");
intern_new_with_options = rb_intern("new_with_options");
intern_tags = rb_intern("tags");
intern_unknown_tag = rb_intern("unknown_tag");
intern_unterminated_tag = rb_intern("unterminated_tag");
intern_unterminated_variable = rb_intern("unterminated_variable");
cLiquidBlock = rb_define_class_under(mLiquid, "Block", cLiquidTag);
rb_define_method(cLiquidBlock, "parse_body", rb_parse_body, 1);
}

View File

@@ -1,8 +0,0 @@
#ifndef LIQUID_BLOCK_H
#define LIQUID_BLOCK_H
void init_liquid_block();
extern VALUE cLiquidBlock;
#endif

View File

@@ -1,3 +1,6 @@
require 'mkmf' require 'mkmf'
$CFLAGS << ' -Wall'
create_makefile("liquid/liquid") dir_config("liquid_ext")
have_library("c", "main")
create_makefile("liquid_ext")

View File

@@ -0,0 +1,98 @@
%{
//uncomment to get more debug instrumentation
//
//#define YY_DEBUG
#include <ruby.h>
#define EMIT(sym, data) \
rb_ary_push(ctx->rb_tokens, rb_ary_new3(2, ID2SYM(rb_intern(sym)), data));
#define yy_rb_str rb_str_new(yytext, yyleng)
#define YYSTYPE VALUE
#define YY_CTX_LOCAL
#define YY_CTX_MEMBERS VALUE rb_tokens; char *p; int p_len;
#define YY_INPUT(buf, result, max_size) { \
result = ctx->p_len; \
if (result>0 || EOF == ctx->p[0]) { \
if (max_size < result) { result = max_size; } \
strncpy(buf, ctx->p, result); \
buf[result] = '\0'; \
yyprintf((stderr, "\nREFILLING %d chars now:<%s>", result, buf)); \
ctx->p += result; ctx->p_len -= result; \
yyprintf((stderr, "\nREFILLING DONE size left: %d <%s>", ctx->p_len, ctx->p)); \
} \
}
%}
grammar = primary
| entity
| range
| hash { EMIT("lookup", Qnil); }
;
hash = '[' (primary|entity) ']';
primary = var:const { EMIT("id", var); }
| var:string { EMIT("id", var); }
| var:numeric { EMIT("id", var); }
;
accessors = hash { EMIT("call", Qnil); }
| '.first' { EMIT("buildin", rb_str_new2("first")); }
| '.last' { EMIT("buildin", rb_str_new2("last")); }
| '.size' { EMIT("buildin", rb_str_new2("size")); }
| '.' <identifier> { EMIT("id", yy_rb_str); EMIT("call", Qnil); }
;
entity = <identifier> { EMIT("id", yy_rb_str); EMIT("lookup", Qnil); }
accessors*
;
rangelet = var:integer { EMIT("id", var); }
| entity
;
range = '(' rangelet '..' rangelet ')' { EMIT("range", Qnil); }
string = ['] < ( !['] . )* > ['] { $$ = yy_rb_str; }
| ["] < ( !["] . )* > ["] { $$ = yy_rb_str; }
;
numeric = float
| integer
;
float = <'-'? digit+'.'digit+> { $$ = rb_funcall(rb_cObject, rb_intern("Float"), 1, yy_rb_str); }
integer = <'-'? digit+> { $$ = rb_funcall(rb_cObject, rb_intern("Integer"), 1, yy_rb_str); }
const = "true" { $$ = Qtrue; }
| 'false' { $$ = Qfalse; }
| 'nil' { $$ = Qnil; }
| 'null' { $$ = Qnil; }
;
digit = [0-9];
identifier = [a-zA-Z][a-zA-Z0-9_\-]*[?!]?;
%%
VALUE liquid_context_parse_impl(VALUE self, VALUE text) {
char *p;
int len;
yycontext ctx;
memset(&ctx, 0, sizeof(yycontext));
ctx.p = RSTRING_PTR(text);
ctx.p_len = (int) RSTRING_LEN(text);
ctx.rb_tokens = rb_ary_new();
yyparse(&ctx);
return ctx.rb_tokens;
}

View File

@@ -1,18 +1,13 @@
#include "liquid_ext.h" #include <ruby.h>
VALUE mLiquid; static VALUE rb_Liquid;
VALUE cLiquidTemplate, cLiquidTag, cLiquidVariable; static VALUE rb_Parser;
ID intern_new;
void Init_liquid(void) VALUE liquid_context_parse_impl(VALUE text);
void Init_liquid_ext()
{ {
intern_new = rb_intern("new"); rb_Liquid = rb_define_module("Liquid");
mLiquid = rb_define_module("Liquid"); rb_Parser = rb_define_class_under(rb_Liquid, "Parser", rb_cObject);
cLiquidTemplate = rb_define_class_under(mLiquid, "Template", rb_cObject); rb_define_singleton_method(rb_Parser, "parse", liquid_context_parse_impl, 1);
cLiquidTag = rb_define_class_under(mLiquid, "Tag", rb_cObject);
cLiquidVariable = rb_define_class_under(mLiquid, "Variable", rb_cObject);
init_liquid_tokenizer();
init_liquid_block();
init_liquid_string_slice();
} }

View File

@@ -1,17 +0,0 @@
#ifndef LIQUID_EXT_H
#define LIQUID_EXT_H
#include <stdbool.h>
#include <ctype.h>
#include <ruby.h>
#include "tokenizer.h"
#include "block.h"
#include "slice.h"
#include "utils.h"
extern ID intern_new;
extern VALUE mLiquid;
extern VALUE cLiquidTemplate, cLiquidTag, cLiquidVariable;
#endif

View File

@@ -1,167 +0,0 @@
#include "liquid_ext.h"
VALUE cLiquidStringSlice;
static void mark_slice(void *ptr)
{
if (!ptr)
return;
struct string_slice *slice = ptr;
rb_gc_mark(slice->source);
}
static void free_slice(void *ptr)
{
struct string_slice *slice = ptr;
xfree(slice);
}
VALUE liquid_string_slice_new(const char *str, long length)
{
return rb_funcall(cLiquidStringSlice, intern_new, 3, rb_str_new(str, length), INT2FIX(0), INT2FIX(length));
}
static VALUE rb_allocate(VALUE klass)
{
struct string_slice *slice;
VALUE obj = Data_Make_Struct(klass, struct string_slice, mark_slice, free_slice, slice);
return obj;
}
static VALUE rb_initialize(VALUE self, VALUE source, VALUE offset_value, VALUE length_value)
{
long offset = rb_fix2int(offset_value);
long length = rb_fix2int(length_value);
if (length < 0)
rb_raise(rb_eArgError, "negative string length");
if (offset < 0)
rb_raise(rb_eArgError, "negative string offset");
if (TYPE(source) == T_DATA && RBASIC_CLASS(source) == cLiquidStringSlice) {
struct string_slice *source_slice = DATA_PTR(source);
source = source_slice->source;
offset += source_slice->str - RSTRING_PTR(source);
} else {
source = rb_string_value(&source);
source = rb_str_dup_frozen(source);
}
struct string_slice *slice;
Data_Get_Struct(self, struct string_slice, slice);
slice->source = source;
slice->str = RSTRING_PTR(source) + offset;
slice->length = length;
if (length > RSTRING_LEN(source) - offset)
rb_raise(rb_eArgError, "slice bounds outside source string bounds");
return Qnil;
}
static VALUE rb_slice_to_str(VALUE self)
{
struct string_slice *slice;
Data_Get_Struct(self, struct string_slice, slice);
VALUE source = slice->source;
if (slice->str == RSTRING_PTR(source) && slice->length == RSTRING_LEN(source))
return source;
source = rb_str_new(slice->str, slice->length);
slice->source = source;
slice->str = RSTRING_PTR(source);
return source;
}
static VALUE rb_slice_slice(VALUE self, VALUE offset, VALUE length)
{
return rb_funcall(cLiquidStringSlice, intern_new, 3, self, offset, length);
}
static VALUE rb_slice_length(VALUE self)
{
struct string_slice *slice;
Data_Get_Struct(self, struct string_slice, slice);
return INT2FIX(slice->length);
}
static VALUE rb_slice_equal(VALUE self, VALUE other)
{
struct string_slice *this_slice;
Data_Get_Struct(self, struct string_slice, this_slice);
const char *other_str;
long other_length;
if (TYPE(other) == T_DATA && RBASIC_CLASS(other) == cLiquidStringSlice) {
struct string_slice *other_slice = DATA_PTR(other);
other_str = other_slice->str;
other_length = other_slice->length;
} else {
other = rb_string_value(&other);
other_length = RSTRING_LEN(other);
other_str = RSTRING_PTR(other);
}
bool equal = this_slice->length == other_length && !memcmp(this_slice->str, other_str, other_length);
return equal ? Qtrue : Qfalse;
}
static VALUE rb_slice_inspect(VALUE self)
{
VALUE quoted = rb_str_inspect(rb_slice_to_str(self));
return rb_sprintf("#<Liquid::StringSlice: %.*s>", (int)RSTRING_LEN(quoted), RSTRING_PTR(quoted));
}
static VALUE rb_slice_join(VALUE klass, VALUE ary)
{
ary = rb_ary_to_ary(ary);
long i;
long result_length = 0;
for (i = 0; i < RARRAY_LEN(ary); i++) {
VALUE element = RARRAY_AREF(ary, i);
if (TYPE(element) == T_DATA && RBASIC_CLASS(element) == cLiquidStringSlice) {
struct string_slice *slice = DATA_PTR(element);
result_length += slice->length;
} else if (TYPE(element) == T_STRING) {
result_length += RSTRING_LEN(element);
}
}
VALUE result = rb_str_buf_new(result_length);
for (i = 0; i < RARRAY_LEN(ary); i++) {
VALUE element = RARRAY_AREF(ary, i);
const char *element_string;
long element_length;
if (TYPE(element) == T_DATA && RBASIC_CLASS(element) == cLiquidStringSlice) {
struct string_slice *slice = DATA_PTR(element);
element_string = slice->str;
element_length = slice->length;
} else if (NIL_P(element)) {
continue;
} else {
element = rb_check_string_type(element);
if (NIL_P(element))
continue;
element_string = RSTRING_PTR(element);
element_length = RSTRING_LEN(element);
}
rb_str_buf_cat(result, element_string, element_length);
}
return result;
}
void init_liquid_string_slice()
{
cLiquidStringSlice = rb_define_class_under(mLiquid, "StringSlice", rb_cObject);
rb_define_singleton_method(cLiquidStringSlice, "join", rb_slice_join, 1);
rb_define_alloc_func(cLiquidStringSlice, rb_allocate);
rb_define_method(cLiquidStringSlice, "initialize", rb_initialize, 3);
rb_define_method(cLiquidStringSlice, "==", rb_slice_equal, 1);
rb_define_method(cLiquidStringSlice, "length", rb_slice_length, 0);
rb_define_alias(cLiquidStringSlice, "size", "length");
rb_define_method(cLiquidStringSlice, "slice", rb_slice_slice, 2);
rb_define_method(cLiquidStringSlice, "to_str", rb_slice_to_str, 0);
rb_define_alias(cLiquidStringSlice, "to_s", "to_str");
rb_define_method(cLiquidStringSlice, "inspect", rb_slice_inspect, 0);
}

View File

@@ -1,18 +0,0 @@
#ifndef LIQUID_SLICE_H
#define LIQUID_SLICE_H
extern VALUE cLiquidStringSlice;
struct string_slice {
VALUE source;
const char *str;
long length;
};
VALUE liquid_string_slice_new(const char *str, long length);
void init_liquid_string_slice();
#define STRING_SLICE_GET_STRUCT(obj) ((struct string_slice *)obj_get_data_ptr(obj, cLiquidStringSlice))
#endif

View File

@@ -1,113 +0,0 @@
#include "liquid_ext.h"
VALUE cLiquidTokenizer;
static void free_tokenizer(void *ptr)
{
struct liquid_tokenizer *tokenizer = ptr;
xfree(tokenizer);
}
static VALUE rb_allocate(VALUE klass)
{
VALUE obj;
struct liquid_tokenizer *tokenizer;
obj = Data_Make_Struct(klass, struct liquid_tokenizer, NULL, free_tokenizer, tokenizer);
return obj;
}
static VALUE rb_initialize(VALUE self, VALUE source)
{
struct liquid_tokenizer *tokenizer;
source = rb_string_value(&source);
Data_Get_Struct(self, struct liquid_tokenizer, tokenizer);
tokenizer->cursor = RSTRING_PTR(source);
tokenizer->length = RSTRING_LEN(source);
return Qnil;
}
void liquid_tokenizer_next(struct liquid_tokenizer *tokenizer, struct token *token)
{
if (tokenizer->length <= 0) {
memset(token, 0, sizeof(*token));
return;
}
token->type = TOKEN_STRING;
char *cursor = tokenizer->cursor;
char *last = tokenizer->cursor + tokenizer->length - 1;
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;
}
char *incomplete_end = cursor;
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;
}
cursor = incomplete_end;
goto found;
} else {
while (cursor < last) {
if (*cursor++ != '}')
continue;
if (*cursor++ != '}') {
incomplete_end = cursor - 1;
continue;
}
token->type = TOKEN_VARIABLE;
goto found;
}
cursor = incomplete_end;
goto found;
}
}
cursor = last + 1;
found:
token->str = tokenizer->cursor;
token->length = cursor - tokenizer->cursor;
tokenizer->cursor += token->length;
tokenizer->length -= token->length;
}
static VALUE rb_next(VALUE self)
{
struct liquid_tokenizer *tokenizer;
Data_Get_Struct(self, struct liquid_tokenizer, tokenizer);
struct token token;
liquid_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, rb_allocate);
rb_define_method(cLiquidTokenizer, "initialize", rb_initialize, 1);
rb_define_method(cLiquidTokenizer, "next", rb_next, 0);
rb_define_alias(cLiquidTokenizer, "shift", "next");
}

View File

@@ -1,30 +0,0 @@
#ifndef LIQUID_TOKENIZER_H
#define LIQUID_TOKENIZER_H
extern VALUE cLiquidTokenizer;
enum token_type {
TOKEN_NONE,
TOKEN_INVALID,
TOKEN_STRING,
TOKEN_TAG,
TOKEN_VARIABLE
};
struct token {
enum token_type type;
char *str;
int length;
};
struct liquid_tokenizer {
char *cursor;
int length;
};
void init_liquid_tokenizer();
void liquid_tokenizer_next(struct liquid_tokenizer *tokenizer, struct token *token);
#define LIQUID_TOKENIZER_GET_STRUCT(obj) ((struct liquid_tokenizer *)obj_get_data_ptr(obj, cLiquidTokenizer))
#endif

View File

@@ -1,21 +0,0 @@
#include <ruby.h>
void raise_type_error(VALUE expected, VALUE got)
{
rb_raise(rb_eTypeError, "wrong argument type %s (expected %s)",
rb_class2name(got), rb_class2name(expected));
}
void check_class(VALUE obj, int type, VALUE klass)
{
Check_Type(obj, type);
VALUE obj_klass = RBASIC_CLASS(obj);
if (obj_klass != klass)
raise_type_error(klass, obj_klass);
}
void *obj_get_data_ptr(VALUE obj, VALUE klass)
{
check_class(obj, T_DATA, klass);
return DATA_PTR(obj);
}

View File

@@ -1,8 +0,0 @@
#ifndef LIQUID_UTILS_H
#define LIQUID_UTILS_H
void raise_type_error(VALUE expected, VALUE got);
void check_class(VALUE klass);
void *obj_get_data_ptr(VALUE obj, VALUE klass);
#endif

View File

@@ -30,22 +30,21 @@ module Liquid
VariableSegment = /[\w\-]/ VariableSegment = /[\w\-]/
VariableStart = /\{\{/ VariableStart = /\{\{/
VariableEnd = /\}\}/ VariableEnd = /\}\}/
VariableIncompleteEnd = /\}\}?/
QuotedString = /"[^"]*"|'[^']*'/ QuotedString = /"[^"]*"|'[^']*'/
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s|:,]+/ StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s|:,]+/
FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/o FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/o
OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/o OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/o
SpacelessFilter = /\A(?:'[^']+'|"[^"]+"|[^'"])*#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/o SpacelessFilter = /^(?:'[^']+'|"[^"]+"|[^'"])*#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/o
Expression = /(?:#{QuotedFragment}(?:#{SpacelessFilter})*)/o Expression = /(?:#{QuotedFragment}(?:#{SpacelessFilter})*)/o
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
AnyStartingTag = /\{\{|\{\%/
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/o
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/o
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
end end
require 'liquid/liquid'
require "liquid/version"
require 'liquid/lexer'
require 'liquid/parser'
require 'liquid/i18n'
require 'liquid/drop' require 'liquid/drop'
require 'liquid/extensions' require 'liquid/extensions'
require 'liquid/errors' require 'liquid/errors'
@@ -63,6 +62,7 @@ require 'liquid/standardfilters'
require 'liquid/condition' require 'liquid/condition'
require 'liquid/module_ex' require 'liquid/module_ex'
require 'liquid/utils' require 'liquid/utils'
require 'liquid_ext'
# Load all the tags of the standard library # Load all the tags of the standard library
# #

View File

@@ -1,37 +1,65 @@
module Liquid module Liquid
class Block < Tag class Block < Tag
def initialize(tag_name, markup, tokens) IsTag = /^#{TagStart}/o
super IsVariable = /^#{VariableStart}/o
parse_body(tokens) FullToken = /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/o
end ContentOfVariable = /^#{VariableStart}(.*)#{VariableEnd}$/o
def blank? def parse(tokens)
@blank || false @nodelist ||= []
end @nodelist.clear
# warnings of this block and all sub-tags while token = tokens.shift
def warnings
all_warnings = []
all_warnings.concat(@warnings) if @warnings
(nodelist || []).each do |node| case token
all_warnings.concat(node.warnings || []) if node.respond_to?(:warnings) when IsTag
if token =~ FullToken
# if we found the proper block delimitor just end parsing here and let the outer block
# proceed
if block_delimiter == $1
end_tag
return
end
# fetch the tag from registered blocks
if tag = Template.tags[$1]
@nodelist << tag.new($1, $2, tokens)
else
# this tag is not registered with the system
# pass it to the current block for special handling or error reporting
unknown_tag($1, $2, tokens)
end
else
raise SyntaxError, "Tag '#{token}' was not properly terminated with regexp: #{TagEnd.inspect} "
end
when IsVariable
@nodelist << create_variable(token)
when ''
# pass
else
@nodelist << token
end
end end
all_warnings # Make sure that its ok to end parsing in the current block.
# Effectively this method will throw and exception unless the current block is
# of type Document
assert_missing_delimitation!
end
def end_tag
end end
def unknown_tag(tag, params, tokens) def unknown_tag(tag, params, tokens)
case tag case tag
when 'else' when 'else'
raise SyntaxError.new(options[:locale].t("errors.syntax.unexpected_else", raise SyntaxError, "#{block_name} tag does not expect else tag"
:block_name => block_name))
when 'end' when 'end'
raise SyntaxError.new(options[:locale].t("errors.syntax.invalid_delimiter", raise SyntaxError, "'end' is not a valid delimiter for #{block_name} tags. use #{block_delimiter}"
:block_name => block_name,
:block_delimiter => block_delimiter))
else else
raise SyntaxError.new(options[:locale].t("errors.syntax.unknown_tag", :tag => tag)) raise SyntaxError, "Unknown tag '#{tag}'"
end end
end end
@@ -43,29 +71,25 @@ module Liquid
@tag_name @tag_name
end end
def create_variable(token)
token.scan(ContentOfVariable) do |content|
return Variable.new(content.first)
end
raise SyntaxError.new("Variable '#{token}' was not properly terminated with regexp: #{VariableEnd.inspect} ")
end
def render(context) def render(context)
render_all(@nodelist, context) render_all(@nodelist, context)
end end
protected protected
def unterminated_variable(token)
raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination", :token => token, :tag_end => VariableEnd.inspect))
end
def unterminated_tag(token)
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination", :token => token, :tag_end => TagEnd.inspect))
end
def assert_missing_delimitation! def assert_missing_delimitation!
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_never_closed", :block_name => block_name)) raise SyntaxError.new("#{block_name} tag was never closed")
end end
def render_all(list, context) def render_all(list, context)
output = [] output = []
context.resource_limits[:render_length_current] = 0
context.resource_limits[:render_score_current] += list.length
list.each do |token| list.each do |token|
# Break out if we have any unhanded interrupts. # Break out if we have any unhanded interrupts.
break if context.has_interrupt? break if context.has_interrupt?
@@ -79,23 +103,13 @@ module Liquid
break break
end end
token_output = (token.respond_to?(:render) ? token.render(context) : token) output << (token.respond_to?(:render) ? token.render(context) : token)
context.increment_used_resources(:render_length_current, token_output)
if context.resource_limits_reached?
context.resource_limits[:reached] = true
raise MemoryError.new("Memory limits exceeded")
end
unless token.is_a?(Block) && token.blank?
output << token_output
end
rescue MemoryError => e
raise e
rescue ::StandardError => e rescue ::StandardError => e
output << (context.handle_error(e)) output << (context.handle_error(e))
end end
end end
StringSlice.join(output) output
end end
end end
end end

View File

@@ -1,5 +1,6 @@
module Liquid module Liquid
# Context keeps the variable stack and resolves variables, as well as keywords # Context keeps the variable stack and resolves variables, as well as keywords
# #
# context['variable'] = 'testing' # context['variable'] = 'testing'
@@ -13,37 +14,21 @@ module Liquid
# #
# context['bob'] #=> nil class Context # context['bob'] #=> nil class Context
class Context class Context
attr_reader :scopes, :errors, :registers, :environments, :resource_limits attr_reader :scopes, :errors, :registers, :environments
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = {}) def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false)
@environments = [environments].flatten @environments = [environments].flatten
@scopes = [(outer_scope || {})] @scopes = [(outer_scope || {})]
@registers = registers @registers = registers
@errors = [] @errors = []
@rethrow_errors = rethrow_errors @rethrow_errors = rethrow_errors
@resource_limits = (resource_limits || {}).merge!({ :render_score_current => 0, :assign_score_current => 0 })
squash_instance_assigns_with_environments squash_instance_assigns_with_environments
@interrupts = [] @interrupts = []
@filters = []
end
def increment_used_resources(key, obj)
@resource_limits[key] += if obj.kind_of?(StringSlice) || obj.kind_of?(String) || obj.kind_of?(Array) || obj.kind_of?(Hash)
obj.length
else
1
end
end
def resource_limits_reached?
(@resource_limits[:render_length_limit] && @resource_limits[:render_length_current] > @resource_limits[:render_length_limit]) ||
(@resource_limits[:render_score_limit] && @resource_limits[:render_score_current] > @resource_limits[:render_score_limit] ) ||
(@resource_limits[:assign_score_limit] && @resource_limits[:assign_score_current] > @resource_limits[:assign_score_limit] )
end end
def strainer def strainer
@strainer ||= Strainer.create(self, @filters) @strainer ||= Strainer.create(self)
end end
# Adds filters to this context. # Adds filters to this context.
@@ -52,26 +37,17 @@ module Liquid
# for that # for that
def add_filters(filters) def add_filters(filters)
filters = [filters].flatten.compact filters = [filters].flatten.compact
filters.each do |f| filters.each do |f|
raise ArgumentError, "Expected module but got: #{f.class}" unless f.is_a?(Module) raise ArgumentError, "Expected module but got: #{f.class}" unless f.is_a?(Module)
Strainer.add_known_filter(f) Strainer.add_known_filter(f)
end strainer.extend(f)
# If strainer is already setup then there's no choice but to use a runtime
# extend call. If strainer is not yet created, we can utilize strainers
# cached class based API, which avoids busting the method cache.
if @strainer
filters.each do |f|
strainer.extend(f)
end
else
@filters.concat filters
end end
end end
# are there any not handled interrupts? # are there any not handled interrupts?
def has_interrupt? def has_interrupt?
@interrupts.any? !@interrupts.empty?
end end
# push an interrupt to the stack. this interrupt is considered not handled. # push an interrupt to the stack. this interrupt is considered not handled.
@@ -136,61 +112,95 @@ module Liquid
@scopes[0] = {} @scopes[0] = {}
end end
# Look up variable, either resolve directly after considering the name. We can directly handle
# Strings, digits, floats and booleans (true,false).
# If no match is made we lookup the variable in the current scope and
# later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
# Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
#
# Example:
# products == empty #=> products.empty?
def resolve(key)
case key
when nil, ""
return nil
when "blank"
return :blank?
when "empty"
return :empty?
end
result = Parser.parse(key)
stack = []
result.each do |(sym, value)|
case sym
when :id
stack.push value
when :lookup
left = stack.pop
value = find_variable(left)
stack.push(harden(value))
when :range
right = stack.pop.to_i
left = stack.pop.to_i
stack.push (left..right)
when :buildin
left = stack.pop
value = invoke_buildin(left, value)
stack.push(harden(value))
when :call
left = stack.pop
right = stack.pop
value = lookup_and_evaluate(right, left)
stack.push(harden(value))
else
raise "unknown #{sym}"
end
end
return stack.first
end
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt> # Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
def []=(key, value) def []=(key, value)
@scopes[0][key] = value @scopes[0][key] = value
end end
def [](key)
resolve(key)
end
def has_key?(key) def has_key?(key)
resolve(key) != nil resolve(key) != nil
end end
private alias_method :[], :resolve
LITERALS = {
nil => nil, 'nil' => nil, 'null' => nil, '' => nil,
'true' => true,
'false' => false,
'blank' => :blank?,
'empty' => :empty?
}
# Look up variable, either resolve directly after considering the name. We can directly handle private
# Strings, digits, floats and booleans (true,false).
# If no match is made we lookup the variable in the current scope and def invoke_buildin(obj, key)
# later move up to the parent blocks to see if we can resolve the variable somewhere up the tree. # as weird as this is, liquid unit tests demand that we prioritize hash lookups
# Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions # to buildins. So if we got a hash and it has a :first element we need to call that
# # instead of sending the first message...
# Example:
# products == empty #=> products.empty? if obj.respond_to?(:has_key?) && obj.has_key?(key)
def resolve(key) return lookup_and_evaluate(obj, key)
if LITERALS.key?(key) end
LITERALS[key]
if obj.respond_to?(key)
return obj.send(key)
else else
case key return nil
when /\A'(.*)'\z/ # Single quoted strings
$1
when /\A"(.*)"\z/ # Double quoted strings
$1
when /\A(-?\d+)\z/ # Integer and floats
$1.to_i
when /\A\((\S+)\.\.(\S+)\)\z/ # Ranges
(resolve($1).to_i..resolve($2).to_i)
when /\A(-?\d[\d\.]+)\z/ # Floats
$1.to_f
else
variable(key)
end
end end
end end
# Fetches an object starting at the local scope and then moving up the hierachy # Fetches an object starting at the local scope and then moving up the hierachy
def find_variable(key) def find_variable(key)
scope = @scopes.find { |s| s.has_key?(key) } scope = @scopes.find { |s| s.has_key?(key) }
variable = nil
if scope.nil? if scope.nil?
@environments.each do |e| @environments.each do |e|
@@ -204,71 +214,34 @@ module Liquid
scope ||= @environments.last || @scopes.last scope ||= @environments.last || @scopes.last
variable ||= lookup_and_evaluate(scope, key) variable ||= lookup_and_evaluate(scope, key)
variable = variable.to_liquid
variable.context = self if variable.respond_to?(:context=)
return variable return variable
end end
# Resolves namespaced queries gracefully.
#
# Example
# @context['hash'] = {"name" => 'tobi'}
# assert_equal 'tobi', @context['hash.name']
# assert_equal 'tobi', @context['hash["name"]']
def variable(markup)
parts = markup.scan(VariableParser)
square_bracketed = /\A\[(.*)\]\z/
first_part = parts.shift
if first_part =~ square_bracketed
first_part = resolve($1)
end
if object = find_variable(first_part)
parts.each do |part|
part = resolve($1) if part_resolved = (part =~ square_bracketed)
# 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?(:[]) and
((object.respond_to?(:has_key?) and object.has_key?(part)) or
(object.respond_to?(:fetch) and part.is_a?(Integer)))
# if its a proc we will replace the entry with the proc
res = lookup_and_evaluate(object, part)
object = res.to_liquid
# Some special cases. If the part wasn't in square brackets and
# no key with the same name was found we interpret following calls
# as commands and call them on the current object
elsif !part_resolved and object.respond_to?(part) and ['size', 'first', 'last'].include?(part)
object = object.send(part.intern).to_liquid
# 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
else
return nil
end
# If we are dealing with a drop here we have to
object.context = self if object.respond_to?(:context=)
end
end
object
end # variable
def lookup_and_evaluate(obj, key) def lookup_and_evaluate(obj, key)
if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=) return nil unless obj.respond_to?(:[])
obj[key] = (value.arity == 0) ? value.call : value.call(self)
else if obj.is_a?(Array)
value return nil unless key.is_a?(Integer)
end end
end # lookup_and_evaluate
value = obj[key]
if value.is_a?(Proc)
# call the proc
value = (value.arity == 0) ? value.call : value.call(self)
# memozie if possible
obj[key] = value if obj.respond_to?(:[]=)
end
value
end
def harden(value)
value = value.to_liquid
value.context = self if value.respond_to?(:context=)
return value
end
def squash_instance_assigns_with_environments def squash_instance_assigns_with_environments
@scopes.last.each_key do |k| @scopes.last.each_key do |k|
@@ -280,6 +253,7 @@ module Liquid
end end
end end
end # squash_instance_assigns_with_environments end # squash_instance_assigns_with_environments
end # Context end # Context
end # Liquid end # Liquid

View File

@@ -1,14 +1,13 @@
module Liquid module Liquid
class Document < Block class Document < Block
# we don't need markup to open this block # we don't need markup to open this block
def initialize(tokens, options = {}) def initialize(tokens)
@options = options parse(tokens)
parse_body(tokens)
end end
# There isn't a real delimiter # There isn't a real delimter
def block_delimiter def block_delimiter
nil []
end end
# Document blocks don't need to be terminated since they are not actually opened # Document blocks don't need to be terminated since they are not actually opened

View File

@@ -44,33 +44,17 @@ module Liquid
true true
end end
def inspect
self.class.to_s
end
def to_liquid def to_liquid
self self
end end
def to_s
self.class.name
end
alias :[] :invoke_drop alias :[] :invoke_drop
private private
# Check for method existence without invoking respond_to?, which creates symbols # Check for method existence without invoking respond_to?, which creates symbols
def self.invokable?(method_name) def self.invokable?(method_name)
unless @invokable_methods @invokable_methods ||= Set.new((public_instance_methods - Liquid::Drop.public_instance_methods).map(&:to_s))
blacklist = Liquid::Drop.public_instance_methods + [:each]
if include?(Enumerable)
blacklist += Enumerable.public_instance_methods
blacklist -= [:sort, :count, :first, :min, :max, :include?]
end
whitelist = [:to_liquid] + (public_instance_methods - blacklist)
@invokable_methods = Set.new(whitelist.map(&:to_s))
end
@invokable_methods.include?(method_name.to_s) @invokable_methods.include?(method_name.to_s)
end end
end end

View File

@@ -8,5 +8,4 @@ module Liquid
class StandardError < Error; end class StandardError < Error; end
class SyntaxError < Error; end class SyntaxError < Error; end
class StackLevelError < Error; end class StackLevelError < Error; end
class MemoryError < Error; end
end end

View File

@@ -1,5 +1,5 @@
module Liquid module Liquid
# A Liquid file system is a way to let your templates retrieve other templates for use with the include tag. # A Liquid file system is way to let your templates retrieve other templates for use with the include tag.
# #
# You can implement subclasses that retrieve templates from the database, from the file system using a different # You can implement subclasses that retrieve templates from the database, from the file system using a different
# path structure, you can provide them as hard-coded inline strings, or any manner that you see fit. # path structure, you can provide them as hard-coded inline strings, or any manner that you see fit.
@@ -31,22 +31,11 @@ module Liquid
# file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid" # file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid"
# file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid" # file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid"
# #
# Optionally in the second argument you can specify a custom pattern for template filenames.
# The Kernel::sprintf format specification is used.
# Default pattern is "_%s.liquid".
#
# Example:
#
# file_system = Liquid::LocalFileSystem.new("/some/path", "%s.html")
#
# file_system.full_path("index") # => "/some/path/index.html"
#
class LocalFileSystem class LocalFileSystem
attr_accessor :root attr_accessor :root
def initialize(root, pattern = "_%s.liquid") def initialize(root)
@root = root @root = root
@pattern = pattern
end end
def read_template_file(template_path, context) def read_template_file(template_path, context)
@@ -57,15 +46,15 @@ module Liquid
end end
def full_path(template_path) def full_path(template_path)
raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /\A[^.\/][a-zA-Z0-9_\/]+\z/ raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /^[^.\/][a-zA-Z0-9_\/]+$/
full_path = if template_path.include?('/') full_path = if template_path.include?('/')
File.join(root, File.dirname(template_path), @pattern % File.basename(template_path)) File.join(root, File.dirname(template_path), "_#{File.basename(template_path)}.liquid")
else else
File.join(root, @pattern % template_path) File.join(root, "_#{template_path}.liquid")
end end
raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path) =~ /\A#{File.expand_path(root)}/ raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path) =~ /^#{File.expand_path(root)}/
full_path full_path
end end

View File

@@ -6,24 +6,28 @@ module Liquid
if markup =~ Syntax if markup =~ Syntax
@variable_name = $1 @variable_name = $1
@collection_name = $2 @collection_name = $2
@idx_i = "#{$1}-#{$2}-i"
@idx_col = "#{$1}-#{$2}-c"
@attributes = {} @attributes = {}
markup.scan(TagAttributes) do |key, value| markup.scan(TagAttributes) do |key, value|
@attributes[key] = value @attributes[key] = value
end end
else else
raise SyntaxError.new(options[:locale].t("errors.syntax.table_row")) raise SyntaxError.new("Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3")
end end
super super
end end
def render(context) def render(context)
context.registers[:tablerowloop] ||= Hash.new(0)
collection = context[@collection_name] or return '' collection = context[@collection_name] or return ''
from = @attributes['offset'] ? context[@attributes['offset']].to_i : 0 from = @attributes['offset'] ? context[@attributes['offset']].to_i : 0
to = @attributes['limit'] ? from + context[@attributes['limit']].to_i : nil to = @attributes['limit'] ? from + context[@attributes['limit']].to_i : nil
collection = Utils.slice_collection(collection, from, to) collection = Utils.slice_collection_using_each(collection, from, to)
length = collection.length length = collection.length
@@ -32,32 +36,21 @@ module Liquid
row = 1 row = 1
col = 0 col = 0
result = "<tr class=\"row1\">\n" result = ["<tr class=\"row1\">\n"]
context.stack do context.stack do
context.registers[:tablerowloop][@idx]
context['tablerowloop'] = lambda { Tablerowloop.new(@idx_i, @idx_col, length) }
collection.each_with_index do |item, index| collection.each_with_index do |item, index|
context.registers[:tablerowloop][@idx_i] = index
context.registers[:tablerowloop][@idx_col] = col
context[@variable_name] = item context[@variable_name] = item
context['tablerowloop'] = {
'length' => length,
'index' => index + 1,
'index0' => index,
'col' => col + 1,
'col0' => col,
'index0' => index,
'rindex' => length - index,
'rindex0' => length - index - 1,
'first' => (index == 0),
'last' => (index == length - 1),
'col_first' => (col == 0),
'col_last' => (col == cols - 1)
}
col += 1 col += 1
result << "<td class=\"col#{col}\">" << super << '</td>' result << "<td class=\"col#{col}\">" << render_all(@nodelist, context) << '</td>'
if col == cols and (index != length - 1) if col == cols and not (index == length - 1)
col = 0 col = 0
row += 1 row += 1
result << "</tr>\n<tr class=\"row#{row}\">" result << "</tr>\n<tr class=\"row#{row}\">"
@@ -68,6 +61,58 @@ module Liquid
result << "</tr>\n" result << "</tr>\n"
result result
end end
private
class Tablerowloop < Liquid::Drop
attr_accessor :length
def initialize(idx_i, idx_col, length)
@idx_i, @idx_col, @length = idx_i, idx_col, length
end
def index
@context.registers[:tablerowloop][@idx_i] + 1
end
def index0
@context.registers[:tablerowloop][@idx_i]
end
def rindex
length - @context.registers[:tablerowloop][@idx_i]
end
def rindex0
length - @context.registers[:tablerowloop][@idx_i] - 1
end
def first
(@context.registers[:tablerowloop][@idx_i] == 0)
end
def last
(@context.registers[:tablerowloop][@idx_i] == length - 1)
end
def col
@context.registers[:tablerowloop][@idx_col] + 1
end
def col0
@context.registers[:tablerowloop][@idx_col]
end
def col_first
(@context.registers[:tablerowloop][@idx_col] == 0)
end
def col_last
(@context.registers[:tablerowloop][@idx_col] == cols - 1)
end
end
end end
Template.register_tag('tablerow', TableRow) Template.register_tag('tablerow', TableRow)

View File

@@ -1,39 +0,0 @@
require 'yaml'
module Liquid
class I18n
DEFAULT_LOCALE = File.join(File.expand_path(File.dirname(__FILE__)), "locales", "en.yml")
class TranslationError < StandardError
end
attr_reader :path
def initialize(path = DEFAULT_LOCALE)
@path = path
end
def translate(name, vars = {})
interpolate(deep_fetch_translation(name), vars)
end
alias_method :t, :translate
def locale
@locale ||= YAML.load_file(@path)
end
private
def interpolate(name, vars)
name.gsub(/%\{(\w+)\}/) {
# raise TranslationError, "Undefined key #{$1} for interpolation in translation #{name}" unless vars[$1.to_sym]
"#{vars[$1.to_sym]}"
}
end
def deep_fetch_translation(name)
name.split('.').reduce(locale) do |level, cur|
level[cur] or raise TranslationError, "Translation for #{name} does not exist in locale #{path}"
end
end
end
end

View File

@@ -1,51 +0,0 @@
require "strscan"
module Liquid
class Lexer
SPECIALS = {
'|' => :pipe,
'.' => :dot,
':' => :colon,
',' => :comma,
'[' => :open_square,
']' => :close_square,
'(' => :open_round,
')' => :close_round
}
IDENTIFIER = /[\w\-?!]+/
SINGLE_STRING_LITERAL = /'[^\']*'/
DOUBLE_STRING_LITERAL = /"[^\"]*"/
NUMBER_LITERAL = /-?\d+(\.\d+)?/
DOTDOT = /\.\./
COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains/
def initialize(input)
@ss = StringScanner.new(input.rstrip)
end
def tokenize
@output = []
while !@ss.eos?
@ss.skip(/\s*/)
tok = case
when t = @ss.scan(COMPARISON_OPERATOR) then [:comparison, t]
when t = @ss.scan(SINGLE_STRING_LITERAL) then [:string, t]
when t = @ss.scan(DOUBLE_STRING_LITERAL) then [:string, t]
when t = @ss.scan(NUMBER_LITERAL) then [:number, t]
when t = @ss.scan(IDENTIFIER) then [:id, t]
when t = @ss.scan(DOTDOT) then [:dotdot, t]
else
c = @ss.getch
if s = SPECIALS[c]
[s,c]
else
raise SyntaxError, "Unexpected character #{c}"
end
end
@output << tok
end
@output << [:end_of_string]
end
end
end

View File

@@ -1,22 +0,0 @@
---
errors:
syntax:
assign: "Syntax Error in 'assign' - Valid syntax: assign [var] = [source]"
capture: "Syntax Error in 'capture' - Valid syntax: capture [var]"
case: "Syntax Error in 'case' - Valid syntax: case [condition]"
case_invalid_when: "Syntax Error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %}"
case_invalid_else: "Syntax Error in tag 'case' - Valid else condition: {% else %} (no parameters) "
cycle: "Syntax Error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]"
for: "Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]"
for_invalid_in: "For loops require an 'in' clause"
for_invalid_attribute: "Invalid attribute in for loop. Valid attributes are limit and offset"
if: "Syntax Error in tag 'if' - Valid syntax: if [expression]"
include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]"
unknown_tag: "Unknown tag '%{tag}'"
invalid_delimiter: "'end' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
unexpected_else: "%{block_name} tag does not expect else tag"
tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
variable_termination: "Variable '%{token}' was not properly terminated with regexp: %{tag_end}"
tag_never_closed: "'%{block_name}' tag was never closed"
meta_syntax_error: "Liquid syntax error: #{e.message}"
table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"

View File

@@ -2,7 +2,7 @@
# This library is free software. It may be used, redistributed and/or modified # This library is free software. It may be used, redistributed and/or modified
# under the same terms as Ruby itself # under the same terms as Ruby itself
# #
# This extension is used in order to expose the object of the implementing class # This extension is usesd in order to expose the object of the implementing class
# to liquid as it were a Drop. It also limits the liquid-callable methods of the instance # to liquid as it were a Drop. It also limits the liquid-callable methods of the instance
# to the allowed method passed with the liquid_methods call # to the allowed method passed with the liquid_methods call
# Example: # Example:

View File

@@ -1,89 +0,0 @@
module Liquid
class Parser
def initialize(input)
l = Lexer.new(input)
@tokens = l.tokenize
@p = 0 # pointer to current location
end
def jump(point)
@p = point
end
def consume(type = nil)
token = @tokens[@p]
if type && token[0] != type
raise SyntaxError, "Expected #{type} but found #{@tokens[@p].first}"
end
@p += 1
token[1]
end
# Only consumes the token if it matches the type
# Returns the token's contents if it was consumed
# or false otherwise.
def consume?(type)
token = @tokens[@p]
return false unless token && token[0] == type
@p += 1
token[1]
end
# Like consume? Except for an :id token of a certain name
def id?(str)
token = @tokens[@p]
return false unless token && token[0] == :id
return false unless token[1] == str
@p += 1
token[1]
end
def look(type, ahead = 0)
tok = @tokens[@p + ahead]
return false unless tok
tok[0] == type
end
def expression
token = @tokens[@p]
if token[0] == :id
variable_signature
elsif [:string, :number].include? token[0]
consume
elsif token.first == :open_round
consume
first = expression
consume(:dotdot)
last = expression
consume(:close_round)
"(#{first}..#{last})"
else
raise SyntaxError, "#{token} is not a valid expression"
end
end
def argument
str = ""
# might be a keyword argument (identifier: expression)
if look(:id) && look(:colon, 1)
str << consume << consume << ' '
end
str << expression
end
def variable_signature
str = consume(:id)
if look(:open_square)
str << consume
str << expression
str << consume(:close_square)
end
if look(:dot)
str << consume
str << variable_signature
end
str
end
end
end

View File

@@ -1,11 +1,8 @@
require 'cgi' require 'cgi'
require 'bigdecimal'
module Liquid module Liquid
module StandardFilters module StandardFilters
HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
# Return the size of an array or of an string # Return the size of an array or of an string
def size(input) def size(input)
@@ -13,12 +10,12 @@ module Liquid
input.respond_to?(:size) ? input.size : 0 input.respond_to?(:size) ? input.size : 0
end end
# convert an input string to DOWNCASE # convert a input string to DOWNCASE
def downcase(input) def downcase(input)
input.to_s.downcase input.to_s.downcase
end end
# convert an input string to UPCASE # convert a input string to UPCASE
def upcase(input) def upcase(input)
input.to_s.upcase input.to_s.upcase
end end
@@ -33,7 +30,9 @@ module Liquid
end end
def escape_once(input) def escape_once(input)
input.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE) ActionView::Helpers::TagHelper.escape_once(input)
rescue NameError
input
end end
alias_method :h, :escape alias_method :h, :escape
@@ -63,27 +62,16 @@ module Liquid
input.split(pattern) input.split(pattern)
end end
def strip(input)
input.to_s.strip
end
def lstrip(input)
input.to_s.lstrip
end
def rstrip(input)
input.to_s.rstrip
end
def strip_html(input) def strip_html(input)
input.to_s.gsub(/<script.*?<\/script>/m, '').gsub(/<!--.*?-->/m, '').gsub(/<style.*?<\/style>/m, '').gsub(/<.*?>/m, '') input.to_s.gsub(/<script.*?<\/script>/, '').gsub(/<!--.*?-->/, '').gsub(/<.*?>/, '')
end end
# Remove all newlines from the string # Remove all newlines from the string
def strip_newlines(input) def strip_newlines(input)
input.to_s.gsub(/\r?\n/, '') input.to_s.gsub(/\n/, '')
end end
# Join elements of the array with certain character between them # Join elements of the array with certain character between them
def join(input, glue = ' ') def join(input, glue = ' ')
[input].flatten.join(glue) [input].flatten.join(glue)
@@ -92,7 +80,7 @@ module Liquid
# Sort elements of the array # Sort elements of the array
# provide optional property with which to sort an array of hashes or drops # provide optional property with which to sort an array of hashes or drops
def sort(input, property = nil) def sort(input, property = nil)
ary = flatten_if_necessary(input) ary = [input].flatten
if property.nil? if property.nil?
ary.sort ary.sort
elsif ary.first.respond_to?('[]') and !ary.first[property].nil? elsif ary.first.respond_to?('[]') and !ary.first[property].nil?
@@ -102,33 +90,24 @@ module Liquid
end end
end end
# Reverse the elements of an array
def reverse(input)
ary = [input].flatten
ary.reverse
end
# map/collect on a given property # map/collect on a given property
def map(input, property) def map(input, property)
flatten_if_necessary(input).map do |e| ary = [input].flatten
e = e.call if e.is_a?(Proc) if ary.first.respond_to?('[]') and !ary.first[property].nil?
ary.map {|e| e[property] }
if property == "to_liquid" elsif ary.first.respond_to?(property)
e ary.map {|e| e.send(property) }
elsif e.respond_to?(:[])
e[property]
end
end end
end end
# Replace occurrences of a string with another # Replace occurrences of a string with another
def replace(input, string, replacement = '') def replace(input, string, replacement = '')
input.to_s.gsub(string, replacement.to_s) input.to_s.gsub(string, replacement)
end end
# Replace the first occurrences of a string with another # Replace the first occurrences of a string with another
def replace_first(input, string, replacement = '') def replace_first(input, string, replacement = '')
input.to_s.sub(string, replacement.to_s) input.to_s.sub(string, replacement)
end end
# remove a substring # remove a substring
@@ -190,27 +169,18 @@ module Liquid
return input.to_s return input.to_s
end end
if ((input.is_a?(String) && !/\A\d+\z/.match(input.to_s).nil?) || input.is_a?(Integer)) && input.to_i > 0 if ((input.is_a?(String) && !/^\d+$/.match(input.to_s).nil?) || input.is_a?(Integer)) && input.to_i > 0
input = Time.at(input.to_i) input = Time.at(input.to_i)
end end
date = if input.is_a?(String) date = input.is_a?(String) ? Time.parse(input) : input
case input.downcase
when 'now', 'today'
Time.now
else
Time.parse(input)
end
else
input
end
if date.respond_to?(:strftime) if date.respond_to?(:strftime)
date.strftime(format.to_s) date.strftime(format.to_s)
else else
input input
end end
rescue rescue => e
input input
end end
@@ -234,63 +204,41 @@ module Liquid
# addition # addition
def plus(input, operand) def plus(input, operand)
apply_operation(input, operand, :+) to_number(input) + to_number(operand)
end end
# subtraction # subtraction
def minus(input, operand) def minus(input, operand)
apply_operation(input, operand, :-) to_number(input) - to_number(operand)
end end
# multiplication # multiplication
def times(input, operand) def times(input, operand)
apply_operation(input, operand, :*) to_number(input) * to_number(operand)
end end
# division # division
def divided_by(input, operand) def divided_by(input, operand)
apply_operation(input, operand, :/) to_number(input) / to_number(operand)
end end
def modulo(input, operand) def modulo(input, operand)
apply_operation(input, operand, :%) to_number(input) % to_number(operand)
end
def default(input, default_value = "")
is_blank = input.respond_to?(:empty?) ? input.empty? : !input
is_blank ? default_value : input
end end
private private
def flatten_if_necessary(input) def to_number(obj)
ary = if input.is_a?(Array) case obj
input.flatten when Numeric
elsif input.is_a?(Enumerable) && !input.is_a?(Hash) obj
input when String
else (obj.strip =~ /^\d+\.\d+$/) ? obj.to_f : obj.to_i
[input].flatten else
0
end
end end
ary.map{ |e| e.respond_to?(:to_liquid) ? e.to_liquid : e }
end
def to_number(obj)
case obj
when Float
BigDecimal.new(obj.to_s)
when Numeric
obj
when String
(obj.strip =~ /\A\d+\.\d+\z/) ? BigDecimal.new(obj) : obj.to_i
else
0
end
end
def apply_operation(input, operand, operation)
result = to_number(input).send(operation, to_number(operand))
result.is_a?(BigDecimal) ? result.to_f : result
end
end end
Template.register_filter(StandardFilters) Template.register_filter(StandardFilters)

View File

@@ -8,14 +8,9 @@ module Liquid
# The Strainer only allows method calls defined in filters given to it via Strainer.global_filter, # The Strainer only allows method calls defined in filters given to it via Strainer.global_filter,
# Context#add_filters or Template.register_filter # Context#add_filters or Template.register_filter
class Strainer #:nodoc: class Strainer #:nodoc:
@@filters = [] @@filters = {}
@@known_filters = Set.new @@known_filters = Set.new
@@known_methods = Set.new @@known_methods = Set.new
@@strainer_class_cache = Hash.new do |hash, filters|
hash[filters] = Class.new(Strainer) do
filters.each { |f| include f }
end
end
def initialize(context) def initialize(context)
@context = context @context = context
@@ -24,7 +19,7 @@ module Liquid
def self.global_filter(filter) def self.global_filter(filter)
raise ArgumentError, "Passed filter is not a module" unless filter.is_a?(Module) raise ArgumentError, "Passed filter is not a module" unless filter.is_a?(Module)
add_known_filter(filter) add_known_filter(filter)
@@filters << filter unless @@filters.include?(filter) @@filters[filter.name] = filter
end end
def self.add_known_filter(filter) def self.add_known_filter(filter)
@@ -37,13 +32,10 @@ module Liquid
end end
end end
def self.strainer_class_cache def self.create(context)
@@strainer_class_cache strainer = Strainer.new(context)
end @@filters.each { |k,m| strainer.extend(m) }
strainer
def self.create(context, filters = [])
filters = @@filters + filters
strainer_class_cache[filters].new(context)
end end
def invoke(method, *args) def invoke(method, *args)
@@ -52,8 +44,6 @@ module Liquid
else else
args.first args.first
end end
rescue ::ArgumentError => e
raise Liquid::ArgumentError.new(e.message)
end end
def invokable?(method) def invokable?(method)

View File

@@ -1,21 +1,16 @@
module Liquid module Liquid
class Tag
attr_accessor :options
attr_reader :nodelist, :warnings
def self.new_with_options(tag_name, markup, tokens, options) class Tag
# Forgive me Matz for I have sinned. I know this code is weird
# but it was necessary to maintain API compatibility. attr_accessor :nodelist
new_tag = self.allocate
new_tag.options = options
new_tag.send(:initialize, tag_name, markup, tokens)
new_tag
end
def initialize(tag_name, markup, tokens) def initialize(tag_name, markup, tokens)
@tag_name = tag_name @tag_name = tag_name
@markup = markup @markup = markup
@options ||= {} # needs || because might be set before initialize parse(tokens)
end
def parse(tokens)
end end
def name def name
@@ -26,31 +21,6 @@ module Liquid
'' ''
end end
def blank?
@blank || false
end
def parse_with_selected_parser(markup)
case @options[:error_mode] || Template.error_mode
when :strict then strict_parse_with_error_context(markup)
when :lax then lax_parse(markup)
when :warn
begin
return strict_parse_with_error_context(markup)
rescue SyntaxError => e
@warnings ||= []
@warnings << e
return lax_parse(markup)
end
end
end
private
def strict_parse_with_error_context(markup)
strict_parse(markup)
rescue SyntaxError => e
e.message << " in \"#{markup.strip}\""
raise e
end
end # Tag end # Tag
end # Liquid
end # Tag

View File

@@ -16,22 +16,17 @@ module Liquid
@to = $1 @to = $1
@from = Variable.new($2) @from = Variable.new($2)
else else
raise SyntaxError.new options[:locale].t("errors.syntax.assign") raise SyntaxError.new("Syntax Error in 'assign' - Valid syntax: assign [var] = [source]")
end end
super super
end end
def render(context) def render(context)
val = @from.render(context) context.scopes.last[@to] = @from.render(context)
context.scopes.last[@to] = val ''
context.increment_used_resources(:assign_score_current, val)
''
end end
def blank?
true
end
end end
Template.register_tag('assign', Assign) Template.register_tag('assign', Assign)

View File

@@ -18,7 +18,7 @@ module Liquid
if markup =~ Syntax if markup =~ Syntax
@to = $1 @to = $1
else else
raise SyntaxError.new(options[:locale].t("errors.syntax.capture")) raise SyntaxError.new("Syntax Error in 'capture' - Valid syntax: capture [var]")
end end
super super
@@ -27,13 +27,8 @@ module Liquid
def render(context) def render(context)
output = super output = super
context.scopes.last[@to] = output context.scopes.last[@to] = output
context.increment_used_resources(:assign_score_current, output)
'' ''
end end
def blank?
true
end
end end
Template.register_tag('capture', Capture) Template.register_tag('capture', Capture)

View File

@@ -9,16 +9,12 @@ module Liquid
if markup =~ Syntax if markup =~ Syntax
@left = $1 @left = $1
else else
raise SyntaxError.new(options[:locale].t("errors.syntax.case")) raise SyntaxError.new("Syntax Error in tag 'case' - Valid syntax: case [condition]")
end end
super super
end end
def nodelist
@blocks.map(&:attachment).flatten
end
def unknown_tag(tag, markup, tokens) def unknown_tag(tag, markup, tokens)
@nodelist = [] @nodelist = []
case tag case tag
@@ -35,7 +31,7 @@ module Liquid
context.stack do context.stack do
execute_else_block = true execute_else_block = true
output = '' output = []
@blocks.each do |block| @blocks.each do |block|
if block.else? if block.else?
return render_all(block.attachment, context) if execute_else_block return render_all(block.attachment, context) if execute_else_block
@@ -52,28 +48,31 @@ module Liquid
def record_when_condition(markup) def record_when_condition(markup)
while markup while markup
# Create a new nodelist and assign it to the new block # Create a new nodelist and assign it to the new block
if not markup =~ WhenSyntax if not markup =~ WhenSyntax
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when")) raise SyntaxError.new("Syntax Error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %} ")
end end
markup = $2 markup = $2
block = Condition.new(@left, '==', $1) block = Condition.new(@left, '==', $1)
block.attach(@nodelist) block.attach(@nodelist)
@blocks.push(block) @blocks.push(block)
end end
end end
def record_else_condition(markup) def record_else_condition(markup)
if not markup.strip.empty? if not markup.strip.empty?
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_else")) raise SyntaxError.new("Syntax Error in tag 'case' - Valid else condition: {% else %} (no parameters) ")
end end
block = ElseCondition.new block = ElseCondition.new
block.attach(@nodelist) block.attach(@nodelist)
@blocks << block @blocks << block
end end
end end
Template.register_tag('case', Case) Template.register_tag('case', Case)

View File

@@ -3,13 +3,6 @@ module Liquid
def render(context) def render(context)
'' ''
end end
def unknown_tag(tag, markup, tokens)
end
def blank?
true
end
end end
Template.register_tag('comment', Comment) Template.register_tag('comment', Comment)

View File

@@ -1,4 +1,5 @@
module Liquid module Liquid
# Cycle is usually used within a loop to alternate between values, like colors or DOM classes. # Cycle is usually used within a loop to alternate between values, like colors or DOM classes.
# #
# {% for item in items %} # {% for item in items %}
@@ -12,19 +13,19 @@ module Liquid
# <div class="green"> Item five</div> # <div class="green"> Item five</div>
# #
class Cycle < Tag class Cycle < Tag
SimpleSyntax = /\A#{QuotedFragment}+/o SimpleSyntax = /^#{QuotedFragment}+/o
NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/o NamedSyntax = /^(#{QuotedFragment})\s*\:\s*(.*)/o
def initialize(tag_name, markup, tokens) def initialize(tag_name, markup, tokens)
case markup case markup
when NamedSyntax when NamedSyntax
@variables = variables_from_string($2) @variables = variables_from_string($2)
@name = $1 @name = $1
when SimpleSyntax when SimpleSyntax
@variables = variables_from_string(markup) @variables = variables_from_string(markup)
@name = "'#{@variables.to_s}'" @name = "'#{@variables.to_s}'"
else else
raise SyntaxError.new(options[:locale].t("errors.syntax.cycle")) raise SyntaxError.new("Syntax Error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]")
end end
super super
end end
@@ -43,17 +44,15 @@ module Liquid
end end
end end
def blank?
false
end
private private
def variables_from_string(markup) def variables_from_string(markup)
markup.split(',').collect do |var| markup.split(',').collect do |var|
var =~ /\s*(#{QuotedFragment})\s*/o var =~ /\s*(#{QuotedFragment})\s*/o
$1 ? $1 : nil $1 ? $1 : nil
end.compact end.compact
end end
end end
Template.register_tag('cycle', Cycle) Template.register_tag('cycle', Cycle)

View File

@@ -44,22 +44,27 @@ module Liquid
# forloop.last:: Returns true if the item is the last item. # forloop.last:: Returns true if the item is the last item.
# #
class For < Block class For < Block
Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
def initialize(tag_name, markup, tokens) def initialize(tag_name, markup, tokens)
parse_with_selected_parser(markup) if markup =~ Syntax
@variable_name = $1
@collection_name = $2
@name = "#{$1}-#{$2}"
@idx = "#{@name}-i"
@reversed = $3
@attributes = {}
markup.scan(TagAttributes) do |key, value|
@attributes[key] = value
end
else
raise SyntaxError.new("Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]")
end
@nodelist = @for_block = [] @nodelist = @for_block = []
super super
end end
def nodelist
if @else_block
@for_block + @else_block
else
@for_block
end
end
def unknown_tag(tag, markup, tokens) def unknown_tag(tag, markup, tokens)
return super unless tag == 'else' return super unless tag == 'else'
@nodelist = @else_block = [] @nodelist = @else_block = []
@@ -83,13 +88,13 @@ module Liquid
limit = context[@attributes['limit']] limit = context[@attributes['limit']]
to = limit ? limit.to_i + from : nil to = limit ? limit.to_i + from : nil
segment = Utils.slice_collection(collection, from, to) segment = Utils.slice_collection_using_each(collection, from, to)
return render_else(context) if segment.empty? return render_else(context) if segment.empty?
segment.reverse! if @reversed segment.reverse! if @reversed
result = '' result = []
length = segment.length length = segment.length
@@ -97,17 +102,10 @@ module Liquid
context.registers[:for][@name] = from + segment.length context.registers[:for][@name] = from + segment.length
context.stack do context.stack do
context['forloop'] = lambda { Forloop.new(@name, @idx, length) }
segment.each_with_index do |item, index| segment.each_with_index do |item, index|
context.registers[:for][@idx] = index
context[@variable_name] = item context[@variable_name] = item
context['forloop'] = {
'name' => @name,
'length' => length,
'index' => index + 1,
'index0' => index,
'rindex' => length - index,
'rindex0' => length - index - 1,
'first' => (index == 0),
'last' => (index == length - 1) }
result << render_all(@for_block, context) result << render_all(@for_block, context)
@@ -122,45 +120,41 @@ module Liquid
result result
end end
protected
def lax_parse(markup)
if markup =~ Syntax
@variable_name = $1
@collection_name = $2
@name = "#{$1}-#{$2}"
@reversed = $3
@attributes = {}
markup.scan(TagAttributes) do |key, value|
@attributes[key] = value
end
else
raise SyntaxError.new(options[:locale].t("errors.syntax.for"))
end
end
def strict_parse(markup)
p = Parser.new(markup)
@variable_name = p.consume(:id)
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in")) unless p.id?('in')
@collection_name = p.expression
@name = "#{@variable_name}-#{@collection_name}"
@reversed = p.id?('reversed')
@attributes = {}
while p.look(:id) && p.look(:colon, 1)
unless attribute = p.id?('limit') || p.id?('offset')
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute"))
end
p.consume
val = p.expression
@attributes[attribute] = val
end
p.consume(:end_of_string)
end
private private
class Forloop < Liquid::Drop
attr_accessor :name, :length
def initialize(name, idx, length)
@name, @idx, @length = name, idx, length
end
def index
@context.registers[:for][@idx] + 1
end
def index0
@context.registers[:for][@idx]
end
def rindex
length - @context.registers[:for][@idx]
end
def rindex0
length - @context.registers[:for][@idx] - 1
end
def first
(@context.registers[:for][@idx] == 0)
end
def last
(@context.registers[:for][@idx] == length - 1)
end
end
def render_else(context) def render_else(context)
return @else_block ? [render_all(@else_block, context)] : '' return @else_block ? [render_all(@else_block, context)] : ''
end end

View File

@@ -1,4 +1,5 @@
module Liquid module Liquid
# If is the conditional block # If is the conditional block
# #
# {% if user.admin %} # {% if user.admin %}
@@ -9,19 +10,18 @@ module Liquid
# #
# There are {% if count < 5 %} less {% else %} more {% endif %} items than you need. # There are {% if count < 5 %} less {% else %} more {% endif %} items than you need.
# #
#
class If < Block class If < Block
SyntaxHelp = "Syntax Error in tag 'if' - Valid syntax: if [expression]"
Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
BOOLEAN_OPERATORS = %w(and or)
def initialize(tag_name, markup, tokens) def initialize(tag_name, markup, tokens)
@blocks = [] @blocks = []
push_block('if', markup)
super
end
def nodelist push_block('if', markup)
@blocks.map(&:attachment).flatten
super
end end
def unknown_tag(tag, markup, tokens) def unknown_tag(tag, markup, tokens)
@@ -49,57 +49,30 @@ module Liquid
block = if tag == 'else' block = if tag == 'else'
ElseCondition.new ElseCondition.new
else else
parse_with_selected_parser(markup)
expressions = markup.scan(ExpressionsAndOperators).reverse
raise(SyntaxError, SyntaxHelp) unless expressions.shift =~ Syntax
condition = Condition.new($1, $2, $3)
while not expressions.empty?
operator = (expressions.shift).to_s.strip
raise(SyntaxError, SyntaxHelp) unless expressions.shift.to_s =~ Syntax
new_condition = Condition.new($1, $2, $3)
new_condition.send(operator.to_sym, condition)
condition = new_condition
end
condition
end end
@blocks.push(block) @blocks.push(block)
@nodelist = block.attach(Array.new) @nodelist = block.attach(Array.new)
end end
def lax_parse(markup)
expressions = markup.scan(ExpressionsAndOperators).reverse
raise(SyntaxError.new(options[:locale].t("errors.syntax.if"))) unless expressions.shift =~ Syntax
condition = Condition.new($1, $2, $3)
while not expressions.empty?
operator = (expressions.shift).to_s.strip
raise(SyntaxError.new(options[:locale].t("errors.syntax.if"))) unless expressions.shift.to_s =~ Syntax
new_condition = Condition.new($1, $2, $3)
raise(SyntaxError.new(options[:locale].t("errors.syntax.if"))) unless BOOLEAN_OPERATORS.include?(operator)
new_condition.send(operator, condition)
condition = new_condition
end
condition
end
def strict_parse(markup)
p = Parser.new(markup)
condition = parse_comparison(p)
while op = (p.id?('and') || p.id?('or'))
new_cond = parse_comparison(p)
new_cond.send(op, condition)
condition = new_cond
end
p.consume(:end_of_string)
condition
end
def parse_comparison(p)
a = p.expression
if op = p.consume?(:comparison)
b = p.expression
Condition.new(a, op, b)
else
Condition.new(a)
end
end
end end
Template.register_tag('if', If) Template.register_tag('if', If)

View File

@@ -4,7 +4,7 @@ module Liquid
def render(context) def render(context)
context.stack do context.stack do
output = super output = render_all(@nodelist, context)
if output != context.registers[:ifchanged] if output != context.registers[:ifchanged]
context.registers[:ifchanged] = output context.registers[:ifchanged] = output

View File

@@ -1,19 +1,4 @@
module Liquid module Liquid
# Include allows templates to relate with other templates
#
# Simply include another template:
#
# {% include 'product' %}
#
# Include a template with a local variable:
#
# {% include 'product' with products[0] %}
#
# Include a template for a collection:
#
# {% include 'product' for products %}
#
class Include < Tag class Include < Tag
Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
@@ -29,18 +14,18 @@ module Liquid
end end
else else
raise SyntaxError.new(options[:locale].t("errors.syntax.include")) raise SyntaxError.new("Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]")
end end
super super
end end
def blank? def parse(tokens)
false
end end
def render(context) def render(context)
partial = load_cached_partial(context) source = _read_template_from_file_system(context)
partial = Liquid::Template.parse(source)
variable = context[@variable_name || @template_name[1..-2]] variable = context[@variable_name || @template_name[1..-2]]
context.stack do context.stack do
@@ -48,35 +33,20 @@ module Liquid
context[key] = context[value] context[key] = context[value]
end end
context_variable_name = @template_name[1..-2].split('/').last
if variable.is_a?(Array) if variable.is_a?(Array)
variable.collect do |var| variable.collect do |variable|
context[context_variable_name] = var context[@template_name[1..-2]] = variable
partial.render(context) partial.render(context)
end end
else else
context[context_variable_name] = variable context[@template_name[1..-2]] = variable
partial.render(context) partial.render(context)
end end
end end
end end
private private
def load_cached_partial(context) def _read_template_from_file_system(context)
cached_partials = context.registers[:cached_partials] || {}
template_name = context[@template_name]
if cached = cached_partials[template_name]
return cached
end
source = read_template_from_file_system(context)
partial = Liquid::Template.parse(source)
cached_partials[template_name] = partial
context.registers[:cached_partials] = cached_partials
partial
end
def read_template_from_file_system(context)
file_system = context.registers[:file_system] || Liquid::Template.file_system file_system = context.registers[:file_system] || Liquid::Template.file_system
# make read_template_file call backwards-compatible. # make read_template_file call backwards-compatible.

View File

@@ -1,11 +1,12 @@
module Liquid module Liquid
# increment is used in a place where one needs to insert a counter # increment is used in a place where one needs to insert a counter
# into a template, and needs the counter to survive across # into a template, and needs the counter to survive across
# multiple instantiations of the template. # multiple instantiations of the template.
# (To achieve the survival, the application must keep the context) # (To achieve the survival, the application must keep the context)
# #
# if the variable does not exist, it is created with value 0. # if the variable does not exist, it is created with value 0.
#
# Hello: {% increment variable %} # Hello: {% increment variable %}
# #
# gives you: # gives you:
@@ -17,6 +18,7 @@ module Liquid
class Increment < Tag class Increment < Tag
def initialize(tag_name, markup, tokens) def initialize(tag_name, markup, tokens)
@variable = markup.strip @variable = markup.strip
super super
end end
@@ -26,9 +28,7 @@ module Liquid
value.to_s value.to_s
end end
def blank? private
false
end
end end
Template.register_tag('increment', Increment) Template.register_tag('increment', Increment)

View File

@@ -1,14 +1,15 @@
module Liquid module Liquid
class Raw < Block class Raw < Block
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/o def parse(tokens)
def parse_body(tokens)
@nodelist ||= [] @nodelist ||= []
@nodelist.clear @nodelist.clear
while token = tokens.shift while token = tokens.shift
if token =~ FullTokenPossiblyInvalid if token =~ FullToken
@nodelist << $1 if $1 != "" if block_delimiter == $1
return if block_delimiter == $2 end_tag
return
end
end end
@nodelist << token if not token.empty? @nodelist << token if not token.empty?
end end
@@ -17,3 +18,4 @@ module Liquid
Template.register_tag('raw', Raw) Template.register_tag('raw', Raw)
end end

View File

@@ -11,9 +11,9 @@ module Liquid
context.stack do context.stack do
# First condition is interpreted backwards ( if not ) # First condition is interpreted backwards ( if not )
first_block = @blocks.first block = @blocks.first
unless first_block.evaluate(context) unless block.evaluate(context)
return render_all(first_block.attachment, context) return render_all(block.attachment, context)
end end
# After the first condition unless works just like if # After the first condition unless works just like if

View File

@@ -14,11 +14,7 @@ module Liquid
# template.render('user_name' => 'bob') # template.render('user_name' => 'bob')
# #
class Template class Template
DEFAULT_OPTIONS = { attr_accessor :root
:locale => I18n.new
}
attr_accessor :root, :resource_limits
@@file_system = BlankFileSystem.new @@file_system = BlankFileSystem.new
class << self class << self
@@ -38,18 +34,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
@error_mode || :lax
end
# Pass a module with filter methods which should be available # Pass a module with filter methods which should be available
# to all liquid views. Good for registering the standard library # to all liquid views. Good for registering the standard library
def register_filter(mod) def register_filter(mod)
@@ -57,31 +41,24 @@ module Liquid
end end
# creates a new <tt>Template</tt> object from liquid source code # creates a new <tt>Template</tt> object from liquid source code
def parse(source, options = {}) def parse(source)
template = Template.new template = Template.new
template.parse(source, options) template.parse(source)
template template
end end
end end
# creates a new <tt>Template</tt> from an array of tokens. Use <tt>Template.parse</tt> instead # creates a new <tt>Template</tt> from an array of tokens. Use <tt>Template.parse</tt> instead
def initialize def initialize
@resource_limits = {}
end end
# Parse source code. # Parse source code.
# Returns self for easy chaining # Returns self for easy chaining
def parse(source, options = {}) def parse(source)
@root = Document.new(tokenize(source), DEFAULT_OPTIONS.merge(options)) @root = Document.new(tokenize(source))
@warnings = nil
self self
end end
def warnings
return [] unless @root
@warnings ||= @root.warnings
end
def registers def registers
@registers ||= {} @registers ||= {}
end end
@@ -115,15 +92,12 @@ module Liquid
context = case args.first context = case args.first
when Liquid::Context when Liquid::Context
args.shift args.shift
when Liquid::Drop
drop = args.shift
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
when Hash when Hash
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits) Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors)
when nil when nil
Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits) Context.new(assigns, instance_assigns, registers, @rethrow_errors)
else else
raise ArgumentError, "Expected Hash or Liquid::Context as parameter" raise ArgumentError, "Expect Hash or Liquid::Context as parameter"
end end
case args.last case args.last
@@ -146,11 +120,8 @@ module Liquid
begin begin
# render the nodelist. # render the nodelist.
# for performance reasons we get an array back here. join will make a string out of it. # for performance reasons we get a array back here. join will make a string out of it
result = @root.render(context) @root.render(context).join
result.respond_to?(:join) ? result.join : result
rescue Liquid::MemoryError => e
context.handle_error(e)
ensure ensure
@errors = context.errors @errors = context.errors
end end
@@ -162,9 +133,16 @@ module Liquid
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

View File

@@ -1,21 +1,9 @@
module Liquid module Liquid
module Utils module Utils
def self.slice_collection(collection, from, to)
if (from != 0 || to != nil) && collection.respond_to?(:load_slice)
collection.load_slice(from, to)
else
slice_collection_using_each(collection, from, to)
end
end
def self.non_blank_string?(collection)
collection.is_a?(String) && collection != ''
end
def self.slice_collection_using_each(collection, from, to) def self.slice_collection_using_each(collection, from, to)
segments = [] segments = []
index = 0 index = 0
yielded = 0
# Maintains Ruby 1.8.7 String#each behaviour on 1.9 # Maintains Ruby 1.8.7 String#each behaviour on 1.9
return [collection] if non_blank_string?(collection) return [collection] if non_blank_string?(collection)
@@ -35,5 +23,9 @@ module Liquid
segments segments
end end
def self.non_blank_string?(collection)
collection.is_a?(String) && collection != ''
end
end end
end end

View File

@@ -12,39 +12,20 @@ module Liquid
# #
class Variable class Variable
FilterParser = /(?:#{FilterSeparator}|(?:\s*(?:#{QuotedFragment}|#{ArgumentSeparator})\s*)+)/o FilterParser = /(?:#{FilterSeparator}|(?:\s*(?:#{QuotedFragment}|#{ArgumentSeparator})\s*)+)/o
EasyParse = /\A *(\w+(?:\.\w+)*) *\z/ attr_accessor :filters, :name
attr_accessor :filters, :name, :warnings
def initialize(markup, options = {}) def initialize(markup)
@markup = markup @markup = markup
@name = nil @name = nil
@options = options || {}
case @options[:error_mode] || Template.error_mode
when :strict then strict_parse(markup)
when :lax then lax_parse(markup)
when :warn
begin
strict_parse(markup)
rescue SyntaxError => e
@warnings ||= []
@warnings << e
lax_parse(markup)
end
end
end
def lax_parse(markup)
@filters = [] @filters = []
if match = markup.match(/\s*(#{QuotedFragment})(.*)/o) if match = markup.match(/\s*(#{QuotedFragment})(.*)/o)
@name = match[1] @name = match[1]
if match[2].match(/#{FilterSeparator}\s*(.*)/o) if match[2].match(/#{FilterSeparator}\s*(.*)/o)
filters = Regexp.last_match(1).scan(FilterParser) filters = Regexp.last_match(1).scan(FilterParser)
filters.each do |f| filters.each do |f|
if matches = f.match(/\s*(\w+)/) if matches = f.match(/\s*(\w+)(?:\s*#{FilterArgumentSeparator}(.*))?/)
filtername = matches[1] filtername = matches[1]
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten filterargs = matches[2].to_s.scan(/(?:\A|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
@filters << [filtername, filterargs] @filters << [filtername, filterargs]
end end
end end
@@ -52,39 +33,6 @@ module Liquid
end end
end end
def strict_parse(markup)
# Very simple valid cases
if markup =~ EasyParse
@name = $1
@filters = []
return
end
@filters = []
p = Parser.new(markup)
# Could be just filters with no input
@name = p.look(:pipe) ? '' : p.expression
while p.consume?(:pipe)
filtername = p.consume(:id)
filterargs = p.consume?(:colon) ? parse_filterargs(p) : []
@filters << [filtername, filterargs]
end
p.consume(:end_of_string)
rescue SyntaxError => e
e.message << " in \"{{#{markup}}}\""
raise e
end
def parse_filterargs(p)
# first argument
filterargs = [p.argument]
# followed by comma separated others
while p.consume?(:comma)
filterargs << p.argument
end
filterargs
end
def render(context) def render(context)
return '' if @name.nil? return '' if @name.nil?
@filters.inject(context[@name]) do |output, filter| @filters.inject(context[@name]) do |output, filter|

View File

@@ -1,4 +0,0 @@
# encoding: utf-8
module Liquid
VERSION = "3.0.0"
end

View File

@@ -1,32 +1,24 @@
# encoding: utf-8 # encoding: utf-8
lib = File.expand_path('../lib/', __FILE__)
$:.unshift lib unless $:.include?(lib)
require "liquid/version"
Gem::Specification.new do |s| Gem::Specification.new do |s|
s.name = "liquid" s.name = "liquid"
s.version = Liquid::VERSION s.version = "2.5.0"
s.platform = Gem::Platform::RUBY s.platform = Gem::Platform::RUBY
s.summary = "A secure, non-evaling end user template engine with aesthetic markup." s.summary = "A secure, non-evaling end user template engine with aesthetic markup."
s.authors = ["Tobias Luetke"] s.authors = ["Tobias Luetke"]
s.email = ["tobi@leetsoft.com"] s.email = ["tobi@leetsoft.com"]
s.homepage = "http://www.liquidmarkup.org" s.homepage = "http://www.liquidmarkup.org"
s.license = "MIT"
#s.description = "A secure, non-evaling end user template engine with aesthetic markup." #s.description = "A secure, non-evaling end user template engine with aesthetic markup."
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}/**/*") +
s.extensions = ['ext/liquid/extconf.rb'] Dir.glob("{ext}/**/*") + %w(MIT-LICENSE README.md)
s.extensions = ['ext/liquid/extconf.rb']
s.extra_rdoc_files = ["History.md", "README.md"] s.extra_rdoc_files = ["History.md", "README.md"]
s.require_path = "lib" s.require_paths = ["lib", "ext"]
s.add_development_dependency 'rake-compiler'
s.add_development_dependency 'stackprof'
s.add_development_dependency 'rake'
s.add_development_dependency 'activesupport'
end end

View File

@@ -2,11 +2,10 @@ require 'rubygems'
require 'benchmark' require 'benchmark'
require File.dirname(__FILE__) + '/theme_runner' require File.dirname(__FILE__) + '/theme_runner'
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
profiler = ThemeRunner.new profiler = ThemeRunner.new
Benchmark.bmbm do |x| Benchmark.bm do |x|
x.report("parse:") { 100.times { profiler.compile } } # x.report("parse:") { 100.times { profiler.compile } }
x.report("parse & run:") { 100.times { profiler.run } } x.report("parse & run:") { 100.times { profiler.run } }
end end

View File

@@ -9,11 +9,9 @@ puts 'Running profiler...'
results = profiler.run_profile results = profiler.run_profile
puts 'Success' puts 'Success'
puts
[RubyProf::FlatPrinter, RubyProf::GraphHtmlPrinter, RubyProf::CallTreePrinter, RubyProf::DotPrinter].each do |klass| filename = (ENV['TMP'] || '/tmp') + "/callgrind.liquid.txt"
filename = (ENV['TMP'] || '/tmp') + (klass.name.include?('Html') ? "/liquid.#{klass.name.downcase}.html" : "/callgrind.liquid.#{klass.name.downcase}.txt") File.open(filename, "w+") do |fp|
filename.gsub!(/:+/, '_') RubyProf::CallTreePrinter.new(results).print(fp, :print_file => true)
File.open(filename, "w+") { |fp| klass.new(results).print(fp, :print_file => true, :min_percent => 3) }
$stderr.puts "wrote #{klass.name} output to #{filename}"
end end
$stderr.puts "wrote RubyProf::CallTreePrinter output to #{filename}"

View File

@@ -54,7 +54,7 @@ module ShopFilter
def product_img_url(url, style = 'small') def product_img_url(url, style = 'small')
unless url =~ /\Aproducts\/([\w\-\_]+)\.(\w{2,4})/ unless url =~ /^products\/([\w\-\_]+)\.(\w{2,4})/
raise ArgumentError, 'filter "size" can only be called on product images' raise ArgumentError, 'filter "size" can only be called on product images'
end end

View File

@@ -1,15 +0,0 @@
require 'stackprof' rescue fail("install stackprof extension/gem")
require File.dirname(__FILE__) + '/theme_runner'
profiler = ThemeRunner.new
profiler.run
results = StackProf.run(mode: :cpu, out: ENV['FILENAME']) do
100.times do
profiler.run
end
end
if results.kind_of?(File)
puts "wrote stackprof dump to #{results.path}"
else
StackProf::Report.new(results).print_text(false, 20)
end

View File

@@ -17,14 +17,14 @@
<ul id="comment-list"> <ul id="comment-list">
{% for comment in article.comments %} {% for comment in article.comments %}
<li> <li>
<div class="comment-details"> <div class="comment-details">
<span class="comment-author">{{ comment.author }}</span> said on <span class="comment-date">{{ comment.created_at | date: "%B %d, %Y" }}</span>: <span class="comment-author">{{ comment.author }}</span> said on <span class="comment-date">{{ comment.created_at | date: "%B %d, %Y" }}</span>:
</div> </div>
<div class="comment"> <div class="comment">
{{ comment.content }} {{ comment.content }}
</div> </div>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>

View File

@@ -10,7 +10,7 @@
<h3 class="title"> <h3 class="title">
<a href="{{article.url}}">{{ article.title }}</a> <a href="{{article.url}}">{{ article.title }}</a>
</h3> </h3>
<h4 class="date">Posted on {{ article.created_at | date: "%B %d, '%y" }} by {{ article.author }}.</h4> <h4 class="date">Posted on {{ article.created_at | date: "%B %d, '%y" }} by {{ article.author }}.</h4>
</div> </div>
<div class="article-body textile"> <div class="article-body textile">

View File

@@ -20,17 +20,17 @@
{% for item in cart.items %} {% for item in cart.items %}
<li id="item-{{item.id}}" class="clearfix"> <li id="item-{{item.id}}" class="clearfix">
<div class="thumb"> <div class="thumb">
<div class="prodimage"> <div class="prodimage">
<a href="{{item.product.url}}" title="View {{item.title}} Page"><img src="{{item.product.featured_image | product_img_url: 'thumb' }}" alt="{{item.title | escape }}" /></a> <a href="{{item.product.url}}" title="View {{item.title}} Page"><img src="{{item.product.featured_image | product_img_url: 'thumb' }}" alt="{{item.title | escape }}" /></a>
</div></div> </div></div>
<h3 style="padding-right: 150px"> <h3 style="padding-right: 150px">
<a href="{{item.product.url}}" title="View {{item.title | escape }} Page"> <a href="{{item.product.url}}" title="View {{item.title | escape }} Page">
{{ item.title }} {{ item.title }}
{% if item.variant.available == true %} {% if item.variant.available == true %}
({{item.variant.title}}) ({{item.variant.title}})
{% endif %} {% endif %}
</a> </a>
</h3> </h3>
<small class="itemcost">Costs {{ item.price | money }} each, <span class="money">{{item.line_price | money }}</span> total.</small> <small class="itemcost">Costs {{ item.price | money }} each, <span class="money">{{item.line_price | money }}</span> total.</small>
<p class="right"> <p class="right">
<label for="updates">How many? </label> <label for="updates">How many? </label>
@@ -49,10 +49,10 @@
</div> </div>
<div class="info"> <div class="info">
<input type="image" value="Checkout!" name="checkout" src="{{ 'checkout.gif' | asset_url }}" /> <input type="image" value="Checkout!" name="checkout" src="{{ 'checkout.gif' | asset_url }}" />
</div> </div>
{% if additional_checkout_buttons %} {% if additional_checkout_buttons %}
<div class="additional-checkout-buttons"> <div class="additional-checkout-buttons">
<p>- or -</p> <p>- or -</p>
{{ content_for_additional_checkout_buttons }} {{ content_for_additional_checkout_buttons }}

View File

@@ -9,7 +9,7 @@
<div class="description"> <div class="description">
<h3><a href="{{product.url}}">{{product.title}}</a></h3> <h3><a href="{{product.url}}">{{product.title}}</a></h3>
<p>{{ product.description | strip_html | truncatewords: 35 }}</p> <p>{{ product.description | strip_html | truncatewords: 35 }}</p>
<p class="money">{{ product.price_min | money }}{% if product.price_varies %} - {{ product.price_max | money }}{% endif %}</p> <p class="money">{{ product.price_min | money }}{% if product.price_varies %} - {{ product.price_max | money }}{% endif %}</p>
</div> </div>
</li> </li>
{% endfor %} {% endfor %}

View File

@@ -5,7 +5,7 @@
<a href="{{ product.url }}"><img src="{{ product.featured_image | product_img_url: 'small' }}" alt="{{ product.title | escape }}" /></a> <a href="{{ product.url }}"><img src="{{ product.featured_image | product_img_url: 'small' }}" alt="{{ product.title | escape }}" /></a>
<h3><a href="{{ product.url }}">{{ product.title }}</a></h3> <h3><a href="{{ product.url }}">{{ product.title }}</a></h3>
<div class="description">{{ product.description | strip_html | truncatewords: 18 }}</div> <div class="description">{{ product.description | strip_html | truncatewords: 18 }}</div>
<p class="money">{{ product.price_min | money }}</p> <p class="money">{{ product.price_min | money }}</p>
</div> </div>
{% endfor %} {% endfor %}
{% for product in collections.frontpage.products offset:1 %} {% for product in collections.frontpage.products offset:1 %}
@@ -18,30 +18,30 @@
</div></div></div> </div></div></div>
<div id="mainarticle"> <div id="mainarticle">
{% assign article = pages.frontpage %} {% assign article = pages.frontpage %}
{% if article.content != "" %} {% if article.content != "" %}
<h2>{{ article.title }}</h2> <h2>{{ article.title }}</h2>
<div class="article-body textile"> <div class="article-body textile">
{{ article.content }} {{ article.content }}
</div> </div>
{% else %} {% else %}
<div class="article-body textile"> <div class="article-body textile">
In <em>Admin &gt; Blogs &amp; Pages</em>, create a page with the handle <strong><code>frontpage</code></strong> and it will show up here.<br /> In <em>Admin &gt; Blogs &amp; Pages</em>, create a page with the handle <strong><code>frontpage</code></strong> and it will show up here.<br />
{{ "Learn more about handles" | link_to: "http://wiki.shopify.com/Handle" }} {{ "Learn more about handles" | link_to "http://wiki.shopify.com/Handle" }}
</div> </div>
{% endif %} {% endif %}
</div> </div>
<br style="clear: both;" /> <br style="clear: both;" />
<div id="articles"> <div id="articles">
{% for article in blogs.news.articles offset:1 %} {% for article in blogs.news.articles offset:1 %}
<div class="article"> <div class="article">
<h2>{{ article.title }}</h2> <h2>{{ article.title }}</h2>
<div class="article-body textile"> <div class="article-body textile">
{{ article.content }} {{ article.content }}
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>

View File

@@ -1,18 +1,18 @@
<div id="productpage"> <div id="productpage">
<div id="productimages"><div id="productimages-top"><div id="productimages-bottom"> <div id="productimages"><div id="productimages-top"><div id="productimages-bottom">
{% for image in product.images %} {% for image in product.images %}
{% if forloop.first %} {% if forloop.first %}
<a href="{{ image | product_img_url: 'large' }}" class="productimage" rel="lightbox"> <a href="{{ image | product_img_url: 'large' }}" class="productimage" rel="lightbox">
<img src="{{ image | product_img_url: 'medium'}}" alt="{{product.title | escape }}" /> <img src="{{ image | product_img_url: 'medium'}}" alt="{{product.title | escape }}" />
</a> </a>
{% else %} {% else %}
<a href="{{ image | product_img_url: 'large' }}" class="productimage-small" rel="lightbox"> <a href="{{ image | product_img_url: 'large' }}" class="productimage-small" rel="lightbox">
<img src="{{ image | product_img_url: 'small'}}" alt="{{product.title | escape }}" /> <img src="{{ image | product_img_url: 'small'}}" alt="{{product.title | escape }}" />
</a> </a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div></div></div> </div></div></div>
<h2>{{ product.title }}</h2> <h2>{{ product.title }}</h2>
@@ -26,15 +26,15 @@
<div id="variant-add"> <div id="variant-add">
<form action="/cart/add" method="post"> <form action="/cart/add" method="post">
<select id="variant-select" name="id" class="product-info-options"> <select id="variant-select" name="id" class="product-info-options">
{% for variant in product.variants %} {% for variant in product.variants %}
<option value="{{ variant.id }}">{{ variant.title }} - {{ variant.price | money }}</option> <option value="{{ variant.id }}">{{ variant.title }} - {{ variant.price | money }}</option>
{% endfor %} {% endfor %}
</select> </select>
<div id="price-field" class="price"></div> <div id="price-field" class="price"></div>
<div style="text-align:center;"><input type="image" name="add" value="Add to Cart" id="add" src="{{ 'addtocart.gif' | asset_url }}" /></div> <div style="text-align:center;"><input type="image" name="add" value="Add to Cart" id="add" src="{{ 'addtocart.gif' | asset_url }}" /></div>
</form> </form>
</div> </div>

View File

@@ -1,67 +1,67 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>{{shop.name}} - {{page_title}}</title> <title>{{shop.name}} - {{page_title}}</title>
{{ 'textile.css' | global_asset_url | stylesheet_tag }} {{ 'textile.css' | global_asset_url | stylesheet_tag }}
{{ 'lightbox/v204/lightbox.css' | global_asset_url | stylesheet_tag }} {{ 'lightbox/v204/lightbox.css' | global_asset_url | stylesheet_tag }}
{{ 'prototype/1.6/prototype.js' | global_asset_url | script_tag }} {{ 'prototype/1.6/prototype.js' | global_asset_url | script_tag }}
{{ 'scriptaculous/1.8.2/scriptaculous.js' | global_asset_url | script_tag }} {{ 'scriptaculous/1.8.2/scriptaculous.js' | global_asset_url | script_tag }}
{{ 'lightbox/v204/lightbox.js' | global_asset_url | script_tag }} {{ 'lightbox/v204/lightbox.js' | global_asset_url | script_tag }}
{{ 'option_selection.js' | shopify_asset_url | script_tag }} {{ 'option_selection.js' | shopify_asset_url | script_tag }}
{{ 'layout.css' | asset_url | stylesheet_tag }} {{ 'layout.css' | asset_url | stylesheet_tag }}
{{ 'shop.js' | asset_url | script_tag }} {{ 'shop.js' | asset_url | script_tag }}
{{ content_for_header }} {{ content_for_header }}
</head> </head>
<body id="page-{{template}}"> <body id="page-{{template}}">
<p class="hide"><a href="#rightsiders">Skip to navigation.</a></p> <p class="hide"><a href="#rightsiders">Skip to navigation.</a></p>
<!-- mini cart --> <!-- mini cart -->
{% if cart.item_count > 0 %} {% if cart.item_count > 0 %}
<div id="minicart" style="display:none;"><div id="minicart-inner"> <div id="minicart" style="display:none;"><div id="minicart-inner">
<div id="minicart-items"> <div id="minicart-items">
<h2>There {{ cart.item_count | pluralize: 'is', 'are' }} {{ cart.item_count }} {{ cart.item_count | pluralize: 'item', 'items' }} in <a href="/cart" title="View your cart">your cart</a>!</h2><h4 style="font-size: 16px; margin: 0 0 10px 0; padding: 0;">Your subtotal is {{ cart.total_price | money }}.</h4> <h2>There {{ cart.item_count | pluralize: 'is', 'are' }} {{ cart.item_count }} {{ cart.item_count | pluralize: 'item', 'items' }} in <a href="/cart" title="View your cart">your cart</a>!</h2><h4 style="font-size: 16px; margin: 0 0 10px 0; padding: 0;">Your subtotal is {{ cart.total_price | money }}.</h4>
{% for item in cart.items %} {% for item in cart.items %}
<div class="thumb"> <div class="thumb">
<div class="prodimage"><a href="{{item.product.url}}" onMouseover="tooltip('{{ item.quantity }} x {{ item.title }} ({{ item.variant.title }})', 200)"; onMouseout="hidetooltip()"><img src="{{ item.product.featured_image | product_img_url: 'thumb' }}" /></a></div> <div class="prodimage"><a href="{{item.product.url}}" onMouseover="tooltip('{{ item.quantity }} x {{ item.title }} ({{ item.variant.title }})', 200)"; onMouseout="hidetooltip()"><img src="{{ item.product.featured_image | product_img_url: 'thumb' }}" /></a></div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<br style="clear:both;" /> <br style="clear:both;" />
</div></div> </div></div>
{% endif %} {% endif %}
<div id="container"> <div id="container">
<div id="header"> <div id="header">
<!-- Begin Header --> <!-- Begin Header -->
<h1 id="logo"><a href="/" title="Go Home">{{shop.name}}</a></h1> <h1 id="logo"><a href="/" title="Go Home">{{shop.name}}</a></h1>
<div id="cartlinks"> <div id="cartlinks">
{% if cart.item_count > 0 %} {% if cart.item_count > 0 %}
<h2 id="cartcount"><a href="/cart" onMouseover="tooltip('There {{ cart.item_count | pluralize: 'is', 'are' }} {{ cart.item_count }} {{ cart.item_count | pluralize: 'item', 'items' }} in your cart!', 200)"; onMouseout="hidetooltip()">{{ cart.item_count }} {{ cart.item_count | pluralize: 'thing', 'things' }}!</a></h2> <h2 id="cartcount"><a href="/cart" onMouseover="tooltip('There {{ cart.item_count | pluralize: 'is', 'are' }} {{ cart.item_count }} {{ cart.item_count | pluralize: 'item', 'items' }} in your cart!', 200)"; onMouseout="hidetooltip()">{{ cart.item_count }} {{ cart.item_count | pluralize: 'thing', 'things' }}!</a></h2>
<a href="/cart" id="minicartswitch" onclick="superSwitch(this, 'minicart', 'Close Mini Cart'); return false;" id="cartswitch">View Mini Cart ({{ cart.total_price | money }})</a> <a href="/cart" id="minicartswitch" onclick="superSwitch(this, 'minicart', 'Close Mini Cart'); return false;" id="cartswitch">View Mini Cart ({{ cart.total_price | money }})</a>
{% endif %} {% endif %}
</div> </div>
<!-- End Header --> <!-- End Header -->
</div> </div>
<hr /> <hr />
<div id="main"> <div id="main">
<div id="content"> <div id="content">
<div id="innercontent"> <div id="innercontent">
{{ content_for_layout }} {{ content_for_layout }}
</div> </div>
</div> </div>
<hr /> <hr />
<div id="rightsiders"> <div id="rightsiders">
<ul class="rightlinks"> <ul class="rightlinks">
{% for link in linklists.main-menu.links %} {% for link in linklists.main-menu.links %}
@@ -83,18 +83,18 @@
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
<hr /><br style="clear:both;" /> <hr /><br style="clear:both;" />
<div id="footer"> <div id="footer">
<div class="footerinner"> <div class="footerinner">
All prices are in {{ shop.currency }}. All prices are in {{ shop.currency }}.
Powered by <a href="http://www.shopify.com" title="Shopify, Hosted E-Commerce">Shopify</a>. Powered by <a href="http://www.shopify.com" title="Shopify, Hosted E-Commerce">Shopify</a>.
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div id="tooltip"></div> <div id="tooltip"></div>

View File

@@ -21,10 +21,10 @@
{{ comment.content }} {{ comment.content }}
</div> </div>
<div class="comment-details"> <div class="comment-details">
Posted by {{ comment.author }} on {{ comment.created_at | date: "%B %d, %Y" }} Posted by {{ comment.author }} on {{ comment.created_at | date: "%B %d, %Y" }}
</div> </div>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>

View File

@@ -9,7 +9,7 @@
{% if cart.item_count == 0 %} {% if cart.item_count == 0 %}
<p>Your shopping cart is empty...</p> <p>Your shopping cart is empty...</p>
<p><a href="/"><img src="{{ 'continue_shopping_icon.gif' | asset_url }}" alt="Continue shopping"/></a><p> <p><a href="/"><img src="{{ 'continue_shopping_icon.gif' | asset_url }}" alt="Continue shopping"/></a><p>
{% else %} {% else %}
<form action="/cart" method="post" id="cart"> <form action="/cart" method="post" id="cart">
@@ -26,21 +26,21 @@
{% for item in cart.items %} {% for item in cart.items %}
<tr class="{% cycle 'odd', 'even' %}"> <tr class="{% cycle 'odd', 'even' %}">
<td class="short">{{ item.product.featured_image | product_img_url: 'thumb' | img_tag }}</td> <td class="short">{{ item.product.featured_image | product_img_url: 'thumb' | img_tag }}</td>
<td><a href="{{item.product.url}}">{{ item.title }}</a></td> <td><a href="{{item.product.url}}">{{ item.title }}</a></td>
<td class="short"><input type="text" class="quantity" name="updates[{{item.variant.id}}]" id="updates_{{item.variant.id}}" value="{{item.quantity}}" onfocus="this.select();"/></td> <td class="short"><input type="text" class="quantity" name="updates[{{item.variant.id}}]" id="updates_{{item.variant.id}}" value="{{item.quantity}}" onfocus="this.select();"/></td>
<td class="cart-price">{{ item.price | money }}</td> <td class="cart-price">{{ item.price | money }}</td>
<td class="cart-price">{{item.line_price | money }}</td> <td class="cart-price">{{item.line_price | money }}</td>
<td class="short"><a href="#" onclick="remove_item({{item.variant.id}}); return false;" class="remove"><img src="{{ 'cancel_icon.gif' | asset_url }}" alt="Remove" /></a></td> <td class="short"><a href="#" onclick="remove_item({{item.variant.id}}); return false;" class="remove"><img src="{{ 'cancel_icon.gif' | asset_url }}" alt="Remove" /></a></td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
<p class="updatebtn"><input type="image" value="Update Cart" name="update" src="{{ 'update_icon.gif' | asset_url }}" alt="Update" /></p> <p class="updatebtn"><input type="image" value="Update Cart" name="update" src="{{ 'update_icon.gif' | asset_url }}" alt="Update" /></p>
<p class="subtotal"> <p class="subtotal">
<strong>Subtotal:</strong> {{cart.total_price | money_with_currency }} <strong>Subtotal:</strong> {{cart.total_price | money_with_currency }}
</p> </p>
<p class="checkout"><input type="image" src="{{ 'checkout_icon.gif' | asset_url }}" alt="Proceed to Checkout" value="Proceed to Checkout" name="checkout" /></p> <p class="checkout"><input type="image" src="{{ 'checkout_icon.gif' | asset_url }}" alt="Proceed to Checkout" value="Proceed to Checkout" name="checkout" /></p>
{% if additional_checkout_buttons %} {% if additional_checkout_buttons %}
<div class="additional-checkout-buttons"> <div class="additional-checkout-buttons">
<p>- or -</p> <p>- or -</p>
{{ content_for_additional_checkout_buttons }} {{ content_for_additional_checkout_buttons }}

View File

@@ -15,7 +15,7 @@
<div class="prod-list-description"> <div class="prod-list-description">
<h3><a href="{{product.url}}">{{product.title}}</a></h3> <h3><a href="{{product.url}}">{{product.title}}</a></h3>
<p>{{ product.description | strip_html | truncatewords: 35 }}</p> <p>{{ product.description | strip_html | truncatewords: 35 }}</p>
<p class="prd-price">{{ product.price_min | money }}{% if product.price_varies %} - {{ product.price_max | money }}{% endif %}</p> <p class="prd-price">{{ product.price_min | money }}{% if product.price_varies %} - {{ product.price_max | money }}{% endif %}</p>
</div> </div>
</li> </li>
{% endfor %} {% endfor %}

View File

@@ -1,32 +1,32 @@
<div id="home-page"> <div id="home-page">
<h3 class="heading-shaded">Featured products...</h3> <h3 class="heading-shaded">Featured products...</h3>
<div class="featured-prod-row clearfix"> <div class="featured-prod-row clearfix">
{% for product in collections.frontpage.products %} {% for product in collections.frontpage.products %}
<div class="featured-prod-item"> <div class="featured-prod-item">
<p> <p>
<a href="{{product.url}}"><img src="{{ product.featured_image | product_img_url: 'small' }}" alt="{{ product.title | escape }}"/></a> <a href="{{product.url}}"><img src="{{ product.featured_image | product_img_url: 'small' }}" alt="{{ product.title | escape }}"/></a>
</p> </p>
<h4><a href="{{product.url}}">{{product.title}}</a></h4> <h4><a href="{{product.url}}">{{product.title}}</a></h4>
{% if product.compare_at_price %} {% if product.compare_at_price %}
{% if product.price_min != product.compare_at_price %} {% if product.price_min != product.compare_at_price %}
<p class="prd-price">Was:<del>{{product.compare_at_price | money}}</del></p> <p class="prd-price">Was:<del>{{product.compare_at_price | money}}</del></p>
<p class="prd-price"><ins>Now: {{product.price_min | money}}</ins></p> <p class="prd-price"><ins>Now: {{product.price_min | money}}</ins></p>
{% endif %} {% endif %}
{% else %} {% else %}
<p class="prd-price"><ins>{{product.price_min | money}}</ins></p> <p class="prd-price"><ins>{{product.price_min | money}}</ins></p>
{% endif %} {% endif %}
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<div id="articles"> <div id="articles">
{% assign article = pages.frontpage %} {% assign article = pages.frontpage %}
{% if article.content != "" %} {% if article.content != "" %}
<h3>{{ article.title }}</h3> <h3>{{ article.title }}</h3>
{{ article.content }} {{ article.content }}
{% else %} {% else %}
In <em>Admin &gt; Blogs &amp; Pages</em>, create a page with the handle <strong><code>frontpage</code></strong> and it will show up here.<br /> In <em>Admin &gt; Blogs &amp; Pages</em>, create a page with the handle <strong><code>frontpage</code></strong> and it will show up here.<br />
{{ "Learn more about handles" | link_to: "http://wiki.shopify.com/Handle" }} {{ "Learn more about handles" | link_to "http://wiki.shopify.com/Handle" }}
{% endif %} {% endif %}
</div> </div>
</div> </div>

View File

@@ -1,31 +1,31 @@
<div id="product-page"> <div id="product-page">
<h2 class="heading-shaded">{{ product.title }}</h2> <h2 class="heading-shaded">{{ product.title }}</h2>
<div id="product-details"> <div id="product-details">
<div id="product-images"> <div id="product-images">
{% for image in product.images %} {% for image in product.images %}
{% if forloop.first %} {% if forloop.first %}
<a href="{{ image | product_img_url: 'large' }}" class="product-image" rel="lightbox[ product]" title=""> <a href="{{ image | product_img_url: 'large' }}" class="product-image" rel="lightbox[ product]" title="">
<img src="{{ image | product_img_url: 'medium'}}" alt="{{product.title | escape }}" /> <img src="{{ image | product_img_url: 'medium'}}" alt="{{product.title | escape }}" />
</a> </a>
{% else %} {% else %}
<a href="{{ image | product_img_url: 'large' }}" class="product-image-small" rel="lightbox[ product]" title=""> <a href="{{ image | product_img_url: 'large' }}" class="product-image-small" rel="lightbox[ product]" title="">
<img src="{{ image | product_img_url: 'small'}}" alt="{{product.title | escape }}" /> <img src="{{ image | product_img_url: 'small'}}" alt="{{product.title | escape }}" />
</a> </a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div> </div>
<ul id="product-info"> <ul id="product-info">
<li>Vendor: {{ product.vendor | link_to_vendor }}</li> <li>Vendor: {{ product.vendor | link_to_vendor }}</li>
<li>Type: {{ product.type | link_to_type }}</li> <li>Type: {{ product.type | link_to_type }}</li>
</ul> </ul>
<small>{{ product.price_min | money }}{% if product.price_varies %} - {{ product.price_max | money }}{% endif %}</small> <small>{{ product.price_min | money }}{% if product.price_varies %} - {{ product.price_max | money }}{% endif %}</small>
<div id="product-options"> <div id="product-options">
{% if product.available %} {% if product.available %}
<form action="/cart/add" method="post"> <form action="/cart/add" method="post">
<select id="product-select" name='id'> <select id="product-select" name='id'>
{% for variant in product.variants %} {% for variant in product.variants %}
@@ -35,16 +35,16 @@
<div id="price-field"></div> <div id="price-field"></div>
<div class="add-to-cart"><input type="image" name="add" value="Add to Cart" id="add" src="{{ 'add-to-cart.gif' | asset_url }}" /></div> <div class="add-to-cart"><input type="image" name="add" value="Add to Cart" id="add" src="{{ 'add-to-cart.gif' | asset_url }}" /></div>
</form> </form>
{% else %} {% else %}
<span>Sold Out!</span> <span>Sold Out!</span>
{% endif %} {% endif %}
</div> </div>
<div class="product-description"> <div class="product-description">
{{ product.description }} {{ product.description }}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -2,15 +2,15 @@
<html xmlns="http://www.w3.org/1999/xhtml" lang="en"> <html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head> <head>
<title>{{shop.name}} - {{page_title}}</title> <title>{{shop.name}} - {{page_title}}</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
{{ 'main.css' | asset_url | stylesheet_tag }} {{ 'main.css' | asset_url | stylesheet_tag }}
{{ 'shop.js' | asset_url | script_tag }} {{ 'shop.js' | asset_url | script_tag }}
{{ 'mootools.js' | asset_url | script_tag }} {{ 'mootools.js' | asset_url | script_tag }}
{{ 'slimbox.js' | asset_url | script_tag }} {{ 'slimbox.js' | asset_url | script_tag }}
{{ 'option_selection.js' | shopify_asset_url | script_tag }} {{ 'option_selection.js' | shopify_asset_url | script_tag }}
{{ 'slimbox.css' | asset_url | stylesheet_tag }} {{ 'slimbox.css' | asset_url | stylesheet_tag }}
{{ content_for_header }} {{ content_for_header }}
</head> </head>
@@ -18,67 +18,67 @@
<body id="page-{{template}}"> <body id="page-{{template}}">
<p class="hide"><a href="#navigation">Skip to navigation.</a></p> <p class="hide"><a href="#navigation">Skip to navigation.</a></p>
<div id="wrapper"> <div id="wrapper">
<div class="content clearfix"> <div class="content clearfix">
<div id="header"> <div id="header">
<h2><a href="/">{{shop.name}}</a></h2> <h2><a href="/">{{shop.name}}</a></h2>
</div> </div>
<div id="left-col"> <div id="left-col">
{{ content_for_layout }} {{ content_for_layout }}
</div> </div>
<div id="right-col"> <div id="right-col">
{% if template != 'cart' %} {% if template != 'cart' %}
<div id="cart-right-col"> <div id="cart-right-col">
<dl id="cart-right-col-info"> <dl id="cart-right-col-info">
<dt>Shopping Cart</dt> <dt>Shopping Cart</dt>
<dd> <dd>
{% if cart.item_count != 0 %} {% if cart.item_count != 0 %}
<a href="/cart">{{ cart.item_count }} {{ cart.item_count | pluralize: 'item', 'items' }}</a> in your cart <a href="/cart">{{ cart.item_count }} {{ cart.item_count | pluralize: 'item', 'items' }}</a> in your cart
{% else %} {% else %}
Your cart is empty Your cart is empty
{% endif %} {% endif %}
</dd> </dd>
</dl> </dl>
</div> </div>
{% endif %} {% endif %}
<div id="search"> <div id="search">
<dl id="searchbox"> <dl id="searchbox">
<dt>Search</dt> <dt>Search</dt>
<dd> <dd>
<form action="/search" method="get"> <form action="/search" method="get">
<fieldset> <fieldset>
<input class="search-input" type="text" onclick="this.select()" value="Search this shop..." name="q" /> <input class="search-input" type="text" onclick="this.select()" value="Search this shop..." name="q" />
</fieldset> </fieldset>
</form> </form>
</dd> </dd>
</dl> </dl>
</div> </div>
<div id="navigation"> <div id="navigation">
<dl class="navbar"> <dl class="navbar">
<dt>Navigation</dt> <dt>Navigation</dt>
{% for link in linklists.main-menu.links %} {% for link in linklists.main-menu.links %}
<dd>{{ link.title | link_to: link.url }}</dd> <dd>{{ link.title | link_to: link.url }}</dd>
{% endfor %} {% endfor %}
</dl> </dl>
{% if tags %} {% if tags %}
<dl class="navbar"> <dl class="navbar">
<dt>Tags</dt> <dt>Tags</dt>
{% for tag in collection.tags %} {% for tag in collection.tags %}
<dd>{{ tag | highlight_active_tag | link_to_tag: tag }}</dd> <dd>{{ tag | highlight_active_tag | link_to_tag: tag }}</dd>
{% endfor %} {% endfor %}
</dl> </dl>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> </div>
<div id="content-padding"></div> <div id="content-padding"></div>
</div> </div>
<div id="footer"> <div id="footer">
{% for link in linklists.footer.links %} {% for link in linklists.footer.links %}
{{ link.title | link_to: link.url }} {% if forloop.rindex != 1 %} | {% endif %} {{ link.title | link_to: link.url }} {% if forloop.rindex != 1 %} | {% endif %}
{% endfor %} {% endfor %}
</div> </div>
</body> </body>

View File

@@ -1,56 +1,56 @@
<div id="page" class="innerpage clearfix"> <div id="page" class="innerpage clearfix">
<div id="text-page"> <div id="text-page">
<div class="entry"> <div class="entry">
<h1>Oh no!</h1> <h1>Oh no!</h1>
<div class="entry-post"> <div class="entry-post">
Seems like you are looking for something that just isn't here. <a href="/">Try heading back to our main page</a>. Or you can checkout some of our featured products below. Seems like you are looking for something that just isn't here. <a href="/">Try heading back to our main page</a>. Or you can checkout some of our featured products below.
</div> </div>
</div> </div>
</div> </div>
<h1>Featured Products</h1> <h1>Featured Products</h1>
<ul class="item-list clearfix"> <ul class="item-list clearfix">
{% for product in collections.frontpage.products %} {% for product in collections.frontpage.products %}
<li> <li>
<form action="/cart/add" method="post"> <form action="/cart/add" method="post">
<div class="item-list-item"> <div class="item-list-item">
<div class="ili-top clearfix"> <div class="ili-top clearfix">
<div class="ili-top-content"> <div class="ili-top-content">
<h2><a href="{{product.url}}">{{product.title}}</a></h2> <h2><a href="{{product.url}}">{{product.title}}</a></h2>
<p>{{ product.description | truncatewords: 15 }}</p> <p>{{ product.description | truncatewords: 15 }}</p>
</div> </div>
<a href="{{product.url}}" class="ili-top-image"><img src="{{ product.featured_image | product_img_url: 'small' }}" alt="{{ product.title | escape }}"/></a> <a href="{{product.url}}" class="ili-top-image"><img src="{{ product.featured_image | product_img_url: 'small' }}" alt="{{ product.title | escape }}"/></a>
</div> </div>
<div class="ili-bottom clearfix"> <div class="ili-bottom clearfix">
<p class="hiddenvariants" style="display: none">{% for variant in product.variants %}<span><input type="radio" name="id" value="{{variant.id}}" id="radio_{{variant.id}}" style="vertical-align: middle;" {%if forloop.first%} checked="checked" {%endif%} /><label for="radio_{{variant.id}}">{{ variant.price | money_with_currency }} - {{ variant.title }}</label></span>{% endfor %}</p> <p class="hiddenvariants" style="display: none">{% for variant in product.variants %}<span><input type="radio" name="id" value="{{variant.id}}" id="radio_{{variant.id}}" style="vertical-align: middle;" {%if forloop.first%} checked="checked" {%endif%} /><label for="radio_{{variant.id}}">{{ variant.price | money_with_currency }} - {{ variant.title }}</label></span>{% endfor %}</p>
<input type="submit" class="" value="Add to Basket" /> <input type="submit" class="" value="Add to Basket" />
<p> <p>
<a href="{{product.url}}">View Details</a> <a href="{{product.url}}">View Details</a>
<span> <span>
{% if product.compare_at_price %} {% if product.compare_at_price %}
{% if product.price_min != product.compare_at_price %} {% if product.price_min != product.compare_at_price %}
{{product.compare_at_price | money}} - {{product.compare_at_price | money}} -
{% endif %} {% endif %}
{% endif %} {% endif %}
<strong> <strong>
{{product.price_min | money}} {{product.price_min | money}}
</strong> </strong>
</span> </span>
</p> </p>
</div> </div>
</div> </div>
</form> </form>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
<!-- end page --> <!-- end page -->

View File

@@ -1,15 +1,15 @@
<div id="page" class="innerpage clearfix"> <div id="page" class="innerpage clearfix">
<div id="text-page"> <div id="text-page">
<div class="entry"> <div class="entry">
<h1><span>{{article.title}}</span></h1> <h1><span>{{article.title}}</span></h1>
<div class="entry-post"> <div class="entry-post">
<div class="meta">{{ article.created_at | date: "%b %d" }}</div> <div class="meta">{{ article.created_at | date: "%b %d" }}</div>
{{ article.content }} {{ article.content }}
</div> </div>
<!-- Comments --> <!-- Comments -->
{% if blog.comments_enabled? %} {% if blog.comments_enabled? %}
<div id="comments"> <div id="comments">
<h2>Comments</h2> <h2>Comments</h2>
@@ -22,10 +22,10 @@
{{ comment.content }} {{ comment.content }}
</div> </div>
<div class="comment-details"> <div class="comment-details">
Posted by <span class="comment-author">{{ comment.author }}</span> on <span class="comment-date">{{ comment.created_at | date: "%B %d, %Y" }}</span> Posted by <span class="comment-author">{{ comment.author }}</span> on <span class="comment-date">{{ comment.created_at | date: "%B %d, %Y" }}</span>
</div> </div>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
@@ -75,24 +75,24 @@
<!-- END Comments --> <!-- END Comments -->
</div> </div>
</div> </div>
<div id="three-reasons" class="clearfix"> <div id="three-reasons" class="clearfix">
<h3>Why Shop With Us?</h3> <h3>Why Shop With Us?</h3>
<ul> <ul>
<li class="two-a"> <li class="two-a">
<h4>24 Hours</h4> <h4>24 Hours</h4>
<p>We're always here to help.</p> <p>We're always here to help.</p>
</li> </li>
<li class="two-c"> <li class="two-c">
<h4>No Spam</h4> <h4>No Spam</h4>
<p>We'll never share your info.</p> <p>We'll never share your info.</p>
</li> </li>
<li class="two-d"> <li class="two-d">
<h4>Secure Servers</h4> <h4>Secure Servers</h4>
<p>Checkout is 256bit encrypted.</p> <p>Checkout is 256bit encrypted.</p>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@@ -1,41 +1,41 @@
<div id="page" class="innerpage clearfix"> <div id="page" class="innerpage clearfix">
<div id="text-page"> <div id="text-page">
<h1>Post from our blog...</h1> <h1>Post from our blog...</h1>
{% paginate blog.articles by 20 %} {% paginate blog.articles by 20 %}
{% for article in blog.articles %} {% for article in blog.articles %}
<div class="entry"> <div class="entry">
<h1><span><a href="{{ article.url }}">{{ article.title }}</a></span></h1> <h1><span><a href="{{ article.url }}">{{ article.title }}</a></span></h1>
<div class="entry-post"> <div class="entry-post">
<div class="meta">{{ article.created_at | date: "%b %d" }}</div> <div class="meta">{{ article.created_at | date: "%b %d" }}</div>
{{ article.content }} {{ article.content }}
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
<div class="paginate clearfix"> <div class="paginate clearfix">
{{ paginate | default_pagination }} {{ paginate | default_pagination }}
</div> </div>
{% endpaginate %} {% endpaginate %}
</div> </div>
<div id="three-reasons" class="clearfix"> <div id="three-reasons" class="clearfix">
<h3>Why Shop With Us?</h3> <h3>Why Shop With Us?</h3>
<ul> <ul>
<li class="two-a"> <li class="two-a">
<h4>24 Hours</h4> <h4>24 Hours</h4>
<p>We're always here to help.</p> <p>We're always here to help.</p>
</li> </li>
<li class="two-c"> <li class="two-c">
<h4>No Spam</h4> <h4>No Spam</h4>
<p>We'll never share your info.</p> <p>We'll never share your info.</p>
</li> </li>
<li class="two-d"> <li class="two-d">
<h4>Secure Servers</h4> <h4>Secure Servers</h4>
<p>Checkout is 256bit encrypted.</p> <p>Checkout is 256bit encrypted.</p>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@@ -5,55 +5,55 @@
} }
</script> </script>
<div id="page" class="innerpage clearfix">. <div id="page" class="innerpage clearfix">.
{% if cart.item_count == 0 %} {% if cart.item_count == 0 %}
<h1>Your cart is currently empty.</h1> <h1>Your cart is currently empty.</h1>
{% else %} {% else %}
<h1>Your Cart <span>({{ cart.item_count }} {{ cart.item_count | pluralize: 'item', 'items' }}, {{cart.total_price | money_with_currency }} total)</span></h1> <h1>Your Cart <span>({{ cart.item_count }} {{ cart.item_count | pluralize: 'item', 'items' }}, {{cart.total_price | money_with_currency }} total)</span></h1>
<form action="/cart" method="post" id="cart-form"> <form action="/cart" method="post" id="cart-form">
<div id="cart-wrap"> <div id="cart-wrap">
<table width="100%" border="0" cellspacing="0" cellpadding="0"> <table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr> <tr>
<th scope="col" class="td-image"><label>Image</label></th> <th scope="col" class="td-image"><label>Image</label></th>
<th scope="col" class="td-title"><label>Product Title</label></th> <th scope="col" class="td-title"><label>Product Title</label></th>
<th scope="col" class="td-count"><label>Count</label></th> <th scope="col" class="td-count"><label>Count</label></th>
<th scope="col" class="td-price"><label>Cost</label></th> <th scope="col" class="td-price"><label>Cost</label></th>
<th scope="col" class="td-delete"><label>Remove</label></th> <th scope="col" class="td-delete"><label>Remove</label></th>
</tr> </tr>
{% for item in cart.items %} {% for item in cart.items %}
<tr class="{% cycle 'reg', 'alt' %}"> <tr class="{% cycle 'reg', 'alt' %}">
<td colspan="5"> <td colspan="5">
<table width="100%" border="0" cellspacing="0" cellpadding="0"> <table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr> <tr>
<td class="td-image"><a href="{{item.product.url}}">{{ item.product.featured_image | product_img_url: 'thumb' | img_tag }}</a></td> <td class="td-image"><a href="{{item.product.url}}">{{ item.product.featured_image | product_img_url: 'thumb' | img_tag }}</a></td>
<td class="td-title"><p>{{ item.title }}</p></td> <td class="td-title"><p>{{ item.title }}</p></td>
<td class="td-count"><label>Count:</label> <input type="text" class="quantity item-count" name="updates[{{item.variant.id}}]" id="updates_{{item.variant.id}}" value="{{item.quantity}}" onfocus="this.select();"/></td> <td class="td-count"><label>Count:</label> <input type="text" class="quantity item-count" name="updates[{{item.variant.id}}]" id="updates_{{item.variant.id}}" value="{{item.quantity}}" onfocus="this.select();"/></td>
<td class="td-price">{{item.line_price | money }}</td> <td class="td-price">{{item.line_price | money }}</td>
<td class="td-delete"><a href="#" onclick="remove_item({{item.variant.id}}); return false;">Remove</a></td> <td class="td-delete"><a href="#" onclick="remove_item({{item.variant.id}}); return false;">Remove</a></td>
</tr> </tr>
</table> </table>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
<div id="finish-up"> <div id="finish-up">
<div class="latest-news-box"> <div class="latest-news-box">
{{ pages.shopping-cart.content }} {{ pages.shopping-cart.content }}
</div> </div>
<p class="order-total"> <p class="order-total">
<span><strong>Order Total:</strong> {{cart.total_price | money_with_currency }}</span> <span><strong>Order Total:</strong> {{cart.total_price | money_with_currency }}</span>
</p> </p>
<p class="update-cart"><input type="submit" value="Refresh Cart" name="update" /></p> <p class="update-cart"><input type="submit" value="Refresh Cart" name="update" /></p>
<p class="go-checkout"><input type="submit" value="Proceed to Checkout" name="checkout" /></p> <p class="go-checkout"><input type="submit" value="Proceed to Checkout" name="checkout" /></p>
{% if additional_checkout_buttons %} {% if additional_checkout_buttons %}
<div class="additional-checkout-buttons"> <div class="additional-checkout-buttons">
@@ -62,73 +62,73 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</form> </form>
{% endif %} {% endif %}
<h1 class="other-products"><span>Other Products You Might Enjoy</span></h1> <h1 class="other-products"><span>Other Products You Might Enjoy</span></h1>
<ul class="item-list clearfix"> <ul class="item-list clearfix">
{% for product in collections.frontpage.products limit:2 %} {% for product in collections.frontpage.products limit:2 %}
<li> <li>
<form action="/cart/add" method="post"> <form action="/cart/add" method="post">
<div class="item-list-item"> <div class="item-list-item">
<div class="ili-top clearfix"> <div class="ili-top clearfix">
<div class="ili-top-content"> <div class="ili-top-content">
<h2><a href="{{product.url}}">{{product.title}}</a></h2> <h2><a href="{{product.url}}">{{product.title}}</a></h2>
<p>{{ product.description | truncatewords: 15 }}</p> <p>{{ product.description | truncatewords: 15 }}</p>
</div> </div>
<a href="{{product.url}}" class="ili-top-image"><img src="{{ product.featured_image | product_img_url: 'small' }}" alt="{{ product.title | escape }}"/></a> <a href="{{product.url}}" class="ili-top-image"><img src="{{ product.featured_image | product_img_url: 'small' }}" alt="{{ product.title | escape }}"/></a>
</div> </div>
<div class="ili-bottom clearfix"> <div class="ili-bottom clearfix">
<p class="hiddenvariants" style="display: none">{% for variant in product.variants %}<span><input type="radio" name="id" value="{{variant.id}}" id="radio_{{variant.id}}" style="vertical-align: middle;" {%if forloop.first%} checked="checked" {%endif%} /><label for="radio_{{variant.id}}">{{ variant.price | money_with_currency }} - {{ variant.title }}</label></span>{% endfor %}</p> <p class="hiddenvariants" style="display: none">{% for variant in product.variants %}<span><input type="radio" name="id" value="{{variant.id}}" id="radio_{{variant.id}}" style="vertical-align: middle;" {%if forloop.first%} checked="checked" {%endif%} /><label for="radio_{{variant.id}}">{{ variant.price | money_with_currency }} - {{ variant.title }}</label></span>{% endfor %}</p>
<input type="submit" class="" value="Add to Basket" /> <input type="submit" class="" value="Add to Basket" />
<p> <p>
<a href="{{product.url}}">View Details</a> <a href="{{product.url}}">View Details</a>
<span> <span>
{% if product.compare_at_price %} {% if product.compare_at_price %}
{% if product.price_min != product.compare_at_price %} {% if product.price_min != product.compare_at_price %}
{{product.compare_at_price | money}} - {{product.compare_at_price | money}} -
{% endif %} {% endif %}
{% endif %} {% endif %}
<strong> <strong>
{{product.price_min | money}} {{product.price_min | money}}
</strong> </strong>
</span> </span>
</p> </p>
</div> </div>
</div> </div>
</form> </form>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
<div id="three-reasons" class="clearfix"> <div id="three-reasons" class="clearfix">
<h3>Why Shop With Us?</h3> <h3>Why Shop With Us?</h3>
<ul> <ul>
<li class="two-a"> <li class="two-a">
<h4>24 Hours</h4> <h4>24 Hours</h4>
<p>We're always here to help.</p> <p>We're always here to help.</p>
</li> </li>
<li class="two-c"> <li class="two-c">
<h4>No Spam</h4> <h4>No Spam</h4>
<p>We'll never share your info.</p> <p>We'll never share your info.</p>
</li> </li>
<li class="two-d"> <li class="two-d">
<h4>Secure Servers</h4> <h4>Secure Servers</h4>
<p>Checkout is 256bit encrypted.</p> <p>Checkout is 256bit encrypted.</p>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
<!-- end page --> <!-- end page -->

View File

@@ -1,70 +1,70 @@
<div id="page" class="innerpage clearfix"> <div id="page" class="innerpage clearfix">
<h1>{{ collection.title }}</h1> <h1>{{ collection.title }}</h1>
{% if collection.description.size > 0 %} {% if collection.description.size > 0 %}
<div class="latest-news">{{ collection.description }}</div> <div class="latest-news">{{ collection.description }}</div>
{% endif %} {% endif %}
{% paginate collection.products by 8 %} {% paginate collection.products by 8 %}
<ul class="item-list clearfix"> <ul class="item-list clearfix">
{% for product in collection.products %} {% for product in collection.products %}
<li> <li>
<form action="/cart/add" method="post"> <form action="/cart/add" method="post">
<div class="item-list-item"> <div class="item-list-item">
<div class="ili-top clearfix"> <div class="ili-top clearfix">
<div class="ili-top-content"> <div class="ili-top-content">
<h2><a href="{{product.url}}">{{product.title}}</a></h2> <h2><a href="{{product.url}}">{{product.title}}</a></h2>
<p>{{ product.description | truncatewords: 15 }}</p> <p>{{ product.description | truncatewords: 15 }}</p>
</div> </div>
<a href="{{product.url}}" class="ili-top-image"><img src="{{ product.featured_image | product_img_url: 'small' }}" alt="{{ product.title | escape }}"/></a> <a href="{{product.url}}" class="ili-top-image"><img src="{{ product.featured_image | product_img_url: 'small' }}" alt="{{ product.title | escape }}"/></a>
</div> </div>
<div class="ili-bottom clearfix"> <div class="ili-bottom clearfix">
<p class="hiddenvariants" style="display: none">{% for variant in product.variants %}<span><input type="radio" name="id" value="{{variant.id}}" id="radio_{{variant.id}}" style="vertical-align: middle;" {%if forloop.first%} checked="checked" {%endif%} /><label for="radio_{{variant.id}}">{{ variant.price | money_with_currency }} - {{ variant.title }}</label></span>{% endfor %}</p> <p class="hiddenvariants" style="display: none">{% for variant in product.variants %}<span><input type="radio" name="id" value="{{variant.id}}" id="radio_{{variant.id}}" style="vertical-align: middle;" {%if forloop.first%} checked="checked" {%endif%} /><label for="radio_{{variant.id}}">{{ variant.price | money_with_currency }} - {{ variant.title }}</label></span>{% endfor %}</p>
<input type="submit" class="" value="Add to Basket" /> <input type="submit" class="" value="Add to Basket" />
<p> <p>
<a href="{{product.url}}">View Details</a> <a href="{{product.url}}">View Details</a>
<span> <span>
{% if product.compare_at_price %} {% if product.compare_at_price %}
{% if product.price_min != product.compare_at_price %} {% if product.price_min != product.compare_at_price %}
{{product.compare_at_price | money}} - {{product.compare_at_price | money}} -
{% endif %} {% endif %}
{% endif %} {% endif %}
<strong> <strong>
{{product.price_min | money}} {{product.price_min | money}}
</strong> </strong>
</span> </span>
</p> </p>
</div> </div>
</div> </div>
</form> </form>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
<div class="paginate clearfix"> <div class="paginate clearfix">
{{ paginate | default_pagination }} {{ paginate | default_pagination }}
</div> </div>
<div id="three-reasons" class="clearfix"> <div id="three-reasons" class="clearfix">
<h3>Why Shop With Us?</h3> <h3>Why Shop With Us?</h3>
<ul> <ul>
<li class="two-a"> <li class="two-a">
<h4>24 Hours</h4> <h4>24 Hours</h4>
<p>We're always here to help.</p> <p>We're always here to help.</p>
</li> </li>
<li class="two-c"> <li class="two-c">
<h4>No Spam</h4> <h4>No Spam</h4>
<p>We'll never share your info.</p> <p>We'll never share your info.</p>
</li> </li>
<li class="two-d"> <li class="two-d">
<h4>Secure Servers</h4> <h4>Secure Servers</h4>
<p>Checkout is 256bit encrypted.</p> <p>Checkout is 256bit encrypted.</p>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
{% endpaginate %} {% endpaginate %}

View File

@@ -1,94 +1,94 @@
<div id="gwrap"> <div id="gwrap">
<div id="gbox"> <div id="gbox">
<h1>Three Great Reasons You Should Shop With Us...</h1> <h1>Three Great Reasons You Should Shop With Us...</h1>
<ul> <ul>
<li class="gbox1"> <li class="gbox1">
<h2>Free Shipping</h2> <h2>Free Shipping</h2>
<p>On all orders over $25</p> <p>On all orders over $25</p>
</li> </li>
<li class="gbox2"> <li class="gbox2">
<h2>Top Quality</h2> <h2>Top Quality</h2>
<p>Hand made in our shop</p> <p>Hand made in our shop</p>
</li> </li>
<li class="gbox3"> <li class="gbox3">
<h2>100% Guarantee</h2> <h2>100% Guarantee</h2>
<p>Any time, any reason</p> <p>Any time, any reason</p>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
<div id="page" class="clearfix"> <div id="page" class="clearfix">
<div class="latest-news">{{pages.alert.content}}</div> <div class="latest-news">{{pages.alert.content}}</div>
<ul class="item-list clearfix"> <ul class="item-list clearfix">
{% for product in collections.frontpage.products %} {% for product in collections.frontpage.products %}
<li> <li>
<form action="/cart/add" method="post"> <form action="/cart/add" method="post">
<div class="item-list-item"> <div class="item-list-item">
<div class="ili-top clearfix"> <div class="ili-top clearfix">
<div class="ili-top-content"> <div class="ili-top-content">
<h2><a href="{{product.url}}">{{product.title}}</a></h2> <h2><a href="{{product.url}}">{{product.title}}</a></h2>
{{ product.description | truncatewords: 15 }}</p> <!-- extra cloding <p> tag for truncation --> {{ product.description | truncatewords: 15 }}</p> <!-- extra cloding <p> tag for truncation -->
</div> </div>
<a href="{{product.url}}" class="ili-top-image"><img src="{{ product.featured_image | product_img_url: 'small' }}" alt="{{ product.title | escape }}"/></a> <a href="{{product.url}}" class="ili-top-image"><img src="{{ product.featured_image | product_img_url: 'small' }}" alt="{{ product.title | escape }}"/></a>
</div> </div>
<div class="ili-bottom clearfix"> <div class="ili-bottom clearfix">
<p class="hiddenvariants" style="display: none">{% for variant in product.variants %}<span><input type="radio" name="id" value="{{variant.id}}" id="radio_{{variant.id}}" style="vertical-align: middle;" {%if forloop.first%} checked="checked" {%endif%} /><label for="radio_{{variant.id}}">{{ variant.price | money_with_currency }} - {{ variant.title }}</label></span>{% endfor %}</p> <p class="hiddenvariants" style="display: none">{% for variant in product.variants %}<span><input type="radio" name="id" value="{{variant.id}}" id="radio_{{variant.id}}" style="vertical-align: middle;" {%if forloop.first%} checked="checked" {%endif%} /><label for="radio_{{variant.id}}">{{ variant.price | money_with_currency }} - {{ variant.title }}</label></span>{% endfor %}</p>
<input type="submit" class="" value="Add to Basket" /> <input type="submit" class="" value="Add to Basket" />
<p> <p>
<a href="{{product.url}}">View Details</a> <a href="{{product.url}}">View Details</a>
<span> <span>
{% if product.compare_at_price %} {% if product.compare_at_price %}
{% if product.price_min != product.compare_at_price %} {% if product.price_min != product.compare_at_price %}
{{product.compare_at_price | money}} - {{product.compare_at_price | money}} -
{% endif %} {% endif %}
{% endif %} {% endif %}
<strong> <strong>
{{product.price_min | money}} {{product.price_min | money}}
</strong> </strong>
</span> </span>
</p> </p>
</div> </div>
</div> </div>
</form> </form>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
<div id="one-two"> <div id="one-two">
<div id="two"> <div id="two">
<h3>Why Shop With Us?</h3> <h3>Why Shop With Us?</h3>
<ul> <ul>
<li class="two-a"> <li class="two-a">
<h4>24 Hours</h4> <h4>24 Hours</h4>
<p>We're always here to help.</p> <p>We're always here to help.</p>
</li> </li>
<li class="two-c"> <li class="two-c">
<h4>No Spam</h4> <h4>No Spam</h4>
<p>We'll never share your info.</p> <p>We'll never share your info.</p>
</li> </li>
<li class="two-b"> <li class="two-b">
<h4>Save Energy</h4> <h4>Save Energy</h4>
<p>We're green, all the way.</p> <p>We're green, all the way.</p>
</li> </li>
<li class="two-d"> <li class="two-d">
<h4>Secure Servers</h4> <h4>Secure Servers</h4>
<p>Checkout is 256bits encrypted.</p> <p>Checkout is 256bits encrypted.</p>
</li> </li>
</ul> </ul>
</div> </div>
<div id="one"> <div id="one">
<h3>Our Company</h3> <h3>Our Company</h3>
{{pages.about-us.content | truncatewords: 49}} <a href="/pages/about-us">read more</a></p> {{pages.about-us.content | truncatewords: 49}} <a href="/pages/about-us">read more</a></p>
</div> </div>
</div> </div>
</div> </div>
<!-- end page --> <!-- end page -->

View File

@@ -1,56 +1,56 @@
<div id="page" class="innerpage clearfix"> <div id="page" class="innerpage clearfix">
<div id="text-page"> <div id="text-page">
<div class="entry"> <div class="entry">
<h1>{{page.title}}</h1> <h1>{{page.title}}</h1>
<div class="entry-post"> <div class="entry-post">
{{page.content}} {{page.content}}
</div> </div>
</div> </div>
</div> </div>
<h1>Featured Products</h1> <h1>Featured Products</h1>
<ul class="item-list clearfix"> <ul class="item-list clearfix">
{% for product in collections.frontpage.products %} {% for product in collections.frontpage.products %}
<li> <li>
<form action="/cart/add" method="post"> <form action="/cart/add" method="post">
<div class="item-list-item"> <div class="item-list-item">
<div class="ili-top clearfix"> <div class="ili-top clearfix">
<div class="ili-top-content"> <div class="ili-top-content">
<h2><a href="{{product.url}}">{{product.title}}</a></h2> <h2><a href="{{product.url}}">{{product.title}}</a></h2>
<p>{{ product.description | truncatewords: 15 }}</p> <p>{{ product.description | truncatewords: 15 }}</p>
</div> </div>
<a href="{{product.url}}" class="ili-top-image"><img src="{{ product.featured_image | product_img_url: 'small' }}" alt="{{ product.title | escape }}"/></a> <a href="{{product.url}}" class="ili-top-image"><img src="{{ product.featured_image | product_img_url: 'small' }}" alt="{{ product.title | escape }}"/></a>
</div> </div>
<div class="ili-bottom clearfix"> <div class="ili-bottom clearfix">
<p class="hiddenvariants" style="display: none">{% for variant in product.variants %}<span><input type="radio" name="id" value="{{variant.id}}" id="radio_{{variant.id}}" style="vertical-align: middle;" {%if forloop.first%} checked="checked" {%endif%} /><label for="radio_{{variant.id}}">{{ variant.price | money_with_currency }} - {{ variant.title }}</label></span>{% endfor %}</p> <p class="hiddenvariants" style="display: none">{% for variant in product.variants %}<span><input type="radio" name="id" value="{{variant.id}}" id="radio_{{variant.id}}" style="vertical-align: middle;" {%if forloop.first%} checked="checked" {%endif%} /><label for="radio_{{variant.id}}">{{ variant.price | money_with_currency }} - {{ variant.title }}</label></span>{% endfor %}</p>
<input type="submit" class="" value="Add to Basket" /> <input type="submit" class="" value="Add to Basket" />
<p> <p>
<a href="{{product.url}}">View Details</a> <a href="{{product.url}}">View Details</a>
<span> <span>
{% if product.compare_at_price %} {% if product.compare_at_price %}
{% if product.price_min != product.compare_at_price %} {% if product.price_min != product.compare_at_price %}
{{product.compare_at_price | money}} - {{product.compare_at_price | money}} -
{% endif %} {% endif %}
{% endif %} {% endif %}
<strong> <strong>
{{product.price_min | money}} {{product.price_min | money}}
</strong> </strong>
</span> </span>
</p> </p>
</div> </div>
</div> </div>
</form> </form>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
<!-- end page --> <!-- end page -->

View File

@@ -1,90 +1,90 @@
<div id="page" class="innerpage clearfix"> <div id="page" class="innerpage clearfix">
<h1>{{ collection.title }} {{ product.title }}</h1> <h1>{{ collection.title }} {{ product.title }}</h1>
<p class="latest-news"><strong>Product Tags: </strong> <p class="latest-news"><strong>Product Tags: </strong>
{% for tag in product.tags %} {% for tag in product.tags %}
<a href="/collections/all/{{ tag }}">{{ tag }}</a> | <a href="/collections/all/{{ tag }}">{{ tag }}</a> |
{% endfor %} {% endfor %}
</p> </p>
<div class="product clearfix"> <div class="product clearfix">
<div class="product-info"> <div class="product-info">
<h1>{{ product.title }}</h1> <h1>{{ product.title }}</h1>
<div class="product-info-description"> <div class="product-info-description">
<p>{{ product.description }} </p> <p>{{ product.description }} </p>
</div> </div>
{% if product.available %} {% if product.available %}
<form action="/cart/add" method="post"> <form action="/cart/add" method="post">
<h2>Product Options:</h2> <h2>Product Options:</h2>
<select id="product-info-options" name="id" class="product-info-options"> <select id="product-info-options" name="id" class="product-info-options">
{% for variant in product.variants %} {% for variant in product.variants %}
<option value="{{ variant.id }}">{{ variant.title }} - {{ variant.price | money }}</option> <option value="{{ variant.id }}">{{ variant.title }} - {{ variant.price | money }}</option>
{% endfor %} {% endfor %}
</select> </select>
<div id="price-field"></div> <div id="price-field"></div>
<div class="product-purchase-btn"> <div class="product-purchase-btn">
<input type="submit" class="add-this-to-cart" id="add-this-to-cart" value="Add to Basket" /> <input type="submit" class="add-this-to-cart" id="add-this-to-cart" value="Add to Basket" />
</div> </div>
</form> </form>
{% else %} {% else %}
<h2>Sold out!</h2> <h2>Sold out!</h2>
<p>Sorry, we're all out of this product. Check back often and order when it returns</p> <p>Sorry, we're all out of this product. Check back often and order when it returns</p>
{% endif %} {% endif %}
</div> </div>
<div class="product-images clearfix"> <div class="product-images clearfix">
{% for image in product.images %} {% for image in product.images %}
{% if forloop.first %} {% if forloop.first %}
<div class="product-image-large"> <div class="product-image-large">
<img src="{{ image | product_img_url: 'medium'}}" alt="{{product.title | escape }}" /> <img src="{{ image | product_img_url: 'medium'}}" alt="{{product.title | escape }}" />
</div> </div>
{% else %} {% else %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<ul class="product-thumbs clearfix"> <ul class="product-thumbs clearfix">
{% for image in product.images %} {% for image in product.images %}
{% if forloop.first %} {% if forloop.first %}
{% else %} {% else %}
<li> <li>
<a href="{{ image | product_img_url: 'large' }}" class="product-thumbs" rel="lightbox[product]" title=""> <a href="{{ image | product_img_url: 'large' }}" class="product-thumbs" rel="lightbox[product]" title="">
<img src="{{ image | product_img_url: 'small'}}" alt="{{product.title | escape }}" /> <img src="{{ image | product_img_url: 'small'}}" alt="{{product.title | escape }}" />
</a> </a>
</li> </li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
</div> </div>
<div id="three-reasons" class="clearfix"> <div id="three-reasons" class="clearfix">
<h3>Why Shop With Us?</h3> <h3>Why Shop With Us?</h3>
<ul> <ul>
<li class="two-a"> <li class="two-a">
<h4>24 Hours</h4> <h4>24 Hours</h4>
<p>We're always here to help.</p> <p>We're always here to help.</p>
</li> </li>
<li class="two-c"> <li class="two-c">
<h4>No Spam</h4> <h4>No Spam</h4>
<p>We'll never share your info.</p> <p>We'll never share your info.</p>
</li> </li>
<li class="two-d"> <li class="two-d">
<h4>Secure Servers</h4> <h4>Secure Servers</h4>
<p>Checkout is 256bit encrypted.</p> <p>Checkout is 256bit encrypted.</p>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
<!-- end page --> <!-- end page -->

View File

@@ -2,50 +2,50 @@
<div id="page" class="innerpage clearfix"> <div id="page" class="innerpage clearfix">
<h1>Search Results</h1> <h1>Search Results</h1>
{% if search.performed %} {% if search.performed %}
{% paginate search.results by 10 %} {% paginate search.results by 10 %}
{% if search.results == empty %} {% if search.results == empty %}
<div class="latest-news">Your search for "{{search.terms | escape}}" did not yield any results</div> <div class="latest-news">Your search for "{{search.terms | escape}}" did not yield any results</div>
{% else %} {% else %}
<ul class="search-list clearfix"> <ul class="search-list clearfix">
{% for item in search.results %} {% for item in search.results %}
<li> <li>
<h3 class="stitle">{{ item.title | link_to: item.url }}</h3> <h3 class="stitle">{{ item.title | link_to: item.url }}</h3>
<p class="sinfo">{{ item.content | strip_html | truncatewords: 65 | highlight: search.terms }} ... <a href="{{item.url}}" title="">view this item</a></p> <p class="sinfo">{{ item.content | strip_html | truncatewords: 65 | highlight: search.terms }} ... <a href="{{item.url}}" title="">view this item</a></p>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}
<div class="paginate clearfix"> <div class="paginate clearfix">
{{ paginate | default_pagination }} {{ paginate | default_pagination }}
</div> </div>
<div id="three-reasons" class="clearfix"> <div id="three-reasons" class="clearfix">
<h3>Why Shop With Us?</h3> <h3>Why Shop With Us?</h3>
<ul> <ul>
<li class="two-a"> <li class="two-a">
<h4>24 Hours</h4> <h4>24 Hours</h4>
<p>We're always here to help.</p> <p>We're always here to help.</p>
</li> </li>
<li class="two-c"> <li class="two-c">
<h4>No Spam</h4> <h4>No Spam</h4>
<p>We'll never share your info.</p> <p>We'll never share your info.</p>
</li> </li>
<li class="two-d"> <li class="two-d">
<h4>Secure Servers</h4> <h4>Secure Servers</h4>
<p>Checkout is 256bit encrypted.</p> <p>Checkout is 256bit encrypted.</p>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
{% endpaginate %} {% endpaginate %}
{% endif %} {% endif %}

View File

@@ -2,15 +2,15 @@
<html xmlns="http://www.w3.org/1999/xhtml"> <html xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
<title>{{shop.name}} - {{page_title}}</title> <title>{{shop.name}} - {{page_title}}</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
{{ 'reset.css' | asset_url | stylesheet_tag }} {{ 'reset.css' | asset_url | stylesheet_tag }}
{{ 'style.css' | asset_url | stylesheet_tag }} {{ 'style.css' | asset_url | stylesheet_tag }}
{{ 'lightbox.css' | asset_url | stylesheet_tag }} {{ 'lightbox.css' | asset_url | stylesheet_tag }}
{{ 'prototype/1.6/prototype.js' | global_asset_url | script_tag }} {{ 'prototype/1.6/prototype.js' | global_asset_url | script_tag }}
{{ 'scriptaculous/1.8.2/scriptaculous.js' | global_asset_url | script_tag }} {{ 'scriptaculous/1.8.2/scriptaculous.js' | global_asset_url | script_tag }}
{{ 'lightbox.js' | asset_url | script_tag }} {{ 'lightbox.js' | asset_url | script_tag }}
{{ 'option_selection.js' | shopify_asset_url | script_tag }} {{ 'option_selection.js' | shopify_asset_url | script_tag }}
{{ content_for_header }} {{ content_for_header }}
@@ -19,70 +19,70 @@
<div id="wrap"> <div id="wrap">
<div id="top"> <div id="top">
<div id="cart"> <div id="cart">
<h3>Shopping Cart</h3> <h3>Shopping Cart</h3>
<p class="cart-count"> <p class="cart-count">
{% if cart.item_count == 0 %} {% if cart.item_count == 0 %}
Your cart is currently empty Your cart is currently empty
{% else %} {% else %}
{{ cart.item_count }} {{ cart.item_count | pluralize: 'item', 'items' }} <span>-</span> Total: {{cart.total_price | money_with_currency }} <span>-</span> <a href="/cart">View Cart</a> {{ cart.item_count }} {{ cart.item_count | pluralize: 'item', 'items' }} <span>-</span> Total: {{cart.total_price | money_with_currency }} <span>-</span> <a href="/cart">View Cart</a>
{% endif %} {% endif %}
</p> </p>
</div> </div>
<div id="site-title"> <div id="site-title">
<h3><a href="/">{{shop.name}}</a></h3> <h3><a href="/">{{shop.name}}</a></h3>
<h4><span>Tribble: A Shopify Theme</span></h4> <h4><span>Tribble: A Shopify Theme</span></h4>
</div> </div>
</div> </div>
<ul id="nav"> <ul id="nav">
{% for link in linklists.main-menu.links %} {% for link in linklists.main-menu.links %}
<li>{{ link.title | link_to: link.url }}</li> <li>{{ link.title | link_to: link.url }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
{{ content_for_layout }} {{ content_for_layout }}
<div id="foot" class="clearfix"> <div id="foot" class="clearfix">
<div class="quick-links"> <div class="quick-links">
<h4>Quick Navigation</h4> <h4>Quick Navigation</h4>
<ul class="clearfix"> <ul class="clearfix">
<li><a href="/">Home</a></li> <li><a href="/">Home</a></li>
<li><a href="#top">Back to top</a></li> <li><a href="#top">Back to top</a></li>
{% for link in linklists.main-menu.links %} {% for link in linklists.main-menu.links %}
<li>{{ link.title | link_to: link.url }}</li> <li>{{ link.title | link_to: link.url }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
<div class="quick-contact"> <div class="quick-contact">
<h4>Quick Contact</h4> <h4>Quick Contact</h4>
<div class="vcard"> <div class="vcard">
<div class="org fn"> <div class="org fn">
<div class="organization-name">Really Great Widget Co.</div> <div class="organization-name">Really Great Widget Co.</div>
</div> </div>
<div class="adr"> <div class="adr">
<span class="street-address">2531 Barrington Court</span> <span class="street-address">2531 Barrington Court</span>
<span class="locality">Hayward</span>, <span class="locality">Hayward</span>,
<abbr title="California" class="region">CA</abbr> <abbr title="California" class="region">CA</abbr>
<span class="postal-code">94545</span> <span class="postal-code">94545</span>
</div> </div>
<a class="email" href="mailto:email@myshopifysite.com"> <a class="email" href="mailto:email@myshopifysite.com">
email@myshopifysite.com email@myshopifysite.com
</a> </a>
<div class="tel"> <div class="tel">
<span class="type">Support:</span> <span class="value">800-555-9954</span> <span class="type">Support:</span> <span class="value">800-555-9954</span>
</div> </div>
</div> </div>
</div> </div>
<p><a href="http://shopify.com" class="we-made">Powered by Shopify</a> &copy; Copyright {{ "now" | date: "%Y" }} {{ shop.name }}, All Rights Reserved. <a href="/blogs/news.xml" id="foot-rss">RSS Feed</a></p> <p><a href="http://shopify.com" class="we-made">Powered by Shopify</a> &copy; Copyright {{ "now" | date: "%Y" }} {{ shop.name }}, All Rights Reserved. <a href="/blogs/news.xml" id="foot-rss">RSS Feed</a></p>
</div> </div>
</div> </div>

View File

@@ -18,10 +18,10 @@
{{ comment.content }} {{ comment.content }}
</div> </div>
<div class="comment-details"> <div class="comment-details">
Posted by {{ comment.author }} on {{ comment.created_at | date: "%B %d, %Y" }} Posted by {{ comment.author }} on {{ comment.created_at | date: "%B %d, %Y" }}
</div> </div>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>

View File

@@ -1,17 +1,17 @@
<h1>Shopping Cart</h1> <h1>Shopping Cart</h1>
{% if cart.item_count == 0 %} {% if cart.item_count == 0 %}
<p><strong>Your shopping basket is empty.</strong> Perhaps a featured item below is of interest...</p> <p><strong>Your shopping basket is empty.</strong> Perhaps a featured item below is of interest...</p>
<table id="gallery"> <table id="gallery">
{% tablerow product in collections.frontpage.products cols: 3 limit: 12 %} {% tablerow product in collections.frontpage.products cols: 3 limit: 12 %}
<div class="gallery-image"> <div class="gallery-image">
<a href="{{ product.url | within: collections.frontpage }}" title="{{ product.title | escape }} &mdash; {{ product.description | strip_html | truncate: 50 | escape }}"><img src="{{ product.images.first | product_img_url: 'medium' }}" alt="{{ product.title | escape }}" /></a> <a href="{{ product.url | within: collections.frontpage }}" title="{{ product.title | escape }} &mdash; {{ product.description | strip_html | truncate: 50 | escape }}"><img src="{{ product.images.first | product_img_url: 'medium' }}" alt="{{ product.title | escape }}" /></a>
</div> </div>
<div class="gallery-info"> <div class="gallery-info">
<a href="{{ product.url | within: collections.frontpage }}">{{ product.title | truncate: 30 }}</a><br /> <a href="{{ product.url | within: collections.frontpage }}">{{ product.title | truncate: 30 }}</a><br />
<small>{{ product.price | money }}{% if product.compare_at_price_max > product.price %} <del>{{ product.compare_at_price_max | money }}</del>{% endif %}</small> <small>{{ product.price | money }}{% if product.compare_at_price_max > product.price %} <del>{{ product.compare_at_price_max | money }}</del>{% endif %}</small>
</div> </div>
{% endtablerow %} {% endtablerow %}
</table> </table>
{% else %} {% else %}
<script type="text/javascript"> <script type="text/javascript">
function remove_item(id) { function remove_item(id) {
@@ -20,39 +20,39 @@
} }
</script> </script>
<form action="/cart" method="post" id="cartform"> <form action="/cart" method="post" id="cartform">
<table id="basket"> <table id="basket">
<tr> <tr>
<th>Item Description</th> <th>Item Description</th>
<th>Price</th> <th>Price</th>
<th>Qty</th> <th>Qty</th>
<th>Delete</th> <th>Delete</th>
<th>Total</th> <th>Total</th>
</tr>{% for item in cart.items %} </tr>{% for item in cart.items %}
<tr class="basket-{% cycle 'odd', 'even' %}"> <tr class="basket-{% cycle 'odd', 'even' %}">
<td class="basket-column-one"> <td class="basket-column-one">
<div class="basket-images"> <div class="basket-images">
<a href="{{ item.product.url }}" title="{{ item.title | escape }} &mdash; {{ item.product.description | strip_html | truncate: 50 | escape }}"><img src="{{ item.product.images.first | product_img_url: 'thumb' }}" alt="{{ item.title | escape }}" /></a> <a href="{{ item.product.url }}" title="{{ item.title | escape }} &mdash; {{ item.product.description | strip_html | truncate: 50 | escape }}"><img src="{{ item.product.images.first | product_img_url: 'thumb' }}" alt="{{ item.title | escape }}" /></a>
</div> </div>
<div class="basket-desc"> <div class="basket-desc">
<p><a href="{{ item.product.url }}">{{ item.title }}</a></p> <p><a href="{{ item.product.url }}">{{ item.title }}</a></p>
{{ item.product.description | strip_html | truncate: 120 }} {{ item.product.description | strip_html | truncate: 120 }}
</div> </div>
</td> </td>
<td class="basket-column">{{ item.price | money }}{% if item.variant.compare_at_price > item.price %}<br /><del>{{ item.variant.compare_at_price | money }}</del>{% endif %}</td> <td class="basket-column">{{ item.price | money }}{% if item.variant.compare_at_price > item.price %}<br /><del>{{ item.variant.compare_at_price | money }}</del>{% endif %}</td>
<td class="basket-column"><input type="text" size="4" name="updates[{{item.variant.id}}]" id="updates_{{ item.variant.id }}" value="{{ item.quantity }}" onfocus="this.select();"/></td> <td class="basket-column"><input type="text" size="4" name="updates[{{item.variant.id}}]" id="updates_{{ item.variant.id }}" value="{{ item.quantity }}" onfocus="this.select();"/></td>
<td class="basket-column"><a href="#" onclick="remove_item({{ item.variant.id }}); return false;">Remove</a></td> <td class="basket-column"><a href="#" onclick="remove_item({{ item.variant.id }}); return false;">Remove</a></td>
<td class="basket-column">{{ item.line_price | money }}</td> <td class="basket-column">{{ item.line_price | money }}</td>
</tr>{% endfor %} </tr>{% endfor %}
</table> </table>
<div id="basket-right"> <div id="basket-right">
<h3>Subtotal {{ cart.total_price | money }}</h3> <h3>Subtotal {{ cart.total_price | money }}</h3>
<input type="image" src="{{ 'update.png' | asset_url }}" id="update-cart" name="update" value="Update" /> <input type="image" src="{{ 'update.png' | asset_url }}" id="update-cart" name="update" value="Update" />
<input type="image" src="{{ 'checkout.png' | asset_url }}" name="checkout" value="Checkout" /> <input type="image" src="{{ 'checkout.png' | asset_url }}" name="checkout" value="Checkout" />
{% if additional_checkout_buttons %} {% if additional_checkout_buttons %}
<div class="additional-checkout-buttons"> <div class="additional-checkout-buttons">
<p>- or -</p> <p>- or -</p>
{{ content_for_additional_checkout_buttons }} {{ content_for_additional_checkout_buttons }}
</div> </div>
{% endif %} {% endif %}
</div> </div>
</form>{% endif %} </form>{% endif %}

View File

@@ -1,19 +1,19 @@
{% paginate collection.products by 12 %}{% if collection.products.size == 0 %} {% paginate collection.products by 12 %}{% if collection.products.size == 0 %}
<strong>No products found in this collection.</strong>{% else %} <strong>No products found in this collection.</strong>{% else %}
<h1>{{ collection.title }}</h1> <h1>{{ collection.title }}</h1>
{{ collection.description }} {{ collection.description }}
<table id="gallery"> <table id="gallery">
{% tablerow product in collection.products cols: 3 %} {% tablerow product in collection.products cols: 3 %}
<div class="gallery-image"> <div class="gallery-image">
<a href="{{ product.url | within: collection }}" title="{{ product.title | escape }} &mdash; {{ product.description | strip_html | truncate: 50 | escape }}"><img src="{{ product.images.first | product_img_url: 'small' }}" alt="{{ product.title | escape }}" /></a> <a href="{{ product.url | within: collection }}" title="{{ product.title | escape }} &mdash; {{ product.description | strip_html | truncate: 50 | escape }}"><img src="{{ product.images.first | product_img_url: 'small' }}" alt="{{ product.title | escape }}" /></a>
</div> </div>
<div class="gallery-info"> <div class="gallery-info">
<a href="{{ product.url | within: collection }}">{{ product.title | truncate: 30 }}</a><br /> <a href="{{ product.url | within: collection }}">{{ product.title | truncate: 30 }}</a><br />
<small>{{ product.price | money }}{% if product.compare_at_price_max > product.price %} <del>{{ product.compare_at_price_max | money }}</del>{% endif %}</small> <small>{{ product.price | money }}{% if product.compare_at_price_max > product.price %} <del>{{ product.compare_at_price_max | money }}</del>{% endif %}</small>
</div> </div>
{% endtablerow %} {% endtablerow %}
</table>{% if paginate.pages > 1 %} </table>{% if paginate.pages > 1 %}
<div id="paginate"> <div id="paginate">
{{ paginate | default_pagination }} {{ paginate | default_pagination }}
</div>{% endif %}{% endif %} </div>{% endif %}{% endif %}
{% endpaginate %} {% endpaginate %}

View File

@@ -1,22 +1,22 @@
<div id="about-excerpt"> <div id="about-excerpt">
{% assign article = pages.frontpage %} {% assign article = pages.frontpage %}
{% if article.content != "" %} {% if article.content != "" %}
<h2>{{ article.title }}</h2> <h2>{{ article.title }}</h2>
{{ article.content }} {{ article.content }}
{% else %} {% else %}
In <em>Admin &gt; Blogs &amp; Pages</em>, create a page with the handle <strong><code>frontpage</code></strong> and it will show up here.<br /> In <em>Admin &gt; Blogs &amp; Pages</em>, create a page with the handle <strong><code>frontpage</code></strong> and it will show up here.<br />
{{ "Learn more about handles" | link_to: "http://wiki.shopify.com/Handle" }} {{ "Learn more about handles" | link_to "http://wiki.shopify.com/Handle" }}
{% endif %} {% endif %}
</div> </div>
<table id="gallery"> <table id="gallery">
{% tablerow product in collections.frontpage.products cols: 3 limit: 12 %} {% tablerow product in collections.frontpage.products cols: 3 limit: 12 %}
<div class="gallery-image"> <div class="gallery-image">
<a href="{{ product.url | within: collections.frontpage }}" title="{{ product.title | escape }} &mdash; {{ product.description | strip_html | truncate: 50 | escape }}"><img src="{{ product.images.first | product_img_url: 'small' }}" alt="{{ product.title | escape }}" /></a> <a href="{{ product.url | within: collections.frontpage }}" title="{{ product.title | escape }} &mdash; {{ product.description | strip_html | truncate: 50 | escape }}"><img src="{{ product.images.first | product_img_url: 'small' }}" alt="{{ product.title | escape }}" /></a>
</div> </div>
<div class="gallery-info"> <div class="gallery-info">
<a href="{{ product.url | within: collections.frontpage }}">{{ product.title | truncate: 30 }}</a><br /> <a href="{{ product.url | within: collections.frontpage }}">{{ product.title | truncate: 30 }}</a><br />
<small>{{ product.price | money }}{% if product.compare_at_price_max > product.price %} <del>{{ product.compare_at_price_max | money }}</del>{% endif %}</small> <small>{{ product.price | money }}{% if product.compare_at_price_max > product.price %} <del>{{ product.compare_at_price_max | money }}</del>{% endif %}</small>
</div> </div>
{% endtablerow %} {% endtablerow %}
</table> </table>

View File

@@ -1,3 +1,3 @@
<h1>{{ page.title }}</h1> <h1>{{ page.title }}</h1>
{{ page.content }} {{ page.content }}

View File

@@ -1,19 +1,19 @@
<div id="product-left"> <div id="product-left">
{% for image in product.images %}{% if forloop.first %}<div id="product-image"> {% for image in product.images %}{% if forloop.first %}<div id="product-image">
<a href="{{ image | product_img_url: 'large' }}" rel="lightbox[images]" title="{{ product.title | escape }}"><img src="{{ image | product_img_url: 'medium' }}" alt="{{ product.title | escape }}" /></a> <a href="{{ image | product_img_url: 'large' }}" rel="lightbox[images]" title="{{ product.title | escape }}"><img src="{{ image | product_img_url: 'medium' }}" alt="{{ product.title | escape }}" /></a>
</div>{% else %} </div>{% else %}
<div class="product-images"> <div class="product-images">
<a href="{{ image | product_img_url: 'large' }}" rel="lightbox[images]" title="{{ product.title | escape }}"><img src="{{ image | product_img_url: 'small' }}" alt="{{ product.title | escape }}" /></a> <a href="{{ image | product_img_url: 'large' }}" rel="lightbox[images]" title="{{ product.title | escape }}"><img src="{{ image | product_img_url: 'small' }}" alt="{{ product.title | escape }}" /></a>
</div>{% endif %}{% endfor %} </div>{% endif %}{% endfor %}
</div> </div>
<div id="product-right"> <div id="product-right">
<h1>{{ product.title }}</h1> <h1>{{ product.title }}</h1>
{{ product.description }} {{ product.description }}
{% if product.available %} {% if product.available %}
<form action="/cart/add" method="post"> <form action="/cart/add" method="post">
<div id="product-variants"> <div id="product-variants">
<div id="price-field"></div> <div id="price-field"></div>
<select id="product-select" name='id'> <select id="product-select" name='id'>
@@ -21,18 +21,18 @@
<option value="{{ variant.id }}">{{ variant.title }} - {{ variant.price | money }}</option> <option value="{{ variant.id }}">{{ variant.title }} - {{ variant.price | money }}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<input type="image" src="{{ 'purchase.png' | asset_url }}" name="add" value="Purchase" id="purchase" /> <input type="image" src="{{ 'purchase.png' | asset_url }}" name="add" value="Purchase" id="purchase" />
</form> </form>
{% else %} {% else %}
<p class="bold-red">This product is temporarily unavailable</p> <p class="bold-red">This product is temporarily unavailable</p>
{% endif %} {% endif %}
<div id="product-details"> <div id="product-details">
<strong>Continue Shopping</strong><br /> <strong>Continue Shopping</strong><br />
Browse more {{ product.type | link_to_type }} or additional {{ product.vendor | link_to_vendor }} products. Browse more {{ product.type | link_to_type }} or additional {{ product.vendor | link_to_vendor }} products.
</div> </div>
</div> </div>

View File

@@ -22,100 +22,100 @@
<body id="page-{{ template }}"> <body id="page-{{ template }}">
<div id="header"> <div id="header">
<div class="container"> <div class="container">
<div id="logo"> <div id="logo">
<h1><a href="/" title="{{ shop.name }}">{{ shop.name }}</a></h1> <h1><a href="/" title="{{ shop.name }}">{{ shop.name }}</a></h1>
</div> </div>
<div id="navigation"> <div id="navigation">
<ul id="navigate"> <ul id="navigate">
<li><a href="/cart">View Cart</a></li> <li><a href="/cart">View Cart</a></li>
{% for link in linklists.main-menu.links reversed %} {% for link in linklists.main-menu.links reversed %}
<li><a href="{{ link.url }}">{{ link.title }}</a></li> <li><a href="{{ link.url }}">{{ link.title }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
<div id="mini-header"> <div id="mini-header">
<div class="container"> <div class="container">
<div id="shopping-cart"> <div id="shopping-cart">
<a href="/cart">Your shopping cart contains {{ cart.item_count }} {{ cart.item_count | pluralize: 'item', 'items' }}</a> <a href="/cart">Your shopping cart contains {{ cart.item_count }} {{ cart.item_count | pluralize: 'item', 'items' }}</a>
</div> </div>
<div id="search-box"> <div id="search-box">
<form action="/search" method="get"> <form action="/search" method="get">
<input type="text" name="q" id="q" /> <input type="text" name="q" id="q" />
<input type="image" src="{{ 'seek.png' | asset_url }}" value="Seek" onclick="this.parentNode.submit(); return false;" id="seek" /> <input type="image" src="{{ 'seek.png' | asset_url }}" value="Seek" onclick="this.parentNode.submit(); return false;" id="seek" />
</form> </form>
</div> </div>
</div> </div>
</div> </div>
<div id="layout"> <div id="layout">
<div class="container"> <div class="container">
<div id="layout-left" {% if template != "cart" %}{% if template != "product" %}style="width:619px"{% endif %}{% endif %}>{% if template == "search" %} <div id="layout-left" {% if template != "cart" %}{% if template != "product" %}style="width:619px"{% endif %}{% endif %}>{% if template == "search" %}
<h1>Search Results</h1>{% endif %} <h1>Search Results</h1>{% endif %}
{{ content_for_layout }} {{ content_for_layout }}
</div>{% if template != "cart" %}{% if template != "product" %} </div>{% if template != "cart" %}{% if template != "product" %}
<div id="layout-right"> <div id="layout-right">
{% if template == "index" %} {% if template == "index" %}
{% if blogs.news.articles.size > 1 %} {% if blogs.news.articles.size > 1 %}
<a href="{{ shop.url }}/blogs/news.xml"><img src="{{ 'feed.png' | asset_url }}" alt="Subscribe" class="feed" /></a> <a href="{{ shop.url }}/blogs/news.xml"><img src="{{ 'feed.png' | asset_url }}" alt="Subscribe" class="feed" /></a>
<h3><a href="/blogs/news">More news</a></h3> <h3><a href="/blogs/news">More news</a></h3>
<ul id="blogs">{% for article in blogs.news.articles limit: 6 offset: 1 %} <ul id="blogs">{% for article in blogs.news.articles limit: 6 offset: 1 %}
<li><a href="{{ article.url }}">{{ article.title | strip_html | truncate: 30 }}</a><br /> <li><a href="{{ article.url }}">{{ article.title | strip_html | truncate: 30 }}</a><br />
<small>{{ article.content | strip_html | truncatewords: 12 }}</small> <small>{{ article.content | strip_html | truncatewords: 12 }}</small>
</li>{% endfor %} </li>{% endfor %}
</ul> </ul>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if template == "collection" %} {% if template == "collection" %}
<h3>Collection Tags</h3> <h3>Collection Tags</h3>
<div id="tags">{% if collection.tags.size == 0 %} <div id="tags">{% if collection.tags.size == 0 %}
No tags found.{% else %} No tags found.{% else %}
<span class="tags">{% for tag in collection.tags %}{% if current_tags contains tag %} {{ tag | highlight_active_tag | link_to_remove_tag: tag }}{% else %} {{ tag | highlight_active_tag | link_to_add_tag: tag }}{% endif %}{% unless forloop.last %}, {% endunless %}{% endfor %}</span>{% endif %} <span class="tags">{% for tag in collection.tags %}{% if current_tags contains tag %} {{ tag | highlight_active_tag | link_to_remove_tag: tag }}{% else %} {{ tag | highlight_active_tag | link_to_add_tag: tag }}{% endif %}{% unless forloop.last %}, {% endunless %}{% endfor %}</span>{% endif %}
</div> </div>
{% endif %} {% endif %}
<h3>Navigation</h3> <h3>Navigation</h3>
<ul id="links"> <ul id="links">
{% for link in linklists.main-menu.links %} {% for link in linklists.main-menu.links %}
<li><a href="{{ link.url }}">{{ link.title }}</a></li> <li><a href="{{ link.url }}">{{ link.title }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
{% if template != "page" %} {% if template != "page" %}
<h3>Featured Products</h3> <h3>Featured Products</h3>
<ul id="featuring">{% for product in collections.frontpage.products limit: 6 %} <ul id="featuring">{% for product in collections.frontpage.products limit: 6 %}
<li class="featuring-list"> <li class="featuring-list">
<div class="featuring-image"> <div class="featuring-image">
<a href="{{ product.url | within: collections.frontpage }}" title="{{ product.title | escape }} &mdash; {{ product.description | strip_html | truncate: 50 }}"><img src="{{ product.images.first | product_img_url: 'icon' }}" alt="{{ product.title | escape }}" /></a> <a href="{{ product.url | within: collections.frontpage }}" title="{{ product.title | escape }} &mdash; {{ product.description | strip_html | truncate: 50 }}"><img src="{{ product.images.first | product_img_url: 'icon' }}" alt="{{ product.title | escape }}" /></a>
</div> </div>
<div class="featuring-info"> <div class="featuring-info">
<a href="{{ product.url | within: collections.frontpage }}">{{ product.title | strip_html | truncate: 28 }}</a><br /> <a href="{{ product.url | within: collections.frontpage }}">{{ product.title | strip_html | truncate: 28 }}</a><br />
<small><span class="light">from</span> {{ product.price | money }}</small> <small><span class="light">from</span> {{ product.price | money }}</small>
</div> </div>
</li>{% endfor %} </li>{% endfor %}
</ul> </ul>
{% endif %} {% endif %}
</div>{% endif %}{% endif %} </div>{% endif %}{% endif %}
</div> </div>
</div> </div>
<div id="footer"> <div id="footer">
<div id="footer-fader"> <div id="footer-fader">
<div class="container"> <div class="container">
<div id="footer-right">{% for link in linklists.footer.links %} <div id="footer-right">{% for link in linklists.footer.links %}
{{ link.title | link_to: link.url }} {% unless forloop.last %}&#124;{% endunless %}{% endfor %} {{ link.title | link_to: link.url }} {% unless forloop.last %}&#124;{% endunless %}{% endfor %}
</div> </div>
<span id="footer-left"> <span id="footer-left">
Copyright &copy; {{ "now" | date: "%Y" }} <a href="/">{{ shop.name }}</a>. All Rights Reserved. All prices {{ shop.currency }}.<br /> Copyright &copy; {{ "now" | date: "%Y" }} <a href="/">{{ shop.name }}</a>. All Rights Reserved. All prices {{ shop.currency }}.<br />
This website is powered by <a href="http://www.shopify.com">Shopify</a>. This website is powered by <a href="http://www.shopify.com">Shopify</a>.
</span> </span>
</div> </div>
</div> </div>
</div> </div>
</body> </body>

View File

@@ -14,17 +14,6 @@ require File.dirname(__FILE__) + '/shopify/liquid'
require File.dirname(__FILE__) + '/shopify/database.rb' require File.dirname(__FILE__) + '/shopify/database.rb'
class ThemeRunner class ThemeRunner
class FileSystem
def initialize(path)
@path = path
end
# Called by Liquid to retrieve a template file
def read_template_file(template_path, context)
File.read(@path + '/' + template_path + '.liquid')
end
end
# Load all templates into memory, do this now so that # Load all templates into memory, do this now so that
# we don't profile IO. # we don't profile IO.
@@ -42,7 +31,6 @@ class ThemeRunner
# Dup assigns because will make some changes to them # Dup assigns because will make some changes to them
@tests.each do |liquid, layout, template_name| @tests.each do |liquid, layout, template_name|
tmpl = Liquid::Template.new tmpl = Liquid::Template.new
tmpl.parse(liquid) tmpl.parse(liquid)
tmpl = Liquid::Template.new tmpl = Liquid::Template.new
@@ -58,14 +46,14 @@ class ThemeRunner
# Compute page_tempalte outside of profiler run, uninteresting to profiler # Compute page_tempalte outside of profiler run, uninteresting to profiler
page_template = File.basename(template_name, File.extname(template_name)) page_template = File.basename(template_name, File.extname(template_name))
compile_and_render(liquid, layout, assigns, page_template, template_name) compile_and_render(liquid, layout, assigns, page_template)
end end
end end
def run_profile def run_profile
RubyProf.measure_mode = RubyProf::WALL_TIME RubyProf.measure_mode = RubyProf::PROCESS_TIME
# Dup assigns because will make some changes to them # Dup assigns because will make some changes to them
assigns = Database.tables.dup assigns = Database.tables.dup
@@ -85,7 +73,7 @@ class ThemeRunner
html = nil html = nil
RubyProf.resume RubyProf.resume
html = compile_and_render(liquid, layout, assigns, page_template, template_name) html = compile_and_render(liquid, layout, assigns, page_template)
RubyProf.pause RubyProf.pause
@@ -99,11 +87,10 @@ class ThemeRunner
RubyProf.stop RubyProf.stop
end end
def compile_and_render(template, layout, assigns, page_template, template_file) def compile_and_render(template, layout, assigns, page_template)
tmpl = Liquid::Template.new tmpl = Liquid::Template.new
tmpl.assigns['page_title'] = 'Page title' tmpl.assigns['page_title'] = 'Page title'
tmpl.assigns['template'] = page_template tmpl.assigns['template'] = page_template
tmpl.registers[:file_system] = ThemeRunner::FileSystem.new(File.dirname(template_file))
content_for_layout = tmpl.parse(template).render(assigns) content_for_layout = tmpl.parse(template).render(assigns)

View File

@@ -1,9 +0,0 @@
---
simple: "less is more"
whatever: "something %{something}"
errors:
i18n:
undefined_interpolation: "undefined key %{key}"
unknown_translation: "translation '%{name}' wasn't found"
syntax:
oops: "something wasn't right"

View File

@@ -18,10 +18,4 @@ class AssignTest < Test::Unit::TestCase
'{% assign foo = values | split: "," %}.{{ foo[1] }}.', '{% assign foo = values | split: "," %}.{{ foo[1] }}.',
'values' => "foo,bar,baz") 'values' => "foo,bar,baz")
end end
def test_assign_syntax_error
assert_match_syntax_error(/assign/,
'{% assign foo not values %}.',
'values' => "foo,bar,baz")
end
end # AssignTest end # AssignTest

View File

@@ -1,106 +0,0 @@
require 'test_helper'
class FoobarTag < Liquid::Tag
def render(*args)
" "
end
Liquid::Template.register_tag('foobar', FoobarTag)
end
class BlankTestFileSystem
def read_template_file(template_path, context)
template_path
end
end
class BlankTest < Test::Unit::TestCase
include Liquid
N = 10
def wrap_in_for(body)
"{% for i in (1..#{N}) %}#{body}{% endfor %}"
end
def wrap_in_if(body)
"{% if true %}#{body}{% endif %}"
end
def wrap(body)
wrap_in_for(body) + wrap_in_if(body)
end
def test_new_tags_are_not_blank_by_default
assert_template_result(" "*N, wrap_in_for("{% foobar %}"))
end
def test_loops_are_blank
assert_template_result("", wrap_in_for(" "))
end
def test_if_else_are_blank
assert_template_result("", "{% if true %} {% elsif false %} {% else %} {% endif %}")
end
def test_unless_is_blank
assert_template_result("", wrap("{% unless true %} {% endunless %}"))
end
def test_mark_as_blank_only_during_parsing
assert_template_result(" "*(N+1), wrap(" {% if false %} this never happens, but still, this block is not blank {% endif %}"))
end
def test_comments_are_blank
assert_template_result("", wrap(" {% comment %} whatever {% endcomment %} "))
end
def test_captures_are_blank
assert_template_result("", wrap(" {% capture foo %} whatever {% endcapture %} "))
end
def test_nested_blocks_are_blank_but_only_if_all_children_are
assert_template_result("", wrap(wrap(" ")))
assert_template_result("\n but this is not "*(N+1),
wrap(%q{{% if true %} {% comment %} this is blank {% endcomment %} {% endif %}
{% if true %} but this is not {% endif %}}))
end
def test_assigns_are_blank
assert_template_result("", wrap(' {% assign foo = "bar" %} '))
end
def test_whitespace_is_blank
assert_template_result("", wrap(" "))
assert_template_result("", wrap("\t"))
end
def test_whitespace_is_not_blank_if_other_stuff_is_present
body = " x "
assert_template_result(body*(N+1), wrap(body))
end
def test_increment_is_not_blank
assert_template_result(" 0"*2*(N+1), wrap("{% assign foo = 0 %} {% increment foo %} {% decrement foo %}"))
end
def test_cycle_is_not_blank
assert_template_result(" "*((N+1)/2)+" ", wrap("{% cycle ' ', ' ' %}"))
end
def test_raw_is_not_blank
assert_template_result(" "*(N+1), wrap(" {% raw %} {% endraw %}"))
end
def test_include_is_blank
Liquid::Template.file_system = BlankTestFileSystem.new
assert_equal "foobar"*(N+1), Template.parse(wrap("{% include 'foobar' %}")).render()
assert_equal " foobar "*(N+1), Template.parse(wrap("{% include ' foobar ' %}")).render()
assert_equal " ", Template.parse(" {% include ' ' %} ").render()
end
def test_case_is_blank
assert_template_result("", wrap(" {% assign foo = 'bar' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} {% endcase %} "))
assert_template_result("", wrap(" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} {% endcase %} "))
assert_template_result(" x "*(N+1), wrap(" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} x {% endcase %} "))
end
end

View File

@@ -12,34 +12,34 @@ class BlockTest < Test::Unit::TestCase
template = Liquid::Template.parse("{{funk}} ") template = Liquid::Template.parse("{{funk}} ")
assert_equal 2, template.root.nodelist.size assert_equal 2, template.root.nodelist.size
assert_equal Variable, template.root.nodelist[0].class assert_equal Variable, template.root.nodelist[0].class
assert_equal StringSlice, template.root.nodelist[1].class assert_equal String, template.root.nodelist[1].class
end end
def test_variable_end def test_variable_end
template = Liquid::Template.parse(" {{funk}}") template = Liquid::Template.parse(" {{funk}}")
assert_equal 2, template.root.nodelist.size assert_equal 2, template.root.nodelist.size
assert_equal StringSlice, template.root.nodelist[0].class assert_equal String, template.root.nodelist[0].class
assert_equal Variable, template.root.nodelist[1].class assert_equal Variable, template.root.nodelist[1].class
end end
def test_variable_middle def test_variable_middle
template = Liquid::Template.parse(" {{funk}} ") template = Liquid::Template.parse(" {{funk}} ")
assert_equal 3, template.root.nodelist.size assert_equal 3, template.root.nodelist.size
assert_equal StringSlice, template.root.nodelist[0].class assert_equal String, template.root.nodelist[0].class
assert_equal Variable, template.root.nodelist[1].class assert_equal Variable, template.root.nodelist[1].class
assert_equal StringSlice, template.root.nodelist[2].class assert_equal String, template.root.nodelist[2].class
end end
def test_variable_many_embedded_fragments def test_variable_many_embedded_fragments
template = Liquid::Template.parse(" {{funk}} {{so}} {{brother}} ") template = Liquid::Template.parse(" {{funk}} {{so}} {{brother}} ")
assert_equal 7, template.root.nodelist.size assert_equal 7, template.root.nodelist.size
assert_equal [StringSlice, Variable, StringSlice, Variable, StringSlice, Variable, StringSlice], assert_equal [String, Variable, String, Variable, String, Variable, String],
block_types(template.root.nodelist) block_types(template.root.nodelist)
end end
def test_with_block def test_with_block
template = Liquid::Template.parse(" {% comment %} {% endcomment %} ") template = Liquid::Template.parse(" {% comment %} {% endcomment %} ")
assert_equal [StringSlice, Comment, StringSlice], block_types(template.root.nodelist) assert_equal [String, Comment, String], block_types(template.root.nodelist)
assert_equal 3, template.root.nodelist.size assert_equal 3, template.root.nodelist.size
end end

View File

@@ -254,12 +254,16 @@ class ContextTest < Test::Unit::TestCase
@context['test'] = {'test' => [1,2,3,4,5]} @context['test'] = {'test' => [1,2,3,4,5]}
assert_equal 1, @context['test.test[0]'] assert_equal 1, @context['test.test[0]']
end
def test_recoursive_array_notation_for_hash
@context['test'] = [{'test' => 'worked'}] @context['test'] = [{'test' => 'worked'}]
assert_equal 'worked', @context['test[0].test'] assert_equal 'worked', @context['test[0].test']
end end
def test_hash_to_array_transition def test_hash_to_array_transition
@context['colors'] = { @context['colors'] = {
'Blue' => ['003366','336699', '6699CC', '99CCFF'], 'Blue' => ['003366','336699', '6699CC', '99CCFF'],
@@ -315,7 +319,7 @@ class ContextTest < Test::Unit::TestCase
@context['nested'] = {'var' => 'tags'} @context['nested'] = {'var' => 'tags'}
@context['products'] = {'count' => 5, 'tags' => ['deepsnow', 'freestyle'] } @context['products'] = {'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
assert_equal 'deepsnow', @context['products[var].first'] #assert_equal 'deepsnow', @context['products[var].first']
assert_equal 'freestyle', @context['products[nested.var].last'] assert_equal 'freestyle', @context['products[nested.var].last']
end end

View File

@@ -55,44 +55,11 @@ class ProductDrop < Liquid::Drop
end end
class EnumerableDrop < Liquid::Drop class EnumerableDrop < Liquid::Drop
def before_method(method)
method
end
def size def size
3 3
end end
def first
1
end
def count
3
end
def min
1
end
def max
3
end
def each
yield 1
yield 2
yield 3
end
end
class RealEnumerableDrop < Liquid::Drop
include Enumerable
def before_method(method)
method
end
def each def each
yield 1 yield 1
yield 2 yield 2
@@ -104,34 +71,23 @@ class DropsTest < Test::Unit::TestCase
include Liquid include Liquid
def test_product_drop def test_product_drop
assert_nothing_raised do assert_nothing_raised do
tpl = Liquid::Template.parse( ' ' ) tpl = Liquid::Template.parse( ' ' )
tpl.render('product' => ProductDrop.new) tpl.render('product' => ProductDrop.new)
end end
end end
def test_drop_does_only_respond_to_whitelisted_methods
assert_equal "", Liquid::Template.parse("{{ product.inspect }}").render('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse("{{ product.pretty_inspect }}").render('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse("{{ product.whatever }}").render('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse('{{ product | map: "inspect" }}').render('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse('{{ product | map: "pretty_inspect" }}').render('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse('{{ product | map: "whatever" }}').render('product' => ProductDrop.new)
end
def test_drops_respond_to_to_liquid
assert_equal "text1", Liquid::Template.parse("{{ product.to_liquid.texts.text }}").render('product' => ProductDrop.new)
assert_equal "text1", Liquid::Template.parse('{{ product | map: "to_liquid" | map: "texts" | map: "text" }}').render('product' => ProductDrop.new)
end
def test_text_drop def test_text_drop
output = Liquid::Template.parse( ' {{ product.texts.text }} ' ).render('product' => ProductDrop.new) output = Liquid::Template.parse( ' {{ product.texts.text }} ' ).render('product' => ProductDrop.new)
assert_equal ' text1 ', output assert_equal ' text1 ', output
end end
def test_unknown_method def test_unknown_method
output = Liquid::Template.parse( ' {{ product.catchall.unknown }} ' ).render('product' => ProductDrop.new) output = Liquid::Template.parse( ' {{ product.catchall.unknown }} ' ).render('product' => ProductDrop.new)
assert_equal ' method: unknown ', output assert_equal ' method: unknown ', output
end end
def test_integer_argument_drop def test_integer_argument_drop
@@ -203,33 +159,6 @@ class DropsTest < Test::Unit::TestCase
assert_equal '3', Liquid::Template.parse( '{{collection.size}}').render('collection' => EnumerableDrop.new) assert_equal '3', Liquid::Template.parse( '{{collection.size}}').render('collection' => EnumerableDrop.new)
end end
def test_enumerable_drop_will_invoke_before_method_for_clashing_method_names
["select", "each", "map", "cycle"].each do |method|
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render('collection' => EnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => EnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render('collection' => RealEnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => RealEnumerableDrop.new)
end
end
def test_some_enumerable_methods_still_get_invoked
[ :count, :max ].each do |method|
assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => RealEnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => RealEnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => EnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => EnumerableDrop.new)
end
assert_equal "yes", Liquid::Template.parse("{% if collection contains 3 %}yes{% endif %}").render('collection' => RealEnumerableDrop.new)
[ :min, :first ].each do |method|
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => RealEnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => RealEnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => EnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => EnumerableDrop.new)
end
end
def test_empty_string_value_access def test_empty_string_value_access
assert_equal '', Liquid::Template.parse('{{ product[value] }}').render('product' => ProductDrop.new, 'value' => '') assert_equal '', Liquid::Template.parse('{{ product[value] }}').render('product' => ProductDrop.new, 'value' => '')
end end
@@ -237,9 +166,4 @@ class DropsTest < Test::Unit::TestCase
def test_nil_value_access def test_nil_value_access
assert_equal '', Liquid::Template.parse('{{ product[value] }}').render('product' => ProductDrop.new, 'value' => nil) assert_equal '', Liquid::Template.parse('{{ product[value] }}').render('product' => ProductDrop.new, 'value' => nil)
end end
def test_default_to_s_on_drops
assert_equal 'ProductDrop', Liquid::Template.parse("{{ product }}").render('product' => ProductDrop.new)
assert_equal 'EnumerableDrop', Liquid::Template.parse('{{ collection }}').render('collection' => EnumerableDrop.new)
end
end # DropsTest end # DropsTest

View File

@@ -58,48 +58,19 @@ class ErrorHandlingTest < Test::Unit::TestCase
def test_missing_endtag_parse_time_error def test_missing_endtag_parse_time_error
assert_raise(Liquid::SyntaxError) do assert_raise(Liquid::SyntaxError) do
Liquid::Template.parse(' {% for a in b %} ... ') template = Liquid::Template.parse(' {% for a in b %} ... ')
end end
end end
def test_unrecognized_operator def test_unrecognized_operator
with_error_mode(:strict) do
assert_raise(SyntaxError) do
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ')
end
end
end
def test_lax_unrecognized_operator
assert_nothing_raised do assert_nothing_raised do
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :lax) template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ')
assert_equal ' Liquid error: Unknown operator =! ', template.render assert_equal ' Liquid error: Unknown operator =! ', template.render
assert_equal 1, template.errors.size assert_equal 1, template.errors.size
assert_equal Liquid::ArgumentError, template.errors.first.class assert_equal Liquid::ArgumentError, template.errors.first.class
end end
end end
def test_strict_error_messages
err = assert_raise(SyntaxError) do
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :strict)
end
assert_equal 'Unexpected character = in "1 =! 2"', err.message
err = assert_raise(SyntaxError) do
Liquid::Template.parse('{{%%%}}', :error_mode => :strict)
end
assert_equal 'Unexpected character % in "{{%%%}}"', err.message
end
def test_warnings
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].message
assert_equal 'Unexpected character % in "{{%%%}}"', template.warnings[1].message
assert_equal 'Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].message
assert_equal '', template.render
end
# Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError # Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError
def test_exceptions_propagate def test_exceptions_propagate
assert_raise Exception do assert_raise Exception do

View File

@@ -26,10 +26,4 @@ class FileSystemTest < Test::Unit::TestCase
file_system.full_path("/etc/passwd") file_system.full_path("/etc/passwd")
end end
end end
def test_custom_template_filename_patterns
file_system = Liquid::LocalFileSystem.new("/some/path", "%s.html")
assert_equal "/some/path/mypartial.html" , file_system.full_path("mypartial")
assert_equal "/some/path/dir/mypartial.html", file_system.full_path("dir/mypartial")
end
end # FileSystemTest end # FileSystemTest

View File

@@ -1,25 +0,0 @@
require 'test_helper'
module MoneyFilter
def money(input)
sprintf(' %d$ ', input)
end
end
module CanadianMoneyFilter
def money(input)
sprintf(' %d$ CAD ', input)
end
end
class HashOrderingTest < Test::Unit::TestCase
include Liquid
def test_global_register_order
Template.register_filter(MoneyFilter)
Template.register_filter(CanadianMoneyFilter)
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, nil)
end
end

View File

@@ -1,37 +0,0 @@
require 'test_helper'
class I18nTest < Test::Unit::TestCase
include Liquid
def setup
@i18n = I18n.new(fixture("en_locale.yml"))
end
def test_simple_translate_string
assert_equal "less is more", @i18n.translate("simple")
end
def test_nested_translate_string
assert_equal "something wasn't right", @i18n.translate("errors.syntax.oops")
end
def test_single_string_interpolation
assert_equal "something different", @i18n.translate("whatever", :something => "different")
end
# def test_raises_translation_error_on_undefined_interpolation_key
# assert_raise I18n::TranslationError do
# @i18n.translate("whatever", :oopstypos => "yes")
# end
# end
def test_raises_unknown_translation
assert_raise I18n::TranslationError do
@i18n.translate("doesnt_exist")
end
end
def test_sets_default_path_to_en
assert_equal I18n::DEFAULT_LOCALE, I18n.new.path
end
end

Some files were not shown because too many files have changed in this diff Show More