Compare commits

..

14 Commits

Author SHA1 Message Date
Tom Burns
0c6bc2b2a3 Allow floats > 9 2013-03-19 15:26:58 -04:00
Tom Burns
56d1913b37 Remove extension Makefile 2013-03-18 17:22:58 -04:00
Tobias Lutke
2f6b67798e fixed but with single char identifiers 2012-10-30 21:41:56 -04:00
Tobias Lutke
82c222ef0f enable weird method endings 2012-10-30 21:17:35 -04:00
Tobias Lutke
2baee3de3e enabled hard mode 2012-10-30 21:12:43 -04:00
Tobias Lutke
a96cfd1297 conquered parser_tests 2012-10-30 21:12:01 -04:00
Tobias Lütke
cf765ebed3 wip 2012-10-30 15:49:13 -04:00
Tobias Lütke
8d234098aa Initial checkin 2012-10-30 13:27:18 -04:00
Tobias Lutke
43b44c007a remove binaries 2012-10-29 21:16:25 -04:00
Tobias Lutke
858cb62c4f various speedups 2012-10-29 21:15:50 -04:00
Tobias Lütke
80be33884e Convert parser to C99 2012-10-29 16:43:42 -04:00
Tobias Lutke
cd040dabd8 Implement naive recusrive descent
Ragel doesn't allow us to recurse so we simply
reinvoke the parser for each step.
2012-10-28 21:55:20 -04:00
Tobias Lütke
18b83a58bd 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
2012-10-28 21:50:18 -04:00
Tobias Lutke
6b64bfb53e fix benchmarks 2012-10-28 21:37:07 -04:00
140 changed files with 3919 additions and 4803 deletions

6
.gitignore vendored
View File

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

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,80 +1,5 @@
# Liquid Version History # Liquid Version History
## 3.0.0 / not yet released / branch "master"
* ...
* Freeze lots of string literals for new Ruby 2.1 optimization, see #297 [Florian Weingarten, fw42]
* Allow newlines in tags and variables, see #324 [Dylan Thacker-Smith, dylanahsmith]
* Tag#parse is called after initialize, which now takes options instead of tokens as the 3rd argument. See #321 [Dylan Thacker-Smith, dylanahsmith]
* Raise `Liquid::ArgumentError` instead of `::ArgumentError` when filter has wrong number of arguments #309 [Bogdan Gusiev, bogdan]
* Add a to_s default for liquid drops, 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]
## 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
* Prevent Object methods from being called on drops
* Avoid symbol injection from liquid
* Added break and continue statements
* Fix filter parser for args without space separators
* Add support for filter keyword arguments
## 2.4.0 / 2012-08-03 ## 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.

104
Rakefile
View File

@@ -1,59 +1,73 @@
#!/usr/bin/env ruby
require 'rubygems'
require 'rake' require 'rake'
require 'rake/clean'
require 'fileutils'
require 'rake/testtask' require 'rake/testtask'
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__) require 'rubygems/package_task'
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
@@ -64,9 +78,9 @@ namespace :profile do
ruby "./performance/profile.rb" ruby "./performance/profile.rb"
end end
desc "Run the liquid profile/performance coverage with strict parsing" desc "Run KCacheGrind"
task :strict do task :grind => :run do
ruby "./performance/profile.rb strict" system "qcachegrind /tmp//callgrind.liquid.txt"
end end
end end
@@ -75,11 +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
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
require 'rake/extensiontask'
Rake::ExtensionTask.new "liquid" do |ext|
ext.lib_dir = "lib/liquid"
end
Rake::Task[:test].prerequisites << :compile
end

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,4 +1,6 @@
require 'mkmf' require 'mkmf'
$CFLAGS << ' -Wall -Werror'
$warnflags.gsub!(/-Wdeclaration-after-statement/, "") dir_config("liquid_ext")
create_makefile("liquid/liquid") have_library("c", "main")
create_makefile("liquid_ext")

View File

@@ -1,9 +0,0 @@
#include "liquid.h"
VALUE mLiquid;
void Init_liquid(void)
{
mLiquid = rb_define_module("Liquid");
init_liquid_tokenizer();
}

View File

@@ -1,11 +0,0 @@
#ifndef LIQUID_H
#define LIQUID_H
#include <ruby.h>
#include <stdbool.h>
#include "tokenizer.h"
extern VALUE mLiquid;
#endif

764
ext/liquid/liquid_context.c Normal file
View File

@@ -0,0 +1,764 @@
/* A recursive-descent parser generated by peg 0.1.9 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define YYRULECOUNT 14
//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)); \
} \
}
#ifndef YY_LOCAL
#define YY_LOCAL(T) static T
#endif
#ifndef YY_ACTION
#define YY_ACTION(T) static T
#endif
#ifndef YY_RULE
#define YY_RULE(T) static T
#endif
#ifndef YY_PARSE
#define YY_PARSE(T) T
#endif
#ifndef YYPARSE
#define YYPARSE yyparse
#endif
#ifndef YYPARSEFROM
#define YYPARSEFROM yyparsefrom
#endif
#ifndef YY_INPUT
#define YY_INPUT(buf, result, max_size) \
{ \
int yyc= getchar(); \
result= (EOF == yyc) ? 0 : (*(buf)= yyc, 1); \
yyprintf((stderr, "<%c>", yyc)); \
}
#endif
#ifndef YY_BEGIN
#define YY_BEGIN ( ctx->begin= ctx->pos, 1)
#endif
#ifndef YY_END
#define YY_END ( ctx->end= ctx->pos, 1)
#endif
#ifdef YY_DEBUG
# define yyprintf(args) fprintf args
#else
# define yyprintf(args)
#endif
#ifndef YYSTYPE
#define YYSTYPE int
#endif
#ifndef YY_PART
typedef struct _yycontext yycontext;
typedef void (*yyaction)(yycontext *ctx, char *yytext, int yyleng);
typedef struct _yythunk { int begin, end; yyaction action; struct _yythunk *next; } yythunk;
struct _yycontext {
char *buf;
int buflen;
int pos;
int limit;
char *text;
int textlen;
int begin;
int end;
int textmax;
yythunk *thunks;
int thunkslen;
int thunkpos;
YYSTYPE yy;
YYSTYPE *val;
YYSTYPE *vals;
int valslen;
#ifdef YY_CTX_MEMBERS
YY_CTX_MEMBERS
#endif
};
#ifdef YY_CTX_LOCAL
#define YY_CTX_PARAM_ yycontext *yyctx,
#define YY_CTX_PARAM yycontext *yyctx
#define YY_CTX_ARG_ yyctx,
#define YY_CTX_ARG yyctx
#else
#define YY_CTX_PARAM_
#define YY_CTX_PARAM
#define YY_CTX_ARG_
#define YY_CTX_ARG
yycontext yyctx0;
yycontext *yyctx= &yyctx0;
#endif
YY_LOCAL(int) yyrefill(yycontext *ctx)
{
int yyn;
while (ctx->buflen - ctx->pos < 512)
{
ctx->buflen *= 2;
ctx->buf= (char *)realloc(ctx->buf, ctx->buflen);
}
YY_INPUT((ctx->buf + ctx->pos), yyn, (ctx->buflen - ctx->pos));
if (!yyn) return 0;
ctx->limit += yyn;
return 1;
}
YY_LOCAL(int) yymatchDot(yycontext *ctx)
{
if (ctx->pos >= ctx->limit && !yyrefill(ctx)) return 0;
++ctx->pos;
return 1;
}
YY_LOCAL(int) yymatchChar(yycontext *ctx, int c)
{
if (ctx->pos >= ctx->limit && !yyrefill(ctx)) return 0;
if ((unsigned char)ctx->buf[ctx->pos] == c)
{
++ctx->pos;
yyprintf((stderr, " ok yymatchChar(ctx, %c) @ %s\n", c, ctx->buf+ctx->pos));
return 1;
}
yyprintf((stderr, " fail yymatchChar(ctx, %c) @ %s\n", c, ctx->buf+ctx->pos));
return 0;
}
YY_LOCAL(int) yymatchString(yycontext *ctx, char *s)
{
int yysav= ctx->pos;
while (*s)
{
if (ctx->pos >= ctx->limit && !yyrefill(ctx)) return 0;
if (ctx->buf[ctx->pos] != *s)
{
ctx->pos= yysav;
return 0;
}
++s;
++ctx->pos;
}
return 1;
}
YY_LOCAL(int) yymatchClass(yycontext *ctx, unsigned char *bits)
{
int c;
if (ctx->pos >= ctx->limit && !yyrefill(ctx)) return 0;
c= (unsigned char)ctx->buf[ctx->pos];
if (bits[c >> 3] & (1 << (c & 7)))
{
++ctx->pos;
yyprintf((stderr, " ok yymatchClass @ %s\n", ctx->buf+ctx->pos));
return 1;
}
yyprintf((stderr, " fail yymatchClass @ %s\n", ctx->buf+ctx->pos));
return 0;
}
YY_LOCAL(void) yyDo(yycontext *ctx, yyaction action, int begin, int end)
{
while (ctx->thunkpos >= ctx->thunkslen)
{
ctx->thunkslen *= 2;
ctx->thunks= (yythunk *)realloc(ctx->thunks, sizeof(yythunk) * ctx->thunkslen);
}
ctx->thunks[ctx->thunkpos].begin= begin;
ctx->thunks[ctx->thunkpos].end= end;
ctx->thunks[ctx->thunkpos].action= action;
++ctx->thunkpos;
}
YY_LOCAL(int) yyText(yycontext *ctx, int begin, int end)
{
int yyleng= end - begin;
if (yyleng <= 0)
yyleng= 0;
else
{
while (ctx->textlen < (yyleng + 1))
{
ctx->textlen *= 2;
ctx->text= (char *)realloc(ctx->text, ctx->textlen);
}
memcpy(ctx->text, ctx->buf + begin, yyleng);
}
ctx->text[yyleng]= '\0';
return yyleng;
}
YY_LOCAL(void) yyDone(yycontext *ctx)
{
int pos;
for (pos= 0; pos < ctx->thunkpos; ++pos)
{
yythunk *thunk= &ctx->thunks[pos];
int yyleng= thunk->end ? yyText(ctx, thunk->begin, thunk->end) : thunk->begin;
yyprintf((stderr, "DO [%d] %p %s\n", pos, thunk->action, ctx->text));
thunk->action(ctx, ctx->text, yyleng);
}
ctx->thunkpos= 0;
}
YY_LOCAL(void) yyCommit(yycontext *ctx)
{
if ((ctx->limit -= ctx->pos))
{
memmove(ctx->buf, ctx->buf + ctx->pos, ctx->limit);
}
ctx->begin -= ctx->pos;
ctx->end -= ctx->pos;
ctx->pos= ctx->thunkpos= 0;
}
YY_LOCAL(int) yyAccept(yycontext *ctx, int tp0)
{
if (tp0)
{
fprintf(stderr, "accept denied at %d\n", tp0);
return 0;
}
else
{
yyDone(ctx);
yyCommit(ctx);
}
return 1;
}
YY_LOCAL(void) yyPush(yycontext *ctx, char *text, int count) { ctx->val += count; }
YY_LOCAL(void) yyPop(yycontext *ctx, char *text, int count) { ctx->val -= count; }
YY_LOCAL(void) yySet(yycontext *ctx, char *text, int count) { ctx->val[count]= ctx->yy; }
#endif /* YY_PART */
#define YYACCEPT yyAccept(ctx, yythunkpos0)
YY_RULE(int) yy_digit(yycontext *ctx); /* 14 */
YY_RULE(int) yy_float(yycontext *ctx); /* 13 */
YY_RULE(int) yy_integer(yycontext *ctx); /* 12 */
YY_RULE(int) yy_rangelet(yycontext *ctx); /* 11 */
YY_RULE(int) yy_identifier(yycontext *ctx); /* 10 */
YY_RULE(int) yy_accessors(yycontext *ctx); /* 9 */
YY_RULE(int) yy_numeric(yycontext *ctx); /* 8 */
YY_RULE(int) yy_string(yycontext *ctx); /* 7 */
YY_RULE(int) yy_const(yycontext *ctx); /* 6 */
YY_RULE(int) yy_hash(yycontext *ctx); /* 5 */
YY_RULE(int) yy_range(yycontext *ctx); /* 4 */
YY_RULE(int) yy_entity(yycontext *ctx); /* 3 */
YY_RULE(int) yy_primary(yycontext *ctx); /* 2 */
YY_RULE(int) yy_grammar(yycontext *ctx); /* 1 */
YY_ACTION(void) yy_4_const(yycontext *ctx, char *yytext, int yyleng)
{
#define yy ctx->yy
#define yypos ctx->pos
#define yythunkpos ctx->thunkpos
yyprintf((stderr, "do yy_4_const\n"));
yy = Qnil; ;
#undef yythunkpos
#undef yypos
#undef yy
}
YY_ACTION(void) yy_3_const(yycontext *ctx, char *yytext, int yyleng)
{
#define yy ctx->yy
#define yypos ctx->pos
#define yythunkpos ctx->thunkpos
yyprintf((stderr, "do yy_3_const\n"));
yy = Qnil; ;
#undef yythunkpos
#undef yypos
#undef yy
}
YY_ACTION(void) yy_2_const(yycontext *ctx, char *yytext, int yyleng)
{
#define yy ctx->yy
#define yypos ctx->pos
#define yythunkpos ctx->thunkpos
yyprintf((stderr, "do yy_2_const\n"));
yy = Qfalse; ;
#undef yythunkpos
#undef yypos
#undef yy
}
YY_ACTION(void) yy_1_const(yycontext *ctx, char *yytext, int yyleng)
{
#define yy ctx->yy
#define yypos ctx->pos
#define yythunkpos ctx->thunkpos
yyprintf((stderr, "do yy_1_const\n"));
yy = Qtrue; ;
#undef yythunkpos
#undef yypos
#undef yy
}
YY_ACTION(void) yy_1_integer(yycontext *ctx, char *yytext, int yyleng)
{
#define yy ctx->yy
#define yypos ctx->pos
#define yythunkpos ctx->thunkpos
yyprintf((stderr, "do yy_1_integer\n"));
yy = rb_funcall(rb_cObject, rb_intern("Integer"), 1, yy_rb_str); ;
#undef yythunkpos
#undef yypos
#undef yy
}
YY_ACTION(void) yy_1_float(yycontext *ctx, char *yytext, int yyleng)
{
#define yy ctx->yy
#define yypos ctx->pos
#define yythunkpos ctx->thunkpos
yyprintf((stderr, "do yy_1_float\n"));
yy = rb_funcall(rb_cObject, rb_intern("Float"), 1, yy_rb_str); ;
#undef yythunkpos
#undef yypos
#undef yy
}
YY_ACTION(void) yy_2_string(yycontext *ctx, char *yytext, int yyleng)
{
#define yy ctx->yy
#define yypos ctx->pos
#define yythunkpos ctx->thunkpos
yyprintf((stderr, "do yy_2_string\n"));
yy = yy_rb_str; ;
#undef yythunkpos
#undef yypos
#undef yy
}
YY_ACTION(void) yy_1_string(yycontext *ctx, char *yytext, int yyleng)
{
#define yy ctx->yy
#define yypos ctx->pos
#define yythunkpos ctx->thunkpos
yyprintf((stderr, "do yy_1_string\n"));
yy = yy_rb_str; ;
#undef yythunkpos
#undef yypos
#undef yy
}
YY_ACTION(void) yy_1_range(yycontext *ctx, char *yytext, int yyleng)
{
#define yy ctx->yy
#define yypos ctx->pos
#define yythunkpos ctx->thunkpos
yyprintf((stderr, "do yy_1_range\n"));
EMIT("range", Qnil); ;
#undef yythunkpos
#undef yypos
#undef yy
}
YY_ACTION(void) yy_1_rangelet(yycontext *ctx, char *yytext, int yyleng)
{
#define var ctx->val[-1]
#define yy ctx->yy
#define yypos ctx->pos
#define yythunkpos ctx->thunkpos
yyprintf((stderr, "do yy_1_rangelet\n"));
EMIT("id", var); ;
#undef yythunkpos
#undef yypos
#undef yy
#undef var
}
YY_ACTION(void) yy_1_entity(yycontext *ctx, char *yytext, int yyleng)
{
#define yy ctx->yy
#define yypos ctx->pos
#define yythunkpos ctx->thunkpos
yyprintf((stderr, "do yy_1_entity\n"));
EMIT("id", yy_rb_str); EMIT("lookup", Qnil); ;
#undef yythunkpos
#undef yypos
#undef yy
}
YY_ACTION(void) yy_5_accessors(yycontext *ctx, char *yytext, int yyleng)
{
#define yy ctx->yy
#define yypos ctx->pos
#define yythunkpos ctx->thunkpos
yyprintf((stderr, "do yy_5_accessors\n"));
EMIT("id", yy_rb_str); EMIT("call", Qnil); ;
#undef yythunkpos
#undef yypos
#undef yy
}
YY_ACTION(void) yy_4_accessors(yycontext *ctx, char *yytext, int yyleng)
{
#define yy ctx->yy
#define yypos ctx->pos
#define yythunkpos ctx->thunkpos
yyprintf((stderr, "do yy_4_accessors\n"));
EMIT("buildin", rb_str_new2("size")); ;
#undef yythunkpos
#undef yypos
#undef yy
}
YY_ACTION(void) yy_3_accessors(yycontext *ctx, char *yytext, int yyleng)
{
#define yy ctx->yy
#define yypos ctx->pos
#define yythunkpos ctx->thunkpos
yyprintf((stderr, "do yy_3_accessors\n"));
EMIT("buildin", rb_str_new2("last")); ;
#undef yythunkpos
#undef yypos
#undef yy
}
YY_ACTION(void) yy_2_accessors(yycontext *ctx, char *yytext, int yyleng)
{
#define yy ctx->yy
#define yypos ctx->pos
#define yythunkpos ctx->thunkpos
yyprintf((stderr, "do yy_2_accessors\n"));
EMIT("buildin", rb_str_new2("first")); ;
#undef yythunkpos
#undef yypos
#undef yy
}
YY_ACTION(void) yy_1_accessors(yycontext *ctx, char *yytext, int yyleng)
{
#define yy ctx->yy
#define yypos ctx->pos
#define yythunkpos ctx->thunkpos
yyprintf((stderr, "do yy_1_accessors\n"));
EMIT("call", Qnil); ;
#undef yythunkpos
#undef yypos
#undef yy
}
YY_ACTION(void) yy_3_primary(yycontext *ctx, char *yytext, int yyleng)
{
#define var ctx->val[-1]
#define yy ctx->yy
#define yypos ctx->pos
#define yythunkpos ctx->thunkpos
yyprintf((stderr, "do yy_3_primary\n"));
EMIT("id", var); ;
#undef yythunkpos
#undef yypos
#undef yy
#undef var
}
YY_ACTION(void) yy_2_primary(yycontext *ctx, char *yytext, int yyleng)
{
#define var ctx->val[-1]
#define yy ctx->yy
#define yypos ctx->pos
#define yythunkpos ctx->thunkpos
yyprintf((stderr, "do yy_2_primary\n"));
EMIT("id", var); ;
#undef yythunkpos
#undef yypos
#undef yy
#undef var
}
YY_ACTION(void) yy_1_primary(yycontext *ctx, char *yytext, int yyleng)
{
#define var ctx->val[-1]
#define yy ctx->yy
#define yypos ctx->pos
#define yythunkpos ctx->thunkpos
yyprintf((stderr, "do yy_1_primary\n"));
EMIT("id", var); ;
#undef yythunkpos
#undef yypos
#undef yy
#undef var
}
YY_ACTION(void) yy_1_grammar(yycontext *ctx, char *yytext, int yyleng)
{
#define yy ctx->yy
#define yypos ctx->pos
#define yythunkpos ctx->thunkpos
yyprintf((stderr, "do yy_1_grammar\n"));
EMIT("lookup", Qnil); ;
#undef yythunkpos
#undef yypos
#undef yy
}
YY_RULE(int) yy_digit(yycontext *ctx)
{ int yypos0= ctx->pos, yythunkpos0= ctx->thunkpos;
yyprintf((stderr, "%s\n", "digit")); if (!yymatchClass(ctx, (unsigned char *)"\000\000\000\000\000\000\377\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l1;
yyprintf((stderr, " ok %s @ %s\n", "digit", ctx->buf+ctx->pos));
return 1;
l1:; ctx->pos= yypos0; ctx->thunkpos= yythunkpos0;
yyprintf((stderr, " fail %s @ %s\n", "digit", ctx->buf+ctx->pos));
return 0;
}
YY_RULE(int) yy_float(yycontext *ctx)
{ int yypos0= ctx->pos, yythunkpos0= ctx->thunkpos;
yyprintf((stderr, "%s\n", "float")); yyText(ctx, ctx->begin, ctx->end); if (!(YY_BEGIN)) goto l2;
{ int yypos3= ctx->pos, yythunkpos3= ctx->thunkpos; if (!yymatchChar(ctx, '-')) goto l3; goto l4;
l3:; ctx->pos= yypos3; ctx->thunkpos= yythunkpos3;
}
l4:; if (!yy_digit(ctx)) goto l2; if (!yymatchChar(ctx, '.')) goto l2; if (!yy_digit(ctx)) goto l2;
l5:;
{ int yypos6= ctx->pos, yythunkpos6= ctx->thunkpos; if (!yy_digit(ctx)) goto l6; goto l5;
l6:; ctx->pos= yypos6; ctx->thunkpos= yythunkpos6;
} yyText(ctx, ctx->begin, ctx->end); if (!(YY_END)) goto l2; yyDo(ctx, yy_1_float, ctx->begin, ctx->end);
yyprintf((stderr, " ok %s @ %s\n", "float", ctx->buf+ctx->pos));
return 1;
l2:; ctx->pos= yypos0; ctx->thunkpos= yythunkpos0;
yyprintf((stderr, " fail %s @ %s\n", "float", ctx->buf+ctx->pos));
return 0;
}
YY_RULE(int) yy_integer(yycontext *ctx)
{ int yypos0= ctx->pos, yythunkpos0= ctx->thunkpos;
yyprintf((stderr, "%s\n", "integer")); yyText(ctx, ctx->begin, ctx->end); if (!(YY_BEGIN)) goto l7;
{ int yypos8= ctx->pos, yythunkpos8= ctx->thunkpos; if (!yymatchChar(ctx, '-')) goto l8; goto l9;
l8:; ctx->pos= yypos8; ctx->thunkpos= yythunkpos8;
}
l9:; if (!yy_digit(ctx)) goto l7;
l10:;
{ int yypos11= ctx->pos, yythunkpos11= ctx->thunkpos; if (!yy_digit(ctx)) goto l11; goto l10;
l11:; ctx->pos= yypos11; ctx->thunkpos= yythunkpos11;
} yyText(ctx, ctx->begin, ctx->end); if (!(YY_END)) goto l7; yyDo(ctx, yy_1_integer, ctx->begin, ctx->end);
yyprintf((stderr, " ok %s @ %s\n", "integer", ctx->buf+ctx->pos));
return 1;
l7:; ctx->pos= yypos0; ctx->thunkpos= yythunkpos0;
yyprintf((stderr, " fail %s @ %s\n", "integer", ctx->buf+ctx->pos));
return 0;
}
YY_RULE(int) yy_rangelet(yycontext *ctx)
{ int yypos0= ctx->pos, yythunkpos0= ctx->thunkpos; yyDo(ctx, yyPush, 1, 0);
yyprintf((stderr, "%s\n", "rangelet"));
{ int yypos13= ctx->pos, yythunkpos13= ctx->thunkpos; if (!yy_integer(ctx)) goto l14; yyDo(ctx, yySet, -1, 0); yyDo(ctx, yy_1_rangelet, ctx->begin, ctx->end); goto l13;
l14:; ctx->pos= yypos13; ctx->thunkpos= yythunkpos13; if (!yy_entity(ctx)) goto l12;
}
l13:;
yyprintf((stderr, " ok %s @ %s\n", "rangelet", ctx->buf+ctx->pos)); yyDo(ctx, yyPop, 1, 0);
return 1;
l12:; ctx->pos= yypos0; ctx->thunkpos= yythunkpos0;
yyprintf((stderr, " fail %s @ %s\n", "rangelet", ctx->buf+ctx->pos));
return 0;
}
YY_RULE(int) yy_identifier(yycontext *ctx)
{ int yypos0= ctx->pos, yythunkpos0= ctx->thunkpos;
yyprintf((stderr, "%s\n", "identifier")); if (!yymatchClass(ctx, (unsigned char *)"\000\000\000\000\000\000\000\000\376\377\377\007\376\377\377\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l15;
l16:;
{ int yypos17= ctx->pos, yythunkpos17= ctx->thunkpos; if (!yymatchClass(ctx, (unsigned char *)"\000\000\000\000\000\040\377\003\376\377\377\207\376\377\377\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l17; goto l16;
l17:; ctx->pos= yypos17; ctx->thunkpos= yythunkpos17;
}
{ int yypos18= ctx->pos, yythunkpos18= ctx->thunkpos; if (!yymatchClass(ctx, (unsigned char *)"\000\000\000\000\002\000\000\200\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l18; goto l19;
l18:; ctx->pos= yypos18; ctx->thunkpos= yythunkpos18;
}
l19:;
yyprintf((stderr, " ok %s @ %s\n", "identifier", ctx->buf+ctx->pos));
return 1;
l15:; ctx->pos= yypos0; ctx->thunkpos= yythunkpos0;
yyprintf((stderr, " fail %s @ %s\n", "identifier", ctx->buf+ctx->pos));
return 0;
}
YY_RULE(int) yy_accessors(yycontext *ctx)
{ int yypos0= ctx->pos, yythunkpos0= ctx->thunkpos;
yyprintf((stderr, "%s\n", "accessors"));
{ int yypos21= ctx->pos, yythunkpos21= ctx->thunkpos; if (!yy_hash(ctx)) goto l22; yyDo(ctx, yy_1_accessors, ctx->begin, ctx->end); goto l21;
l22:; ctx->pos= yypos21; ctx->thunkpos= yythunkpos21; if (!yymatchString(ctx, ".first")) goto l23; yyDo(ctx, yy_2_accessors, ctx->begin, ctx->end); goto l21;
l23:; ctx->pos= yypos21; ctx->thunkpos= yythunkpos21; if (!yymatchString(ctx, ".last")) goto l24; yyDo(ctx, yy_3_accessors, ctx->begin, ctx->end); goto l21;
l24:; ctx->pos= yypos21; ctx->thunkpos= yythunkpos21; if (!yymatchString(ctx, ".size")) goto l25; yyDo(ctx, yy_4_accessors, ctx->begin, ctx->end); goto l21;
l25:; ctx->pos= yypos21; ctx->thunkpos= yythunkpos21; if (!yymatchChar(ctx, '.')) goto l20; yyText(ctx, ctx->begin, ctx->end); if (!(YY_BEGIN)) goto l20; if (!yy_identifier(ctx)) goto l20; yyText(ctx, ctx->begin, ctx->end); if (!(YY_END)) goto l20; yyDo(ctx, yy_5_accessors, ctx->begin, ctx->end);
}
l21:;
yyprintf((stderr, " ok %s @ %s\n", "accessors", ctx->buf+ctx->pos));
return 1;
l20:; ctx->pos= yypos0; ctx->thunkpos= yythunkpos0;
yyprintf((stderr, " fail %s @ %s\n", "accessors", ctx->buf+ctx->pos));
return 0;
}
YY_RULE(int) yy_numeric(yycontext *ctx)
{ int yypos0= ctx->pos, yythunkpos0= ctx->thunkpos;
yyprintf((stderr, "%s\n", "numeric"));
{ int yypos27= ctx->pos, yythunkpos27= ctx->thunkpos; if (!yy_float(ctx)) goto l28; goto l27;
l28:; ctx->pos= yypos27; ctx->thunkpos= yythunkpos27; if (!yy_integer(ctx)) goto l26;
}
l27:;
yyprintf((stderr, " ok %s @ %s\n", "numeric", ctx->buf+ctx->pos));
return 1;
l26:; ctx->pos= yypos0; ctx->thunkpos= yythunkpos0;
yyprintf((stderr, " fail %s @ %s\n", "numeric", ctx->buf+ctx->pos));
return 0;
}
YY_RULE(int) yy_string(yycontext *ctx)
{ int yypos0= ctx->pos, yythunkpos0= ctx->thunkpos;
yyprintf((stderr, "%s\n", "string"));
{ int yypos30= ctx->pos, yythunkpos30= ctx->thunkpos; if (!yymatchClass(ctx, (unsigned char *)"\000\000\000\000\200\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l31; yyText(ctx, ctx->begin, ctx->end); if (!(YY_BEGIN)) goto l31;
l32:;
{ int yypos33= ctx->pos, yythunkpos33= ctx->thunkpos;
{ int yypos34= ctx->pos, yythunkpos34= ctx->thunkpos; if (!yymatchClass(ctx, (unsigned char *)"\000\000\000\000\200\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l34; goto l33;
l34:; ctx->pos= yypos34; ctx->thunkpos= yythunkpos34;
} if (!yymatchDot(ctx)) goto l33; goto l32;
l33:; ctx->pos= yypos33; ctx->thunkpos= yythunkpos33;
} yyText(ctx, ctx->begin, ctx->end); if (!(YY_END)) goto l31; if (!yymatchClass(ctx, (unsigned char *)"\000\000\000\000\200\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l31; yyDo(ctx, yy_1_string, ctx->begin, ctx->end); goto l30;
l31:; ctx->pos= yypos30; ctx->thunkpos= yythunkpos30; if (!yymatchClass(ctx, (unsigned char *)"\000\000\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l29; yyText(ctx, ctx->begin, ctx->end); if (!(YY_BEGIN)) goto l29;
l35:;
{ int yypos36= ctx->pos, yythunkpos36= ctx->thunkpos;
{ int yypos37= ctx->pos, yythunkpos37= ctx->thunkpos; if (!yymatchClass(ctx, (unsigned char *)"\000\000\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l37; goto l36;
l37:; ctx->pos= yypos37; ctx->thunkpos= yythunkpos37;
} if (!yymatchDot(ctx)) goto l36; goto l35;
l36:; ctx->pos= yypos36; ctx->thunkpos= yythunkpos36;
} yyText(ctx, ctx->begin, ctx->end); if (!(YY_END)) goto l29; if (!yymatchClass(ctx, (unsigned char *)"\000\000\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000")) goto l29; yyDo(ctx, yy_2_string, ctx->begin, ctx->end);
}
l30:;
yyprintf((stderr, " ok %s @ %s\n", "string", ctx->buf+ctx->pos));
return 1;
l29:; ctx->pos= yypos0; ctx->thunkpos= yythunkpos0;
yyprintf((stderr, " fail %s @ %s\n", "string", ctx->buf+ctx->pos));
return 0;
}
YY_RULE(int) yy_const(yycontext *ctx)
{ int yypos0= ctx->pos, yythunkpos0= ctx->thunkpos;
yyprintf((stderr, "%s\n", "const"));
{ int yypos39= ctx->pos, yythunkpos39= ctx->thunkpos; if (!yymatchString(ctx, "true")) goto l40; yyDo(ctx, yy_1_const, ctx->begin, ctx->end); goto l39;
l40:; ctx->pos= yypos39; ctx->thunkpos= yythunkpos39; if (!yymatchString(ctx, "false")) goto l41; yyDo(ctx, yy_2_const, ctx->begin, ctx->end); goto l39;
l41:; ctx->pos= yypos39; ctx->thunkpos= yythunkpos39; if (!yymatchString(ctx, "nil")) goto l42; yyDo(ctx, yy_3_const, ctx->begin, ctx->end); goto l39;
l42:; ctx->pos= yypos39; ctx->thunkpos= yythunkpos39; if (!yymatchString(ctx, "null")) goto l38; yyDo(ctx, yy_4_const, ctx->begin, ctx->end);
}
l39:;
yyprintf((stderr, " ok %s @ %s\n", "const", ctx->buf+ctx->pos));
return 1;
l38:; ctx->pos= yypos0; ctx->thunkpos= yythunkpos0;
yyprintf((stderr, " fail %s @ %s\n", "const", ctx->buf+ctx->pos));
return 0;
}
YY_RULE(int) yy_hash(yycontext *ctx)
{ int yypos0= ctx->pos, yythunkpos0= ctx->thunkpos;
yyprintf((stderr, "%s\n", "hash")); if (!yymatchChar(ctx, '[')) goto l43;
{ int yypos44= ctx->pos, yythunkpos44= ctx->thunkpos; if (!yy_primary(ctx)) goto l45; goto l44;
l45:; ctx->pos= yypos44; ctx->thunkpos= yythunkpos44; if (!yy_entity(ctx)) goto l43;
}
l44:; if (!yymatchChar(ctx, ']')) goto l43;
yyprintf((stderr, " ok %s @ %s\n", "hash", ctx->buf+ctx->pos));
return 1;
l43:; ctx->pos= yypos0; ctx->thunkpos= yythunkpos0;
yyprintf((stderr, " fail %s @ %s\n", "hash", ctx->buf+ctx->pos));
return 0;
}
YY_RULE(int) yy_range(yycontext *ctx)
{ int yypos0= ctx->pos, yythunkpos0= ctx->thunkpos;
yyprintf((stderr, "%s\n", "range")); if (!yymatchChar(ctx, '(')) goto l46; if (!yy_rangelet(ctx)) goto l46; if (!yymatchString(ctx, "..")) goto l46; if (!yy_rangelet(ctx)) goto l46; if (!yymatchChar(ctx, ')')) goto l46; yyDo(ctx, yy_1_range, ctx->begin, ctx->end);
yyprintf((stderr, " ok %s @ %s\n", "range", ctx->buf+ctx->pos));
return 1;
l46:; ctx->pos= yypos0; ctx->thunkpos= yythunkpos0;
yyprintf((stderr, " fail %s @ %s\n", "range", ctx->buf+ctx->pos));
return 0;
}
YY_RULE(int) yy_entity(yycontext *ctx)
{ int yypos0= ctx->pos, yythunkpos0= ctx->thunkpos;
yyprintf((stderr, "%s\n", "entity")); yyText(ctx, ctx->begin, ctx->end); if (!(YY_BEGIN)) goto l47; if (!yy_identifier(ctx)) goto l47; yyText(ctx, ctx->begin, ctx->end); if (!(YY_END)) goto l47; yyDo(ctx, yy_1_entity, ctx->begin, ctx->end);
l48:;
{ int yypos49= ctx->pos, yythunkpos49= ctx->thunkpos; if (!yy_accessors(ctx)) goto l49; goto l48;
l49:; ctx->pos= yypos49; ctx->thunkpos= yythunkpos49;
}
yyprintf((stderr, " ok %s @ %s\n", "entity", ctx->buf+ctx->pos));
return 1;
l47:; ctx->pos= yypos0; ctx->thunkpos= yythunkpos0;
yyprintf((stderr, " fail %s @ %s\n", "entity", ctx->buf+ctx->pos));
return 0;
}
YY_RULE(int) yy_primary(yycontext *ctx)
{ int yypos0= ctx->pos, yythunkpos0= ctx->thunkpos; yyDo(ctx, yyPush, 1, 0);
yyprintf((stderr, "%s\n", "primary"));
{ int yypos51= ctx->pos, yythunkpos51= ctx->thunkpos; if (!yy_const(ctx)) goto l52; yyDo(ctx, yySet, -1, 0); yyDo(ctx, yy_1_primary, ctx->begin, ctx->end); goto l51;
l52:; ctx->pos= yypos51; ctx->thunkpos= yythunkpos51; if (!yy_string(ctx)) goto l53; yyDo(ctx, yySet, -1, 0); yyDo(ctx, yy_2_primary, ctx->begin, ctx->end); goto l51;
l53:; ctx->pos= yypos51; ctx->thunkpos= yythunkpos51; if (!yy_numeric(ctx)) goto l50; yyDo(ctx, yySet, -1, 0); yyDo(ctx, yy_3_primary, ctx->begin, ctx->end);
}
l51:;
yyprintf((stderr, " ok %s @ %s\n", "primary", ctx->buf+ctx->pos)); yyDo(ctx, yyPop, 1, 0);
return 1;
l50:; ctx->pos= yypos0; ctx->thunkpos= yythunkpos0;
yyprintf((stderr, " fail %s @ %s\n", "primary", ctx->buf+ctx->pos));
return 0;
}
YY_RULE(int) yy_grammar(yycontext *ctx)
{ int yypos0= ctx->pos, yythunkpos0= ctx->thunkpos;
yyprintf((stderr, "%s\n", "grammar"));
{ int yypos55= ctx->pos, yythunkpos55= ctx->thunkpos; if (!yy_primary(ctx)) goto l56; goto l55;
l56:; ctx->pos= yypos55; ctx->thunkpos= yythunkpos55; if (!yy_entity(ctx)) goto l57; goto l55;
l57:; ctx->pos= yypos55; ctx->thunkpos= yythunkpos55; if (!yy_range(ctx)) goto l58; goto l55;
l58:; ctx->pos= yypos55; ctx->thunkpos= yythunkpos55; if (!yy_hash(ctx)) goto l54; yyDo(ctx, yy_1_grammar, ctx->begin, ctx->end);
}
l55:;
yyprintf((stderr, " ok %s @ %s\n", "grammar", ctx->buf+ctx->pos));
return 1;
l54:; ctx->pos= yypos0; ctx->thunkpos= yythunkpos0;
yyprintf((stderr, " fail %s @ %s\n", "grammar", ctx->buf+ctx->pos));
return 0;
}
#ifndef YY_PART
typedef int (*yyrule)(yycontext *ctx);
YY_PARSE(int) YYPARSEFROM(YY_CTX_PARAM_ yyrule yystart)
{
int yyok;
if (!yyctx->buflen)
{
yyctx->buflen= 1024;
yyctx->buf= (char *)malloc(yyctx->buflen);
yyctx->textlen= 1024;
yyctx->text= (char *)malloc(yyctx->textlen);
yyctx->thunkslen= 32;
yyctx->thunks= (yythunk *)malloc(sizeof(yythunk) * yyctx->thunkslen);
yyctx->valslen= 32;
yyctx->vals= (YYSTYPE *)malloc(sizeof(YYSTYPE) * yyctx->valslen);
yyctx->begin= yyctx->end= yyctx->pos= yyctx->limit= yyctx->thunkpos= 0;
}
yyctx->begin= yyctx->end= yyctx->pos;
yyctx->thunkpos= 0;
yyctx->val= yyctx->vals;
yyok= yystart(yyctx);
if (yyok) yyDone(yyctx);
yyCommit(yyctx);
return yyok;
}
YY_PARSE(int) YYPARSE(YY_CTX_PARAM)
{
return YYPARSEFROM(YY_CTX_ARG_ yy_grammar);
}
#endif
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

@@ -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;
}

13
ext/liquid/liquid_ext.c Normal file
View File

@@ -0,0 +1,13 @@
#include <ruby.h>
static VALUE rb_Liquid;
static VALUE rb_Parser;
VALUE liquid_context_parse_impl(VALUE text);
void Init_liquid_ext()
{
rb_Liquid = rb_define_module("Liquid");
rb_Parser = rb_define_class_under(rb_Liquid, "Parser", rb_cObject);
rb_define_singleton_method(rb_Parser, "parse", liquid_context_parse_impl, 1);
}

View File

@@ -1,137 +0,0 @@
#include "liquid.h"
VALUE cLiquidTokenizer;
static void tokenizer_mark(void *ptr) {
tokenizer_t *tokenizer = ptr;
rb_gc_mark(tokenizer->source);
}
static void tokenizer_free(void *ptr)
{
tokenizer_t *tokenizer = ptr;
xfree(tokenizer);
}
static size_t tokenizer_memsize(const void *ptr)
{
return ptr ? sizeof(tokenizer_t) : 0;
}
const rb_data_type_t tokenizer_data_type = {
"liquid_tokenizer",
{tokenizer_mark, tokenizer_free, tokenizer_memsize,},
#ifdef RUBY_TYPED_FREE_IMMEDIATELY
NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
#endif
};
static VALUE tokenizer_allocate(VALUE klass)
{
VALUE obj;
tokenizer_t *tokenizer;
obj = TypedData_Make_Struct(klass, tokenizer_t, &tokenizer_data_type, tokenizer);
tokenizer->source = Qnil;
return obj;
}
static VALUE tokenizer_initialize_method(VALUE self, VALUE source)
{
tokenizer_t *tokenizer;
Check_Type(source, T_STRING);
Tokenizer_Get_Struct(self, tokenizer);
source = rb_str_dup_frozen(source);
tokenizer->source = source;
tokenizer->cursor = RSTRING_PTR(source);
tokenizer->length = RSTRING_LEN(source);
return Qnil;
}
void tokenizer_next(tokenizer_t *tokenizer, token_t *token)
{
if (tokenizer->length <= 0) {
memset(token, 0, sizeof(*token));
return;
}
const char *cursor = tokenizer->cursor;
const char *last = cursor + tokenizer->length - 1;
token->str = cursor;
token->type = TOKEN_STRING;
while (cursor < last) {
if (*cursor++ != '{')
continue;
char c = *cursor++;
if (c != '%' && c != '{')
continue;
if (cursor - tokenizer->cursor > 2) {
token->type = TOKEN_STRING;
cursor -= 2;
goto found;
}
token->type = TOKEN_INVALID;
if (c == '%') {
while (cursor < last) {
if (*cursor++ != '%')
continue;
c = *cursor++;
while (c == '%' && cursor <= last)
c = *cursor++;
if (c != '}')
continue;
token->type = TOKEN_TAG;
goto found;
}
// unterminated tag
cursor = tokenizer->cursor + 2;
goto found;
} else {
while (cursor < last) {
if (*cursor++ != '}')
continue;
if (*cursor++ != '}') {
// variable incomplete end, used to end raw tags
cursor--;
goto found;
}
token->type = TOKEN_VARIABLE;
goto found;
}
// unterminated variable
cursor = tokenizer->cursor + 2;
goto found;
}
}
cursor = last + 1;
found:
token->length = cursor - tokenizer->cursor;
tokenizer->cursor += token->length;
tokenizer->length -= token->length;
}
static VALUE tokenizer_next_method(VALUE self)
{
tokenizer_t *tokenizer;
Tokenizer_Get_Struct(self, tokenizer);
token_t token;
tokenizer_next(tokenizer, &token);
if (token.type == TOKEN_NONE)
return Qnil;
return rb_str_new(token.str, token.length);
}
void init_liquid_tokenizer()
{
cLiquidTokenizer = rb_define_class_under(mLiquid, "Tokenizer", rb_cObject);
rb_define_alloc_func(cLiquidTokenizer, tokenizer_allocate);
rb_define_method(cLiquidTokenizer, "initialize", tokenizer_initialize_method, 1);
rb_define_method(cLiquidTokenizer, "next", tokenizer_next_method, 0);
rb_define_alias(cLiquidTokenizer, "shift", "next");
}

View File

@@ -1,31 +0,0 @@
#ifndef LIQUID_TOKENIZER_H
#define LIQUID_TOKENIZER_H
enum token_type {
TOKEN_NONE,
TOKEN_INVALID,
TOKEN_STRING,
TOKEN_TAG,
TOKEN_VARIABLE
};
typedef struct token {
enum token_type type;
const char *str;
long length;
} token_t;
typedef struct tokenizer {
VALUE source;
const char *cursor;
long length;
} tokenizer_t;
extern VALUE cLiquidTokenizer;
extern const rb_data_type_t tokenizer_data_type;
#define Tokenizer_Get_Struct(obj, sval) TypedData_Get_Struct(obj, tokenizer_t, &tokenizer_data_type, sval)
void init_liquid_tokenizer();
void tokenizer_next(tokenizer_t *tokenizer, token_t *token);
#endif

View File

@@ -21,25 +21,30 @@
module Liquid module Liquid
FilterSeparator = /\|/ FilterSeparator = /\|/
ArgumentSeparator = ','.freeze ArgumentSeparator = ','
FilterArgumentSeparator = ':'.freeze FilterArgumentSeparator = ':'
VariableAttributeSeparator = '.'.freeze VariableAttributeSeparator = '.'
TagStart = /\{\%/ TagStart = /\{\%/
TagEnd = /\%\}/ TagEnd = /\%\}/
VariableSignature = /\(?[\w\-\.\[\]]\)?/ VariableSignature = /\(?[\w\-\.\[\]]\)?/
VariableSegment = /[\w\-]/ VariableSegment = /[\w\-]/
VariableStart = /\{\{/ VariableStart = /\{\{/
VariableEnd = /\}\}/ VariableEnd = /\}\}/
VariableIncompleteEnd = /\}\}?/
QuotedString = /"[^"]*"|'[^']*'/ QuotedString = /"[^"]*"|'[^']*'/
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s|:,]+/
FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/o
OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/o
SpacelessFilter = /^(?:'[^']+'|"[^"]+"|[^'"])*#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/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/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'
@@ -52,17 +57,13 @@ require 'liquid/document'
require 'liquid/variable' require 'liquid/variable'
require 'liquid/file_system' require 'liquid/file_system'
require 'liquid/template' require 'liquid/template'
require 'liquid/htmltags'
require 'liquid/standardfilters' 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
# #
Dir[File.dirname(__FILE__) + '/liquid/tags/*.rb'].each { |f| require f } Dir[File.dirname(__FILE__) + '/liquid/tags/*.rb'].each { |f| require f }
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
require 'liquid/liquid'
else
require 'liquid/tokenizer'
end

View File

@@ -1,28 +1,22 @@
module Liquid module Liquid
class Block < Tag
IsTag = /\A#{TagStart}/o
IsVariable = /\A#{VariableStart}/o
FullToken = /\A#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
ContentOfVariable = /\A#{VariableStart}(.*)#{VariableEnd}\z/om
def blank? class Block < Tag
@blank || false IsTag = /^#{TagStart}/o
end IsVariable = /^#{VariableStart}/o
FullToken = /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/o
ContentOfVariable = /^#{VariableStart}(.*)#{VariableEnd}$/o
def parse(tokens) def parse(tokens)
@blank = true
@nodelist ||= [] @nodelist ||= []
@nodelist.clear @nodelist.clear
# All child tags of the current block.
@children = []
while token = tokens.shift while token = tokens.shift
case token case token
when IsTag when IsTag
if token =~ FullToken if token =~ FullToken
# if we found the proper block delimiter just end parsing here and let the outer block # if we found the proper block delimitor just end parsing here and let the outer block
# proceed # proceed
if block_delimiter == $1 if block_delimiter == $1
end_tag end_tag
@@ -31,63 +25,41 @@ module Liquid
# fetch the tag from registered blocks # fetch the tag from registered blocks
if tag = Template.tags[$1] if tag = Template.tags[$1]
new_tag = tag.parse($1, $2, tokens, @options) @nodelist << tag.new($1, $2, tokens)
@blank &&= new_tag.blank?
@nodelist << new_tag
@children << new_tag
else else
# this tag is not registered with the system # this tag is not registered with the system
# pass it to the current block for special handling or error reporting # pass it to the current block for special handling or error reporting
unknown_tag($1, $2, tokens) unknown_tag($1, $2, tokens)
end end
else else
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination".freeze, :token => token, :tag_end => TagEnd.inspect)) raise SyntaxError, "Tag '#{token}' was not properly terminated with regexp: #{TagEnd.inspect} "
end end
when IsVariable when IsVariable
new_var = create_variable(token) @nodelist << create_variable(token)
@nodelist << new_var when ''
@children << new_var
@blank = false
when ''.freeze
# pass # pass
else else
@nodelist << token @nodelist << token
@blank &&= (token =~ /\A\s*\z/)
end end
end end
# Make sure that it's ok to end parsing in the current block. # Make sure that its ok to end parsing in the current block.
# Effectively this method will throw an exception unless the current block is # Effectively this method will throw and exception unless the current block is
# of type Document # of type Document
assert_missing_delimitation! assert_missing_delimitation!
end end
# warnings of this block and all sub-tags
def warnings
all_warnings = []
all_warnings.concat(@warnings) if @warnings
(@children || []).each do |node|
all_warnings.concat(node.warnings || [])
end
all_warnings
end
def end_tag def end_tag
end end
def unknown_tag(tag, params, tokens) def unknown_tag(tag, params, tokens)
case tag case tag
when 'else'.freeze when 'else'
raise SyntaxError.new(options[:locale].t("errors.syntax.unexpected_else".freeze, raise SyntaxError, "#{block_name} tag does not expect else tag"
:block_name => block_name)) when 'end'
when 'end'.freeze raise SyntaxError, "'end' is not a valid delimiter for #{block_name} tags. use #{block_delimiter}"
raise SyntaxError.new(options[:locale].t("errors.syntax.invalid_delimiter".freeze,
:block_name => block_name,
:block_delimiter => block_delimiter))
else else
raise SyntaxError.new(options[:locale].t("errors.syntax.unknown_tag".freeze, :tag => tag)) raise SyntaxError, "Unknown tag '#{tag}'"
end end
end end
@@ -101,9 +73,9 @@ module Liquid
def create_variable(token) def create_variable(token)
token.scan(ContentOfVariable) do |content| token.scan(ContentOfVariable) do |content|
return Variable.new(content.first, @options) return Variable.new(content.first)
end end
raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination".freeze, :token => token, :tag_end => VariableEnd.inspect)) raise SyntaxError.new("Variable '#{token}' was not properly terminated with regexp: #{VariableEnd.inspect} ")
end end
def render(context) def render(context)
@@ -113,14 +85,11 @@ module Liquid
protected protected
def assert_missing_delimitation! def assert_missing_delimitation!
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_never_closed".freeze, :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?
@@ -134,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".freeze)
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
output.join output
end end
end end
end end

View File

@@ -8,14 +8,14 @@ module Liquid
# #
class Condition #:nodoc: class Condition #:nodoc:
@@operators = { @@operators = {
'=='.freeze => lambda { |cond, left, right| cond.send(:equal_variables, left, right) }, '==' => lambda { |cond, left, right| cond.send(:equal_variables, left, right) },
'!='.freeze => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) }, '!=' => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
'<>'.freeze => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) }, '<>' => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
'<'.freeze => :<, '<' => :<,
'>'.freeze => :>, '>' => :>,
'>='.freeze => :>=, '>=' => :>=,
'<='.freeze => :<=, '<=' => :<=,
'contains'.freeze => lambda { |cond, left, right| left && right ? left.include?(right) : false } 'contains' => lambda { |cond, left, right| left && right ? left.include?(right) : false }
} }
def self.operators def self.operators
@@ -61,7 +61,7 @@ module Liquid
end end
def inspect def inspect
"#<Condition #{[@left, @operator, @right].compact.join(' '.freeze)}>" "#<Condition #{[@left, @operator, @right].compact.join(' ')}>"
end end
private private

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?(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,16 @@ 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.extend(f)
end
# 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.
@@ -97,13 +72,17 @@ module Liquid
end end
def invoke(method, *args) def invoke(method, *args)
strainer.invoke(method, *args) if strainer.respond_to?(method)
strainer.__send__(method, *args)
else
args.first
end
end end
# Push new local scope on the stack. use <tt>Context#stack</tt> instead # Push new local scope on the stack. use <tt>Context#stack</tt> instead
def push(new_scope={}) def push(new_scope={})
@scopes.unshift(new_scope) @scopes.unshift(new_scope)
raise StackLevelError, "Nesting too deep".freeze if @scopes.length > 100 raise StackLevelError, "Nesting too deep" if @scopes.length > 100
end end
# Merge a hash of variables in the current local scope # Merge a hash of variables in the current local scope
@@ -136,61 +115,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'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil,
'true'.freeze => true,
'false'.freeze => false,
'blank'.freeze => :blank?,
'empty'.freeze => :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/m # Single quoted strings
$1
when /\A"(.*)"\z/m # 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 +217,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/m
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'.freeze, 'first'.freeze, 'last'.freeze].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 +256,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,11 +1,11 @@
module Liquid module Liquid
class Document < Block class Document < Block
def self.parse(tokens, options={}) # we don't need markup to open this block
# we don't need markup to open this block def initialize(tokens)
super(nil, nil, tokens, options) parse(tokens)
end end
# There isn't a real delimiter # There isn't a real delimter
def block_delimiter def block_delimiter
[] []
end end

View File

@@ -1,5 +1,3 @@
require 'set'
module Liquid module Liquid
# A drop in liquid is a class which allows you to export DOM like things to liquid. # A drop in liquid is a class which allows you to export DOM like things to liquid.
@@ -33,8 +31,8 @@ module Liquid
# called by liquid to invoke a drop # called by liquid to invoke a drop
def invoke_drop(method_or_key) def invoke_drop(method_or_key)
if method_or_key && method_or_key != EMPTY_STRING && self.class.invokable?(method_or_key) if method_or_key && method_or_key != EMPTY_STRING && self.class.public_method_defined?(method_or_key.to_s.to_sym)
send(method_or_key) send(method_or_key.to_s.to_sym)
else else
before_method(method_or_key) before_method(method_or_key)
end end
@@ -44,34 +42,10 @@ 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
# Check for method existence without invoking respond_to?, which creates symbols
def self.invokable?(method_name)
unless @invokable_methods
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)
end
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".freeze) 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?('/'.freeze) 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

119
lib/liquid/htmltags.rb Normal file
View File

@@ -0,0 +1,119 @@
module Liquid
class TableRow < Block
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
def initialize(tag_name, markup, tokens)
if markup =~ Syntax
@variable_name = $1
@collection_name = $2
@idx_i = "#{$1}-#{$2}-i"
@idx_col = "#{$1}-#{$2}-c"
@attributes = {}
markup.scan(TagAttributes) do |key, value|
@attributes[key] = value
end
else
raise SyntaxError.new("Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3")
end
super
end
def render(context)
context.registers[:tablerowloop] ||= Hash.new(0)
collection = context[@collection_name] or return ''
from = @attributes['offset'] ? context[@attributes['offset']].to_i : 0
to = @attributes['limit'] ? from + context[@attributes['limit']].to_i : nil
collection = Utils.slice_collection_using_each(collection, from, to)
length = collection.length
cols = context[@attributes['cols']].to_i
row = 1
col = 0
result = ["<tr class=\"row1\">\n"]
context.stack do
context.registers[:tablerowloop][@idx]
context['tablerowloop'] = lambda { Tablerowloop.new(@idx_i, @idx_col, length) }
collection.each_with_index do |item, index|
context.registers[:tablerowloop][@idx_i] = index
context.registers[:tablerowloop][@idx_col] = col
context[@variable_name] = item
col += 1
result << "<td class=\"col#{col}\">" << render_all(@nodelist, context) << '</td>'
if col == cols and not (index == length - 1)
col = 0
row += 1
result << "</tr>\n<tr class=\"row#{row}\">"
end
end
end
result << "</tr>\n"
result
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
Template.register_tag('tablerow', TableRow)
end

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('.'.freeze).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

@@ -5,7 +5,7 @@ module Liquid
attr_reader :message attr_reader :message
def initialize(message=nil) def initialize(message=nil)
@message = message || "interrupt".freeze @message = message || "interrupt"
end end
end end

View File

@@ -1,51 +0,0 @@
require "strscan"
module Liquid
class Lexer
SPECIALS = {
'|'.freeze => :pipe,
'.'.freeze => :dot,
':'.freeze => :colon,
','.freeze => :comma,
'['.freeze => :open_square,
']'.freeze => :close_square,
'('.freeze => :open_round,
')'.freeze => :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,90 +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 << ' '.freeze
end
str << expression
str
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,29 +1,21 @@
require 'cgi' require 'cgi'
require 'bigdecimal'
module Liquid module Liquid
module StandardFilters module StandardFilters
HTML_ESCAPE = {
'&'.freeze => '&amp;'.freeze,
'>'.freeze => '&gt;'.freeze,
'<'.freeze => '&lt;'.freeze,
'"'.freeze => '&quot;'.freeze,
"'".freeze => '&#39;'.freeze
}
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)
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
@@ -38,25 +30,27 @@ 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
# Truncate a string down to x characters # Truncate a string down to x characters
def truncate(input, length = 50, truncate_string = "...".freeze) def truncate(input, length = 50, truncate_string = "...")
if input.nil? then return end if input.nil? then return end
l = length.to_i - truncate_string.length l = length.to_i - truncate_string.length
l = 0 if l < 0 l = 0 if l < 0
input.length > length.to_i ? input[0...l] + truncate_string : input input.length > length.to_i ? input[0...l] + truncate_string : input
end end
def truncatewords(input, words = 15, truncate_string = "...".freeze) def truncatewords(input, words = 15, truncate_string = "...")
if input.nil? then return end if input.nil? then return end
wordlist = input.to_s.split wordlist = input.to_s.split
l = words.to_i - 1 l = words.to_i - 1
l = 0 if l < 0 l = 0 if l < 0
wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string : input wordlist.length > l ? wordlist[0..l].join(" ") + truncate_string : input
end end
# Split input string into an array of substrings separated by given pattern. # Split input string into an array of substrings separated by given pattern.
@@ -68,83 +62,62 @@ 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)
empty = ''.freeze input.to_s.gsub(/<script.*?<\/script>/, '').gsub(/<!--.*?-->/, '').gsub(/<.*?>/, '')
input.to_s.gsub(/<script.*?<\/script>/m, empty).gsub(/<!--.*?-->/m, empty).gsub(/<style.*?<\/style>/m, empty).gsub(/<.*?>/m, empty)
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/, ''.freeze) 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 = ' '.freeze) def join(input, glue = ' ')
[input].flatten.join(glue) [input].flatten.join(glue)
end end
# 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?('[]'.freeze) and !ary.first[property].nil? elsif ary.first.respond_to?('[]') and !ary.first[property].nil?
ary.sort {|a,b| a[property] <=> b[property] } ary.sort {|a,b| a[property] <=> b[property] }
elsif ary.first.respond_to?(property) elsif ary.first.respond_to?(property)
ary.sort {|a,b| a.send(property) <=> b.send(property) } ary.sort {|a,b| a.send(property) <=> b.send(property) }
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".freeze 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 = ''.freeze) 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 = ''.freeze) 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
def remove(input, string) def remove(input, string)
input.to_s.gsub(string, ''.freeze) input.to_s.gsub(string, '')
end end
# remove the first occurrences of a substring # remove the first occurrences of a substring
def remove_first(input, string) def remove_first(input, string)
input.to_s.sub(string, ''.freeze) input.to_s.sub(string, '')
end end
# add one string to another # add one string to another
@@ -159,7 +132,7 @@ module Liquid
# Add <br /> tags in front of all newlines in input string # Add <br /> tags in front of all newlines in input string
def newline_to_br(input) def newline_to_br(input)
input.to_s.gsub(/\n/, "<br />\n".freeze) input.to_s.gsub(/\n/, "<br />\n")
end end
# Reformat a date # Reformat a date
@@ -196,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'.freeze, 'today'.freeze
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
@@ -240,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 = "".freeze)
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

@@ -2,20 +2,24 @@ require 'set'
module Liquid module Liquid
parent_object = if defined? BlankObject
BlankObject
else
Object
end
# Strainer is the parent class for the filters system. # Strainer is the parent class for the filters system.
# New filters are mixed into the strainer class which is then instantiated for each liquid template render run. # New filters are mixed into the strainer class which is then instanciated for each liquid template render run.
# #
# The Strainer only allows method calls defined in filters given to it via Strainer.global_filter, # One of the strainer's responsibilities is to keep malicious method calls out
# Context#add_filters or Template.register_filter class Strainer < parent_object #:nodoc:
class Strainer #:nodoc: INTERNAL_METHOD = /^__/
@@filters = [] @@required_methods = Set.new([:__id__, :__send__, :respond_to?, :kind_of?, :extend, :methods, :singleton_methods, :class, :object_id])
@@known_filters = Set.new
@@known_methods = Set.new # Ruby 1.9.2 introduces Object#respond_to_missing?, which is invoked by Object#respond_to?
@@strainer_class_cache = Hash.new do |hash, filters| @@required_methods << :respond_to_missing? if Object.respond_to? :respond_to_missing?
hash[filters] = Class.new(Strainer) do
filters.each { |f| include f } @@filters = {}
end
end
def initialize(context) def initialize(context)
@context = context @context = context
@@ -23,41 +27,28 @@ 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) @@filters[filter.name] = filter
@@filters << filter unless @@filters.include?(filter)
end end
def self.add_known_filter(filter) def self.create(context)
unless @@known_filters.include?(filter) strainer = Strainer.new(context)
@@method_blacklist ||= Set.new(Strainer.instance_methods.map(&:to_s)) @@filters.each { |k,m| strainer.extend(m) }
new_methods = filter.instance_methods.map(&:to_s) strainer
new_methods.reject!{ |m| @@method_blacklist.include?(m) } end
@@known_methods.merge(new_methods)
@@known_filters.add(filter) def respond_to?(method, include_private = false)
method_name = method.to_s
return false if method_name =~ INTERNAL_METHOD
return false if @@required_methods.include?(method_name)
super
end
# remove all standard methods from the bucket so circumvent security
# problems
instance_methods.each do |m|
unless @@required_methods.include?(m.to_sym)
undef_method m
end end
end end
def self.strainer_class_cache
@@strainer_class_cache
end
def self.create(context, filters = [])
filters = @@filters + filters
strainer_class_cache[filters].new(context)
end
def invoke(method, *args)
if invokable?(method)
send(method, *args)
else
args.first
end
rescue ::ArgumentError => e
raise Liquid::ArgumentError.new(e.message)
end
def invokable?(method)
@@known_methods.include?(method.to_s) && respond_to?(method)
end
end end
end end

View File

@@ -1,22 +1,13 @@
module Liquid module Liquid
class Tag class Tag
attr_accessor :options
attr_reader :nodelist, :warnings
class << self attr_accessor :nodelist
def parse(tag_name, markup, tokens, options)
tag = new(tag_name, markup, options)
tag.parse(tokens)
tag
end
private :new def initialize(tag_name, markup, tokens)
end
def initialize(tag_name, markup, options)
@tag_name = tag_name @tag_name = tag_name
@markup = markup @markup = markup
@options = options parse(tokens)
end end
def parse(tokens) def parse(tokens)
@@ -27,34 +18,9 @@ module Liquid
end end
def render(context) def render(context)
''.freeze ''
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

@@ -9,29 +9,25 @@ module Liquid
# {{ foo }} # {{ foo }}
# #
class Assign < Tag class Assign < Tag
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/o
def initialize(tag_name, markup, options) def initialize(tag_name, markup, tokens)
super
if markup =~ Syntax if markup =~ Syntax
@to = $1 @to = $1
@from = Variable.new($2) @from = Variable.new($2)
else else
raise SyntaxError.new options[:locale].t("errors.syntax.assign".freeze) raise SyntaxError.new("Syntax Error in 'assign' - Valid syntax: assign [var] = [source]")
end end
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)
''.freeze
end end
def blank?
true
end
end end
Template.register_tag('assign'.freeze, Assign) Template.register_tag('assign', Assign)
end end

View File

@@ -17,5 +17,5 @@ module Liquid
end end
Template.register_tag('break'.freeze, Break) Template.register_tag('break', Break)
end end

View File

@@ -14,26 +14,22 @@ module Liquid
class Capture < Block class Capture < Block
Syntax = /(\w+)/ Syntax = /(\w+)/
def initialize(tag_name, markup, options) def initialize(tag_name, markup, tokens)
super
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
end end
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) ''
''.freeze
end
def blank?
true
end end
end end
Template.register_tag('capture'.freeze, Capture) Template.register_tag('capture', Capture)
end end

View File

@@ -1,29 +1,26 @@
module Liquid module Liquid
class Case < Block class Case < Block
Syntax = /(#{QuotedFragment})/o Syntax = /(#{QuotedFragment})/o
WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/om WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/o
def initialize(tag_name, markup, options) def initialize(tag_name, markup, tokens)
super
@blocks = [] @blocks = []
if markup =~ Syntax if markup =~ Syntax
@left = $1 @left = $1
else else
raise SyntaxError.new(options[:locale].t("errors.syntax.case".freeze)) raise SyntaxError.new("Syntax Error in tag 'case' - Valid syntax: case [condition]")
end end
end
def nodelist super
@blocks.map(&:attachment).flatten
end end
def unknown_tag(tag, markup, tokens) def unknown_tag(tag, markup, tokens)
@nodelist = [] @nodelist = []
case tag case tag
when 'when'.freeze when 'when'
record_when_condition(markup) record_when_condition(markup)
when 'else'.freeze when 'else'
record_else_condition(markup) record_else_condition(markup)
else else
super super
@@ -34,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
@@ -51,29 +48,32 @@ 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".freeze)) 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, '=='.freeze, $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".freeze)) 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'.freeze, Case) Template.register_tag('case', Case)
end end

View File

@@ -1,16 +1,9 @@
module Liquid module Liquid
class Comment < Block class Comment < Block
def render(context) def render(context)
''.freeze ''
end
def unknown_tag(tag, markup, tokens)
end
def blank?
true
end end
end end
Template.register_tag('comment'.freeze, Comment) Template.register_tag('comment', Comment)
end end

View File

@@ -1,4 +1,5 @@
module Liquid module Liquid
# Continue tag to be used to break out of a for loop. # Continue tag to be used to break out of a for loop.
# #
# == Basic Usage: # == Basic Usage:
@@ -9,10 +10,12 @@ module Liquid
# {% endfor %} # {% endfor %}
# #
class Continue < Tag class Continue < Tag
def interrupt def interrupt
ContinueInterrupt.new ContinueInterrupt.new
end end
end end
Template.register_tag('continue'.freeze, Continue) Template.register_tag('continue', Continue)
end end

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,21 +13,21 @@ 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*(.*)/om NamedSyntax = /^(#{QuotedFragment})\s*\:\s*(.*)/o
def initialize(tag_name, markup, options) def initialize(tag_name, markup, tokens)
super
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".freeze)) raise SyntaxError.new("Syntax Error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]")
end end
super
end end
def render(context) def render(context)
@@ -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

@@ -19,9 +19,10 @@ module Liquid
# Hello: -3 # Hello: -3
# #
class Decrement < Tag class Decrement < Tag
def initialize(tag_name, markup, options) def initialize(tag_name, markup, tokens)
super
@variable = markup.strip @variable = markup.strip
super
end end
def render(context) def render(context)
@@ -34,5 +35,5 @@ module Liquid
private private
end end
Template.register_tag('decrement'.freeze, Decrement) Template.register_tag('decrement', Decrement)
end end

View File

@@ -44,24 +44,29 @@ 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, options) def initialize(tag_name, markup, tokens)
super if markup =~ Syntax
parse_with_selected_parser(markup) @variable_name = $1
@nodelist = @for_block = [] @collection_name = $2
end @name = "#{$1}-#{$2}"
@idx = "#{@name}-i"
def nodelist @reversed = $3
if @else_block @attributes = {}
@for_block + @else_block markup.scan(TagAttributes) do |key, value|
@attributes[key] = value
end
else else
@for_block raise SyntaxError.new("Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]")
end end
@nodelist = @for_block = []
super
end end
def unknown_tag(tag, markup, tokens) def unknown_tag(tag, markup, tokens)
return super unless tag == 'else'.freeze return super unless tag == 'else'
@nodelist = @else_block = [] @nodelist = @else_block = []
end end
@@ -74,22 +79,22 @@ module Liquid
# Maintains Ruby 1.8.7 String#each behaviour on 1.9 # Maintains Ruby 1.8.7 String#each behaviour on 1.9
return render_else(context) unless iterable?(collection) return render_else(context) unless iterable?(collection)
from = if @attributes['offset'.freeze] == 'continue'.freeze from = if @attributes['offset'] == 'continue'
context.registers[:for][@name].to_i context.registers[:for][@name].to_i
else else
context[@attributes['offset'.freeze]].to_i context[@attributes['offset']].to_i
end end
limit = context[@attributes['limit'.freeze]] 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,18 +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'.freeze] = {
'name'.freeze => @name,
'length'.freeze => length,
'index'.freeze => index + 1,
'index0'.freeze => index,
'rindex'.freeze => length - index,
'rindex0'.freeze => length - index - 1,
'first'.freeze => (index == 0),
'last'.freeze => (index == length - 1)
}
result << render_all(@for_block, context) result << render_all(@for_block, context)
@@ -123,53 +120,49 @@ 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".freeze))
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".freeze)) unless p.id?('in'.freeze)
@collection_name = p.expression
@name = "#{@variable_name}-#{@collection_name}"
@reversed = p.id?('reversed'.freeze)
@attributes = {}
while p.look(:id) && p.look(:colon, 1)
unless attribute = p.id?('limit'.freeze) || p.id?('offset'.freeze)
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute".freeze))
end
p.consume
val = p.expression
@attributes[attribute] = val
end
p.consume(:end_of_string)
end
private private
def render_else(context) class Forloop < Liquid::Drop
return @else_block ? [render_all(@else_block, context)] : ''.freeze attr_accessor :name, :length
end
def iterable?(collection) def initialize(name, idx, length)
collection.respond_to?(:each) || Utils.non_blank_string?(collection) @name, @idx, @length = name, idx, length
end 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)
return @else_block ? [render_all(@else_block, context)] : ''
end
def iterable?(collection)
collection.respond_to?(:each) || Utils.non_blank_string?(collection)
end
end end
Template.register_tag('for'.freeze, For) Template.register_tag('for', For)
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,23 +10,22 @@ 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, options) def initialize(tag_name, markup, tokens)
super
@blocks = [] @blocks = []
push_block('if'.freeze, markup)
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)
if ['elsif'.freeze, 'else'.freeze].include?(tag) if ['elsif', 'else'].include?(tag)
push_block(tag, markup) push_block(tag, markup)
else else
super super
@@ -39,68 +39,41 @@ module Liquid
return render_all(block.attachment, context) return render_all(block.attachment, context)
end end
end end
''.freeze ''
end end
end end
private private
def push_block(tag, markup) def push_block(tag, markup)
block = if tag == 'else'.freeze 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".freeze))) 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".freeze))) unless expressions.shift.to_s =~ Syntax
new_condition = Condition.new($1, $2, $3)
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) 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'.freeze) || p.id?('or'.freeze))
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'.freeze, If) Template.register_tag('if', If)
end end

View File

@@ -10,11 +10,11 @@ module Liquid
context.registers[:ifchanged] = output context.registers[:ifchanged] = output
output output
else else
''.freeze ''
end end
end end
end end
end end
Template.register_tag('ifchanged'.freeze, Ifchanged) Template.register_tag('ifchanged', Ifchanged)
end end

View File

@@ -1,25 +1,8 @@
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
def initialize(tag_name, markup, options) def initialize(tag_name, markup, tokens)
super
if markup =~ Syntax if markup =~ Syntax
@template_name = $1 @template_name = $1
@@ -31,19 +14,18 @@ module Liquid
end end
else else
raise SyntaxError.new(options[:locale].t("errors.syntax.include".freeze)) raise SyntaxError.new("Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]")
end end
super
end end
def parse(tokens) def parse(tokens)
end end
def blank?
false
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
@@ -51,35 +33,20 @@ module Liquid
context[key] = context[value] context[key] = context[value]
end end
context_variable_name = @template_name[1..-2].split('/'.freeze).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.
@@ -94,5 +61,5 @@ module Liquid
end end
end end
Template.register_tag('include'.freeze, Include) Template.register_tag('include', Include)
end end

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:
@@ -15,9 +16,10 @@ module Liquid
# Hello: 2 # Hello: 2
# #
class Increment < Tag class Increment < Tag
def initialize(tag_name, markup, options) def initialize(tag_name, markup, tokens)
super
@variable = markup.strip @variable = markup.strip
super
end end
def render(context) def render(context)
@@ -26,10 +28,8 @@ module Liquid
value.to_s value.to_s
end end
def blank? private
false
end
end end
Template.register_tag('increment'.freeze, Increment) Template.register_tag('increment', Increment)
end end

View File

@@ -1,14 +1,12 @@
module Liquid module Liquid
class Raw < Block class Raw < Block
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
def parse(tokens) def parse(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 != "".freeze if block_delimiter == $1
if block_delimiter == $2
end_tag end_tag
return return
end end
@@ -18,5 +16,6 @@ module Liquid
end end
end end
Template.register_tag('raw'.freeze, Raw) Template.register_tag('raw', Raw)
end end

View File

@@ -1,73 +0,0 @@
module Liquid
class TableRow < Block
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
def initialize(tag_name, markup, options)
super
if markup =~ Syntax
@variable_name = $1
@collection_name = $2
@attributes = {}
markup.scan(TagAttributes) do |key, value|
@attributes[key] = value
end
else
raise SyntaxError.new(options[:locale].t("errors.syntax.table_row".freeze))
end
end
def render(context)
collection = context[@collection_name] or return ''.freeze
from = @attributes['offset'.freeze] ? context[@attributes['offset'.freeze]].to_i : 0
to = @attributes['limit'.freeze] ? from + context[@attributes['limit'.freeze]].to_i : nil
collection = Utils.slice_collection(collection, from, to)
length = collection.length
cols = context[@attributes['cols'.freeze]].to_i
row = 1
col = 0
result = "<tr class=\"row1\">\n"
context.stack do
collection.each_with_index do |item, index|
context[@variable_name] = item
context['tablerowloop'.freeze] = {
'length'.freeze => length,
'index'.freeze => index + 1,
'index0'.freeze => index,
'col'.freeze => col + 1,
'col0'.freeze => col,
'index0'.freeze => index,
'rindex'.freeze => length - index,
'rindex0'.freeze => length - index - 1,
'first'.freeze => (index == 0),
'last'.freeze => (index == length - 1),
'col_first'.freeze => (col == 0),
'col_last'.freeze => (col == cols - 1)
}
col += 1
result << "<td class=\"col#{col}\">" << render_all(@nodelist, context) << '</td>'
if col == cols and (index != length - 1)
col = 0
row += 1
result << "</tr>\n<tr class=\"row#{row}\">"
end
end
end
result << "</tr>\n"
result
end
end
Template.register_tag('tablerow'.freeze, TableRow)
end

View File

@@ -1,6 +1,7 @@
require File.dirname(__FILE__) + '/if' require File.dirname(__FILE__) + '/if'
module Liquid module Liquid
# Unless is a conditional just like 'if' but works on the inverse logic. # Unless is a conditional just like 'if' but works on the inverse logic.
# #
# {% unless x < 0 %} x is greater than zero {% end %} # {% unless x < 0 %} x is greater than zero {% end %}
@@ -10,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
@@ -22,10 +23,11 @@ module Liquid
end end
end end
''.freeze ''
end end
end end
end end
Template.register_tag('unless'.freeze, Unless)
Template.register_tag('unless', Unless)
end end

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.parse(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
@@ -110,20 +87,17 @@ module Liquid
# filters and tags and might be useful to integrate liquid more with its host application # filters and tags and might be useful to integrate liquid more with its host application
# #
def render(*args) def render(*args)
return ''.freeze if @root.nil? return '' if @root.nil?
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,20 +0,0 @@
module Liquid
class Tokenizer
VariableIncompleteEnd = /\}\}?/
AnyStartingTag = /\{\{|\{\%/
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
def initialize(source)
@tokens = source.split(TemplateParser)
# removes the rogue empty element at the beginning of the array
@tokens.shift if @tokens[0] && @tokens[0].empty?
end
def next
@tokens.shift
end
alias_method :shift, :next
end
end

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 != ''.freeze
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

@@ -11,92 +11,34 @@ module Liquid
# {{ user | link }} # {{ user | link }}
# #
class Variable class Variable
FilterParser = /(?:#{FilterSeparator}|(?:\s*(?:#{QuotedFragment}|#{ArgumentSeparator})\s*)+)/o FilterParser = /(?:#{FilterSeparator}|(?:\s*(?!(?:#{FilterSeparator}))(?:#{QuotedFragment}|\S+)\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})(.*)/om) if match = markup.match(/\s*(#{QuotedFragment})(.*)/o)
@name = match[1] @name = match[1]
if match[2].match(/#{FilterSeparator}\s*(.*)/om) 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+)/)
filtername = matches[1] filtername = matches[1]
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*(#{QuotedFragment})/o).flatten
@filters << [filtername, filterargs] @filters << [filtername.to_sym, filterargs]
end end
end end
end end
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) ? ''.freeze : 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 ''.freeze if @name.nil? return '' if @name.nil?
@filters.inject(context[@name]) do |output, filter| @filters.inject(context[@name]) do |output, filter|
filterargs = [] filterargs = filter[1].to_a.collect do |a|
keyword_args = {} context[a]
filter[1].to_a.each do |a|
if matches = a.match(/\A#{TagAttributes}\z/o)
keyword_args[matches[1]] = context[matches[2]]
else
filterargs << context[a]
end
end end
filterargs << keyword_args unless keyword_args.empty?
begin begin
output = context.invoke(filter[0], output, *filterargs) output = context.invoke(filter[0], output, *filterargs)
rescue FilterNotFound rescue FilterNotFound

View File

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

View File

@@ -1,34 +1,21 @@
# 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.4.1"
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}/**/*") + %w(MIT-LICENSE README.md)
s.extra_rdoc_files = ["History.md", "README.md"] s.extra_rdoc_files = ["History.md", "README.md"]
s.require_path = "lib" s.require_path = "lib"
s.add_development_dependency 'rake'
s.add_development_dependency 'activesupport'
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
s.extensions = ['ext/liquid/extconf.rb']
s.add_development_dependency 'rake-compiler'
s.add_development_dependency 'stackprof' if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.1.0")
end
end end

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

@@ -1,13 +1,17 @@
require 'stackprof' rescue fail("install stackprof extension/gem") require 'rubygems'
require 'ruby-prof' rescue fail("install ruby-prof extension/gem")
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
profiler.run
results = StackProf.run(mode: :cpu) do puts 'Running profiler...'
100.times do
profiler.run results = profiler.run_profile
end
puts 'Success'
filename = (ENV['TMP'] || '/tmp') + "/callgrind.liquid.txt"
File.open(filename, "w+") do |fp|
RubyProf::CallTreePrinter.new(results).print(fp, :print_file => true)
end end
StackProf::Report.new(results).print_text(false, 20) $stderr.puts "wrote RubyProf::CallTreePrinter output to #{filename}"
File.write(ENV['FILENAME'], Marshal.dump(results)) if ENV['FILENAME']

View File

@@ -1,15 +1,15 @@
class CommentForm < Liquid::Block class CommentForm < Liquid::Block
Syntax = /(#{Liquid::VariableSignature}+)/ Syntax = /(#{Liquid::VariableSignature}+)/
def initialize(tag_name, markup, options) def initialize(tag_name, markup, tokens)
super
if markup =~ Syntax if markup =~ Syntax
@variable_name = $1 @variable_name = $1
@attributes = {} @attributes = {}
else else
raise SyntaxError.new("Syntax Error in 'comment_form' - Valid syntax: comment_form [article]") raise SyntaxError.new("Syntax Error in 'comment_form' - Valid syntax: comment_form [article]")
end end
super
end end
def render(context) def render(context)

View File

@@ -1,9 +1,7 @@
class Paginate < Liquid::Block class Paginate < Liquid::Block
Syntax = /(#{Liquid::QuotedFragment})\s*(by\s*(\d+))?/ Syntax = /(#{Liquid::QuotedFragment})\s*(by\s*(\d+))?/
def initialize(tag_name, markup, options) def initialize(tag_name, markup, tokens)
super
@nodelist = [] @nodelist = []
if markup =~ Syntax if markup =~ Syntax
@@ -21,6 +19,8 @@ class Paginate < Liquid::Block
else else
raise SyntaxError.new("Syntax Error in tag 'paginate' - Valid syntax: paginate [collection] by number") raise SyntaxError.new("Syntax Error in tag 'paginate' - Valid syntax: paginate [collection] by number")
end end
super
end end
def render(context) def render(context)

View File

@@ -45,16 +45,16 @@ module ShopFilter
end end
def url_for_vendor(vendor_title) def url_for_vendor(vendor_title)
"/collections/#{to_handle(vendor_title)}" "/collections/#{vendor_title.to_handle}"
end end
def url_for_type(type_title) def url_for_type(type_title)
"/collections/#{to_handle(type_title)}" "/collections/#{type_title.to_handle}"
end end
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
@@ -95,16 +95,4 @@ module ShopFilter
input == 1 ? singular : plural input == 1 ? singular : plural
end end
private
def to_handle(str)
result = str.dup
result.downcase!
result.delete!("'\"()[]")
result.gsub!(/\W+/, '-')
result.gsub!(/-+\z/, '') if result[-1] == '-'
result.gsub!(/\A-+/, '') if result[0] == '-'
result
end
end 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

@@ -8,24 +8,12 @@
require 'rubygems' require 'rubygems'
require 'active_support' require 'active_support'
require 'active_support/json'
require 'yaml' require 'yaml'
require 'digest/md5' require 'digest/md5'
require File.dirname(__FILE__) + '/shopify/liquid' require File.dirname(__FILE__) + '/shopify/liquid'
require File.dirname(__FILE__) + '/shopify/database.rb' require File.dirname(__FILE__) + '/shopify/database.rb'
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.
@@ -43,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
@@ -59,23 +46,57 @@ 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 compile_and_render(template, layout, assigns, page_template, template_file) def run_profile
RubyProf.measure_mode = RubyProf::PROCESS_TIME
# Dup assigns because will make some changes to them
assigns = Database.tables.dup
@tests.each do |liquid, layout, template_name|
# Compute page_tempalte outside of profiler run, uninteresting to profiler
html = nil
page_template = File.basename(template_name, File.extname(template_name))
unless @started
RubyProf.start
RubyProf.pause
@started = true
end
html = nil
RubyProf.resume
html = compile_and_render(liquid, layout, assigns, page_template)
RubyProf.pause
# return the result and the MD5 of the content, this can be used to detect regressions between liquid version
$stdout.puts "* rendered template %s, content: %s" % [template_name, Digest::MD5.hexdigest(html)]
# Uncomment to dump html files to /tmp so that you can inspect for errors
# File.open("/tmp/#{File.basename(template_name)}.html", "w+") { |fp| fp <<html}
end
RubyProf.stop
end
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)
if layout if layout
assigns['content_for_layout'] = content_for_layout assigns['content_for_layout'] = content_for_layout
tmpl.parse(layout).render!(assigns) tmpl.parse(layout).render(assigns)
else else
content_for_layout content_for_layout
end end

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_template_result "foobar"*(N+1), wrap("{% include 'foobar' %}")
assert_template_result " foobar "*(N+1), wrap("{% include ' foobar ' %}")
assert_template_result " "*(N+1), wrap(" {% include ' ' %} ")
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

@@ -19,7 +19,7 @@ class CaptureTest < Test::Unit::TestCase
{{var}} {{var}}
END_TEMPLATE END_TEMPLATE
template = Template.parse(template_source) template = Template.parse(template_source)
rendered = template.render! rendered = template.render
assert_equal "test-string", rendered.gsub(/\s/, '') assert_equal "test-string", rendered.gsub(/\s/, '')
end end
@@ -34,7 +34,7 @@ class CaptureTest < Test::Unit::TestCase
{{ first }}-{{ second }} {{ first }}-{{ second }}
END_TEMPLATE END_TEMPLATE
template = Template.parse(template_source) template = Template.parse(template_source)
rendered = template.render! rendered = template.render
assert_equal "3-3", rendered.gsub(/\s/, '') assert_equal "3-3", rendered.gsub(/\s/, '')
end end
end # CaptureTest end # CaptureTest

View File

@@ -176,8 +176,8 @@ class ContextTest < Test::Unit::TestCase
end end
Template.register_filter(global) Template.register_filter(global)
assert_equal 'Global test', Template.parse("{{'test' | notice }}").render! assert_equal 'Global test', Template.parse("{{'test' | notice }}").render
assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, :filters => [local]) assert_equal 'Local test', Template.parse("{{'test' | notice }}").render({}, :filters => [local])
end end
def test_only_intended_filters_make_it_there def test_only_intended_filters_make_it_there
@@ -189,10 +189,10 @@ class ContextTest < Test::Unit::TestCase
end end
context = Context.new context = Context.new
assert_equal "Wookie", context.invoke("hi", "Wookie") methods_before = context.strainer.methods.map { |method| method.to_s }
context.add_filters(filter) context.add_filters(filter)
assert_equal "Wookie hi!", context.invoke("hi", "Wookie") methods_after = context.strainer.methods.map { |method| method.to_s }
assert_equal (methods_before + ["hi"]).sort, methods_after.sort
end end
def test_add_item_in_outer_scope def test_add_item_in_outer_scope
@@ -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,142 +71,92 @@ 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
output = Liquid::Template.parse( ' {{ product.catchall[8] }} ' ).render!('product' => ProductDrop.new) output = Liquid::Template.parse( ' {{ product.catchall[8] }} ' ).render('product' => ProductDrop.new)
assert_equal ' method: 8 ', output assert_equal ' method: 8 ', output
end end
def test_text_array_drop def test_text_array_drop
output = Liquid::Template.parse( '{% for text in product.texts.array %} {{text}} {% endfor %}' ).render!('product' => ProductDrop.new) output = Liquid::Template.parse( '{% for text in product.texts.array %} {{text}} {% endfor %}' ).render('product' => ProductDrop.new)
assert_equal ' text1 text2 ', output assert_equal ' text1 text2 ', output
end end
def test_context_drop def test_context_drop
output = Liquid::Template.parse( ' {{ context.bar }} ' ).render!('context' => ContextDrop.new, 'bar' => "carrot") output = Liquid::Template.parse( ' {{ context.bar }} ' ).render('context' => ContextDrop.new, 'bar' => "carrot")
assert_equal ' carrot ', output assert_equal ' carrot ', output
end end
def test_nested_context_drop def test_nested_context_drop
output = Liquid::Template.parse( ' {{ product.context.foo }} ' ).render!('product' => ProductDrop.new, 'foo' => "monkey") output = Liquid::Template.parse( ' {{ product.context.foo }} ' ).render('product' => ProductDrop.new, 'foo' => "monkey")
assert_equal ' monkey ', output assert_equal ' monkey ', output
end end
def test_protected def test_protected
output = Liquid::Template.parse( ' {{ product.callmenot }} ' ).render!('product' => ProductDrop.new) output = Liquid::Template.parse( ' {{ product.callmenot }} ' ).render('product' => ProductDrop.new)
assert_equal ' ', output assert_equal ' ', output
end end
def test_object_methods_not_allowed
[:dup, :clone, :singleton_class, :eval, :class_eval, :inspect].each do |method|
output = Liquid::Template.parse(" {{ product.#{method} }} ").render!('product' => ProductDrop.new)
assert_equal ' ', output
end
end
def test_scope def test_scope
assert_equal '1', Liquid::Template.parse( '{{ context.scopes }}' ).render!('context' => ContextDrop.new) assert_equal '1', Liquid::Template.parse( '{{ context.scopes }}' ).render('context' => ContextDrop.new)
assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ context.scopes }}{%endfor%}' ).render!('context' => ContextDrop.new, 'dummy' => [1]) assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ context.scopes }}{%endfor%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}' ).render!('context' => ContextDrop.new, 'dummy' => [1]) assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
end end
def test_scope_though_proc def test_scope_though_proc
assert_equal '1', Liquid::Template.parse( '{{ s }}' ).render!('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }) assert_equal '1', Liquid::Template.parse( '{{ s }}' ).render('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] })
assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ s }}{%endfor%}' ).render!('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1]) assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ s }}{%endfor%}' ).render('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1])
assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}' ).render!('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1]) assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}' ).render('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1])
end end
def test_scope_with_assigns def test_scope_with_assigns
assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{{a}}' ).render!('context' => ContextDrop.new) assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{{a}}' ).render('context' => ContextDrop.new)
assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}' ).render!('context' => ContextDrop.new, 'dummy' => [1]) assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
assert_equal 'test', Liquid::Template.parse( '{% assign header_gif = "test"%}{{header_gif}}' ).render!('context' => ContextDrop.new) assert_equal 'test', Liquid::Template.parse( '{% assign header_gif = "test"%}{{header_gif}}' ).render('context' => ContextDrop.new)
assert_equal 'test', Liquid::Template.parse( "{% assign header_gif = 'test'%}{{header_gif}}" ).render!('context' => ContextDrop.new) assert_equal 'test', Liquid::Template.parse( "{% assign header_gif = 'test'%}{{header_gif}}" ).render('context' => ContextDrop.new)
end end
def test_scope_from_tags def test_scope_from_tags
assert_equal '1', Liquid::Template.parse( '{% for i in context.scopes_as_array %}{{i}}{% endfor %}' ).render!('context' => ContextDrop.new, 'dummy' => [1]) assert_equal '1', Liquid::Template.parse( '{% for i in context.scopes_as_array %}{{i}}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '12', Liquid::Template.parse( '{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}' ).render!('context' => ContextDrop.new, 'dummy' => [1]) assert_equal '12', Liquid::Template.parse( '{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}' ).render!('context' => ContextDrop.new, 'dummy' => [1]) assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1])
end end
def test_access_context_from_drop def test_access_context_from_drop
assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{{ context.loop_pos }}{% endfor %}' ).render!('context' => ContextDrop.new, 'dummy' => [1,2,3]) assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{{ context.loop_pos }}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1,2,3])
end end
def test_enumerable_drop def test_enumerable_drop
assert_equal '123', Liquid::Template.parse( '{% for c in collection %}{{c}}{% endfor %}').render!('collection' => EnumerableDrop.new) assert_equal '123', Liquid::Template.parse( '{% for c in collection %}{{c}}{% endfor %}').render('collection' => EnumerableDrop.new)
end end
def test_enumerable_drop_size def test_enumerable_drop_size
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
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 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
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
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
end # DropsTest end # DropsTest

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