mirror of
https://github.com/kemko/liquid.git
synced 2026-01-06 02:05:41 +03:00
Compare commits
206 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8b41c8856 | ||
|
|
4c22bacbba | ||
|
|
09a5b57ebe | ||
|
|
8059da4938 | ||
|
|
af50f71224 | ||
|
|
136b6763e6 | ||
|
|
ad184fbfc9 | ||
|
|
380828f807 | ||
|
|
fc8c45ebe6 | ||
|
|
072c12dc47 | ||
|
|
29cdabc30e | ||
|
|
df5980f23f | ||
|
|
5ee4f960e8 | ||
|
|
0343f6dc94 | ||
|
|
40fba9ee6c | ||
|
|
0a2f21386d | ||
|
|
e7bcf04d1d | ||
|
|
0dac6fe88a | ||
|
|
f37a984fd7 | ||
|
|
0e41c2c6e9 | ||
|
|
7b52dfcb95 | ||
|
|
1fa029ab67 | ||
|
|
26eb9a0817 | ||
|
|
e305edc3b8 | ||
|
|
c94b5e87c9 | ||
|
|
dd3196b22e | ||
|
|
86ba2f4174 | ||
|
|
5bdfb62bf2 | ||
|
|
77db92de54 | ||
|
|
b0cba5298a | ||
|
|
93fcd5687c | ||
|
|
14a17520de | ||
|
|
0beb4a4793 | ||
|
|
324d26d405 | ||
|
|
047900d0dd | ||
|
|
f6f89fd0aa | ||
|
|
a57d576708 | ||
|
|
eb68a751ac | ||
|
|
355199dac4 | ||
|
|
c8f38ad9d0 | ||
|
|
ed4b61bfd3 | ||
|
|
8f978ecd1a | ||
|
|
98c184f2fb | ||
|
|
615e48fe29 | ||
|
|
6cde98319f | ||
|
|
15b53b77d6 | ||
|
|
48f50eea3b | ||
|
|
ace12e29da | ||
|
|
f98949117d | ||
|
|
7fdb789eac | ||
|
|
c92efd3ab9 | ||
|
|
ff570c3ddc | ||
|
|
824231284c | ||
|
|
ee2902761c | ||
|
|
f6eacbf875 | ||
|
|
c5afdc529a | ||
|
|
84f0c1bef8 | ||
|
|
1458396733 | ||
|
|
346e92aaa6 | ||
|
|
3b3961be39 | ||
|
|
8ca00982b6 | ||
|
|
525e1ff195 | ||
|
|
8f4b398c7a | ||
|
|
d5d41a8202 | ||
|
|
c8bd0b91b3 | ||
|
|
bc76c0daaf | ||
|
|
be4a04ed85 | ||
|
|
8dcf44e99d | ||
|
|
a892e69a88 | ||
|
|
bf53e517f5 | ||
|
|
bacacf2fd0 | ||
|
|
1b43bf5686 | ||
|
|
83e71ace99 | ||
|
|
4dc9cc0ea1 | ||
|
|
87b8ee7341 | ||
|
|
07f7d63bea | ||
|
|
1af28a6eb8 | ||
|
|
65dfd57bb5 | ||
|
|
8b1dff9d98 | ||
|
|
8896b55fa5 | ||
|
|
c0b9d53548 | ||
|
|
24ddaf1a9c | ||
|
|
673826630c | ||
|
|
554675d1f8 | ||
|
|
11e1379570 | ||
|
|
3e13ed4ba1 | ||
|
|
b004acf856 | ||
|
|
182d3fefb6 | ||
|
|
17d818b453 | ||
|
|
0453d7e299 | ||
|
|
4da7b36139 | ||
|
|
c7336e0cc1 | ||
|
|
f43e973e67 | ||
|
|
bbc405a24c | ||
|
|
f9027d54ab | ||
|
|
84be895db2 | ||
|
|
b20a594f25 | ||
|
|
76272a1afa | ||
|
|
61a6deb43b | ||
|
|
ee14775f83 | ||
|
|
2332d86156 | ||
|
|
fbfda1a189 | ||
|
|
f0ecd02199 | ||
|
|
4a103a9dde | ||
|
|
0f38fe3596 | ||
|
|
cd3f976288 | ||
|
|
b53601100f | ||
|
|
5c5e7de31e | ||
|
|
f91233450f | ||
|
|
429e492984 | ||
|
|
d36a1c518b | ||
|
|
40a37c3fb6 | ||
|
|
37309678de | ||
|
|
cfb60c2f1b | ||
|
|
668ee5e1c4 | ||
|
|
b4fbcea114 | ||
|
|
c16697746b | ||
|
|
f01d0dbea6 | ||
|
|
10c151e3aa | ||
|
|
d6e13faa43 | ||
|
|
0f5441b09e | ||
|
|
7a3746ad77 | ||
|
|
24511556d3 | ||
|
|
5621556b3a | ||
|
|
fd230bef14 | ||
|
|
e9f3a8e4d3 | ||
|
|
8ca4868bff | ||
|
|
f92da6948d | ||
|
|
a1b156f0d4 | ||
|
|
eca520025c | ||
|
|
9c9a7ce8d3 | ||
|
|
b7837ce218 | ||
|
|
b81469d183 | ||
|
|
f488058789 | ||
|
|
f5d75718e9 | ||
|
|
c280936afa | ||
|
|
e47d1af03a | ||
|
|
81f6c79c53 | ||
|
|
3174411407 | ||
|
|
f7c4b0cdec | ||
|
|
3d1f582318 | ||
|
|
27db1def63 | ||
|
|
5cfa13d7a4 | ||
|
|
cb12497859 | ||
|
|
4c2e2f8a24 | ||
|
|
a2df5a421d | ||
|
|
437e9201de | ||
|
|
fd263bba0f | ||
|
|
0d9353591b | ||
|
|
1849c24f2c | ||
|
|
f8288546f8 | ||
|
|
076ae903c0 | ||
|
|
ba5e65f685 | ||
|
|
b699c93bae | ||
|
|
85c1503378 | ||
|
|
ab760649ee | ||
|
|
f4fb2f159c | ||
|
|
a5cd494717 | ||
|
|
f8915f3cd2 | ||
|
|
e92540a9bf | ||
|
|
482115d784 | ||
|
|
94ff457744 | ||
|
|
1e8c081b42 | ||
|
|
2b17e24b16 | ||
|
|
9075b428b1 | ||
|
|
8760b5e8c4 | ||
|
|
50b2ebee56 | ||
|
|
23203c0122 | ||
|
|
27fe76c0dd | ||
|
|
8913a5615a | ||
|
|
690b3ff27f | ||
|
|
8c1bbfec57 | ||
|
|
b8fbd2b4fa | ||
|
|
ba5a9f2e47 | ||
|
|
1e309ba74b | ||
|
|
485340713a | ||
|
|
2af4ea1295 | ||
|
|
c5dfcd29b0 | ||
|
|
f9c289372d | ||
|
|
a556ae6c26 | ||
|
|
f7d1e1d0c1 | ||
|
|
28fd2222c8 | ||
|
|
1cac09831d | ||
|
|
9913895b81 | ||
|
|
d706db3bd7 | ||
|
|
17dd85868d | ||
|
|
6e967f7f3a | ||
|
|
38b4543bf1 | ||
|
|
1300210f05 | ||
|
|
a48e162237 | ||
|
|
7bcb565668 | ||
|
|
c3e6cde67f | ||
|
|
0b36540b78 | ||
|
|
50bd34fd78 | ||
|
|
ee41b3f4a3 | ||
|
|
b48ad7da3a | ||
|
|
afc3944a4a | ||
|
|
c79abf1f87 | ||
|
|
90b40ffb4b | ||
|
|
fea9c54768 | ||
|
|
05d9976e16 | ||
|
|
740cd6e762 | ||
|
|
5d0004a87e | ||
|
|
12112dee35 | ||
|
|
5c4938f443 | ||
|
|
4a2bbafeb4 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@
|
|||||||
pkg
|
pkg
|
||||||
*.rbc
|
*.rbc
|
||||||
.rvmrc
|
.rvmrc
|
||||||
|
.ruby-version
|
||||||
|
|||||||
26
CONTRIBUTING.md
Normal file
26
CONTRIBUTING.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
41
History.md
41
History.md
@@ -1,5 +1,46 @@
|
|||||||
# Liquid Version History
|
# Liquid Version History
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 2.6.0 / Master branch (not yet released)
|
||||||
|
|
||||||
|
* ...
|
||||||
|
* 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]
|
||||||
|
* Fix security issue with map filter, see #230, #232, #234, #237 [Florian Weingarten, fw42]
|
||||||
|
* Improved whitespace stripping for blank blocks, related to #216 [Florian Weingarten, fw42]
|
||||||
|
* 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]
|
||||||
|
* 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.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
|
||||||
|
|||||||
31
README.md
31
README.md
@@ -1,5 +1,12 @@
|
|||||||
# 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 from Shopify](http://wiki.shopify.com/Liquid)
|
||||||
|
* [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:
|
||||||
@@ -41,4 +48,28 @@ 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
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
[](http://travis-ci.org/Shopify/liquid)
|
[](http://travis-ci.org/Shopify/liquid)
|
||||||
|
|||||||
26
Rakefile
26
Rakefile
@@ -7,12 +7,28 @@ require 'rubygems/package_task'
|
|||||||
|
|
||||||
task :default => 'test'
|
task :default => 'test'
|
||||||
|
|
||||||
Rake::TestTask.new(:test) do |t|
|
desc 'run test suite with default parser'
|
||||||
|
Rake::TestTask.new(:base_test) do |t|
|
||||||
t.libs << '.' << 'lib' << 'test'
|
t.libs << '.' << 'lib' << 'test'
|
||||||
t.test_files = FileList['test/liquid/**/*_test.rb']
|
t.test_files = FileList['test/liquid/**/*_test.rb']
|
||||||
t.verbose = false
|
t.verbose = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
desc 'run test suite with warn error mode'
|
||||||
|
task :warn_test do
|
||||||
|
ENV['LIQUID_PARSER_MODE'] = 'warn'
|
||||||
|
Rake::Task['base_test'].invoke
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'runs test suite with both strict and lax parsers'
|
||||||
|
task :test do
|
||||||
|
ENV['LIQUID_PARSER_MODE'] = 'lax'
|
||||||
|
Rake::Task['base_test'].invoke
|
||||||
|
ENV['LIQUID_PARSER_MODE'] = 'strict'
|
||||||
|
Rake::Task['base_test'].reenable
|
||||||
|
Rake::Task['base_test'].invoke
|
||||||
|
end
|
||||||
|
|
||||||
gemspec = eval(File.read('liquid.gemspec'))
|
gemspec = eval(File.read('liquid.gemspec'))
|
||||||
Gem::PackageTask.new(gemspec) do |pkg|
|
Gem::PackageTask.new(gemspec) do |pkg|
|
||||||
pkg.gem_spec = gemspec
|
pkg.gem_spec = gemspec
|
||||||
@@ -25,11 +41,15 @@ end
|
|||||||
|
|
||||||
namespace :benchmark do
|
namespace :benchmark do
|
||||||
|
|
||||||
desc "Run the liquid benchmark"
|
desc "Run the liquid benchmark with lax parsing"
|
||||||
task :run do
|
task :run do
|
||||||
ruby "./performance/benchmark.rb"
|
ruby "./performance/benchmark.rb lax"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
desc "Run the liquid benchmark with strict parsing"
|
||||||
|
task :strict do
|
||||||
|
ruby "./performance/benchmark.rb strict"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,11 +23,7 @@ class Servlet < LiquidServlet
|
|||||||
end
|
end
|
||||||
|
|
||||||
def products
|
def products
|
||||||
{ 'products' => products_list, 'section' => 'Snowboards', 'cool_products' => true}
|
{ 'products' => products_list, 'description' => description, '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
|
||||||
@@ -38,4 +34,8 @@ 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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -45,6 +45,10 @@ module Liquid
|
|||||||
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'
|
||||||
|
|||||||
@@ -1,22 +1,28 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
class Block < Tag
|
class Block < Tag
|
||||||
IsTag = /^#{TagStart}/o
|
IsTag = /^#{TagStart}/o
|
||||||
IsVariable = /^#{VariableStart}/o
|
IsVariable = /^#{VariableStart}/o
|
||||||
FullToken = /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/o
|
FullToken = /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/o
|
||||||
ContentOfVariable = /^#{VariableStart}(.*)#{VariableEnd}$/o
|
ContentOfVariable = /^#{VariableStart}(.*)#{VariableEnd}$/o
|
||||||
|
|
||||||
|
def blank?
|
||||||
|
@blank || false
|
||||||
|
end
|
||||||
|
|
||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
|
@blank = true
|
||||||
@nodelist ||= []
|
@nodelist ||= []
|
||||||
@nodelist.clear
|
@nodelist.clear
|
||||||
|
|
||||||
while token = tokens.shift
|
# All child tags of the current block.
|
||||||
|
@children = []
|
||||||
|
|
||||||
|
while token = tokens.shift
|
||||||
case token
|
case token
|
||||||
when IsTag
|
when IsTag
|
||||||
if token =~ FullToken
|
if token =~ FullToken
|
||||||
|
|
||||||
# if we found the proper block delimitor just end parsing here and let the outer block
|
# if we found the proper block delimiter just end parsing here and let the outer block
|
||||||
# proceed
|
# proceed
|
||||||
if block_delimiter == $1
|
if block_delimiter == $1
|
||||||
end_tag
|
end_tag
|
||||||
@@ -25,41 +31,63 @@ module Liquid
|
|||||||
|
|
||||||
# fetch the tag from registered blocks
|
# fetch the tag from registered blocks
|
||||||
if tag = Template.tags[$1]
|
if tag = Template.tags[$1]
|
||||||
@nodelist << tag.new($1, $2, tokens)
|
new_tag = tag.new_with_options($1, $2, tokens, @options || {})
|
||||||
|
@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, "Tag '#{token}' was not properly terminated with regexp: #{TagEnd.inspect} "
|
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination", :token => token, :tag_end => TagEnd.inspect))
|
||||||
end
|
end
|
||||||
when IsVariable
|
when IsVariable
|
||||||
@nodelist << create_variable(token)
|
new_var = create_variable(token)
|
||||||
|
@nodelist << new_var
|
||||||
|
@children << new_var
|
||||||
|
@blank = false
|
||||||
when ''
|
when ''
|
||||||
# pass
|
# pass
|
||||||
else
|
else
|
||||||
@nodelist << token
|
@nodelist << token
|
||||||
|
@blank &&= (token =~ /\A\s*\z/)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Make sure that its ok to end parsing in the current block.
|
# Make sure that it's ok to end parsing in the current block.
|
||||||
# Effectively this method will throw and exception unless the current block is
|
# Effectively this method will throw an 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'
|
when 'else'
|
||||||
raise SyntaxError, "#{block_name} tag does not expect else tag"
|
raise SyntaxError.new(options[:locale].t("errors.syntax.unexpected_else",
|
||||||
|
:block_name => block_name))
|
||||||
when 'end'
|
when 'end'
|
||||||
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",
|
||||||
|
:block_name => block_name,
|
||||||
|
:block_delimiter => block_delimiter))
|
||||||
else
|
else
|
||||||
raise SyntaxError, "Unknown tag '#{tag}'"
|
raise SyntaxError.new(options[:locale].t("errors.syntax.unknown_tag", :tag => tag))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -73,9 +101,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)
|
return Variable.new(content.first, @options)
|
||||||
end
|
end
|
||||||
raise SyntaxError.new("Variable '#{token}' was not properly terminated with regexp: #{VariableEnd.inspect} ")
|
raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination", :token => token, :tag_end => VariableEnd.inspect))
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
@@ -85,11 +113,14 @@ module Liquid
|
|||||||
protected
|
protected
|
||||||
|
|
||||||
def assert_missing_delimitation!
|
def assert_missing_delimitation!
|
||||||
raise SyntaxError.new("#{block_name} tag was never closed")
|
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_never_closed", :block_name => block_name))
|
||||||
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?
|
||||||
@@ -103,7 +134,17 @@ module Liquid
|
|||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
output << (token.respond_to?(:render) ? token.render(context) : token)
|
token_output = (token.respond_to?(:render) ? token.render(context) : token)
|
||||||
|
context.resource_limits[:render_length_current] += (token_output.respond_to?(:length) ? token_output.length : 1)
|
||||||
|
if context.resource_limits_reached?
|
||||||
|
context.resource_limits[:reached] = true
|
||||||
|
raise MemoryError.new("Memory limits exceeded")
|
||||||
|
end
|
||||||
|
unless token.is_a?(Block) && token.blank?
|
||||||
|
output << token_output
|
||||||
|
end
|
||||||
|
rescue MemoryError => e
|
||||||
|
raise e
|
||||||
rescue ::StandardError => e
|
rescue ::StandardError => e
|
||||||
output << (context.handle_error(e))
|
output << (context.handle_error(e))
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -13,19 +13,26 @@ module Liquid
|
|||||||
#
|
#
|
||||||
# context['bob'] #=> nil class Context
|
# context['bob'] #=> nil class Context
|
||||||
class Context
|
class Context
|
||||||
attr_reader :scopes, :errors, :registers, :environments
|
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
|
||||||
|
|
||||||
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false)
|
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = {})
|
||||||
@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 = []
|
||||||
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
|
||||||
|
|
||||||
def strainer
|
def strainer
|
||||||
@strainer ||= Strainer.create(self)
|
@strainer ||= Strainer.create(self)
|
||||||
end
|
end
|
||||||
@@ -39,13 +46,14 @@ module Liquid
|
|||||||
|
|
||||||
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)
|
strainer.extend(f)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# are there any not handled interrupts?
|
# are there any not handled interrupts?
|
||||||
def has_interrupt?
|
def has_interrupt?
|
||||||
!@interrupts.empty?
|
@interrupts.any?
|
||||||
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.
|
||||||
@@ -71,11 +79,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def invoke(method, *args)
|
def invoke(method, *args)
|
||||||
if strainer.respond_to?(method)
|
strainer.invoke(method, *args)
|
||||||
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
|
||||||
@@ -168,6 +172,7 @@ module Liquid
|
|||||||
# 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|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Document < Block
|
class Document < Block
|
||||||
# we don't need markup to open this block
|
# we don't need markup to open this block
|
||||||
def initialize(tokens)
|
def initialize(tokens, options = {})
|
||||||
|
@options = options
|
||||||
parse(tokens)
|
parse(tokens)
|
||||||
end
|
end
|
||||||
|
|
||||||
# There isn't a real delimter
|
# There isn't a real delimiter
|
||||||
def block_delimiter
|
def block_delimiter
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
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.
|
||||||
@@ -31,8 +33,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.public_method_defined?(method_or_key.to_s.to_sym)
|
if method_or_key && method_or_key != EMPTY_STRING && self.class.invokable?(method_or_key)
|
||||||
send(method_or_key.to_s.to_sym)
|
send(method_or_key)
|
||||||
else
|
else
|
||||||
before_method(method_or_key)
|
before_method(method_or_key)
|
||||||
end
|
end
|
||||||
@@ -42,10 +44,31 @@ module Liquid
|
|||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def inspect
|
||||||
|
self.class.to_s
|
||||||
|
end
|
||||||
|
|
||||||
def to_liquid
|
def to_liquid
|
||||||
self
|
self
|
||||||
end
|
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
|
||||||
|
# Ruby 1.8 compatibility: call to_s on method names (which are strings in 1.8, but already symbols in 1.9)
|
||||||
|
blacklist = (Liquid::Drop.public_instance_methods + [:each]).map(&:to_s)
|
||||||
|
if include?(Enumerable)
|
||||||
|
blacklist += Enumerable.public_instance_methods.map(&:to_s)
|
||||||
|
blacklist -= [:sort, :count, :first, :min, :max, :include?].map(&:to_s)
|
||||||
|
end
|
||||||
|
whitelist = [:to_liquid] + (public_instance_methods.map(&:to_s) - blacklist.map(&:to_s))
|
||||||
|
@invokable_methods = Set.new(whitelist.map(&:to_s))
|
||||||
|
end
|
||||||
|
@invokable_methods.include?(method_name.to_s)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ 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
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
# A Liquid file system is way to let your templates retrieve other templates for use with the include tag.
|
# A Liquid file system is a way to let your templates retrieve other templates for use with the include tag.
|
||||||
#
|
#
|
||||||
# You can implement subclasses that retrieve templates from the database, from the file system using a different
|
# 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.
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ module Liquid
|
|||||||
@attributes[key] = value
|
@attributes[key] = value
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
raise SyntaxError.new("Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3")
|
raise SyntaxError.new(options[:locale].t("errors.syntax.table_row"))
|
||||||
end
|
end
|
||||||
|
|
||||||
super
|
super
|
||||||
@@ -57,7 +57,7 @@ module Liquid
|
|||||||
|
|
||||||
result << "<td class=\"col#{col}\">" << render_all(@nodelist, context) << '</td>'
|
result << "<td class=\"col#{col}\">" << render_all(@nodelist, context) << '</td>'
|
||||||
|
|
||||||
if col == cols and not (index == length - 1)
|
if col == cols and (index != length - 1)
|
||||||
col = 0
|
col = 0
|
||||||
row += 1
|
row += 1
|
||||||
result << "</tr>\n<tr class=\"row#{row}\">"
|
result << "</tr>\n<tr class=\"row#{row}\">"
|
||||||
|
|||||||
39
lib/liquid/i18n.rb
Normal file
39
lib/liquid/i18n.rb
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
require 'yaml'
|
||||||
|
|
||||||
|
module Liquid
|
||||||
|
class I18n
|
||||||
|
DEFAULT_LOCALE = File.join(File.expand_path(File.dirname(__FILE__)), "locales", "en.yml")
|
||||||
|
|
||||||
|
class TranslationError < StandardError
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :path
|
||||||
|
|
||||||
|
def initialize(path = DEFAULT_LOCALE)
|
||||||
|
@path = path
|
||||||
|
end
|
||||||
|
|
||||||
|
def translate(name, vars = {})
|
||||||
|
interpolate(deep_fetch_translation(name), vars)
|
||||||
|
end
|
||||||
|
alias_method :t, :translate
|
||||||
|
|
||||||
|
def locale
|
||||||
|
@locale ||= YAML.load_file(@path)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def interpolate(name, vars)
|
||||||
|
name.gsub(/%{(\w+)}/) {
|
||||||
|
# raise TranslationError, "Undefined key #{$1} for interpolation in translation #{name}" unless vars[$1.to_sym]
|
||||||
|
"#{vars[$1.to_sym]}"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def deep_fetch_translation(name)
|
||||||
|
name.split('.').reduce(locale) do |level, cur|
|
||||||
|
level[cur] or raise TranslationError, "Translation for #{name} does not exist in locale #{path}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
49
lib/liquid/lexer.rb
Normal file
49
lib/liquid/lexer.rb
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
require "strscan"
|
||||||
|
module Liquid
|
||||||
|
class Lexer
|
||||||
|
SPECIALS = {
|
||||||
|
'|' => :pipe,
|
||||||
|
'.' => :dot,
|
||||||
|
':' => :colon,
|
||||||
|
',' => :comma,
|
||||||
|
'[' => :open_square,
|
||||||
|
']' => :close_square,
|
||||||
|
'(' => :open_round,
|
||||||
|
')' => :close_round
|
||||||
|
}
|
||||||
|
IDENTIFIER = /[\w\-?!]+/
|
||||||
|
SINGLE_STRING_LITERAL = /'[^\']*'/
|
||||||
|
DOUBLE_STRING_LITERAL = /"[^\"]*"/
|
||||||
|
NUMBER_LITERAL = /-?\d+(\.\d+)?/
|
||||||
|
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]
|
||||||
|
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
|
||||||
22
lib/liquid/locales/en.yml
Normal file
22
lib/liquid/locales/en.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
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"
|
||||||
@@ -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 usesd in order to expose the object of the implementing class
|
# This extension is used 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:
|
||||||
|
|||||||
90
lib/liquid/parser.rb
Normal file
90
lib/liquid/parser.rb
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
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(:dot)
|
||||||
|
consume(:dot)
|
||||||
|
last = expression
|
||||||
|
consume(:close_round)
|
||||||
|
"(#{first}..#{last})"
|
||||||
|
else
|
||||||
|
raise SyntaxError, "#{token} is not a valid expression"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def argument
|
||||||
|
str = ""
|
||||||
|
# might be a keyword argument (identifier: expression)
|
||||||
|
if look(:id) && look(:colon, 1)
|
||||||
|
str << consume << consume << ' '
|
||||||
|
end
|
||||||
|
|
||||||
|
str << expression
|
||||||
|
end
|
||||||
|
|
||||||
|
def variable_signature
|
||||||
|
str = consume(:id)
|
||||||
|
if look(:open_square)
|
||||||
|
str << consume
|
||||||
|
str << expression
|
||||||
|
str << consume(:close_square)
|
||||||
|
end
|
||||||
|
if look(:dot)
|
||||||
|
str << consume
|
||||||
|
str << variable_signature
|
||||||
|
end
|
||||||
|
str
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
require 'cgi'
|
require 'cgi'
|
||||||
|
require 'bigdecimal'
|
||||||
|
|
||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
@@ -10,12 +11,12 @@ module Liquid
|
|||||||
input.respond_to?(:size) ? input.size : 0
|
input.respond_to?(:size) ? input.size : 0
|
||||||
end
|
end
|
||||||
|
|
||||||
# convert a input string to DOWNCASE
|
# convert an input string to DOWNCASE
|
||||||
def downcase(input)
|
def downcase(input)
|
||||||
input.to_s.downcase
|
input.to_s.downcase
|
||||||
end
|
end
|
||||||
|
|
||||||
# convert a input string to UPCASE
|
# convert an input string to UPCASE
|
||||||
def upcase(input)
|
def upcase(input)
|
||||||
input.to_s.upcase
|
input.to_s.upcase
|
||||||
end
|
end
|
||||||
@@ -42,7 +43,8 @@ module Liquid
|
|||||||
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
|
truncated = RUBY_VERSION[0,3] == "1.8" ? input.scan(/./mu)[0...l].to_s : input[0...l]
|
||||||
|
input.length > length.to_i ? truncated + truncate_string : input
|
||||||
end
|
end
|
||||||
|
|
||||||
def truncatewords(input, words = 15, truncate_string = "...")
|
def truncatewords(input, words = 15, truncate_string = "...")
|
||||||
@@ -63,15 +65,14 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def strip_html(input)
|
def strip_html(input)
|
||||||
input.to_s.gsub(/<script.*?<\/script>/, '').gsub(/<!--.*?-->/, '').gsub(/<.*?>/, '')
|
input.to_s.gsub(/<script.*?<\/script>/m, '').gsub(/<!--.*?-->/m, '').gsub(/<style.*?<\/style>/m, '').gsub(/<.*?>/m, '')
|
||||||
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(/\n/, '')
|
input.to_s.gsub(/\r?\n/, '')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# Join elements of the array with certain character between them
|
# Join elements of the array with certain character between them
|
||||||
def join(input, glue = ' ')
|
def join(input, glue = ' ')
|
||||||
[input].flatten.join(glue)
|
[input].flatten.join(glue)
|
||||||
@@ -80,7 +81,7 @@ module Liquid
|
|||||||
# Sort elements of the array
|
# Sort elements of the array
|
||||||
# provide optional property with which to sort an array of hashes or drops
|
# provide optional property with which to sort an array of hashes or drops
|
||||||
def sort(input, property = nil)
|
def sort(input, property = nil)
|
||||||
ary = [input].flatten
|
ary = flatten_if_necessary(input)
|
||||||
if property.nil?
|
if property.nil?
|
||||||
ary.sort
|
ary.sort
|
||||||
elsif ary.first.respond_to?('[]') and !ary.first[property].nil?
|
elsif ary.first.respond_to?('[]') and !ary.first[property].nil?
|
||||||
@@ -90,24 +91,33 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Reverse the elements of an array
|
||||||
|
def reverse(input)
|
||||||
|
ary = [input].flatten
|
||||||
|
ary.reverse
|
||||||
|
end
|
||||||
|
|
||||||
# map/collect on a given property
|
# map/collect on a given property
|
||||||
def map(input, property)
|
def map(input, property)
|
||||||
ary = [input].flatten
|
flatten_if_necessary(input).map do |e|
|
||||||
if ary.first.respond_to?('[]') and !ary.first[property].nil?
|
e = e.call if e.is_a?(Proc)
|
||||||
ary.map {|e| e[property] }
|
|
||||||
elsif ary.first.respond_to?(property)
|
if property == "to_liquid"
|
||||||
ary.map {|e| e.send(property) }
|
e
|
||||||
|
elsif e.respond_to?(:[])
|
||||||
|
e[property]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Replace occurrences of a string with another
|
# Replace occurrences of a string with another
|
||||||
def replace(input, string, replacement = '')
|
def replace(input, string, replacement = '')
|
||||||
input.to_s.gsub(string, replacement)
|
input.to_s.gsub(string, replacement.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Replace the first occurrences of a string with another
|
# Replace the first occurrences of a string with another
|
||||||
def replace_first(input, string, replacement = '')
|
def replace_first(input, string, replacement = '')
|
||||||
input.to_s.sub(string, replacement)
|
input.to_s.sub(string, replacement.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
# remove a substring
|
# remove a substring
|
||||||
@@ -173,14 +183,23 @@ module Liquid
|
|||||||
input = Time.at(input.to_i)
|
input = Time.at(input.to_i)
|
||||||
end
|
end
|
||||||
|
|
||||||
date = input.is_a?(String) ? Time.parse(input) : input
|
date = if input.is_a?(String)
|
||||||
|
case input.downcase
|
||||||
|
when 'now', 'today'
|
||||||
|
Time.now
|
||||||
|
else
|
||||||
|
Time.parse(input)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
input
|
||||||
|
end
|
||||||
|
|
||||||
if date.respond_to?(:strftime)
|
if date.respond_to?(:strftime)
|
||||||
date.strftime(format.to_s)
|
date.strftime(format.to_s)
|
||||||
else
|
else
|
||||||
input
|
input
|
||||||
end
|
end
|
||||||
rescue => e
|
rescue
|
||||||
input
|
input
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -204,41 +223,58 @@ module Liquid
|
|||||||
|
|
||||||
# addition
|
# addition
|
||||||
def plus(input, operand)
|
def plus(input, operand)
|
||||||
to_number(input) + to_number(operand)
|
apply_operation(input, operand, :+)
|
||||||
end
|
end
|
||||||
|
|
||||||
# subtraction
|
# subtraction
|
||||||
def minus(input, operand)
|
def minus(input, operand)
|
||||||
to_number(input) - to_number(operand)
|
apply_operation(input, operand, :-)
|
||||||
end
|
end
|
||||||
|
|
||||||
# multiplication
|
# multiplication
|
||||||
def times(input, operand)
|
def times(input, operand)
|
||||||
to_number(input) * to_number(operand)
|
apply_operation(input, operand, :*)
|
||||||
end
|
end
|
||||||
|
|
||||||
# division
|
# division
|
||||||
def divided_by(input, operand)
|
def divided_by(input, operand)
|
||||||
to_number(input) / to_number(operand)
|
apply_operation(input, operand, :/)
|
||||||
end
|
end
|
||||||
|
|
||||||
def modulo(input, operand)
|
def modulo(input, operand)
|
||||||
to_number(input) % to_number(operand)
|
apply_operation(input, operand, :%)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def to_number(obj)
|
def flatten_if_necessary(input)
|
||||||
case obj
|
ary = if input.is_a?(Array)
|
||||||
when Numeric
|
input.flatten
|
||||||
obj
|
elsif input.kind_of?(Enumerable)
|
||||||
when String
|
input
|
||||||
(obj.strip =~ /^\d+\.\d+$/) ? obj.to_f : obj.to_i
|
else
|
||||||
else
|
[input].flatten
|
||||||
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 =~ /^\d+\.\d+$/) ? 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)
|
||||||
|
|||||||
@@ -2,24 +2,15 @@ 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 instanciated for each liquid template render run.
|
# New filters are mixed into the strainer class which is then instantiated for each liquid template render run.
|
||||||
#
|
#
|
||||||
# One of the strainer's responsibilities is to keep malicious method calls out
|
# The Strainer only allows method calls defined in filters given to it via Strainer.global_filter,
|
||||||
class Strainer < parent_object #:nodoc:
|
# Context#add_filters or Template.register_filter
|
||||||
INTERNAL_METHOD = /^__/
|
class Strainer #:nodoc:
|
||||||
@@required_methods = Set.new([:__id__, :__send__, :respond_to?, :kind_of?, :extend, :methods, :singleton_methods, :class, :object_id])
|
@@filters = []
|
||||||
|
@@known_filters = Set.new
|
||||||
# Ruby 1.9.2 introduces Object#respond_to_missing?, which is invoked by Object#respond_to?
|
@@known_methods = Set.new
|
||||||
@@required_methods << :respond_to_missing? if Object.respond_to? :respond_to_missing?
|
|
||||||
|
|
||||||
@@filters = {}
|
|
||||||
|
|
||||||
def initialize(context)
|
def initialize(context)
|
||||||
@context = context
|
@context = context
|
||||||
@@ -27,28 +18,36 @@ 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)
|
||||||
@@filters[filter.name] = filter
|
add_known_filter(filter)
|
||||||
|
@@filters << filter unless @@filters.include?(filter)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.add_known_filter(filter)
|
||||||
|
unless @@known_filters.include?(filter)
|
||||||
|
@@method_blacklist ||= Set.new(Strainer.instance_methods.map(&:to_s))
|
||||||
|
new_methods = filter.instance_methods.map(&:to_s)
|
||||||
|
new_methods.reject!{ |m| @@method_blacklist.include?(m) }
|
||||||
|
@@known_methods.merge(new_methods)
|
||||||
|
@@known_filters.add(filter)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.create(context)
|
def self.create(context)
|
||||||
strainer = Strainer.new(context)
|
strainer = Strainer.new(context)
|
||||||
@@filters.each { |k,m| strainer.extend(m) }
|
@@filters.each { |m| strainer.extend(m) }
|
||||||
strainer
|
strainer
|
||||||
end
|
end
|
||||||
|
|
||||||
def respond_to?(method, include_private = false)
|
def invoke(method, *args)
|
||||||
method_name = method.to_s
|
if invokable?(method)
|
||||||
return false if method_name =~ INTERNAL_METHOD
|
send(method, *args)
|
||||||
return false if @@required_methods.include?(method_name)
|
else
|
||||||
super
|
args.first
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# remove all standard methods from the bucket so circumvent security
|
def invokable?(method)
|
||||||
# problems
|
@@known_methods.include?(method.to_s) && respond_to?(method)
|
||||||
instance_methods.each do |m|
|
|
||||||
unless @@required_methods.include?(m.to_sym)
|
|
||||||
undef_method m
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,12 +1,21 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
class Tag
|
class Tag
|
||||||
|
attr_accessor :nodelist, :options
|
||||||
|
attr_reader :warnings
|
||||||
|
|
||||||
attr_accessor :nodelist
|
def self.new_with_options(tag_name, markup, tokens, options)
|
||||||
|
# Forgive me Matz for I have sinned. I know this code is weird
|
||||||
|
# but it was necessary to maintain API compatibility.
|
||||||
|
new_tag = self.allocate
|
||||||
|
new_tag.options = options
|
||||||
|
new_tag.send(:initialize, tag_name, markup, tokens)
|
||||||
|
new_tag
|
||||||
|
end
|
||||||
|
|
||||||
def initialize(tag_name, markup, tokens)
|
def initialize(tag_name, markup, tokens)
|
||||||
@tag_name = tag_name
|
@tag_name = tag_name
|
||||||
@markup = markup
|
@markup = markup
|
||||||
|
@options ||= {} # needs || because might be set before initialize
|
||||||
parse(tokens)
|
parse(tokens)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -21,6 +30,31 @@ module Liquid
|
|||||||
''
|
''
|
||||||
end
|
end
|
||||||
|
|
||||||
end # Tag
|
def blank?
|
||||||
|
@blank || true
|
||||||
|
end
|
||||||
|
|
||||||
end # Tag
|
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 # Liquid
|
||||||
|
|||||||
@@ -16,15 +16,17 @@ module Liquid
|
|||||||
@to = $1
|
@to = $1
|
||||||
@from = Variable.new($2)
|
@from = Variable.new($2)
|
||||||
else
|
else
|
||||||
raise SyntaxError.new("Syntax Error in 'assign' - Valid syntax: assign [var] = [source]")
|
raise SyntaxError.new options[:locale].t("errors.syntax.assign")
|
||||||
end
|
end
|
||||||
|
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
context.scopes.last[@to] = @from.render(context)
|
val = @from.render(context)
|
||||||
''
|
context.scopes.last[@to] = val
|
||||||
|
context.resource_limits[:assign_score_current] += (val.respond_to?(:length) ? val.length : 1)
|
||||||
|
''
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ module Liquid
|
|||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@to = $1
|
@to = $1
|
||||||
else
|
else
|
||||||
raise SyntaxError.new("Syntax Error in 'capture' - Valid syntax: capture [var]")
|
raise SyntaxError.new(options[:locale].t("errors.syntax.capture"))
|
||||||
end
|
end
|
||||||
|
|
||||||
super
|
super
|
||||||
@@ -27,8 +27,13 @@ module Liquid
|
|||||||
def render(context)
|
def render(context)
|
||||||
output = super
|
output = super
|
||||||
context.scopes.last[@to] = output
|
context.scopes.last[@to] = output
|
||||||
|
context.resource_limits[:assign_score_current] += (output.respond_to?(:length) ? output.length : 1)
|
||||||
''
|
''
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def blank?
|
||||||
|
true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('capture', Capture)
|
Template.register_tag('capture', Capture)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ module Liquid
|
|||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@left = $1
|
@left = $1
|
||||||
else
|
else
|
||||||
raise SyntaxError.new("Syntax Error in tag 'case' - Valid syntax: case [condition]")
|
raise SyntaxError.new(options[:locale].t("errors.syntax.case"))
|
||||||
end
|
end
|
||||||
|
|
||||||
super
|
super
|
||||||
@@ -48,31 +48,28 @@ 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("Syntax Error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %} ")
|
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when"))
|
||||||
end
|
end
|
||||||
|
|
||||||
markup = $2
|
markup = $2
|
||||||
|
|
||||||
block = Condition.new(@left, '==', $1)
|
block = Condition.new(@left, '==', $1)
|
||||||
block.attach(@nodelist)
|
block.attach(@nodelist)
|
||||||
@blocks.push(block)
|
@blocks.push(block)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def record_else_condition(markup)
|
def record_else_condition(markup)
|
||||||
|
|
||||||
if not markup.strip.empty?
|
if not markup.strip.empty?
|
||||||
raise SyntaxError.new("Syntax Error in tag 'case' - Valid else condition: {% else %} (no parameters) ")
|
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_else"))
|
||||||
end
|
end
|
||||||
|
|
||||||
block = ElseCondition.new
|
block = ElseCondition.new
|
||||||
block.attach(@nodelist)
|
block.attach(@nodelist)
|
||||||
@blocks << block
|
@blocks << block
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('case', Case)
|
Template.register_tag('case', Case)
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ module Liquid
|
|||||||
def render(context)
|
def render(context)
|
||||||
''
|
''
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def blank?
|
||||||
|
true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('comment', Comment)
|
Template.register_tag('comment', Comment)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
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 %}
|
||||||
@@ -19,13 +18,13 @@ module Liquid
|
|||||||
def initialize(tag_name, markup, tokens)
|
def initialize(tag_name, markup, tokens)
|
||||||
case markup
|
case markup
|
||||||
when NamedSyntax
|
when NamedSyntax
|
||||||
@variables = variables_from_string($2)
|
@variables = variables_from_string($2)
|
||||||
@name = $1
|
@name = $1
|
||||||
when SimpleSyntax
|
when SimpleSyntax
|
||||||
@variables = variables_from_string(markup)
|
@variables = variables_from_string(markup)
|
||||||
@name = "'#{@variables.to_s}'"
|
@name = "'#{@variables.to_s}'"
|
||||||
else
|
else
|
||||||
raise SyntaxError.new("Syntax Error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]")
|
raise SyntaxError.new(options[:locale].t("errors.syntax.cycle"))
|
||||||
end
|
end
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
@@ -44,15 +43,17 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
def blank?
|
||||||
|
false
|
||||||
def variables_from_string(markup)
|
|
||||||
markup.split(',').collect do |var|
|
|
||||||
var =~ /\s*(#{QuotedFragment})\s*/o
|
|
||||||
$1 ? $1 : nil
|
|
||||||
end.compact
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def variables_from_string(markup)
|
||||||
|
markup.split(',').collect do |var|
|
||||||
|
var =~ /\s*(#{QuotedFragment})\s*/o
|
||||||
|
$1 ? $1 : nil
|
||||||
|
end.compact
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('cycle', Cycle)
|
Template.register_tag('cycle', Cycle)
|
||||||
|
|||||||
@@ -44,22 +44,10 @@ 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 = /(\w+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
|
Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
|
||||||
|
|
||||||
def initialize(tag_name, markup, tokens)
|
def initialize(tag_name, markup, tokens)
|
||||||
if markup =~ Syntax
|
parse_with_selected_parser(markup)
|
||||||
@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("Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]")
|
|
||||||
end
|
|
||||||
|
|
||||||
@nodelist = @for_block = []
|
@nodelist = @for_block = []
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
@@ -112,7 +100,7 @@ module Liquid
|
|||||||
'rindex' => length - index,
|
'rindex' => length - index,
|
||||||
'rindex0' => length - index - 1,
|
'rindex0' => length - index - 1,
|
||||||
'first' => (index == 0),
|
'first' => (index == 0),
|
||||||
'last' => (index == length - 1) }
|
'last' => (index == length - 1) }
|
||||||
|
|
||||||
result << render_all(@for_block, context)
|
result << render_all(@for_block, context)
|
||||||
|
|
||||||
@@ -127,6 +115,43 @@ module Liquid
|
|||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def lax_parse(markup)
|
||||||
|
if markup =~ Syntax
|
||||||
|
@variable_name = $1
|
||||||
|
@collection_name = $2
|
||||||
|
@name = "#{$1}-#{$2}"
|
||||||
|
@reversed = $3
|
||||||
|
@attributes = {}
|
||||||
|
markup.scan(TagAttributes) do |key, value|
|
||||||
|
@attributes[key] = value
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise SyntaxError.new(options[:locale].t("errors.syntax.for"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def strict_parse(markup)
|
||||||
|
p = Parser.new(markup)
|
||||||
|
@variable_name = p.consume(:id)
|
||||||
|
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in")) unless p.id?('in')
|
||||||
|
@collection_name = p.expression
|
||||||
|
@name = "#{@variable_name}-#{@collection_name}"
|
||||||
|
@reversed = p.id?('reversed')
|
||||||
|
|
||||||
|
@attributes = {}
|
||||||
|
while p.look(:id) && p.look(:colon, 1)
|
||||||
|
unless attribute = p.id?('limit') || p.id?('offset')
|
||||||
|
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute"))
|
||||||
|
end
|
||||||
|
p.consume
|
||||||
|
val = p.expression
|
||||||
|
@attributes[attribute] = val
|
||||||
|
end
|
||||||
|
p.consume(:end_of_string)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def render_else(context)
|
def render_else(context)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
# If is the conditional block
|
# If is the conditional block
|
||||||
#
|
#
|
||||||
# {% if user.admin %}
|
# {% if user.admin %}
|
||||||
@@ -10,17 +9,13 @@ 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
|
||||||
|
|
||||||
def initialize(tag_name, markup, tokens)
|
def initialize(tag_name, markup, tokens)
|
||||||
@blocks = []
|
@blocks = []
|
||||||
|
|
||||||
push_block('if', markup)
|
push_block('if', markup)
|
||||||
|
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -49,30 +44,56 @@ module Liquid
|
|||||||
block = if tag == 'else'
|
block = if tag == 'else'
|
||||||
ElseCondition.new
|
ElseCondition.new
|
||||||
else
|
else
|
||||||
|
parse_with_selected_parser(markup)
|
||||||
expressions = markup.scan(ExpressionsAndOperators).reverse
|
|
||||||
raise(SyntaxError, SyntaxHelp) unless expressions.shift =~ Syntax
|
|
||||||
|
|
||||||
condition = Condition.new($1, $2, $3)
|
|
||||||
|
|
||||||
while not expressions.empty?
|
|
||||||
operator = (expressions.shift).to_s.strip
|
|
||||||
|
|
||||||
raise(SyntaxError, SyntaxHelp) unless expressions.shift.to_s =~ Syntax
|
|
||||||
|
|
||||||
new_condition = Condition.new($1, $2, $3)
|
|
||||||
new_condition.send(operator.to_sym, condition)
|
|
||||||
condition = new_condition
|
|
||||||
end
|
|
||||||
|
|
||||||
condition
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@blocks.push(block)
|
@blocks.push(block)
|
||||||
@nodelist = block.attach(Array.new)
|
@nodelist = block.attach(Array.new)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def lax_parse(markup)
|
||||||
|
expressions = markup.scan(ExpressionsAndOperators).reverse
|
||||||
|
raise(SyntaxError.new(options[:locale].t("errors.syntax.if"))) unless expressions.shift =~ Syntax
|
||||||
|
|
||||||
|
condition = Condition.new($1, $2, $3)
|
||||||
|
|
||||||
|
while not expressions.empty?
|
||||||
|
operator = (expressions.shift).to_s.strip
|
||||||
|
|
||||||
|
raise(SyntaxError.new(options[:locale].t("errors.syntax.if"))) unless expressions.shift.to_s =~ Syntax
|
||||||
|
|
||||||
|
new_condition = Condition.new($1, $2, $3)
|
||||||
|
new_condition.send(operator.to_sym, condition)
|
||||||
|
condition = new_condition
|
||||||
|
end
|
||||||
|
|
||||||
|
condition
|
||||||
|
end
|
||||||
|
|
||||||
|
def strict_parse(markup)
|
||||||
|
p = Parser.new(markup)
|
||||||
|
|
||||||
|
condition = parse_comparison(p)
|
||||||
|
|
||||||
|
while op = (p.id?('and') || p.id?('or'))
|
||||||
|
new_cond = parse_comparison(p)
|
||||||
|
new_cond.send(op, condition)
|
||||||
|
condition = new_cond
|
||||||
|
end
|
||||||
|
p.consume(:end_of_string)
|
||||||
|
|
||||||
|
condition
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_comparison(p)
|
||||||
|
a = p.expression
|
||||||
|
if op = p.consume?(:comparison)
|
||||||
|
b = p.expression
|
||||||
|
Condition.new(a, op, b)
|
||||||
|
else
|
||||||
|
Condition.new(a)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('if', If)
|
Template.register_tag('if', If)
|
||||||
|
|||||||
@@ -1,4 +1,19 @@
|
|||||||
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
|
||||||
|
|
||||||
@@ -14,7 +29,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
raise SyntaxError.new("Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]")
|
raise SyntaxError.new(options[:locale].t("errors.syntax.include"))
|
||||||
end
|
end
|
||||||
|
|
||||||
super
|
super
|
||||||
@@ -23,9 +38,12 @@ module Liquid
|
|||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def blank?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
source = _read_template_from_file_system(context)
|
partial = load_cached_partial(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
|
||||||
@@ -34,8 +52,8 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
if variable.is_a?(Array)
|
if variable.is_a?(Array)
|
||||||
variable.collect do |variable|
|
variable.collect do |var|
|
||||||
context[@template_name[1..-2]] = variable
|
context[@template_name[1..-2]] = var
|
||||||
partial.render(context)
|
partial.render(context)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@@ -46,7 +64,21 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def _read_template_from_file_system(context)
|
def load_cached_partial(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.
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
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:
|
||||||
@@ -18,7 +17,6 @@ module Liquid
|
|||||||
class Increment < Tag
|
class Increment < Tag
|
||||||
def initialize(tag_name, markup, tokens)
|
def initialize(tag_name, markup, tokens)
|
||||||
@variable = markup.strip
|
@variable = markup.strip
|
||||||
|
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -28,7 +26,9 @@ module Liquid
|
|||||||
value.to_s
|
value.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
def blank?
|
||||||
|
false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('increment', Increment)
|
Template.register_tag('increment', Increment)
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Raw < Block
|
class Raw < Block
|
||||||
|
FullTokenPossiblyInvalid = /^(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/o
|
||||||
|
|
||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
@nodelist ||= []
|
@nodelist ||= []
|
||||||
@nodelist.clear
|
@nodelist.clear
|
||||||
|
|
||||||
while token = tokens.shift
|
while token = tokens.shift
|
||||||
if token =~ FullToken
|
if token =~ FullTokenPossiblyInvalid
|
||||||
if block_delimiter == $1
|
@nodelist << $1 if $1 != ""
|
||||||
|
if block_delimiter == $2
|
||||||
end_tag
|
end_tag
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -18,4 +20,3 @@ module Liquid
|
|||||||
|
|
||||||
Template.register_tag('raw', Raw)
|
Template.register_tag('raw', Raw)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ module Liquid
|
|||||||
context.stack do
|
context.stack do
|
||||||
|
|
||||||
# First condition is interpreted backwards ( if not )
|
# First condition is interpreted backwards ( if not )
|
||||||
block = @blocks.first
|
first_block = @blocks.first
|
||||||
unless block.evaluate(context)
|
unless first_block.evaluate(context)
|
||||||
return render_all(block.attachment, context)
|
return render_all(first_block.attachment, context)
|
||||||
end
|
end
|
||||||
|
|
||||||
# After the first condition unless works just like if
|
# After the first condition unless works just like if
|
||||||
|
|||||||
@@ -14,7 +14,11 @@ module Liquid
|
|||||||
# template.render('user_name' => 'bob')
|
# template.render('user_name' => 'bob')
|
||||||
#
|
#
|
||||||
class Template
|
class Template
|
||||||
attr_accessor :root
|
DEFAULT_OPTIONS = {
|
||||||
|
:locale => I18n.new
|
||||||
|
}
|
||||||
|
|
||||||
|
attr_accessor :root, :resource_limits
|
||||||
@@file_system = BlankFileSystem.new
|
@@file_system = BlankFileSystem.new
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
@@ -34,6 +38,18 @@ 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)
|
||||||
@@ -41,24 +57,31 @@ 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)
|
def parse(source, options = {})
|
||||||
template = Template.new
|
template = Template.new
|
||||||
template.parse(source)
|
template.parse(source, options)
|
||||||
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)
|
def parse(source, options = {})
|
||||||
@root = Document.new(tokenize(source))
|
@root = Document.new(tokenize(source), DEFAULT_OPTIONS.merge(options))
|
||||||
|
@warnings = nil
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def warnings
|
||||||
|
return [] unless @root
|
||||||
|
@warnings ||= @root.warnings
|
||||||
|
end
|
||||||
|
|
||||||
def registers
|
def registers
|
||||||
@registers ||= {}
|
@registers ||= {}
|
||||||
end
|
end
|
||||||
@@ -92,12 +115,15 @@ module Liquid
|
|||||||
context = case args.first
|
context = case args.first
|
||||||
when Liquid::Context
|
when Liquid::Context
|
||||||
args.shift
|
args.shift
|
||||||
|
when Liquid::Drop
|
||||||
|
drop = args.shift
|
||||||
|
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
|
||||||
when Hash
|
when Hash
|
||||||
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors)
|
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
|
||||||
when nil
|
when nil
|
||||||
Context.new(assigns, instance_assigns, registers, @rethrow_errors)
|
Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits)
|
||||||
else
|
else
|
||||||
raise ArgumentError, "Expect Hash or Liquid::Context as parameter"
|
raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
|
||||||
end
|
end
|
||||||
|
|
||||||
case args.last
|
case args.last
|
||||||
@@ -120,9 +146,11 @@ module Liquid
|
|||||||
|
|
||||||
begin
|
begin
|
||||||
# render the nodelist.
|
# render the nodelist.
|
||||||
# for performance reasons we get a array back here. join will make a string out of it
|
# for performance reasons we get an array back here. join will make a string out of it.
|
||||||
result = @root.render(context)
|
result = @root.render(context)
|
||||||
result.respond_to?(:join) ? result.join : result
|
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
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ module Liquid
|
|||||||
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)
|
||||||
|
|||||||
@@ -11,12 +11,31 @@ module Liquid
|
|||||||
# {{ user | link }}
|
# {{ user | link }}
|
||||||
#
|
#
|
||||||
class Variable
|
class Variable
|
||||||
FilterParser = /(?:#{FilterSeparator}|(?:\s*(?!(?:#{FilterSeparator}))(?:#{QuotedFragment}|\S+)\s*)+)/o
|
FilterParser = /(?:#{FilterSeparator}|(?:\s*(?:#{QuotedFragment}|#{ArgumentSeparator})\s*)+)/o
|
||||||
attr_accessor :filters, :name
|
EasyParse = /^ *(\w+(?:\.\w+)*) *$/
|
||||||
|
attr_accessor :filters, :name, :warnings
|
||||||
|
|
||||||
def initialize(markup)
|
def initialize(markup, options = {})
|
||||||
@markup = markup
|
@markup = markup
|
||||||
@name = nil
|
@name = nil
|
||||||
|
@options = options || {}
|
||||||
|
|
||||||
|
|
||||||
|
case @options[:error_mode] || Template.error_mode
|
||||||
|
when :strict then strict_parse(markup)
|
||||||
|
when :lax then lax_parse(markup)
|
||||||
|
when :warn
|
||||||
|
begin
|
||||||
|
strict_parse(markup)
|
||||||
|
rescue SyntaxError => e
|
||||||
|
@warnings ||= []
|
||||||
|
@warnings << e
|
||||||
|
lax_parse(markup)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def lax_parse(markup)
|
||||||
@filters = []
|
@filters = []
|
||||||
if match = markup.match(/\s*(#{QuotedFragment})(.*)/o)
|
if match = markup.match(/\s*(#{QuotedFragment})(.*)/o)
|
||||||
@name = match[1]
|
@name = match[1]
|
||||||
@@ -25,20 +44,60 @@ module Liquid
|
|||||||
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*(#{QuotedFragment})/o).flatten
|
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
|
||||||
@filters << [filtername.to_sym, filterargs]
|
@filters << [filtername, 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) ? '' : p.expression
|
||||||
|
while p.consume?(:pipe)
|
||||||
|
filtername = p.consume(:id)
|
||||||
|
filterargs = p.consume?(:colon) ? parse_filterargs(p) : []
|
||||||
|
@filters << [filtername, filterargs]
|
||||||
|
end
|
||||||
|
p.consume(:end_of_string)
|
||||||
|
rescue SyntaxError => e
|
||||||
|
e.message << " in \"{{#{markup}}}\""
|
||||||
|
raise e
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_filterargs(p)
|
||||||
|
# first argument
|
||||||
|
filterargs = [p.argument]
|
||||||
|
# followed by comma separated others
|
||||||
|
while p.consume?(:comma)
|
||||||
|
filterargs << p.argument
|
||||||
|
end
|
||||||
|
filterargs
|
||||||
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
return '' if @name.nil?
|
return '' if @name.nil?
|
||||||
@filters.inject(context[@name]) do |output, filter|
|
@filters.inject(context[@name]) do |output, filter|
|
||||||
filterargs = filter[1].to_a.collect do |a|
|
filterargs = []
|
||||||
context[a]
|
keyword_args = {}
|
||||||
|
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
|
||||||
|
|||||||
4
lib/liquid/version.rb
Normal file
4
lib/liquid/version.rb
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
module Liquid
|
||||||
|
VERSION = "2.6.0"
|
||||||
|
end
|
||||||
@@ -1,13 +1,18 @@
|
|||||||
# 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 = "2.4.1"
|
s.version = Liquid::VERSION
|
||||||
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"
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ 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.bmbm do |x|
|
||||||
x.report("parse & run:") { 10.times { profiler.run(false) } }
|
x.report("parse:") { 100.times { profiler.compile } }
|
||||||
|
x.report("parse & run:") { 100.times { profiler.run } }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ profiler = ThemeRunner.new
|
|||||||
|
|
||||||
puts 'Running profiler...'
|
puts 'Running profiler...'
|
||||||
|
|
||||||
results = profiler.run
|
results = profiler.run_profile
|
||||||
|
|
||||||
puts 'Success'
|
puts 'Success'
|
||||||
puts
|
puts
|
||||||
|
|
||||||
[RubyProf::FlatPrinter, RubyProf::GraphPrinter, RubyProf::GraphHtmlPrinter, RubyProf::CallTreePrinter].each do |klass|
|
[RubyProf::FlatPrinter, RubyProf::GraphHtmlPrinter, RubyProf::CallTreePrinter, RubyProf::DotPrinter].each do |klass|
|
||||||
filename = (ENV['TMP'] || '/tmp') + (klass.name.include?('Html') ? "/liquid.#{klass.name.downcase}.html" : "/callgrind.liquid.#{klass.name.downcase}.txt")
|
filename = (ENV['TMP'] || '/tmp') + (klass.name.include?('Html') ? "/liquid.#{klass.name.downcase}.html" : "/callgrind.liquid.#{klass.name.downcase}.txt")
|
||||||
filename.gsub!(/:+/, '_')
|
filename.gsub!(/:+/, '_')
|
||||||
File.open(filename, "w+") { |fp| klass.new(results).print(fp, :print_file => true) }
|
File.open(filename, "w+") { |fp| klass.new(results).print(fp, :print_file => true, :min_percent => 3) }
|
||||||
$stderr.puts "wrote #{klass.name} output to #{filename}"
|
$stderr.puts "wrote #{klass.name} output to #{filename}"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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 }}
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
@@ -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>
|
|
||||||
{% else %}
|
|
||||||
<div class="article-body textile">
|
|
||||||
In <em>Admin > Blogs & 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" }}
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% else %}
|
||||||
|
<div class="article-body textile">
|
||||||
|
In <em>Admin > Blogs & 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" }}
|
||||||
|
</div>
|
||||||
|
{% 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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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 }}
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
@@ -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 > Blogs & Pages</em>, create a page with the handle <strong><code>frontpage</code></strong> and it will show up here.<br />
|
In <em>Admin > Blogs & 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>
|
||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 -->
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
@@ -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 -->
|
||||||
|
|||||||
@@ -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 -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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 -->
|
||||||
|
|||||||
@@ -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 %}
|
||||||
@@ -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> © 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> © 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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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 }} — {{ 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 }} — {{ 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 }} — {{ 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 }} — {{ 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 %}
|
||||||
@@ -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 }} — {{ 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 }} — {{ 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 %}
|
||||||
@@ -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 > Blogs & Pages</em>, create a page with the handle <strong><code>frontpage</code></strong> and it will show up here.<br />
|
In <em>Admin > Blogs & 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 }} — {{ 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 }} — {{ 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>
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<h1>{{ page.title }}</h1>
|
<h1>{{ page.title }}</h1>
|
||||||
{{ page.content }}
|
{{ page.content }}
|
||||||
|
|
||||||
@@ -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>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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 }} — {{ 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 }} — {{ 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 %}|{% endunless %}{% endfor %}
|
{{ link.title | link_to: link.url }} {% unless forloop.last %}|{% endunless %}{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<span id="footer-left">
|
<span id="footer-left">
|
||||||
Copyright © {{ "now" | date: "%Y" }} <a href="/">{{ shop.name }}</a>. All Rights Reserved. All prices {{ shop.currency }}.<br />
|
Copyright © {{ "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>
|
||||||
|
|||||||
@@ -14,6 +14,17 @@ 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.
|
||||||
@@ -27,8 +38,33 @@ class ThemeRunner
|
|||||||
end.compact
|
end.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def compile
|
||||||
|
# Dup assigns because will make some changes to them
|
||||||
|
|
||||||
def run()
|
@tests.each do |liquid, layout, template_name|
|
||||||
|
|
||||||
|
tmpl = Liquid::Template.new
|
||||||
|
tmpl.parse(liquid)
|
||||||
|
tmpl = Liquid::Template.new
|
||||||
|
tmpl.parse(layout)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def run
|
||||||
|
# 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
|
||||||
|
page_template = File.basename(template_name, File.extname(template_name))
|
||||||
|
compile_and_render(liquid, layout, assigns, page_template, template_name)
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def run_profile
|
||||||
RubyProf.measure_mode = RubyProf::WALL_TIME
|
RubyProf.measure_mode = RubyProf::WALL_TIME
|
||||||
|
|
||||||
# Dup assigns because will make some changes to them
|
# Dup assigns because will make some changes to them
|
||||||
@@ -49,7 +85,7 @@ class ThemeRunner
|
|||||||
html = nil
|
html = nil
|
||||||
|
|
||||||
RubyProf.resume
|
RubyProf.resume
|
||||||
html = compile_and_render(liquid, layout, assigns, page_template)
|
html = compile_and_render(liquid, layout, assigns, page_template, template_name)
|
||||||
RubyProf.pause
|
RubyProf.pause
|
||||||
|
|
||||||
|
|
||||||
@@ -63,10 +99,11 @@ class ThemeRunner
|
|||||||
RubyProf.stop
|
RubyProf.stop
|
||||||
end
|
end
|
||||||
|
|
||||||
def compile_and_render(template, layout, assigns, page_template)
|
def compile_and_render(template, layout, assigns, page_template, template_file)
|
||||||
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)
|
||||||
|
|
||||||
|
|||||||
9
test/fixtures/en_locale.yml
vendored
Normal file
9
test/fixtures/en_locale.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
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"
|
||||||
@@ -18,4 +18,10 @@ 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
|
||||||
|
|||||||
94
test/liquid/blank_test.rb
Normal file
94
test/liquid/blank_test.rb
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
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_loops_are_blank
|
||||||
|
assert_template_result("", wrap_in_for(" "))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_if_else_are_blank
|
||||||
|
assert_template_result("", "{% if true %} {% elsif false %} {% else %} {% endif %}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_unless_is_blank
|
||||||
|
assert_template_result("", wrap("{% unless true %} {% endunless %}"))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_mark_as_blank_only_during_parsing
|
||||||
|
assert_template_result(" "*(N+1), wrap(" {% if false %} this never happens, but still, this block is not blank {% endif %}"))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_comments_are_blank
|
||||||
|
assert_template_result("", wrap(" {% comment %} whatever {% endcomment %} "))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_captures_are_blank
|
||||||
|
assert_template_result("", wrap(" {% capture foo %} whatever {% endcapture %} "))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_nested_blocks_are_blank_but_only_if_all_children_are
|
||||||
|
assert_template_result("", wrap(wrap(" ")))
|
||||||
|
assert_template_result("\n but this is not "*(N+1),
|
||||||
|
wrap(%q{{% if true %} {% comment %} this is blank {% endcomment %} {% endif %}
|
||||||
|
{% if true %} but this is not {% endif %}}))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_assigns_are_blank
|
||||||
|
assert_template_result("", wrap(' {% assign foo = "bar" %} '))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_whitespace_is_blank
|
||||||
|
assert_template_result("", wrap(" "))
|
||||||
|
assert_template_result("", wrap("\t"))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_whitespace_is_not_blank_if_other_stuff_is_present
|
||||||
|
body = " x "
|
||||||
|
assert_template_result(body*(N+1), wrap(body))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_increment_is_not_blank
|
||||||
|
assert_template_result(" 0"*2*(N+1), wrap("{% assign foo = 0 %} {% increment foo %} {% decrement foo %}"))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_cycle_is_not_blank
|
||||||
|
assert_template_result(" "*((N+1)/2)+" ", wrap("{% cycle ' ', ' ' %}"))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_raw_is_not_blank
|
||||||
|
assert_template_result(" "*(N+1), wrap(" {% raw %} {% endraw %}"))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_include_is_blank
|
||||||
|
Liquid::Template.file_system = BlankTestFileSystem.new
|
||||||
|
assert_equal "foobar"*(N+1), Template.parse(wrap("{% include 'foobar' %}")).render()
|
||||||
|
assert_equal " foobar "*(N+1), Template.parse(wrap("{% include ' foobar ' %}")).render()
|
||||||
|
assert_equal " ", Template.parse(" {% include ' ' %} ").render()
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_case_is_blank
|
||||||
|
assert_template_result("", wrap(" {% assign foo = 'bar' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} {% endcase %} "))
|
||||||
|
assert_template_result("", wrap(" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} {% endcase %} "))
|
||||||
|
assert_template_result(" x "*(N+1), wrap(" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} x {% endcase %} "))
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -189,10 +189,10 @@ class ContextTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
context = Context.new
|
context = Context.new
|
||||||
methods_before = context.strainer.methods.map { |method| method.to_s }
|
assert_equal "Wookie", context.invoke("hi", "Wookie")
|
||||||
|
|
||||||
context.add_filters(filter)
|
context.add_filters(filter)
|
||||||
methods_after = context.strainer.methods.map { |method| method.to_s }
|
assert_equal "Wookie hi!", context.invoke("hi", "Wookie")
|
||||||
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
|
||||||
|
|||||||
@@ -55,11 +55,44 @@ 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
|
||||||
@@ -71,23 +104,34 @@ 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
|
||||||
@@ -115,6 +159,13 @@ class DropsTest < Test::Unit::TestCase
|
|||||||
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])
|
||||||
@@ -152,6 +203,33 @@ class DropsTest < Test::Unit::TestCase
|
|||||||
assert_equal '3', Liquid::Template.parse( '{{collection.size}}').render('collection' => EnumerableDrop.new)
|
assert_equal '3', Liquid::Template.parse( '{{collection.size}}').render('collection' => EnumerableDrop.new)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_enumerable_drop_will_invoke_before_method_for_clashing_method_names
|
||||||
|
["select", "each", "map", "cycle"].each do |method|
|
||||||
|
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render('collection' => EnumerableDrop.new)
|
||||||
|
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => EnumerableDrop.new)
|
||||||
|
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render('collection' => RealEnumerableDrop.new)
|
||||||
|
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => RealEnumerableDrop.new)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_some_enumerable_methods_still_get_invoked
|
||||||
|
[ :count, :max ].each do |method|
|
||||||
|
assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => RealEnumerableDrop.new)
|
||||||
|
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => RealEnumerableDrop.new)
|
||||||
|
assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => EnumerableDrop.new)
|
||||||
|
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => EnumerableDrop.new)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal "yes", Liquid::Template.parse("{% if collection contains 3 %}yes{% endif %}").render('collection' => RealEnumerableDrop.new)
|
||||||
|
|
||||||
|
[ :min, :first ].each do |method|
|
||||||
|
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => RealEnumerableDrop.new)
|
||||||
|
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => RealEnumerableDrop.new)
|
||||||
|
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => EnumerableDrop.new)
|
||||||
|
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => EnumerableDrop.new)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_empty_string_value_access
|
def test_empty_string_value_access
|
||||||
assert_equal '', Liquid::Template.parse('{{ product[value] }}').render('product' => ProductDrop.new, 'value' => '')
|
assert_equal '', Liquid::Template.parse('{{ product[value] }}').render('product' => ProductDrop.new, 'value' => '')
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -58,19 +58,48 @@ class ErrorHandlingTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
def test_missing_endtag_parse_time_error
|
def test_missing_endtag_parse_time_error
|
||||||
assert_raise(Liquid::SyntaxError) do
|
assert_raise(Liquid::SyntaxError) do
|
||||||
template = Liquid::Template.parse(' {% for a in b %} ... ')
|
Liquid::Template.parse(' {% for a in b %} ... ')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_unrecognized_operator
|
def test_unrecognized_operator
|
||||||
|
with_error_mode(:strict) do
|
||||||
|
assert_raise(SyntaxError) do
|
||||||
|
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_lax_unrecognized_operator
|
||||||
assert_nothing_raised do
|
assert_nothing_raised do
|
||||||
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ')
|
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :lax)
|
||||||
assert_equal ' Liquid error: Unknown operator =! ', template.render
|
assert_equal ' Liquid error: Unknown operator =! ', template.render
|
||||||
assert_equal 1, template.errors.size
|
assert_equal 1, template.errors.size
|
||||||
assert_equal Liquid::ArgumentError, template.errors.first.class
|
assert_equal Liquid::ArgumentError, template.errors.first.class
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_strict_error_messages
|
||||||
|
err = assert_raise(SyntaxError) do
|
||||||
|
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :strict)
|
||||||
|
end
|
||||||
|
assert_equal 'Unexpected character = in "1 =! 2"', err.message
|
||||||
|
|
||||||
|
err = assert_raise(SyntaxError) do
|
||||||
|
Liquid::Template.parse('{{%%%}}', :error_mode => :strict)
|
||||||
|
end
|
||||||
|
assert_equal 'Unexpected character % in "{{%%%}}"', err.message
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_warnings
|
||||||
|
template = Liquid::Template.parse('{% if ~~~ %}{{%%%}}{% else %}{{ hello. }}{% endif %}', :error_mode => :warn)
|
||||||
|
assert_equal 3, template.warnings.size
|
||||||
|
assert_equal 'Unexpected character ~ in "~~~"', template.warnings[0].message
|
||||||
|
assert_equal 'Unexpected character % in "{{%%%}}"', template.warnings[1].message
|
||||||
|
assert_equal 'Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].message
|
||||||
|
assert_equal '', template.render
|
||||||
|
end
|
||||||
|
|
||||||
# Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError
|
# Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError
|
||||||
def test_exceptions_propagate
|
def test_exceptions_propagate
|
||||||
assert_raise Exception do
|
assert_raise Exception do
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ module CanadianMoneyFilter
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module SubstituteFilter
|
||||||
|
def substitute(input, params={})
|
||||||
|
input.gsub(/%\{(\w+)\}/) { |match| params[$1] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class FiltersTest < Test::Unit::TestCase
|
class FiltersTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
@@ -92,6 +98,13 @@ class FiltersTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
assert_equal 1000, Variable.new("var | xyzzy").render(@context)
|
assert_equal 1000, Variable.new("var | xyzzy").render(@context)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_filter_with_keyword_arguments
|
||||||
|
@context['surname'] = 'john'
|
||||||
|
@context.add_filters(SubstituteFilter)
|
||||||
|
output = Variable.new(%! 'hello %{first_name}, %{last_name}' | substitute: first_name: surname, last_name: 'doe' !).render(@context)
|
||||||
|
assert_equal 'hello john, doe', output
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class FiltersInTemplate < Test::Unit::TestCase
|
class FiltersInTemplate < Test::Unit::TestCase
|
||||||
|
|||||||
25
test/liquid/hash_ordering_test.rb
Normal file
25
test/liquid/hash_ordering_test.rb
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
module MoneyFilter
|
||||||
|
def money(input)
|
||||||
|
sprintf(' %d$ ', input)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module CanadianMoneyFilter
|
||||||
|
def money(input)
|
||||||
|
sprintf(' %d$ CAD ', input)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class HashOrderingTest < Test::Unit::TestCase
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
def test_global_register_order
|
||||||
|
Template.register_filter(MoneyFilter)
|
||||||
|
Template.register_filter(CanadianMoneyFilter)
|
||||||
|
|
||||||
|
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
37
test/liquid/i18n_test.rb
Normal file
37
test/liquid/i18n_test.rb
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class I18nTest < Test::Unit::TestCase
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
def setup
|
||||||
|
@i18n = I18n.new(fixture("en_locale.yml"))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_simple_translate_string
|
||||||
|
assert_equal "less is more", @i18n.translate("simple")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_nested_translate_string
|
||||||
|
assert_equal "something wasn't right", @i18n.translate("errors.syntax.oops")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_single_string_interpolation
|
||||||
|
assert_equal "something different", @i18n.translate("whatever", :something => "different")
|
||||||
|
end
|
||||||
|
|
||||||
|
# def test_raises_translation_error_on_undefined_interpolation_key
|
||||||
|
# assert_raise I18n::TranslationError do
|
||||||
|
# @i18n.translate("whatever", :oopstypos => "yes")
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
|
def test_raises_unknown_translation
|
||||||
|
assert_raise I18n::TranslationError do
|
||||||
|
@i18n.translate("doesnt_exist")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_sets_default_path_to_en
|
||||||
|
assert_equal I18n::DEFAULT_LOCALE, I18n.new.path
|
||||||
|
end
|
||||||
|
end
|
||||||
48
test/liquid/lexer_test.rb
Normal file
48
test/liquid/lexer_test.rb
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class LexerTest < Test::Unit::TestCase
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
def test_strings
|
||||||
|
tokens = Lexer.new(%! 'this is a test""' "wat 'lol'"!).tokenize
|
||||||
|
assert_equal [[:string,%!'this is a test""'!], [:string, %!"wat 'lol'"!], [:end_of_string]], tokens
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_integer
|
||||||
|
tokens = Lexer.new('hi 50').tokenize
|
||||||
|
assert_equal [[:id,'hi'], [:number, '50'], [:end_of_string]], tokens
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_float
|
||||||
|
tokens = Lexer.new('hi 5.0').tokenize
|
||||||
|
assert_equal [[:id,'hi'], [:number, '5.0'], [:end_of_string]], tokens
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_comparison
|
||||||
|
tokens = Lexer.new('== <> contains').tokenize
|
||||||
|
assert_equal [[:comparison,'=='], [:comparison, '<>'], [:comparison, 'contains'], [:end_of_string]], tokens
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_specials
|
||||||
|
tokens = Lexer.new('| .:').tokenize
|
||||||
|
assert_equal [[:pipe, '|'], [:dot, '.'], [:colon, ':'], [:end_of_string]], tokens
|
||||||
|
tokens = Lexer.new('[,]').tokenize
|
||||||
|
assert_equal [[:open_square, '['], [:comma, ','], [:close_square, ']'], [:end_of_string]], tokens
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_fancy_identifiers
|
||||||
|
tokens = Lexer.new('hi! five?').tokenize
|
||||||
|
assert_equal [[:id,'hi!'], [:id, 'five?'], [:end_of_string]], tokens
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_whitespace
|
||||||
|
tokens = Lexer.new("five|\n\t ==").tokenize
|
||||||
|
assert_equal [[:id,'five'], [:pipe, '|'], [:comparison, '=='], [:end_of_string]], tokens
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_unexpected_character
|
||||||
|
assert_raises(SyntaxError) do
|
||||||
|
Lexer.new("%").tokenize
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
74
test/liquid/parser_test.rb
Normal file
74
test/liquid/parser_test.rb
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class ParserTest < Test::Unit::TestCase
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
def test_consume
|
||||||
|
p = Parser.new("wat: 7")
|
||||||
|
assert_equal 'wat', p.consume(:id)
|
||||||
|
assert_equal ':', p.consume(:colon)
|
||||||
|
assert_equal '7', p.consume(:number)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_jump
|
||||||
|
p = Parser.new("wat: 7")
|
||||||
|
p.jump(2)
|
||||||
|
assert_equal '7', p.consume(:number)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_consume?
|
||||||
|
p = Parser.new("wat: 7")
|
||||||
|
assert_equal 'wat', p.consume?(:id)
|
||||||
|
assert_equal false, p.consume?(:dot)
|
||||||
|
assert_equal ':', p.consume(:colon)
|
||||||
|
assert_equal '7', p.consume?(:number)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_id?
|
||||||
|
p = Parser.new("wat 6 Peter Hegemon")
|
||||||
|
assert_equal 'wat', p.id?('wat')
|
||||||
|
assert_equal false, p.id?('endgame')
|
||||||
|
assert_equal '6', p.consume(:number)
|
||||||
|
assert_equal 'Peter', p.id?('Peter')
|
||||||
|
assert_equal false, p.id?('Achilles')
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_look
|
||||||
|
p = Parser.new("wat 6 Peter Hegemon")
|
||||||
|
assert_equal true, p.look(:id)
|
||||||
|
assert_equal 'wat', p.consume(:id)
|
||||||
|
assert_equal false, p.look(:comparison)
|
||||||
|
assert_equal true, p.look(:number)
|
||||||
|
assert_equal true, p.look(:id, 1)
|
||||||
|
assert_equal false, p.look(:number, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_expressions
|
||||||
|
p = Parser.new("hi.there hi[5].! hi.there.bob")
|
||||||
|
assert_equal 'hi.there', p.expression
|
||||||
|
assert_equal 'hi[5].!', p.expression
|
||||||
|
assert_equal 'hi.there.bob', p.expression
|
||||||
|
|
||||||
|
p = Parser.new("567 6.0 'lol' \"wut\"")
|
||||||
|
assert_equal '567', p.expression
|
||||||
|
assert_equal '6.0', p.expression
|
||||||
|
assert_equal "'lol'", p.expression
|
||||||
|
assert_equal '"wut"', p.expression
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_arguments
|
||||||
|
p = Parser.new("filter: hi.there[5], keyarg: 7")
|
||||||
|
assert_equal 'filter', p.consume(:id)
|
||||||
|
assert_equal ':', p.consume(:colon)
|
||||||
|
assert_equal 'hi.there[5]', p.argument
|
||||||
|
assert_equal ',', p.consume(:comma)
|
||||||
|
assert_equal 'keyarg: 7', p.argument
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_invalid_expression
|
||||||
|
assert_raises(SyntaxError) do
|
||||||
|
p = Parser.new("==")
|
||||||
|
p.expression
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -31,22 +31,60 @@ class ParsingQuirksTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
def test_error_on_empty_filter
|
def test_error_on_empty_filter
|
||||||
assert_nothing_raised do
|
assert_nothing_raised do
|
||||||
Template.parse("{{test |a|b|}}")
|
|
||||||
Template.parse("{{test}}")
|
Template.parse("{{test}}")
|
||||||
Template.parse("{{|test|}}")
|
Template.parse("{{|test}}")
|
||||||
|
end
|
||||||
|
with_error_mode(:strict) do
|
||||||
|
assert_raise(SyntaxError) do
|
||||||
|
Template.parse("{{test |a|b|}}")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_meaningless_parens
|
def test_meaningless_parens_error
|
||||||
assigns = {'b' => 'bar', 'c' => 'baz'}
|
with_error_mode(:strict) do
|
||||||
markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
|
assert_raise(SyntaxError) do
|
||||||
assert_template_result(' YES ',"{% if #{markup} %} YES {% endif %}", assigns)
|
markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
|
||||||
|
Template.parse("{% if #{markup} %} YES {% endif %}")
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_unexpected_characters_silently_eat_logic
|
def test_unexpected_characters_syntax_error
|
||||||
markup = "true && false"
|
with_error_mode(:strict) do
|
||||||
assert_template_result(' YES ',"{% if #{markup} %} YES {% endif %}")
|
assert_raise(SyntaxError) do
|
||||||
markup = "false || true"
|
markup = "true && false"
|
||||||
assert_template_result('',"{% if #{markup} %} YES {% endif %}")
|
Template.parse("{% if #{markup} %} YES {% endif %}")
|
||||||
|
end
|
||||||
|
assert_raise(SyntaxError) do
|
||||||
|
markup = "false || true"
|
||||||
|
Template.parse("{% if #{markup} %} YES {% endif %}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_no_error_on_lax_empty_filter
|
||||||
|
assert_nothing_raised do
|
||||||
|
Template.parse("{{test |a|b|}}", :error_mode => :lax)
|
||||||
|
Template.parse("{{test}}", :error_mode => :lax)
|
||||||
|
Template.parse("{{|test|}}", :error_mode => :lax)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_meaningless_parens_lax
|
||||||
|
with_error_mode(:lax) do
|
||||||
|
assigns = {'b' => 'bar', 'c' => 'baz'}
|
||||||
|
markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
|
||||||
|
assert_template_result(' YES ',"{% if #{markup} %} YES {% endif %}", assigns)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_unexpected_characters_silently_eat_logic_lax
|
||||||
|
with_error_mode(:lax) do
|
||||||
|
markup = "true && false"
|
||||||
|
assert_template_result(' YES ',"{% if #{markup} %} YES {% endif %}")
|
||||||
|
markup = "false || true"
|
||||||
|
assert_template_result('',"{% if #{markup} %} YES {% endif %}")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end # ParsingQuirksTest
|
end # ParsingQuirksTest
|
||||||
|
|||||||
@@ -38,4 +38,27 @@ class SecurityTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
assert_equal expected, Template.parse(text).render(@assigns, :filters => SecurityFilter)
|
assert_equal expected, Template.parse(text).render(@assigns, :filters => SecurityFilter)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_does_not_add_filters_to_symbol_table
|
||||||
|
current_symbols = Symbol.all_symbols
|
||||||
|
|
||||||
|
test = %( {{ "some_string" | a_bad_filter }} )
|
||||||
|
|
||||||
|
template = Template.parse(test)
|
||||||
|
assert_equal [], (Symbol.all_symbols - current_symbols)
|
||||||
|
|
||||||
|
template.render
|
||||||
|
assert_equal [], (Symbol.all_symbols - current_symbols)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_does_not_add_drop_methods_to_symbol_table
|
||||||
|
current_symbols = Symbol.all_symbols
|
||||||
|
|
||||||
|
drop = Drop.new
|
||||||
|
drop.invoke_drop("custom_method_1")
|
||||||
|
drop.invoke_drop("custom_method_2")
|
||||||
|
drop.invoke_drop("custom_method_3")
|
||||||
|
|
||||||
|
assert_equal [], (Symbol.all_symbols - current_symbols)
|
||||||
|
end
|
||||||
end # SecurityTest
|
end # SecurityTest
|
||||||
|
|||||||
@@ -1,9 +1,44 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class Filters
|
class Filters
|
||||||
include Liquid::StandardFilters
|
include Liquid::StandardFilters
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class TestThing
|
||||||
|
def initialize
|
||||||
|
@foo = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
"woot: #{@foo}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def [](whatever)
|
||||||
|
to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_liquid
|
||||||
|
@foo += 1
|
||||||
|
self
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class TestDrop < Liquid::Drop
|
||||||
|
def test
|
||||||
|
"testfoo"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class TestEnumerable < Liquid::Drop
|
||||||
|
include Enumerable
|
||||||
|
|
||||||
|
def each(&block)
|
||||||
|
[ { "foo" => 1, "bar" => 2 }, { "foo" => 2, "bar" => 1 }, { "foo" => 3, "bar" => 3 } ].each(&block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class StandardFiltersTest < Test::Unit::TestCase
|
class StandardFiltersTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
@@ -37,6 +72,7 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
assert_equal '1234567890', @filters.truncate('1234567890', 20)
|
assert_equal '1234567890', @filters.truncate('1234567890', 20)
|
||||||
assert_equal '...', @filters.truncate('1234567890', 0)
|
assert_equal '...', @filters.truncate('1234567890', 0)
|
||||||
assert_equal '1234567890', @filters.truncate('1234567890')
|
assert_equal '1234567890', @filters.truncate('1234567890')
|
||||||
|
assert_equal "测试...", @filters.truncate("测试测试测试测试", 5)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_strip
|
def test_strip
|
||||||
@@ -61,12 +97,16 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
assert_equal 'one two...', @filters.truncatewords('one two three', 2)
|
assert_equal 'one two...', @filters.truncatewords('one two three', 2)
|
||||||
assert_equal 'one two three', @filters.truncatewords('one two three')
|
assert_equal 'one two three', @filters.truncatewords('one two three')
|
||||||
assert_equal 'Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13”...', @filters.truncatewords('Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13” x 16” x 10.5” high) with cover.', 15)
|
assert_equal 'Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13”...', @filters.truncatewords('Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13” x 16” x 10.5” high) with cover.', 15)
|
||||||
|
assert_equal "测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_strip_html
|
def test_strip_html
|
||||||
assert_equal 'test', @filters.strip_html("<div>test</div>")
|
assert_equal 'test', @filters.strip_html("<div>test</div>")
|
||||||
assert_equal 'test', @filters.strip_html("<div id='test'>test</div>")
|
assert_equal 'test', @filters.strip_html("<div id='test'>test</div>")
|
||||||
assert_equal '', @filters.strip_html("<script type='text/javascript'>document.write('some stuff');</script>")
|
assert_equal '', @filters.strip_html("<script type='text/javascript'>document.write('some stuff');</script>")
|
||||||
|
assert_equal '', @filters.strip_html("<style type='text/css'>foo bar</style>")
|
||||||
|
assert_equal 'test', @filters.strip_html("<div\nclass='multiline'>test</div>")
|
||||||
|
assert_equal 'test', @filters.strip_html("<!-- foo bar \n test -->test")
|
||||||
assert_equal '', @filters.strip_html(nil)
|
assert_equal '', @filters.strip_html(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -80,12 +120,46 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
assert_equal [{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], @filters.sort([{"a" => 4}, {"a" => 3}, {"a" => 1}, {"a" => 2}], "a")
|
assert_equal [{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], @filters.sort([{"a" => 4}, {"a" => 3}, {"a" => 1}, {"a" => 2}], "a")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_reverse
|
||||||
|
assert_equal [4,3,2,1], @filters.reverse([1,2,3,4])
|
||||||
|
end
|
||||||
|
|
||||||
def test_map
|
def test_map
|
||||||
assert_equal [1,2,3,4], @filters.map([{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], 'a')
|
assert_equal [1,2,3,4], @filters.map([{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], 'a')
|
||||||
assert_template_result 'abc', "{{ ary | map:'foo' | map:'bar' }}",
|
assert_template_result 'abc', "{{ ary | map:'foo' | map:'bar' }}",
|
||||||
'ary' => [{'foo' => {'bar' => 'a'}}, {'foo' => {'bar' => 'b'}}, {'foo' => {'bar' => 'c'}}]
|
'ary' => [{'foo' => {'bar' => 'a'}}, {'foo' => {'bar' => 'b'}}, {'foo' => {'bar' => 'c'}}]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_map_doesnt_call_arbitrary_stuff
|
||||||
|
assert_equal "", Liquid::Template.parse('{{ "foo" | map: "__id__" }}').render
|
||||||
|
assert_equal "", Liquid::Template.parse('{{ "foo" | map: "inspect" }}').render
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_map_calls_to_liquid
|
||||||
|
t = TestThing.new
|
||||||
|
assert_equal "woot: 1", Liquid::Template.parse('{{ foo | map: "whatever" }}').render("foo" => [t])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_sort_calls_to_liquid
|
||||||
|
t = TestThing.new
|
||||||
|
assert_equal "woot: 1", Liquid::Template.parse('{{ foo | sort: "whatever" }}').render("foo" => [t])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_map_over_proc
|
||||||
|
drop = TestDrop.new
|
||||||
|
p = Proc.new{ drop }
|
||||||
|
templ = '{{ procs | map: "test" }}'
|
||||||
|
assert_equal "testfoo", Liquid::Template.parse(templ).render("procs" => [p])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_map_works_on_enumerables
|
||||||
|
assert_equal "123", Liquid::Template.parse('{{ foo | map: "foo" }}').render!("foo" => TestEnumerable.new)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_sort_works_on_enumerables
|
||||||
|
assert_equal "213", Liquid::Template.parse('{{ foo | sort: "bar" | map: "foo" }}').render!("foo" => TestEnumerable.new)
|
||||||
|
end
|
||||||
|
|
||||||
def test_date
|
def test_date
|
||||||
assert_equal 'May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B")
|
assert_equal 'May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B")
|
||||||
assert_equal 'June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B")
|
assert_equal 'June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B")
|
||||||
@@ -103,6 +177,8 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
assert_equal '07/05/2006', @filters.date("2006-07-05 10:00:00", "%m/%d/%Y")
|
assert_equal '07/05/2006', @filters.date("2006-07-05 10:00:00", "%m/%d/%Y")
|
||||||
|
|
||||||
assert_equal "07/16/2004", @filters.date("Fri Jul 16 01:00:00 2004", "%m/%d/%Y")
|
assert_equal "07/16/2004", @filters.date("Fri Jul 16 01:00:00 2004", "%m/%d/%Y")
|
||||||
|
assert_equal "#{Date.today.year}", @filters.date('now', '%Y')
|
||||||
|
assert_equal "#{Date.today.year}", @filters.date('today', '%Y')
|
||||||
|
|
||||||
assert_equal nil, @filters.date(nil, "%B")
|
assert_equal nil, @filters.date(nil, "%B")
|
||||||
|
|
||||||
@@ -119,9 +195,9 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_replace
|
def test_replace
|
||||||
assert_equal 'b b b b', @filters.replace("a a a a", 'a', 'b')
|
assert_equal '2 2 2 2', @filters.replace('1 1 1 1', '1', 2)
|
||||||
assert_equal 'b a a a', @filters.replace_first("a a a a", 'a', 'b')
|
assert_equal '2 1 1 1', @filters.replace_first('1 1 1 1', '1', 2)
|
||||||
assert_template_result 'b a a a', "{{ 'a a a a' | replace_first: 'a', 'b' }}"
|
assert_template_result '2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_remove
|
def test_remove
|
||||||
@@ -136,6 +212,7 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
def test_strip_newlines
|
def test_strip_newlines
|
||||||
assert_template_result 'abc', "{{ source | strip_newlines }}", 'source' => "a\nb\nc"
|
assert_template_result 'abc', "{{ source | strip_newlines }}", 'source' => "a\nb\nc"
|
||||||
|
assert_template_result 'abc', "{{ source | strip_newlines }}", 'source' => "a\r\nb\nc"
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_newlines_to_br
|
def test_newlines_to_br
|
||||||
@@ -160,6 +237,8 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
assert_match(/(6\.3)|(6\.(0{13})1)/, Template.parse("{{ '2.1' | times:3 }}").render)
|
assert_match(/(6\.3)|(6\.(0{13})1)/, Template.parse("{{ '2.1' | times:3 }}").render)
|
||||||
|
|
||||||
assert_template_result "6", "{{ '2.1' | times:3 | replace: '.','-' | plus:0}}"
|
assert_template_result "6", "{{ '2.1' | times:3 | replace: '.','-' | plus:0}}"
|
||||||
|
|
||||||
|
assert_template_result "7.25", "{{ 0.0725 | times:100 }}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_divided_by
|
def test_divided_by
|
||||||
@@ -171,6 +250,8 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
assert_template_result "5", "{{ 15 | divided_by:3 }}"
|
assert_template_result "5", "{{ 15 | divided_by:3 }}"
|
||||||
assert_template_result "Liquid error: divided by 0", "{{ 5 | divided_by:0 }}"
|
assert_template_result "Liquid error: divided by 0", "{{ 5 | divided_by:0 }}"
|
||||||
|
|
||||||
|
assert_template_result "0.5", "{{ 2.0 | divided_by:4 }}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_modulo
|
def test_modulo
|
||||||
|
|||||||
@@ -3,23 +3,50 @@ require 'test_helper'
|
|||||||
class StrainerTest < Test::Unit::TestCase
|
class StrainerTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
|
module AccessScopeFilters
|
||||||
|
def public_filter
|
||||||
|
"public"
|
||||||
|
end
|
||||||
|
|
||||||
|
def private_filter
|
||||||
|
"private"
|
||||||
|
end
|
||||||
|
private :private_filter
|
||||||
|
end
|
||||||
|
|
||||||
|
Strainer.global_filter(AccessScopeFilters)
|
||||||
|
|
||||||
def test_strainer
|
def test_strainer
|
||||||
strainer = Strainer.create(nil)
|
strainer = Strainer.create(nil)
|
||||||
assert_equal false, strainer.respond_to?('__test__')
|
assert_equal 5, strainer.invoke('size', 'input')
|
||||||
assert_equal false, strainer.respond_to?('test')
|
assert_equal "public", strainer.invoke("public_filter")
|
||||||
assert_equal false, strainer.respond_to?('instance_eval')
|
|
||||||
assert_equal false, strainer.respond_to?('__send__')
|
|
||||||
assert_equal true, strainer.respond_to?('size') # from the standard lib
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_should_respond_to_two_parameters
|
def test_strainer_only_invokes_public_filter_methods
|
||||||
strainer = Strainer.create(nil)
|
strainer = Strainer.create(nil)
|
||||||
assert_equal true, strainer.respond_to?('size', false)
|
assert_equal false, strainer.invokable?('__test__')
|
||||||
|
assert_equal false, strainer.invokable?('test')
|
||||||
|
assert_equal false, strainer.invokable?('instance_eval')
|
||||||
|
assert_equal false, strainer.invokable?('__send__')
|
||||||
|
assert_equal true, strainer.invokable?('size') # from the standard lib
|
||||||
end
|
end
|
||||||
|
|
||||||
# Asserts that Object#respond_to_missing? is not being undefined in Ruby versions where it has been implemented
|
def test_strainer_returns_nil_if_no_filter_method_found
|
||||||
# Currently this method is only present in Ruby v1.9.2, or higher
|
strainer = Strainer.create(nil)
|
||||||
def test_object_respond_to_missing
|
assert_nil strainer.invoke("private_filter")
|
||||||
assert_equal Object.respond_to?(:respond_to_missing?), Strainer.create(nil).respond_to?(:respond_to_missing?)
|
assert_nil strainer.invoke("undef_the_filter")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_strainer_returns_first_argument_if_no_method_and_arguments_given
|
||||||
|
strainer = Strainer.create(nil)
|
||||||
|
assert_equal "password", strainer.invoke("undef_the_method", "password")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_strainer_only_allows_methods_defined_in_filters
|
||||||
|
strainer = Strainer.create(nil)
|
||||||
|
assert_equal "1 + 1", strainer.invoke("instance_eval", "1 + 1")
|
||||||
|
assert_equal "puts", strainer.invoke("__send__", "puts", "Hi Mom")
|
||||||
|
assert_equal "has_method?", strainer.invoke("invoke", "has_method?", "invoke")
|
||||||
|
end
|
||||||
|
|
||||||
end # StrainerTest
|
end # StrainerTest
|
||||||
|
|||||||
@@ -281,4 +281,17 @@ HERE
|
|||||||
def test_blank_string_not_iterable
|
def test_blank_string_not_iterable
|
||||||
assert_template_result('', "{% for char in characters %}I WILL NOT BE OUTPUT{% endfor %}", 'characters' => '')
|
assert_template_result('', "{% for char in characters %}I WILL NOT BE OUTPUT{% endfor %}", 'characters' => '')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_bad_variable_naming_in_for_loop
|
||||||
|
assert_raise(Liquid::SyntaxError) do
|
||||||
|
Liquid::Template.parse('{% for a/b in x %}{% endfor %}')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_spacing_with_variable_naming_in_for_loop
|
||||||
|
expected = '12345'
|
||||||
|
template = '{% for item in items %}{{item}}{% endfor %}'
|
||||||
|
assigns = {'items' => [1,2,3,4,5]}
|
||||||
|
assert_template_result(expected, template, assigns)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -39,6 +39,15 @@ class OtherFileSystem
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class CountingFileSystem
|
||||||
|
attr_reader :count
|
||||||
|
def read_template_file(template_path, context)
|
||||||
|
@count ||= 0
|
||||||
|
@count += 1
|
||||||
|
'from CountingFileSystem'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class IncludeTagTest < Test::Unit::TestCase
|
class IncludeTagTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
@@ -136,4 +145,22 @@ class IncludeTagTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
assert_equal "Product: Draft 151cm ", Template.parse("{% include template for product %}").render("template" => 'product', 'product' => { 'title' => 'Draft 151cm'})
|
assert_equal "Product: Draft 151cm ", Template.parse("{% include template for product %}").render("template" => 'product', 'product' => { 'title' => 'Draft 151cm'})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_include_tag_caches_second_read_of_same_partial
|
||||||
|
file_system = CountingFileSystem.new
|
||||||
|
assert_equal 'from CountingFileSystemfrom CountingFileSystem',
|
||||||
|
Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render({}, :registers => {:file_system => file_system})
|
||||||
|
assert_equal 1, file_system.count
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_include_tag_doesnt_cache_partials_across_renders
|
||||||
|
file_system = CountingFileSystem.new
|
||||||
|
assert_equal 'from CountingFileSystem',
|
||||||
|
Template.parse("{% include 'pick_a_source' %}").render({}, :registers => {:file_system => file_system})
|
||||||
|
assert_equal 1, file_system.count
|
||||||
|
|
||||||
|
assert_equal 'from CountingFileSystem',
|
||||||
|
Template.parse("{% include 'pick_a_source' %}").render({}, :registers => {:file_system => file_system})
|
||||||
|
assert_equal 2, file_system.count
|
||||||
|
end
|
||||||
end # IncludeTagTest
|
end # IncludeTagTest
|
||||||
@@ -9,7 +9,16 @@ class RawTagTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_output_in_raw
|
def test_output_in_raw
|
||||||
assert_template_result '{{ test }}',
|
assert_template_result '{{ test }}', '{% raw %}{{ test }}{% endraw %}'
|
||||||
'{% raw %}{{ test }}{% endraw %}'
|
end
|
||||||
|
|
||||||
|
def test_open_tag_in_raw
|
||||||
|
assert_template_result ' Foobar {% invalid ', '{% raw %} Foobar {% invalid {% endraw %}'
|
||||||
|
assert_template_result ' Foobar invalid %} ', '{% raw %} Foobar invalid %} {% endraw %}'
|
||||||
|
assert_template_result ' Foobar {{ invalid ', '{% raw %} Foobar {{ invalid {% endraw %}'
|
||||||
|
assert_template_result ' Foobar invalid }} ', '{% raw %} Foobar invalid }} {% endraw %}'
|
||||||
|
assert_template_result ' Foobar {% invalid {% {% endraw ', '{% raw %} Foobar {% invalid {% {% endraw {% endraw %}'
|
||||||
|
assert_template_result ' Foobar {% {% {% ', '{% raw %} Foobar {% {% {% {% endraw %}'
|
||||||
|
assert_template_result ' test {% raw %} {% endraw %}', '{% raw %} test {% raw %} {% {% endraw %}endraw %}'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,19 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
|
class TemplateContextDrop < Liquid::Drop
|
||||||
|
def before_method(method)
|
||||||
|
method
|
||||||
|
end
|
||||||
|
|
||||||
|
def foo
|
||||||
|
'fizzbuzz'
|
||||||
|
end
|
||||||
|
|
||||||
|
def baz
|
||||||
|
@context.registers['lulz']
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class TemplateTest < Test::Unit::TestCase
|
class TemplateTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
@@ -71,4 +85,76 @@ class TemplateTest < Test::Unit::TestCase
|
|||||||
assert_equal '1', t.render(assigns)
|
assert_equal '1', t.render(assigns)
|
||||||
@global = nil
|
@global = nil
|
||||||
end
|
end
|
||||||
end # TemplateTest
|
|
||||||
|
def test_resource_limits_render_length
|
||||||
|
t = Template.parse("0123456789")
|
||||||
|
t.resource_limits = { :render_length_limit => 5 }
|
||||||
|
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
||||||
|
assert t.resource_limits[:reached]
|
||||||
|
t.resource_limits = { :render_length_limit => 10 }
|
||||||
|
assert_equal "0123456789", t.render()
|
||||||
|
assert_not_nil t.resource_limits[:render_length_current]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_resource_limits_render_score
|
||||||
|
t = Template.parse("{% for a in (1..10) %} {% for a in (1..10) %} foo {% endfor %} {% endfor %}")
|
||||||
|
t.resource_limits = { :render_score_limit => 50 }
|
||||||
|
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
||||||
|
assert t.resource_limits[:reached]
|
||||||
|
t = Template.parse("{% for a in (1..100) %} foo {% endfor %}")
|
||||||
|
t.resource_limits = { :render_score_limit => 50 }
|
||||||
|
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
||||||
|
assert t.resource_limits[:reached]
|
||||||
|
t.resource_limits = { :render_score_limit => 200 }
|
||||||
|
assert_equal (" foo " * 100), t.render()
|
||||||
|
assert_not_nil t.resource_limits[:render_score_current]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_resource_limits_assign_score
|
||||||
|
t = Template.parse("{% assign foo = 42 %}{% assign bar = 23 %}")
|
||||||
|
t.resource_limits = { :assign_score_limit => 1 }
|
||||||
|
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
||||||
|
assert t.resource_limits[:reached]
|
||||||
|
t.resource_limits = { :assign_score_limit => 2 }
|
||||||
|
assert_equal "", t.render()
|
||||||
|
assert_not_nil t.resource_limits[:assign_score_current]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_resource_limits_aborts_rendering_after_first_error
|
||||||
|
t = Template.parse("{% for a in (1..100) %} foo1 {% endfor %} bar {% for a in (1..100) %} foo2 {% endfor %}")
|
||||||
|
t.resource_limits = { :render_score_limit => 50 }
|
||||||
|
assert_equal "Liquid error: Memory limits exceeded", t.render()
|
||||||
|
assert t.resource_limits[:reached]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_resource_limits_hash_in_template_gets_updated_even_if_no_limits_are_set
|
||||||
|
t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")
|
||||||
|
t.render()
|
||||||
|
assert t.resource_limits[:assign_score_current] > 0
|
||||||
|
assert t.resource_limits[:render_score_current] > 0
|
||||||
|
assert t.resource_limits[:render_length_current] > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_can_use_drop_as_context
|
||||||
|
t = Template.new
|
||||||
|
t.registers['lulz'] = 'haha'
|
||||||
|
drop = TemplateContextDrop.new
|
||||||
|
assert_equal 'fizzbuzz', t.parse('{{foo}}').render(drop)
|
||||||
|
assert_equal 'bar', t.parse('{{bar}}').render(drop)
|
||||||
|
assert_equal 'haha', t.parse("{{baz}}").render(drop)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_sets_default_localization_in_document
|
||||||
|
t = Template.new
|
||||||
|
t.parse('')
|
||||||
|
assert_instance_of I18n, t.root.options[:locale]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_sets_default_localization_in_context_with_quick_initialization
|
||||||
|
t = Template.new
|
||||||
|
t.parse('{{foo}}', :locale => I18n.new(fixture("en_locale.yml")))
|
||||||
|
|
||||||
|
assert_instance_of I18n, t.root.options[:locale]
|
||||||
|
assert_equal fixture("en_locale.yml"), t.root.options[:locale].path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|||||||
@@ -11,67 +11,77 @@ class VariableTest < Test::Unit::TestCase
|
|||||||
def test_filters
|
def test_filters
|
||||||
var = Variable.new('hello | textileze')
|
var = Variable.new('hello | textileze')
|
||||||
assert_equal 'hello', var.name
|
assert_equal 'hello', var.name
|
||||||
assert_equal [[:textileze,[]]], var.filters
|
assert_equal [["textileze",[]]], var.filters
|
||||||
|
|
||||||
var = Variable.new('hello | textileze | paragraph')
|
var = Variable.new('hello | textileze | paragraph')
|
||||||
assert_equal 'hello', var.name
|
assert_equal 'hello', var.name
|
||||||
assert_equal [[:textileze,[]], [:paragraph,[]]], var.filters
|
assert_equal [["textileze",[]], ["paragraph",[]]], var.filters
|
||||||
|
|
||||||
var = Variable.new(%! hello | strftime: '%Y'!)
|
var = Variable.new(%! hello | strftime: '%Y'!)
|
||||||
assert_equal 'hello', var.name
|
assert_equal 'hello', var.name
|
||||||
assert_equal [[:strftime,["'%Y'"]]], var.filters
|
assert_equal [["strftime",["'%Y'"]]], var.filters
|
||||||
|
|
||||||
var = Variable.new(%! 'typo' | link_to: 'Typo', true !)
|
var = Variable.new(%! 'typo' | link_to: 'Typo', true !)
|
||||||
assert_equal %!'typo'!, var.name
|
assert_equal %!'typo'!, var.name
|
||||||
assert_equal [[:link_to,["'Typo'", "true"]]], var.filters
|
assert_equal [["link_to",["'Typo'", "true"]]], var.filters
|
||||||
|
|
||||||
var = Variable.new(%! 'typo' | link_to: 'Typo', false !)
|
var = Variable.new(%! 'typo' | link_to: 'Typo', false !)
|
||||||
assert_equal %!'typo'!, var.name
|
assert_equal %!'typo'!, var.name
|
||||||
assert_equal [[:link_to,["'Typo'", "false"]]], var.filters
|
assert_equal [["link_to",["'Typo'", "false"]]], var.filters
|
||||||
|
|
||||||
var = Variable.new(%! 'foo' | repeat: 3 !)
|
var = Variable.new(%! 'foo' | repeat: 3 !)
|
||||||
assert_equal %!'foo'!, var.name
|
assert_equal %!'foo'!, var.name
|
||||||
assert_equal [[:repeat,["3"]]], var.filters
|
assert_equal [["repeat",["3"]]], var.filters
|
||||||
|
|
||||||
var = Variable.new(%! 'foo' | repeat: 3, 3 !)
|
var = Variable.new(%! 'foo' | repeat: 3, 3 !)
|
||||||
assert_equal %!'foo'!, var.name
|
assert_equal %!'foo'!, var.name
|
||||||
assert_equal [[:repeat,["3","3"]]], var.filters
|
assert_equal [["repeat",["3","3"]]], var.filters
|
||||||
|
|
||||||
var = Variable.new(%! 'foo' | repeat: 3, 3, 3 !)
|
var = Variable.new(%! 'foo' | repeat: 3, 3, 3 !)
|
||||||
assert_equal %!'foo'!, var.name
|
assert_equal %!'foo'!, var.name
|
||||||
assert_equal [[:repeat,["3","3","3"]]], var.filters
|
assert_equal [["repeat",["3","3","3"]]], var.filters
|
||||||
|
|
||||||
var = Variable.new(%! hello | strftime: '%Y, okay?'!)
|
var = Variable.new(%! hello | strftime: '%Y, okay?'!)
|
||||||
assert_equal 'hello', var.name
|
assert_equal 'hello', var.name
|
||||||
assert_equal [[:strftime,["'%Y, okay?'"]]], var.filters
|
assert_equal [["strftime",["'%Y, okay?'"]]], var.filters
|
||||||
|
|
||||||
var = Variable.new(%! hello | things: "%Y, okay?", 'the other one'!)
|
var = Variable.new(%! hello | things: "%Y, okay?", 'the other one'!)
|
||||||
assert_equal 'hello', var.name
|
assert_equal 'hello', var.name
|
||||||
assert_equal [[:things,["\"%Y, okay?\"","'the other one'"]]], var.filters
|
assert_equal [["things",["\"%Y, okay?\"","'the other one'"]]], var.filters
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_filter_with_date_parameter
|
def test_filter_with_date_parameter
|
||||||
|
|
||||||
var = Variable.new(%! '2006-06-06' | date: "%m/%d/%Y"!)
|
var = Variable.new(%! '2006-06-06' | date: "%m/%d/%Y"!)
|
||||||
assert_equal "'2006-06-06'", var.name
|
assert_equal "'2006-06-06'", var.name
|
||||||
assert_equal [[:date,["\"%m/%d/%Y\""]]], var.filters
|
assert_equal [["date",["\"%m/%d/%Y\""]]], var.filters
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_filters_without_whitespace
|
def test_filters_without_whitespace
|
||||||
var = Variable.new('hello | textileze | paragraph')
|
var = Variable.new('hello | textileze | paragraph')
|
||||||
assert_equal 'hello', var.name
|
assert_equal 'hello', var.name
|
||||||
assert_equal [[:textileze,[]], [:paragraph,[]]], var.filters
|
assert_equal [["textileze",[]], ["paragraph",[]]], var.filters
|
||||||
|
|
||||||
var = Variable.new('hello|textileze|paragraph')
|
var = Variable.new('hello|textileze|paragraph')
|
||||||
assert_equal 'hello', var.name
|
assert_equal 'hello', var.name
|
||||||
assert_equal [[:textileze,[]], [:paragraph,[]]], var.filters
|
assert_equal [["textileze",[]], ["paragraph",[]]], var.filters
|
||||||
|
|
||||||
|
var = Variable.new("hello|replace:'foo','bar'|textileze")
|
||||||
|
assert_equal 'hello', var.name
|
||||||
|
assert_equal [["replace", ["'foo'", "'bar'"]], ["textileze", []]], var.filters
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_symbol
|
def test_symbol
|
||||||
var = Variable.new("http://disney.com/logo.gif | image: 'med' ")
|
var = Variable.new("http://disney.com/logo.gif | image: 'med' ", :error_mode => :lax)
|
||||||
assert_equal 'http://disney.com/logo.gif', var.name
|
assert_equal "http://disney.com/logo.gif", var.name
|
||||||
assert_equal [[:image,["'med'"]]], var.filters
|
assert_equal [["image",["'med'"]]], var.filters
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_string_to_filter
|
||||||
|
var = Variable.new("'http://disney.com/logo.gif' | image: 'med' ")
|
||||||
|
assert_equal "'http://disney.com/logo.gif'", var.name
|
||||||
|
assert_equal [["image",["'med'"]]], var.filters
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_string_single_quoted
|
def test_string_single_quoted
|
||||||
@@ -103,6 +113,26 @@ class VariableTest < Test::Unit::TestCase
|
|||||||
var = Variable.new(%| test.test |)
|
var = Variable.new(%| test.test |)
|
||||||
assert_equal 'test.test', var.name
|
assert_equal 'test.test', var.name
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_filter_with_keyword_arguments
|
||||||
|
var = Variable.new(%! hello | things: greeting: "world", farewell: 'goodbye'!)
|
||||||
|
assert_equal 'hello', var.name
|
||||||
|
assert_equal [['things',["greeting: \"world\"","farewell: 'goodbye'"]]], var.filters
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_lax_filter_argument_parsing
|
||||||
|
var = Variable.new(%! number_of_comments | pluralize: 'comment': 'comments' !, :error_mode => :lax)
|
||||||
|
assert_equal 'number_of_comments', var.name
|
||||||
|
assert_equal [['pluralize',["'comment'","'comments'"]]], var.filters
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_strict_filter_argument_parsing
|
||||||
|
with_error_mode(:strict) do
|
||||||
|
assert_raises(SyntaxError) do
|
||||||
|
Variable.new(%! number_of_comments | pluralize: 'comment': 'comments' !)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,26 @@ begin
|
|||||||
rescue LoadError
|
rescue LoadError
|
||||||
puts "Couldn't load ruby-debug. gem install ruby-debug if you need it."
|
puts "Couldn't load ruby-debug. gem install ruby-debug if you need it."
|
||||||
end
|
end
|
||||||
require File.join(File.dirname(__FILE__), '..', 'lib', 'liquid')
|
|
||||||
|
$:.unshift(File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib'))
|
||||||
|
require 'liquid.rb'
|
||||||
|
|
||||||
|
mode = :strict
|
||||||
|
if env_mode = ENV['LIQUID_PARSER_MODE']
|
||||||
|
puts "-- #{env_mode.upcase} ERROR MODE"
|
||||||
|
mode = env_mode.to_sym
|
||||||
|
end
|
||||||
|
Liquid::Template.error_mode = mode
|
||||||
|
|
||||||
|
|
||||||
module Test
|
module Test
|
||||||
module Unit
|
module Unit
|
||||||
|
class TestCase
|
||||||
|
def fixture(name)
|
||||||
|
File.join(File.expand_path(File.dirname(__FILE__)), "fixtures", name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
module Assertions
|
module Assertions
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
@@ -24,6 +39,20 @@ module Test
|
|||||||
|
|
||||||
assert_match expected, Template.parse(template).render(assigns)
|
assert_match expected, Template.parse(template).render(assigns)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def assert_match_syntax_error(match, template, registers = {})
|
||||||
|
exception = assert_raise(Liquid::SyntaxError) {
|
||||||
|
Template.parse(template).render(assigns)
|
||||||
|
}
|
||||||
|
assert_match match, exception.message
|
||||||
|
end
|
||||||
|
|
||||||
|
def with_error_mode(mode)
|
||||||
|
old_mode = Liquid::Template.error_mode
|
||||||
|
Liquid::Template.error_mode = mode
|
||||||
|
yield
|
||||||
|
Liquid::Template.error_mode = old_mode
|
||||||
|
end
|
||||||
end # Assertions
|
end # Assertions
|
||||||
end # Unit
|
end # Unit
|
||||||
end # Test
|
end # Test
|
||||||
|
|||||||
Reference in New Issue
Block a user