mirror of
https://github.com/kemko/liquid.git
synced 2026-01-02 08:15:41 +03:00
Compare commits
16 Commits
objects-op
...
liquid_c_e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb807e9484 | ||
|
|
e53884ce08 | ||
|
|
78fe69259d | ||
|
|
afe8474a2b | ||
|
|
e404f6d8e1 | ||
|
|
8867d6a65f | ||
|
|
141fa898d0 | ||
|
|
58b04bae74 | ||
|
|
28737171f7 | ||
|
|
594b6da8c5 | ||
|
|
cf5fa5923d | ||
|
|
7cc10b9abf | ||
|
|
20beec2de3 | ||
|
|
06486fd21d | ||
|
|
7dcf578898 | ||
|
|
006e7475f4 |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -4,5 +4,8 @@
|
|||||||
pkg
|
pkg
|
||||||
*.rbc
|
*.rbc
|
||||||
.rvmrc
|
.rvmrc
|
||||||
.ruby-version
|
*.o
|
||||||
Gemfile.lock
|
*.bundle
|
||||||
|
ext/liquid/Makefile
|
||||||
|
ext/liquid/liquid_context.c
|
||||||
|
|
||||||
|
|||||||
21
.travis.yml
21
.travis.yml
@@ -1,18 +1,11 @@
|
|||||||
language: ruby
|
|
||||||
|
|
||||||
rvm:
|
rvm:
|
||||||
- 2.0
|
- 1.8.7
|
||||||
- 2.1
|
- 1.9.3
|
||||||
- 2.2
|
- ree
|
||||||
- ruby-head
|
- jruby-18mode
|
||||||
- jruby-head
|
- jruby-19mode
|
||||||
- rbx-2
|
- rbx-18mode
|
||||||
|
- rbx-19mode
|
||||||
sudo: false
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
allow_failures:
|
|
||||||
- rvm: jruby-head
|
|
||||||
|
|
||||||
script: "rake test"
|
script: "rake test"
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
# How to contribute
|
|
||||||
|
|
||||||
## Things we will merge
|
|
||||||
|
|
||||||
* Bugfixes
|
|
||||||
* Performance improvements
|
|
||||||
* Features which are likely to be useful to the majority of Liquid users
|
|
||||||
|
|
||||||
## Things we won't merge
|
|
||||||
|
|
||||||
* Code which introduces considerable performance degrations
|
|
||||||
* Code which touches performance critical parts of Liquid and comes without benchmarks
|
|
||||||
* Features which are not important for most people (we want to keep the core Liquid code small and tidy)
|
|
||||||
* Features which can easily be implemented on top of Liquid (for example as a custom filter or custom filesystem)
|
|
||||||
* Code which comes without tests
|
|
||||||
* Code which breaks existing tests
|
|
||||||
|
|
||||||
## Workflow
|
|
||||||
|
|
||||||
* Fork the Liquid repository
|
|
||||||
* Create a new branch in your fork
|
|
||||||
* If it makes sense, add tests for your code and run a performance benchmark
|
|
||||||
* Make sure all tests pass
|
|
||||||
* Create a pull request
|
|
||||||
* In the description, ping one of [@boourns](https://github.com/boourns), [@fw42](https://github.com/fw42), [@camilo](https://github.com/camilo), [@dylanahsmith](https://github.com/dylanahsmith), or [@arthurnn](https://github.com/arthurnn) and ask for a code review.
|
|
||||||
|
|
||||||
10
Gemfile
10
Gemfile
@@ -1,10 +0,0 @@
|
|||||||
source 'https://rubygems.org'
|
|
||||||
|
|
||||||
gemspec
|
|
||||||
gem 'stackprof', platforms: :mri_21
|
|
||||||
gem 'allocation_tracer', platforms: :mri_21
|
|
||||||
|
|
||||||
group :test do
|
|
||||||
gem 'spy', '0.4.1'
|
|
||||||
gem 'benchmark-ips'
|
|
||||||
end
|
|
||||||
114
History.md
114
History.md
@@ -1,115 +1,4 @@
|
|||||||
# Liquid Change Log
|
# Liquid Version History
|
||||||
|
|
||||||
## 4.0.0 / not yet released / branch "master"
|
|
||||||
### Changed
|
|
||||||
* Add forloop.parentloop as a reference to the parent loop (#520) [Justin Li, pushrax]
|
|
||||||
* Block parsing moved to BlockBody class (#458) [Dylan Thacker-Smith, dylanahsmith]
|
|
||||||
* Add concat filter to concatenate arrays (#429) [Diogo Beato, dvbeato]
|
|
||||||
* Ruby 1.9 support dropped (#491) [Justin Li, pushrax]
|
|
||||||
* Liquid::Template.file_system's read_template_file method is no longer passed the context. (#441) [James Reid-Smith, sunblaze]
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
* Fix capturing into variables with a hyphen in the name (#505) [Florian Weingarten, fw42]
|
|
||||||
* Fix case sensitivity regression in date standard filter (#499) [Kelley Reynolds, kreynolds]
|
|
||||||
* Disallow filters with no variable in strict mode (#475) [Justin Li, pushrax]
|
|
||||||
* Disallow variable names in the strict parser that are not valid in the lax parser (#463) [Justin Li, pushrax]
|
|
||||||
* Fix BlockBody#warnings taking exponential time to compute (#486) [Justin Li, pushrax]
|
|
||||||
|
|
||||||
## 3.0.2 / 2015-04-24 / branch "3-0-stable"
|
|
||||||
|
|
||||||
* Expose VariableLookup private members (#551) [Justin Li, pushrax]
|
|
||||||
* Documentation fixes
|
|
||||||
|
|
||||||
## 3.0.1 / 2015-01-23
|
|
||||||
|
|
||||||
* Remove duplicate `index0` key in TableRow tag (#502) [Alfred Xing]
|
|
||||||
|
|
||||||
## 3.0.0 / 2014-11-12
|
|
||||||
|
|
||||||
* Removed Block#end_tag. Instead, override parse with `super` followed by your code. See #446 [Dylan Thacker-Smith, dylanahsmith]
|
|
||||||
* Fixed condition with wrong data types (#423) [Bogdan Gusiev]
|
|
||||||
* Add url_encode to standard filters (#421) [Derrick Reimer, djreimer]
|
|
||||||
* Add uniq to standard filters [Florian Weingarten, fw42]
|
|
||||||
* Add exception_handler feature (#397) and #254 [Bogdan Gusiev, bogdan and Florian Weingarten, fw42]
|
|
||||||
* Optimize variable parsing to avoid repeated regex evaluation during template rendering #383 [Jason Hiltz-Laforge, jasonhl]
|
|
||||||
* Optimize checking for block interrupts to reduce object allocation #380 [Jason Hiltz-Laforge, jasonhl]
|
|
||||||
* Properly set context rethrow_errors on render! #349 [Thierry Joyal, tjoyal]
|
|
||||||
* Fix broken rendering of variables which are equal to false (#345) [Florian Weingarten, fw42]
|
|
||||||
* Remove ActionView template handler [Dylan Thacker-Smith, dylanahsmith]
|
|
||||||
* Freeze lots of string literals for new Ruby 2.1 optimization (#297) [Florian Weingarten, fw42]
|
|
||||||
* Allow newlines in tags and variables (#324) [Dylan Thacker-Smith, dylanahsmith]
|
|
||||||
* Tag#parse is called after initialize, which now takes options instead of tokens as the 3rd argument. See #321 [Dylan Thacker-Smith, dylanahsmith]
|
|
||||||
* Raise `Liquid::ArgumentError` instead of `::ArgumentError` when filter has wrong number of arguments #309 [Bogdan Gusiev, bogdan]
|
|
||||||
* Add a to_s default for liquid drops (#306) [Adam Doeler, releod]
|
|
||||||
* Add strip, lstrip, and rstrip to standard filters [Florian Weingarten, fw42]
|
|
||||||
* Make if, for & case tags return complete and consistent nodelists (#250) [Nick Jones, dntj]
|
|
||||||
* Prevent arbitrary method invocation on condition objects (#274) [Dylan Thacker-Smith, dylanahsmith]
|
|
||||||
* Don't call to_sym when creating conditions for security reasons (#273) [Bouke van der Bijl, bouk]
|
|
||||||
* Fix resource counting bug with respond_to?(:length) (#263) [Florian Weingarten, fw42]
|
|
||||||
* Allow specifying custom patterns for template filenames (#284) [Andrei Gladkyi, agladkyi]
|
|
||||||
* Allow drops to optimize loading a slice of elements (#282) [Tom Burns, boourns]
|
|
||||||
* Support for passing variables to snippets in subdirs (#271) [Joost Hietbrink, joost]
|
|
||||||
* Add a class cache to avoid runtime extend calls (#249) [James Tucker, raggi]
|
|
||||||
* Remove some legacy Ruby 1.8 compatibility code (#276) [Florian Weingarten, fw42]
|
|
||||||
* Add default filter to standard filters (#267) [Derrick Reimer, djreimer]
|
|
||||||
* Add optional strict parsing and warn parsing (#235) [Tristan Hume, trishume]
|
|
||||||
* Add I18n syntax error translation (#241) [Simon Hørup Eskildsen, Sirupsen]
|
|
||||||
* Make sort filter work on enumerable drops (#239) [Florian Weingarten, fw42]
|
|
||||||
* Fix clashing method names in enumerable drops (#238) [Florian Weingarten, fw42]
|
|
||||||
* Make map filter work on enumerable drops (#233) [Florian Weingarten, fw42]
|
|
||||||
* Improved whitespace stripping for blank blocks, related to #216 [Florian Weingarten, fw42]
|
|
||||||
|
|
||||||
## 2.6.1 / 2014-01-10 / branch "2-6-stable"
|
|
||||||
|
|
||||||
Security fix, cherry-picked from master (4e14a65):
|
|
||||||
* Don't call to_sym when creating conditions for security reasons (#273) [Bouke van der Bijl, bouk]
|
|
||||||
* Prevent arbitrary method invocation on condition objects (#274) [Dylan Thacker-Smith, dylanahsmith]
|
|
||||||
|
|
||||||
## 2.6.0 / 2013-11-25
|
|
||||||
|
|
||||||
IMPORTANT: Liquid 2.6 is going to be the last version of Liquid which maintains explicit Ruby 1.8 compatability.
|
|
||||||
The following releases will only be tested against Ruby 1.9 and Ruby 2.0 and are likely to break on Ruby 1.8.
|
|
||||||
|
|
||||||
* Bugfix for #106: fix example servlet [gnowoel]
|
|
||||||
* Bugfix for #97: strip_html filter supports multi-line tags [Jo Liss, joliss]
|
|
||||||
* Bugfix for #114: strip_html filter supports style tags [James Allardice, jamesallardice]
|
|
||||||
* Bugfix for #117: 'now' support for date filter in Ruby 1.9 [Notre Dame Webgroup, ndwebgroup]
|
|
||||||
* Bugfix for #166: truncate filter on UTF-8 strings with Ruby 1.8 [Florian Weingarten, fw42]
|
|
||||||
* Bugfix for #204: 'raw' parsing bug [Florian Weingarten, fw42]
|
|
||||||
* Bugfix for #150: 'for' parsing bug [Peter Schröder, phoet]
|
|
||||||
* Bugfix for #126: Strip CRLF in strip_newline [Peter Schröder, phoet]
|
|
||||||
* Bugfix for #174, "can't convert Fixnum into String" for "replace" [wǒ_is神仙, jsw0528]
|
|
||||||
* Allow a Liquid::Drop to be passed into Template#render [Daniel Huckstep, darkhelmet]
|
|
||||||
* Resource limits [Florian Weingarten, fw42]
|
|
||||||
* Add reverse filter [Jay Strybis, unreal]
|
|
||||||
* Add utf-8 support
|
|
||||||
* Use array instead of Hash to keep the registered filters [Tasos Stathopoulos, astathopoulos]
|
|
||||||
* Cache tokenized partial templates [Tom Burns, boourns]
|
|
||||||
* Avoid warnings in Ruby 1.9.3 [Marcus Stollsteimer, stomar]
|
|
||||||
* Better documentation for 'include' tag (closes #163) [Peter Schröder, phoet]
|
|
||||||
* Use of BigDecimal on filters to have better precision (closes #155) [Arthur Nogueira Neves, arthurnn]
|
|
||||||
|
|
||||||
## 2.5.5 / 2014-01-10 / branch "2-5-stable"
|
|
||||||
|
|
||||||
Security fix, cherry-picked from master (4e14a65):
|
|
||||||
* Don't call to_sym when creating conditions for security reasons (#273) [Bouke van der Bijl, bouk]
|
|
||||||
* Prevent arbitrary method invocation on condition objects (#274) [Dylan Thacker-Smith, dylanahsmith]
|
|
||||||
|
|
||||||
## 2.5.4 / 2013-11-11
|
|
||||||
|
|
||||||
* Fix "can't convert Fixnum into String" for "replace" (#173), [wǒ_is神仙, jsw0528]
|
|
||||||
|
|
||||||
## 2.5.3 / 2013-10-09
|
|
||||||
|
|
||||||
* #232, #234, #237: Fix map filter bugs [Florian Weingarten, fw42]
|
|
||||||
|
|
||||||
## 2.5.2 / 2013-09-03 / deleted
|
|
||||||
|
|
||||||
Yanked from rubygems, as it contained too many changes that broke compatibility. Those changes will be on following major releases.
|
|
||||||
|
|
||||||
## 2.5.1 / 2013-07-24
|
|
||||||
|
|
||||||
* #230: Fix security issue with map filter, Use invoke_drop in map filter [Florian Weingarten, fw42]
|
|
||||||
|
|
||||||
## 2.5.0 / 2013-03-06
|
## 2.5.0 / 2013-03-06
|
||||||
|
|
||||||
@@ -119,7 +8,6 @@ Yanked from rubygems, as it contained too many changes that broke compatibility.
|
|||||||
* Fix filter parser for args without space separators
|
* Fix filter parser for args without space separators
|
||||||
* Add support for filter keyword arguments
|
* Add support for filter keyword arguments
|
||||||
|
|
||||||
|
|
||||||
## 2.4.0 / 2012-08-03
|
## 2.4.0 / 2012-08-03
|
||||||
|
|
||||||
* Performance improvements
|
* Performance improvements
|
||||||
|
|||||||
33
README.md
33
README.md
@@ -1,14 +1,5 @@
|
|||||||
[](http://travis-ci.org/Shopify/liquid)
|
|
||||||
[](http://inch-ci.org/github/Shopify/liquid)
|
|
||||||
|
|
||||||
# Liquid template engine
|
# Liquid template engine
|
||||||
|
|
||||||
* [Contributing guidelines](CONTRIBUTING.md)
|
|
||||||
* [Version history](History.md)
|
|
||||||
* [Liquid documentation from Shopify](http://docs.shopify.com/themes/liquid-basics)
|
|
||||||
* [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki)
|
|
||||||
* [Website](http://liquidmarkup.org/)
|
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Liquid is a template engine which was written with very specific requirements:
|
Liquid is a template engine which was written with very specific requirements:
|
||||||
@@ -50,26 +41,4 @@ For standard use you can just pass it the content of a file and call render with
|
|||||||
@template.render('name' => 'tobi') # => "hi tobi"
|
@template.render('name' => 'tobi') # => "hi tobi"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Error Modes
|
[](http://travis-ci.org/Shopify/liquid)
|
||||||
|
|
||||||
Setting the error mode of Liquid lets you specify how strictly you want your templates to be interpreted.
|
|
||||||
Normally the parser is very lax and will accept almost anything without error. Unfortunately this can make
|
|
||||||
it very hard to debug and can lead to unexpected behaviour.
|
|
||||||
|
|
||||||
Liquid also comes with a stricter parser that can be used when editing templates to give better error messages
|
|
||||||
when templates are invalid. You can enable this new parser like this:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
Liquid::Template.error_mode = :strict # Raises a SyntaxError when invalid syntax is used
|
|
||||||
Liquid::Template.error_mode = :warn # Adds errors to template.errors but continues as normal
|
|
||||||
Liquid::Template.error_mode = :lax # The default mode, accepts almost anything.
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want to set the error mode only on specific templates you can pass `:error_mode` as an option to `parse`:
|
|
||||||
```ruby
|
|
||||||
Liquid::Template.parse(source, :error_mode => :strict)
|
|
||||||
```
|
|
||||||
This is useful for doing things like enabling strict mode only in the theme editor.
|
|
||||||
|
|
||||||
It is recommended that you enable `:strict` or `:warn` mode on new apps to stop invalid templates from being created.
|
|
||||||
It is also recommended that you use it in the template editors of existing apps to give editors better error messages.
|
|
||||||
|
|||||||
112
Rakefile
112
Rakefile
@@ -1,59 +1,73 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
require 'rubygems'
|
||||||
require 'rake'
|
require 'rake'
|
||||||
|
require 'rake/clean'
|
||||||
|
require 'fileutils'
|
||||||
require 'rake/testtask'
|
require 'rake/testtask'
|
||||||
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
require 'rubygems/package_task'
|
||||||
require "liquid/version"
|
|
||||||
|
|
||||||
task :default => 'test'
|
task :default => [:compile, :test]
|
||||||
|
|
||||||
desc 'run test suite with default parser'
|
task :gen do
|
||||||
Rake::TestTask.new(:base_test) do |t|
|
sh "leg -oext/liquid/liquid_context.c ext/liquid/liquid_context.leg"
|
||||||
|
end
|
||||||
|
|
||||||
|
task :compile => [:gen, :liquid_ext]
|
||||||
|
|
||||||
|
extension = "liquid_ext"
|
||||||
|
ext = "ext/liquid"
|
||||||
|
ext_so = "#{ext}/#{extension}.#{RbConfig::CONFIG['DLEXT']}"
|
||||||
|
ext_files = FileList[
|
||||||
|
"#{ext}/*.c",
|
||||||
|
"#{ext}/*.h",
|
||||||
|
"#{ext}/*.leg",
|
||||||
|
"#{ext}/extconf.rb",
|
||||||
|
"#{ext}/Makefile",
|
||||||
|
"lib"
|
||||||
|
]
|
||||||
|
|
||||||
|
task "lib" do
|
||||||
|
directory "lib"
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Builds just the #{extension} extension"
|
||||||
|
task extension.to_sym => [:gen, "#{ext}/Makefile", ext_so ]
|
||||||
|
|
||||||
|
file "#{ext}/Makefile" => ["#{ext}/extconf.rb"] do
|
||||||
|
Dir.chdir(ext) do ruby "extconf.rb" end
|
||||||
|
end
|
||||||
|
|
||||||
|
file ext_so => ext_files do
|
||||||
|
Dir.chdir(ext) do
|
||||||
|
sh "make"
|
||||||
|
end
|
||||||
|
cp ext_so, "lib"
|
||||||
|
end
|
||||||
|
|
||||||
|
Rake::TestTask.new(:test => [:gen, 'liquid_ext']) do |t|
|
||||||
t.libs << '.' << 'lib' << 'test'
|
t.libs << '.' << 'lib' << 'test'
|
||||||
t.test_files = FileList['test/{integration,unit}/**/*_test.rb']
|
t.test_files = FileList['test/liquid/**/*_test.rb']
|
||||||
t.verbose = false
|
t.verbose = false
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'run test suite with warn error mode'
|
gemspec = eval(File.read('liquid.gemspec'))
|
||||||
task :warn_test do
|
Gem::PackageTask.new(gemspec) do |pkg|
|
||||||
ENV['LIQUID_PARSER_MODE'] = 'warn'
|
pkg.gem_spec = gemspec
|
||||||
Rake::Task['base_test'].invoke
|
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'runs test suite with both strict and lax parsers'
|
desc "Build the gem and release it to rubygems.org"
|
||||||
task :test do
|
task :release => :gem do
|
||||||
ENV['LIQUID_PARSER_MODE'] = 'lax'
|
sh "gem push pkg/liquid-#{gemspec.version}.gem"
|
||||||
Rake::Task['base_test'].invoke
|
|
||||||
ENV['LIQUID_PARSER_MODE'] = 'strict'
|
|
||||||
Rake::Task['base_test'].reenable
|
|
||||||
Rake::Task['base_test'].invoke
|
|
||||||
end
|
|
||||||
|
|
||||||
task :gem => :build
|
|
||||||
task :build do
|
|
||||||
system "gem build liquid.gemspec"
|
|
||||||
end
|
|
||||||
|
|
||||||
task :install => :build do
|
|
||||||
system "gem install liquid-#{Liquid::VERSION}.gem"
|
|
||||||
end
|
|
||||||
|
|
||||||
task :release => :build do
|
|
||||||
system "git tag -a v#{Liquid::VERSION} -m 'Tagging #{Liquid::VERSION}'"
|
|
||||||
system "git push --tags"
|
|
||||||
system "gem push liquid-#{Liquid::VERSION}.gem"
|
|
||||||
system "rm liquid-#{Liquid::VERSION}.gem"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
namespace :benchmark do
|
namespace :benchmark do
|
||||||
|
|
||||||
desc "Run the liquid benchmark with lax parsing"
|
desc "Run the liquid benchmark"
|
||||||
task :run do
|
task :run do
|
||||||
ruby "./performance/benchmark.rb lax"
|
ruby "./performance/benchmark.rb"
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "Run the liquid benchmark with strict parsing"
|
|
||||||
task :strict do
|
|
||||||
ruby "./performance/benchmark.rb strict"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -64,23 +78,9 @@ namespace :profile do
|
|||||||
ruby "./performance/profile.rb"
|
ruby "./performance/profile.rb"
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "Run the liquid profile/performance coverage with strict parsing"
|
desc "Run KCacheGrind"
|
||||||
task :strict do
|
task :grind => :run do
|
||||||
ruby "./performance/profile.rb strict"
|
system "qcachegrind /tmp//callgrind.liquid.txt"
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
namespace :memory do
|
|
||||||
|
|
||||||
desc "Run the liquid memory tracer"
|
|
||||||
task :run do
|
|
||||||
ruby "./performance/memory.rb"
|
|
||||||
end
|
|
||||||
|
|
||||||
desc "Run the liquid memory tracer with strict parsing"
|
|
||||||
task :strict do
|
|
||||||
ruby "./performance/memory.rb strict"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -23,7 +23,11 @@ class Servlet < LiquidServlet
|
|||||||
end
|
end
|
||||||
|
|
||||||
def products
|
def products
|
||||||
{ 'products' => products_list, 'more_products' => more_products_list, 'description' => description, 'section' => 'Snowboards', 'cool_products' => true}
|
{ 'products' => products_list, 'section' => 'Snowboards', 'cool_products' => true}
|
||||||
|
end
|
||||||
|
|
||||||
|
def description
|
||||||
|
"List of Products ~ This is a list of products with price and description."
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -34,13 +38,4 @@ class Servlet < LiquidServlet
|
|||||||
{'name' => 'Arbor Diamond', 'price' => 59900, 'description' => 'the *arbor diamond* is a made up product because im obsessed with arbor and have no creativity'}]
|
{'name' => 'Arbor Diamond', 'price' => 59900, 'description' => 'the *arbor diamond* is a made up product because im obsessed with arbor and have no creativity'}]
|
||||||
end
|
end
|
||||||
|
|
||||||
def more_products_list
|
|
||||||
[{'name' => 'Arbor Catalyst', 'price' => 39900, 'description' => 'the *arbor catalyst* is an advanced drop-through for freestyle and flatground performance and versatility' },
|
|
||||||
{'name' => 'Arbor Fish', 'price' => 40000, 'description' => 'the *arbor fish* is a compact pin that features an extended wheelbase and time-honored teardrop shape'}]
|
|
||||||
end
|
|
||||||
|
|
||||||
def description
|
|
||||||
"List of Products ~ This is a list of products with price and description."
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -11,10 +11,9 @@ class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet
|
|||||||
private
|
private
|
||||||
|
|
||||||
def handle(type, req, res)
|
def handle(type, req, res)
|
||||||
@request = req
|
@request, @response = req, res
|
||||||
@response = res
|
|
||||||
|
|
||||||
@request.path_info =~ /(\w+)\z/
|
@request.path_info =~ /(\w+)$/
|
||||||
@action = $1 || 'index'
|
@action = $1 || 'index'
|
||||||
@assigns = send(@action) if respond_to?(@action)
|
@assigns = send(@action) if respond_to?(@action)
|
||||||
|
|
||||||
|
|||||||
@@ -3,4 +3,4 @@
|
|||||||
<p>It is {{date}}</p>
|
<p>It is {{date}}</p>
|
||||||
|
|
||||||
|
|
||||||
<p>Check out the <a href="/products">Products</a> screen </p>
|
<p>Check out the <a href="http://localhost:3000/products">Products</a> screen </p>
|
||||||
@@ -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>
|
||||||
{% assign all_products = products | concat: more_products %}
|
|
||||||
<h1>{{ description | split: '~' | first }}</h1>
|
|
||||||
|
|
||||||
<h2>{{ description | split: '~' | last }}</h2>
|
<h1>{{ description | split: '~' | first }}</h1>
|
||||||
|
|
||||||
<h2>There are currently {{all_products | count}} products in the {{section}} catalog</h2>
|
<h2>{{ description | split: '~' | last }}</h2>
|
||||||
|
|
||||||
|
<h2>There are currently {{products | count}} products in the {{section}} catalog</h2>
|
||||||
|
|
||||||
{% if cool_products %}
|
{% if cool_products %}
|
||||||
Cool products :)
|
Cool products :)
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
<ul id="products">
|
<ul id="products">
|
||||||
|
|
||||||
{% for product in all_products %}
|
{% for product in products %}
|
||||||
<li>
|
<li>
|
||||||
<h2>{{product.name}}</h2>
|
<h2>{{product.name}}</h2>
|
||||||
Only {{product.price | price }}
|
Only {{product.price | price }}
|
||||||
@@ -45,5 +45,5 @@
|
|||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
6
ext/liquid/extconf.rb
Normal file
6
ext/liquid/extconf.rb
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
require 'mkmf'
|
||||||
|
|
||||||
|
dir_config("liquid_ext")
|
||||||
|
have_library("c", "main")
|
||||||
|
|
||||||
|
create_makefile("liquid_ext")
|
||||||
98
ext/liquid/liquid_context.leg
Normal file
98
ext/liquid/liquid_context.leg
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
|
||||||
|
%{
|
||||||
|
//uncomment to get more debug instrumentation
|
||||||
|
//
|
||||||
|
//#define YY_DEBUG
|
||||||
|
|
||||||
|
#include <ruby.h>
|
||||||
|
|
||||||
|
#define EMIT(sym, data) \
|
||||||
|
rb_ary_push(ctx->rb_tokens, rb_ary_new3(2, ID2SYM(rb_intern(sym)), data));
|
||||||
|
|
||||||
|
#define yy_rb_str rb_str_new(yytext, yyleng)
|
||||||
|
|
||||||
|
#define YYSTYPE VALUE
|
||||||
|
#define YY_CTX_LOCAL
|
||||||
|
#define YY_CTX_MEMBERS VALUE rb_tokens; char *p; int p_len;
|
||||||
|
|
||||||
|
#define YY_INPUT(buf, result, max_size) { \
|
||||||
|
result = ctx->p_len; \
|
||||||
|
if (result>0 || EOF == ctx->p[0]) { \
|
||||||
|
if (max_size < result) { result = max_size; } \
|
||||||
|
strncpy(buf, ctx->p, result); \
|
||||||
|
buf[result] = '\0'; \
|
||||||
|
yyprintf((stderr, "\nREFILLING %d chars now:<%s>", result, buf)); \
|
||||||
|
ctx->p += result; ctx->p_len -= result; \
|
||||||
|
yyprintf((stderr, "\nREFILLING DONE size left: %d <%s>", ctx->p_len, ctx->p)); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
%}
|
||||||
|
|
||||||
|
grammar = primary
|
||||||
|
| entity
|
||||||
|
| range
|
||||||
|
| hash { EMIT("lookup", Qnil); }
|
||||||
|
;
|
||||||
|
|
||||||
|
hash = '[' (primary|entity) ']';
|
||||||
|
|
||||||
|
primary = var:const { EMIT("id", var); }
|
||||||
|
| var:string { EMIT("id", var); }
|
||||||
|
| var:numeric { EMIT("id", var); }
|
||||||
|
;
|
||||||
|
|
||||||
|
accessors = hash { EMIT("call", Qnil); }
|
||||||
|
| '.first' { EMIT("buildin", rb_str_new2("first")); }
|
||||||
|
| '.last' { EMIT("buildin", rb_str_new2("last")); }
|
||||||
|
| '.size' { EMIT("buildin", rb_str_new2("size")); }
|
||||||
|
| '.' <identifier> { EMIT("id", yy_rb_str); EMIT("call", Qnil); }
|
||||||
|
;
|
||||||
|
|
||||||
|
entity = <identifier> { EMIT("id", yy_rb_str); EMIT("lookup", Qnil); }
|
||||||
|
accessors*
|
||||||
|
;
|
||||||
|
|
||||||
|
rangelet = var:integer { EMIT("id", var); }
|
||||||
|
| entity
|
||||||
|
;
|
||||||
|
|
||||||
|
range = '(' rangelet '..' rangelet ')' { EMIT("range", Qnil); }
|
||||||
|
|
||||||
|
|
||||||
|
string = ['] < ( !['] . )* > ['] { $$ = yy_rb_str; }
|
||||||
|
| ["] < ( !["] . )* > ["] { $$ = yy_rb_str; }
|
||||||
|
;
|
||||||
|
|
||||||
|
numeric = float
|
||||||
|
| integer
|
||||||
|
;
|
||||||
|
|
||||||
|
float = <'-'? digit+'.'digit+> { $$ = rb_funcall(rb_cObject, rb_intern("Float"), 1, yy_rb_str); }
|
||||||
|
integer = <'-'? digit+> { $$ = rb_funcall(rb_cObject, rb_intern("Integer"), 1, yy_rb_str); }
|
||||||
|
|
||||||
|
|
||||||
|
const = "true" { $$ = Qtrue; }
|
||||||
|
| 'false' { $$ = Qfalse; }
|
||||||
|
| 'nil' { $$ = Qnil; }
|
||||||
|
| 'null' { $$ = Qnil; }
|
||||||
|
;
|
||||||
|
|
||||||
|
digit = [0-9];
|
||||||
|
identifier = [a-zA-Z][a-zA-Z0-9_\-]*[?!]?;
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
VALUE liquid_context_parse_impl(VALUE self, VALUE text) {
|
||||||
|
char *p;
|
||||||
|
int len;
|
||||||
|
yycontext ctx;
|
||||||
|
|
||||||
|
memset(&ctx, 0, sizeof(yycontext));
|
||||||
|
ctx.p = RSTRING_PTR(text);
|
||||||
|
ctx.p_len = (int) RSTRING_LEN(text);
|
||||||
|
ctx.rb_tokens = rb_ary_new();
|
||||||
|
|
||||||
|
yyparse(&ctx);
|
||||||
|
|
||||||
|
return ctx.rb_tokens;
|
||||||
|
}
|
||||||
13
ext/liquid/liquid_ext.c
Normal file
13
ext/liquid/liquid_ext.c
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#include <ruby.h>
|
||||||
|
|
||||||
|
static VALUE rb_Liquid;
|
||||||
|
static VALUE rb_Parser;
|
||||||
|
|
||||||
|
VALUE liquid_context_parse_impl(VALUE text);
|
||||||
|
|
||||||
|
void Init_liquid_ext()
|
||||||
|
{
|
||||||
|
rb_Liquid = rb_define_module("Liquid");
|
||||||
|
rb_Parser = rb_define_class_under(rb_Liquid, "Parser", rb_cObject);
|
||||||
|
rb_define_singleton_method(rb_Parser, "parse", liquid_context_parse_impl, 1);
|
||||||
|
}
|
||||||
8
init.rb
Normal file
8
init.rb
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
require 'liquid'
|
||||||
|
require 'extras/liquid_view'
|
||||||
|
|
||||||
|
if defined? ActionView::Template and ActionView::Template.respond_to? :register_template_handler
|
||||||
|
ActionView::Template
|
||||||
|
else
|
||||||
|
ActionView::Base
|
||||||
|
end.register_template_handler(:liquid, LiquidView)
|
||||||
51
lib/extras/liquid_view.rb
Normal file
51
lib/extras/liquid_view.rb
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# LiquidView is a action view extension class. You can register it with rails
|
||||||
|
# and use liquid as an template system for .liquid files
|
||||||
|
#
|
||||||
|
# Example
|
||||||
|
#
|
||||||
|
# ActionView::Base::register_template_handler :liquid, LiquidView
|
||||||
|
class LiquidView
|
||||||
|
PROTECTED_ASSIGNS = %w( template_root response _session template_class action_name request_origin session template
|
||||||
|
_response url _request _cookies variables_added _flash params _headers request cookies
|
||||||
|
ignore_missing_templates flash _params logger before_filter_chain_aborted headers )
|
||||||
|
PROTECTED_INSTANCE_VARIABLES = %w( @_request @controller @_first_render @_memoized__pick_template @view_paths
|
||||||
|
@helpers @assigns_added @template @_render_stack @template_format @assigns )
|
||||||
|
|
||||||
|
def self.call(template)
|
||||||
|
"LiquidView.new(self).render(template, local_assigns)"
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(view)
|
||||||
|
@view = view
|
||||||
|
end
|
||||||
|
|
||||||
|
def render(template, local_assigns = nil)
|
||||||
|
@view.controller.headers["Content-Type"] ||= 'text/html; charset=utf-8'
|
||||||
|
|
||||||
|
# Rails 2.2 Template has source, but not locals
|
||||||
|
if template.respond_to?(:source) && !template.respond_to?(:locals)
|
||||||
|
assigns = (@view.instance_variables - PROTECTED_INSTANCE_VARIABLES).inject({}) do |hash, ivar|
|
||||||
|
hash[ivar[1..-1]] = @view.instance_variable_get(ivar)
|
||||||
|
hash
|
||||||
|
end
|
||||||
|
else
|
||||||
|
assigns = @view.assigns.reject{ |k,v| PROTECTED_ASSIGNS.include?(k) }
|
||||||
|
end
|
||||||
|
|
||||||
|
source = template.respond_to?(:source) ? template.source : template
|
||||||
|
local_assigns = (template.respond_to?(:locals) ? template.locals : local_assigns) || {}
|
||||||
|
|
||||||
|
if content_for_layout = @view.instance_variable_get("@content_for_layout")
|
||||||
|
assigns['content_for_layout'] = content_for_layout
|
||||||
|
end
|
||||||
|
assigns.merge!(local_assigns.stringify_keys)
|
||||||
|
|
||||||
|
liquid = Liquid::Template.parse(source)
|
||||||
|
liquid.render(assigns, :filters => [@view.controller.master_helper_module], :registers => {:action_view => @view, :controller => @view.controller})
|
||||||
|
end
|
||||||
|
|
||||||
|
def compilable?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -21,9 +21,9 @@
|
|||||||
|
|
||||||
module Liquid
|
module Liquid
|
||||||
FilterSeparator = /\|/
|
FilterSeparator = /\|/
|
||||||
ArgumentSeparator = ','.freeze
|
ArgumentSeparator = ','
|
||||||
FilterArgumentSeparator = ':'.freeze
|
FilterArgumentSeparator = ':'
|
||||||
VariableAttributeSeparator = '.'.freeze
|
VariableAttributeSeparator = '.'
|
||||||
TagStart = /\{\%/
|
TagStart = /\{\%/
|
||||||
TagEnd = /\%\}/
|
TagEnd = /\%\}/
|
||||||
VariableSignature = /\(?[\w\-\.\[\]]\)?/
|
VariableSignature = /\(?[\w\-\.\[\]]\)?/
|
||||||
@@ -33,43 +33,36 @@ module Liquid
|
|||||||
VariableIncompleteEnd = /\}\}?/
|
VariableIncompleteEnd = /\}\}?/
|
||||||
QuotedString = /"[^"]*"|'[^']*'/
|
QuotedString = /"[^"]*"|'[^']*'/
|
||||||
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
|
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
|
||||||
|
StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s|:,]+/
|
||||||
|
FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/o
|
||||||
|
OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/o
|
||||||
|
SpacelessFilter = /^(?:'[^']+'|"[^"]+"|[^'"])*#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/o
|
||||||
|
Expression = /(?:#{QuotedFragment}(?:#{SpacelessFilter})*)/o
|
||||||
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
|
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
|
||||||
AnyStartingTag = /\{\{|\{\%/
|
AnyStartingTag = /\{\{|\{\%/
|
||||||
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
|
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/o
|
||||||
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
|
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/o
|
||||||
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
|
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
|
||||||
|
|
||||||
singleton_class.send(:attr_accessor, :cache_classes)
|
|
||||||
self.cache_classes = true
|
|
||||||
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'
|
||||||
require 'liquid/interrupts'
|
require 'liquid/interrupts'
|
||||||
require 'liquid/strainer'
|
require 'liquid/strainer'
|
||||||
require 'liquid/expression'
|
|
||||||
require 'liquid/context'
|
require 'liquid/context'
|
||||||
require 'liquid/parser_switching'
|
|
||||||
require 'liquid/tag'
|
require 'liquid/tag'
|
||||||
require 'liquid/block'
|
require 'liquid/block'
|
||||||
require 'liquid/block_body'
|
|
||||||
require 'liquid/document'
|
require 'liquid/document'
|
||||||
require 'liquid/variable'
|
require 'liquid/variable'
|
||||||
require 'liquid/variable_lookup'
|
|
||||||
require 'liquid/range_lookup'
|
|
||||||
require 'liquid/file_system'
|
require 'liquid/file_system'
|
||||||
require 'liquid/resource_limits'
|
|
||||||
require 'liquid/template'
|
require 'liquid/template'
|
||||||
|
require 'liquid/htmltags'
|
||||||
require 'liquid/standardfilters'
|
require 'liquid/standardfilters'
|
||||||
require 'liquid/condition'
|
require 'liquid/condition'
|
||||||
require 'liquid/module_ex'
|
require 'liquid/module_ex'
|
||||||
require 'liquid/utils'
|
require 'liquid/utils'
|
||||||
require 'liquid/token'
|
require 'liquid_ext'
|
||||||
|
|
||||||
# Load all the tags of the standard library
|
# Load all the tags of the standard library
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,79 +1,115 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
class Block < Tag
|
class Block < Tag
|
||||||
def initialize(tag_name, markup, options)
|
IsTag = /^#{TagStart}/o
|
||||||
super
|
IsVariable = /^#{VariableStart}/o
|
||||||
@blank = true
|
FullToken = /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/o
|
||||||
end
|
ContentOfVariable = /^#{VariableStart}(.*)#{VariableEnd}$/o
|
||||||
|
|
||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
@body = BlockBody.new
|
@nodelist ||= []
|
||||||
while more = parse_body(@body, tokens)
|
@nodelist.clear
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def render(context)
|
while token = tokens.shift
|
||||||
@body.render(context)
|
|
||||||
end
|
|
||||||
|
|
||||||
def blank?
|
case token
|
||||||
@blank
|
when IsTag
|
||||||
end
|
if token =~ FullToken
|
||||||
|
|
||||||
def nodelist
|
# if we found the proper block delimitor just end parsing here and let the outer block
|
||||||
@body.nodelist
|
# proceed
|
||||||
end
|
if block_delimiter == $1
|
||||||
|
end_tag
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
# warnings of this block and all sub-tags
|
# fetch the tag from registered blocks
|
||||||
def warnings
|
if tag = Template.tags[$1]
|
||||||
all_warnings = []
|
@nodelist << tag.new($1, $2, tokens)
|
||||||
all_warnings.concat(@warnings) if @warnings
|
else
|
||||||
|
# this tag is not registered with the system
|
||||||
(nodelist || []).each do |node|
|
# pass it to the current block for special handling or error reporting
|
||||||
all_warnings.concat(node.warnings || []) if node.respond_to?(:warnings)
|
unknown_tag($1, $2, tokens)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise SyntaxError, "Tag '#{token}' was not properly terminated with regexp: #{TagEnd.inspect} "
|
||||||
|
end
|
||||||
|
when IsVariable
|
||||||
|
@nodelist << create_variable(token)
|
||||||
|
when ''
|
||||||
|
# pass
|
||||||
|
else
|
||||||
|
@nodelist << token
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
all_warnings
|
# Make sure that its ok to end parsing in the current block.
|
||||||
|
# Effectively this method will throw and exception unless the current block is
|
||||||
|
# of type Document
|
||||||
|
assert_missing_delimitation!
|
||||||
|
end
|
||||||
|
|
||||||
|
def end_tag
|
||||||
end
|
end
|
||||||
|
|
||||||
def unknown_tag(tag, params, tokens)
|
def unknown_tag(tag, params, tokens)
|
||||||
case tag
|
case tag
|
||||||
when 'else'.freeze
|
when 'else'
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.unexpected_else".freeze,
|
raise SyntaxError, "#{block_name} tag does not expect else tag"
|
||||||
:block_name => block_name))
|
when 'end'
|
||||||
when 'end'.freeze
|
raise SyntaxError, "'end' is not a valid delimiter for #{block_name} tags. use #{block_delimiter}"
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.invalid_delimiter".freeze,
|
|
||||||
:block_name => block_name,
|
|
||||||
:block_delimiter => block_delimiter))
|
|
||||||
else
|
else
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.unknown_tag".freeze, :tag => tag))
|
raise SyntaxError, "Unknown tag '#{tag}'"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def block_delimiter
|
||||||
|
"end#{block_name}"
|
||||||
|
end
|
||||||
|
|
||||||
def block_name
|
def block_name
|
||||||
@tag_name
|
@tag_name
|
||||||
end
|
end
|
||||||
|
|
||||||
def block_delimiter
|
def create_variable(token)
|
||||||
@block_delimiter ||= "end#{block_name}"
|
token.scan(ContentOfVariable) do |content|
|
||||||
|
return Variable.new(content.first)
|
||||||
|
end
|
||||||
|
raise SyntaxError.new("Variable '#{token}' was not properly terminated with regexp: #{VariableEnd.inspect} ")
|
||||||
|
end
|
||||||
|
|
||||||
|
def render(context)
|
||||||
|
render_all(@nodelist, context)
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def parse_body(body, tokens)
|
def assert_missing_delimitation!
|
||||||
body.parse(tokens, options) do |end_tag_name, end_tag_params|
|
raise SyntaxError.new("#{block_name} tag was never closed")
|
||||||
@blank &&= body.blank?
|
end
|
||||||
|
|
||||||
return false if end_tag_name == block_delimiter
|
def render_all(list, context)
|
||||||
unless end_tag_name
|
output = []
|
||||||
raise SyntaxError.new(@options[:locale].t("errors.syntax.tag_never_closed".freeze, :block_name => block_name))
|
list.each do |token|
|
||||||
|
# Break out if we have any unhanded interrupts.
|
||||||
|
break if context.has_interrupt?
|
||||||
|
|
||||||
|
begin
|
||||||
|
# If we get an Interrupt that means the block must stop processing. An
|
||||||
|
# Interrupt is any command that stops block execution such as {% break %}
|
||||||
|
# or {% continue %}
|
||||||
|
if token.is_a? Continue or token.is_a? Break
|
||||||
|
context.push_interrupt(token.interrupt)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
output << (token.respond_to?(:render) ? token.render(context) : token)
|
||||||
|
rescue ::StandardError => e
|
||||||
|
output << (context.handle_error(e))
|
||||||
end
|
end
|
||||||
|
|
||||||
# this tag is not registered with the system
|
|
||||||
# pass it to the current block for special handling or error reporting
|
|
||||||
unknown_tag(end_tag_name, end_tag_params, tokens)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
true
|
output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,132 +0,0 @@
|
|||||||
module Liquid
|
|
||||||
class BlockBody
|
|
||||||
FullToken = /\A#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
|
|
||||||
ContentOfVariable = /\A#{VariableStart}(.*)#{VariableEnd}\z/om
|
|
||||||
TAGSTART = "{%".freeze
|
|
||||||
VARSTART = "{{".freeze
|
|
||||||
|
|
||||||
attr_reader :nodelist
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
@nodelist = []
|
|
||||||
@blank = true
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse(tokens, options)
|
|
||||||
while token = tokens.shift
|
|
||||||
begin
|
|
||||||
unless token.empty?
|
|
||||||
case
|
|
||||||
when token.start_with?(TAGSTART)
|
|
||||||
if token =~ FullToken
|
|
||||||
tag_name = $1
|
|
||||||
markup = $2
|
|
||||||
# fetch the tag from registered blocks
|
|
||||||
if tag = Template.tags[tag_name]
|
|
||||||
markup = token.child(markup) if token.is_a?(Token)
|
|
||||||
new_tag = tag.parse(tag_name, markup, tokens, options)
|
|
||||||
new_tag.line_number = token.line_number if token.is_a?(Token)
|
|
||||||
@blank &&= new_tag.blank?
|
|
||||||
@nodelist << new_tag
|
|
||||||
else
|
|
||||||
# end parsing if we reach an unknown tag and let the caller decide
|
|
||||||
# determine how to proceed
|
|
||||||
return yield tag_name, markup
|
|
||||||
end
|
|
||||||
else
|
|
||||||
raise_missing_tag_terminator(token, options)
|
|
||||||
end
|
|
||||||
when token.start_with?(VARSTART)
|
|
||||||
new_var = create_variable(token, options)
|
|
||||||
new_var.line_number = token.line_number if token.is_a?(Token)
|
|
||||||
@nodelist << new_var
|
|
||||||
@blank = false
|
|
||||||
else
|
|
||||||
@nodelist << token
|
|
||||||
@blank &&= !!(token =~ /\A\s*\z/)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
rescue SyntaxError => e
|
|
||||||
e.set_line_number_from_token(token)
|
|
||||||
raise
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
yield nil, nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def blank?
|
|
||||||
@blank
|
|
||||||
end
|
|
||||||
|
|
||||||
def warnings
|
|
||||||
all_warnings = []
|
|
||||||
nodelist.each do |node|
|
|
||||||
all_warnings.concat(node.warnings || []) if node.respond_to?(:warnings)
|
|
||||||
end
|
|
||||||
all_warnings
|
|
||||||
end
|
|
||||||
|
|
||||||
def render(context)
|
|
||||||
@output ||= []
|
|
||||||
@output.clear
|
|
||||||
context.resource_limits.render_score += @nodelist.length
|
|
||||||
|
|
||||||
@nodelist.each do |token|
|
|
||||||
# Break out if we have any unhanded interrupts.
|
|
||||||
break if context.has_interrupt?
|
|
||||||
|
|
||||||
begin
|
|
||||||
# If we get an Interrupt that means the block must stop processing. An
|
|
||||||
# Interrupt is any command that stops block execution such as {% break %}
|
|
||||||
# or {% continue %}
|
|
||||||
if token.is_a?(Continue) or token.is_a?(Break)
|
|
||||||
context.push_interrupt(token.interrupt)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
token_output = render_token(token, context)
|
|
||||||
|
|
||||||
unless token.is_a?(Block) && token.blank?
|
|
||||||
@output << token_output
|
|
||||||
end
|
|
||||||
rescue MemoryError => e
|
|
||||||
raise e
|
|
||||||
rescue ::StandardError => e
|
|
||||||
@output << context.handle_error(e, token)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@output.join
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def render_token(token, context)
|
|
||||||
token_output = (token.respond_to?(:render) ? token.render(context) : token)
|
|
||||||
token_str = token_output.is_a?(Array) ? token_output.join : token_output.to_s
|
|
||||||
|
|
||||||
context.resource_limits.render_length += token_str.length
|
|
||||||
if context.resource_limits.reached?
|
|
||||||
raise MemoryError.new("Memory limits exceeded".freeze)
|
|
||||||
end
|
|
||||||
token_str
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_variable(token, options)
|
|
||||||
token.scan(ContentOfVariable) do |content|
|
|
||||||
markup = token.is_a?(Token) ? token.child(content.first) : content.first
|
|
||||||
return Variable.new(markup, options)
|
|
||||||
end
|
|
||||||
raise_missing_variable_terminator(token, options)
|
|
||||||
end
|
|
||||||
|
|
||||||
def raise_missing_tag_terminator(token, options)
|
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination".freeze, :token => token, :tag_end => TagEnd.inspect))
|
|
||||||
end
|
|
||||||
|
|
||||||
def raise_missing_variable_terminator(token, options)
|
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination".freeze, :token => token, :tag_end => VariableEnd.inspect))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -3,21 +3,19 @@ module Liquid
|
|||||||
#
|
#
|
||||||
# Example:
|
# Example:
|
||||||
#
|
#
|
||||||
# c = Condition.new(1, '==', 1)
|
# c = Condition.new('1', '==', '1')
|
||||||
# c.evaluate #=> true
|
# c.evaluate #=> true
|
||||||
#
|
#
|
||||||
class Condition #:nodoc:
|
class Condition #:nodoc:
|
||||||
@@operators = {
|
@@operators = {
|
||||||
'=='.freeze => lambda { |cond, left, right| cond.send(:equal_variables, left, right) },
|
'==' => lambda { |cond, left, right| cond.send(:equal_variables, left, right) },
|
||||||
'!='.freeze => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
|
'!=' => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
|
||||||
'<>'.freeze => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
|
'<>' => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
|
||||||
'<'.freeze => :<,
|
'<' => :<,
|
||||||
'>'.freeze => :>,
|
'>' => :>,
|
||||||
'>='.freeze => :>=,
|
'>=' => :>=,
|
||||||
'<='.freeze => :<=,
|
'<=' => :<=,
|
||||||
'contains'.freeze => lambda { |cond, left, right|
|
'contains' => lambda { |cond, left, right| left && right ? left.include?(right) : false }
|
||||||
left && right && left.respond_to?(:include?) ? left.include?(right) : false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def self.operators
|
def self.operators
|
||||||
@@ -28,9 +26,7 @@ module Liquid
|
|||||||
attr_accessor :left, :operator, :right
|
attr_accessor :left, :operator, :right
|
||||||
|
|
||||||
def initialize(left = nil, operator = nil, right = nil)
|
def initialize(left = nil, operator = nil, right = nil)
|
||||||
@left = left
|
@left, @operator, @right = left, operator, right
|
||||||
@operator = operator
|
|
||||||
@right = right
|
|
||||||
@child_relation = nil
|
@child_relation = nil
|
||||||
@child_condition = nil
|
@child_condition = nil
|
||||||
end
|
end
|
||||||
@@ -49,13 +45,11 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def or(condition)
|
def or(condition)
|
||||||
@child_relation = :or
|
@child_relation, @child_condition = :or, condition
|
||||||
@child_condition = condition
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def and(condition)
|
def and(condition)
|
||||||
@child_relation = :and
|
@child_relation, @child_condition = :and, condition
|
||||||
@child_condition = condition
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def attach(attachment)
|
def attach(attachment)
|
||||||
@@ -67,7 +61,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def inspect
|
def inspect
|
||||||
"#<Condition #{[@left, @operator, @right].compact.join(' '.freeze)}>"
|
"#<Condition #{[@left, @operator, @right].compact.join(' ')}>"
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -96,21 +90,16 @@ module Liquid
|
|||||||
# If the operator is empty this means that the decision statement is just
|
# If the operator is empty this means that the decision statement is just
|
||||||
# a single variable. We can just poll this variable from the context and
|
# a single variable. We can just poll this variable from the context and
|
||||||
# return this as the result.
|
# return this as the result.
|
||||||
return context.evaluate(left) if op == nil
|
return context[left] if op == nil
|
||||||
|
|
||||||
left = context.evaluate(left)
|
left, right = context[left], context[right]
|
||||||
right = context.evaluate(right)
|
|
||||||
|
|
||||||
operation = self.class.operators[op] || raise(Liquid::ArgumentError.new("Unknown operator #{op}"))
|
operation = self.class.operators[op] || raise(ArgumentError.new("Unknown operator #{op}"))
|
||||||
|
|
||||||
if operation.respond_to?(:call)
|
if operation.respond_to?(:call)
|
||||||
operation.call(self, left, right)
|
operation.call(self, left, right)
|
||||||
elsif left.respond_to?(operation) and right.respond_to?(operation)
|
elsif left.respond_to?(operation) and right.respond_to?(operation)
|
||||||
begin
|
left.send(operation, right)
|
||||||
left.send(operation, right)
|
|
||||||
rescue ::ArgumentError => e
|
|
||||||
raise Liquid::ArgumentError.new(e.message)
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
|
|
||||||
# Context keeps the variable stack and resolves variables, as well as keywords
|
# Context keeps the variable stack and resolves variables, as well as keywords
|
||||||
#
|
#
|
||||||
# context['variable'] = 'testing'
|
# context['variable'] = 'testing'
|
||||||
@@ -13,32 +14,21 @@ module Liquid
|
|||||||
#
|
#
|
||||||
# context['bob'] #=> nil class Context
|
# context['bob'] #=> nil class Context
|
||||||
class Context
|
class Context
|
||||||
attr_reader :scopes, :registers, :environments, :resource_limits
|
attr_reader :scopes, :errors, :registers, :environments
|
||||||
attr_accessor :exception_handler
|
|
||||||
|
|
||||||
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
|
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false)
|
||||||
@environments = [environments].flatten
|
@environments = [environments].flatten
|
||||||
@scopes = [(outer_scope || {})]
|
@scopes = [(outer_scope || {})]
|
||||||
@registers = registers
|
@registers = registers
|
||||||
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
|
@errors = []
|
||||||
|
@rethrow_errors = rethrow_errors
|
||||||
squash_instance_assigns_with_environments
|
squash_instance_assigns_with_environments
|
||||||
|
|
||||||
@this_stack_used = false
|
@interrupts = []
|
||||||
|
|
||||||
if rethrow_errors
|
|
||||||
self.exception_handler = ->(e) { true }
|
|
||||||
end
|
|
||||||
|
|
||||||
@interrupts = nil
|
|
||||||
@filters = []
|
|
||||||
end
|
|
||||||
|
|
||||||
def errors
|
|
||||||
@errors ||= []
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def strainer
|
def strainer
|
||||||
@strainer ||= Strainer.create(self, @filters)
|
@strainer ||= Strainer.create(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Adds filters to this context.
|
# Adds filters to this context.
|
||||||
@@ -47,44 +37,49 @@ module Liquid
|
|||||||
# for that
|
# for that
|
||||||
def add_filters(filters)
|
def add_filters(filters)
|
||||||
filters = [filters].flatten.compact
|
filters = [filters].flatten.compact
|
||||||
@filters += filters
|
|
||||||
@strainer = nil
|
filters.each do |f|
|
||||||
|
raise ArgumentError, "Expected module but got: #{f.class}" unless f.is_a?(Module)
|
||||||
|
Strainer.add_known_filter(f)
|
||||||
|
strainer.extend(f)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# are there any not handled interrupts?
|
# are there any not handled interrupts?
|
||||||
def has_interrupt?
|
def has_interrupt?
|
||||||
@interrupts && !@interrupts.empty?
|
!@interrupts.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
# push an interrupt to the stack. this interrupt is considered not handled.
|
# push an interrupt to the stack. this interrupt is considered not handled.
|
||||||
def push_interrupt(e)
|
def push_interrupt(e)
|
||||||
(@interrupts ||= []).push(e)
|
@interrupts.push(e)
|
||||||
end
|
end
|
||||||
|
|
||||||
# pop an interrupt from the stack
|
# pop an interrupt from the stack
|
||||||
def pop_interrupt
|
def pop_interrupt
|
||||||
@interrupts.pop if @interrupts
|
@interrupts.pop
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_error(e)
|
||||||
def handle_error(e, token=nil)
|
|
||||||
if e.is_a?(Liquid::Error)
|
|
||||||
e.set_line_number_from_token(token)
|
|
||||||
end
|
|
||||||
|
|
||||||
errors.push(e)
|
errors.push(e)
|
||||||
raise if exception_handler && exception_handler.call(e)
|
raise if @rethrow_errors
|
||||||
Liquid::Error.render(e)
|
|
||||||
|
case e
|
||||||
|
when SyntaxError
|
||||||
|
"Liquid syntax error: #{e.message}"
|
||||||
|
else
|
||||||
|
"Liquid error: #{e.message}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def invoke(method, *args)
|
def invoke(method, *args)
|
||||||
strainer.invoke(method, *args).to_liquid
|
strainer.invoke(method, *args)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
|
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
|
||||||
def push(new_scope={})
|
def push(new_scope={})
|
||||||
@scopes.unshift(new_scope)
|
@scopes.unshift(new_scope)
|
||||||
raise StackLevelError, "Nesting too deep".freeze if @scopes.length > 100
|
raise StackLevelError, "Nesting too deep" if @scopes.length > 100
|
||||||
end
|
end
|
||||||
|
|
||||||
# Merge a hash of variables in the current local scope
|
# Merge a hash of variables in the current local scope
|
||||||
@@ -106,33 +101,17 @@ module Liquid
|
|||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# context['var] #=> nil
|
# context['var] #=> nil
|
||||||
def stack(new_scope=nil)
|
def stack(new_scope={})
|
||||||
old_stack_used = @this_stack_used
|
push(new_scope)
|
||||||
if new_scope
|
|
||||||
push(new_scope)
|
|
||||||
@this_stack_used = true
|
|
||||||
else
|
|
||||||
@this_stack_used = false
|
|
||||||
end
|
|
||||||
|
|
||||||
yield
|
yield
|
||||||
ensure
|
ensure
|
||||||
pop if @this_stack_used
|
pop
|
||||||
@this_stack_used = old_stack_used
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def clear_instance_assigns
|
def clear_instance_assigns
|
||||||
@scopes[0] = {}
|
@scopes[0] = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
|
|
||||||
def []=(key, value)
|
|
||||||
unless @this_stack_used
|
|
||||||
@this_stack_used = true
|
|
||||||
push({})
|
|
||||||
end
|
|
||||||
@scopes[0][key] = value
|
|
||||||
end
|
|
||||||
|
|
||||||
# Look up variable, either resolve directly after considering the name. We can directly handle
|
# Look up variable, either resolve directly after considering the name. We can directly handle
|
||||||
# Strings, digits, floats and booleans (true,false).
|
# Strings, digits, floats and booleans (true,false).
|
||||||
@@ -142,56 +121,128 @@ module Liquid
|
|||||||
#
|
#
|
||||||
# Example:
|
# Example:
|
||||||
# products == empty #=> products.empty?
|
# products == empty #=> products.empty?
|
||||||
def [](expression)
|
def resolve(key)
|
||||||
evaluate(Expression.parse(expression))
|
case key
|
||||||
end
|
when nil, ""
|
||||||
|
return nil
|
||||||
|
when "blank"
|
||||||
|
return :blank?
|
||||||
|
when "empty"
|
||||||
|
return :empty?
|
||||||
|
end
|
||||||
|
|
||||||
def has_key?(key)
|
result = Parser.parse(key)
|
||||||
self[key] != nil
|
stack = []
|
||||||
end
|
|
||||||
|
|
||||||
def evaluate(object)
|
result.each do |(sym, value)|
|
||||||
object.respond_to?(:evaluate) ? object.evaluate(self) : object
|
|
||||||
end
|
|
||||||
|
|
||||||
# Fetches an object starting at the local scope and then moving up the hierachy
|
case sym
|
||||||
def find_variable(key)
|
when :id
|
||||||
|
stack.push value
|
||||||
|
when :lookup
|
||||||
|
left = stack.pop
|
||||||
|
value = find_variable(left)
|
||||||
|
|
||||||
# This was changed from find() to find_index() because this is a very hot
|
stack.push(harden(value))
|
||||||
# path and find_index() is optimized in MRI to reduce object allocation
|
when :range
|
||||||
index = @scopes.find_index { |s| s.has_key?(key) }
|
right = stack.pop.to_i
|
||||||
scope = @scopes[index] if index
|
left = stack.pop.to_i
|
||||||
|
|
||||||
variable = nil
|
stack.push (left..right)
|
||||||
|
when :buildin
|
||||||
|
left = stack.pop
|
||||||
|
value = invoke_buildin(left, value)
|
||||||
|
|
||||||
if scope.nil?
|
stack.push(harden(value))
|
||||||
@environments.each do |e|
|
when :call
|
||||||
variable = lookup_and_evaluate(e, key)
|
left = stack.pop
|
||||||
unless variable.nil?
|
right = stack.pop
|
||||||
scope = e
|
value = lookup_and_evaluate(right, left)
|
||||||
break
|
|
||||||
end
|
stack.push(harden(value))
|
||||||
|
else
|
||||||
|
raise "unknown #{sym}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
scope ||= @environments.last || @scopes.last
|
return stack.first
|
||||||
variable ||= lookup_and_evaluate(scope, key)
|
|
||||||
|
|
||||||
variable = variable.to_liquid
|
|
||||||
variable.context = self if variable.respond_to?(:context=)
|
|
||||||
|
|
||||||
return variable
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def lookup_and_evaluate(obj, key)
|
|
||||||
if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=)
|
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
|
||||||
obj[key] = (value.arity == 0) ? value.call : value.call(self)
|
def []=(key, value)
|
||||||
else
|
@scopes[0][key] = value
|
||||||
value
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def has_key?(key)
|
||||||
|
resolve(key) != nil
|
||||||
|
end
|
||||||
|
|
||||||
|
alias_method :[], :resolve
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def invoke_buildin(obj, key)
|
||||||
|
# as weird as this is, liquid unit tests demand that we prioritize hash lookups
|
||||||
|
# to buildins. So if we got a hash and it has a :first element we need to call that
|
||||||
|
# instead of sending the first message...
|
||||||
|
|
||||||
|
if obj.respond_to?(:has_key?) && obj.has_key?(key)
|
||||||
|
return lookup_and_evaluate(obj, key)
|
||||||
|
end
|
||||||
|
|
||||||
|
if obj.respond_to?(key)
|
||||||
|
return obj.send(key)
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Fetches an object starting at the local scope and then moving up the hierachy
|
||||||
|
def find_variable(key)
|
||||||
|
scope = @scopes.find { |s| s.has_key?(key) }
|
||||||
|
|
||||||
|
if scope.nil?
|
||||||
|
@environments.each do |e|
|
||||||
|
if variable = lookup_and_evaluate(e, key)
|
||||||
|
scope = e
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
scope ||= @environments.last || @scopes.last
|
||||||
|
variable ||= lookup_and_evaluate(scope, key)
|
||||||
|
|
||||||
|
return variable
|
||||||
|
end
|
||||||
|
|
||||||
|
def lookup_and_evaluate(obj, key)
|
||||||
|
return nil unless obj.respond_to?(:[])
|
||||||
|
|
||||||
|
if obj.is_a?(Array)
|
||||||
|
return nil unless key.is_a?(Integer)
|
||||||
|
end
|
||||||
|
|
||||||
|
value = obj[key]
|
||||||
|
|
||||||
|
if value.is_a?(Proc)
|
||||||
|
# call the proc
|
||||||
|
value = (value.arity == 0) ? value.call : value.call(self)
|
||||||
|
|
||||||
|
# memozie if possible
|
||||||
|
obj[key] = value if obj.respond_to?(:[]=)
|
||||||
|
end
|
||||||
|
|
||||||
|
value
|
||||||
|
end
|
||||||
|
|
||||||
|
def harden(value)
|
||||||
|
value = value.to_liquid
|
||||||
|
value.context = self if value.respond_to?(:context=)
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
|
||||||
def squash_instance_assigns_with_environments
|
def squash_instance_assigns_with_environments
|
||||||
@scopes.last.each_key do |k|
|
@scopes.last.each_key do |k|
|
||||||
@environments.each do |env|
|
@environments.each do |env|
|
||||||
@@ -202,5 +253,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end # squash_instance_assigns_with_environments
|
end # squash_instance_assigns_with_environments
|
||||||
|
|
||||||
end # Context
|
end # Context
|
||||||
|
|
||||||
end # Liquid
|
end # Liquid
|
||||||
|
|||||||
@@ -1,24 +1,17 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Document < BlockBody
|
class Document < Block
|
||||||
def self.parse(tokens, options)
|
# we don't need markup to open this block
|
||||||
doc = new
|
def initialize(tokens)
|
||||||
doc.parse(tokens, options)
|
parse(tokens)
|
||||||
doc
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse(tokens, options)
|
# There isn't a real delimter
|
||||||
super do |end_tag_name, end_tag_params|
|
def block_delimiter
|
||||||
unknown_tag(end_tag_name, options) if end_tag_name
|
[]
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def unknown_tag(tag, options)
|
# Document blocks don't need to be terminated since they are not actually opened
|
||||||
case tag
|
def assert_missing_delimitation!
|
||||||
when 'else'.freeze, 'end'.freeze
|
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.unexpected_outer_tag".freeze, :tag => tag))
|
|
||||||
else
|
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.unknown_tag".freeze, :tag => tag))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -44,38 +44,18 @@ module Liquid
|
|||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def inspect
|
|
||||||
self.class.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_liquid
|
def to_liquid
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s
|
|
||||||
self.class.name
|
|
||||||
end
|
|
||||||
|
|
||||||
alias :[] :invoke_drop
|
alias :[] :invoke_drop
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Check for method existence without invoking respond_to?, which creates symbols
|
# Check for method existence without invoking respond_to?, which creates symbols
|
||||||
def self.invokable?(method_name)
|
def self.invokable?(method_name)
|
||||||
self.invokable_methods.include?(method_name.to_s)
|
@invokable_methods ||= Set.new((public_instance_methods - Liquid::Drop.public_instance_methods).map(&:to_s))
|
||||||
end
|
@invokable_methods.include?(method_name.to_s)
|
||||||
|
|
||||||
def self.invokable_methods
|
|
||||||
unless @invokable_methods
|
|
||||||
blacklist = Liquid::Drop.public_instance_methods + [:each]
|
|
||||||
if include?(Enumerable)
|
|
||||||
blacklist += Enumerable.public_instance_methods
|
|
||||||
blacklist -= [:sort, :count, :first, :min, :max, :include?]
|
|
||||||
end
|
|
||||||
whitelist = [:to_liquid] + (public_instance_methods - blacklist)
|
|
||||||
@invokable_methods = Set.new(whitelist.map(&:to_s))
|
|
||||||
end
|
|
||||||
@invokable_methods
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,60 +1,11 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Error < ::StandardError
|
class Error < ::StandardError; end
|
||||||
attr_accessor :line_number
|
|
||||||
attr_accessor :markup_context
|
|
||||||
|
|
||||||
def to_s(with_prefix=true)
|
|
||||||
str = ""
|
|
||||||
str << message_prefix if with_prefix
|
|
||||||
str << super()
|
|
||||||
|
|
||||||
if markup_context
|
|
||||||
str << " "
|
|
||||||
str << markup_context
|
|
||||||
end
|
|
||||||
|
|
||||||
str
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_line_number_from_token(token)
|
|
||||||
return unless token.respond_to?(:line_number)
|
|
||||||
return if self.line_number
|
|
||||||
self.line_number = token.line_number
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.render(e)
|
|
||||||
if e.is_a?(Liquid::Error)
|
|
||||||
e.to_s
|
|
||||||
else
|
|
||||||
"Liquid error: #{e.to_s}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def message_prefix
|
|
||||||
str = ""
|
|
||||||
if is_a?(SyntaxError)
|
|
||||||
str << "Liquid syntax error"
|
|
||||||
else
|
|
||||||
str << "Liquid error"
|
|
||||||
end
|
|
||||||
|
|
||||||
if line_number
|
|
||||||
str << " (line #{line_number})"
|
|
||||||
end
|
|
||||||
|
|
||||||
str << ": "
|
|
||||||
str
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class ArgumentError < Error; end
|
class ArgumentError < Error; end
|
||||||
class ContextError < Error; end
|
class ContextError < Error; end
|
||||||
|
class FilterNotFound < Error; end
|
||||||
class FileSystemError < Error; end
|
class FileSystemError < Error; end
|
||||||
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 TaintedError < Error; end
|
|
||||||
class MemoryError < Error; end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
module Liquid
|
|
||||||
class Expression
|
|
||||||
LITERALS = {
|
|
||||||
nil => nil, 'nil'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil,
|
|
||||||
'true'.freeze => true,
|
|
||||||
'false'.freeze => false,
|
|
||||||
'blank'.freeze => :blank?,
|
|
||||||
'empty'.freeze => :empty?
|
|
||||||
}
|
|
||||||
|
|
||||||
def self.parse(markup)
|
|
||||||
if LITERALS.key?(markup)
|
|
||||||
LITERALS[markup]
|
|
||||||
else
|
|
||||||
case markup
|
|
||||||
when /\A'(.*)'\z/m # Single quoted strings
|
|
||||||
$1
|
|
||||||
when /\A"(.*)"\z/m # Double quoted strings
|
|
||||||
$1
|
|
||||||
when /\A(-?\d+)\z/ # Integer and floats
|
|
||||||
$1.to_i
|
|
||||||
when /\A\((\S+)\.\.(\S+)\)\z/ # Ranges
|
|
||||||
RangeLookup.parse($1, $2)
|
|
||||||
when /\A(-?\d[\d\.]+)\z/ # Floats
|
|
||||||
$1.to_f
|
|
||||||
else
|
|
||||||
VariableLookup.parse(markup)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
# A Liquid file system is a way to let your templates retrieve other templates for use with the include tag.
|
# A Liquid file system is way to let your templates retrieve other templates for use with the include tag.
|
||||||
#
|
#
|
||||||
# You can implement subclasses that retrieve templates from the database, from the file system using a different
|
# You can implement subclasses that retrieve templates from the database, from the file system using a different
|
||||||
# path structure, you can provide them as hard-coded inline strings, or any manner that you see fit.
|
# path structure, you can provide them as hard-coded inline strings, or any manner that you see fit.
|
||||||
@@ -14,7 +14,7 @@ module Liquid
|
|||||||
# This will parse the template with a LocalFileSystem implementation rooted at 'template_path'.
|
# This will parse the template with a LocalFileSystem implementation rooted at 'template_path'.
|
||||||
class BlankFileSystem
|
class BlankFileSystem
|
||||||
# Called by Liquid to retrieve a template file
|
# Called by Liquid to retrieve a template file
|
||||||
def read_template_file(template_path)
|
def read_template_file(template_path, context)
|
||||||
raise FileSystemError, "This liquid context does not allow includes."
|
raise FileSystemError, "This liquid context does not allow includes."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -31,25 +31,14 @@ module Liquid
|
|||||||
# file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid"
|
# file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid"
|
||||||
# file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid"
|
# file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid"
|
||||||
#
|
#
|
||||||
# Optionally in the second argument you can specify a custom pattern for template filenames.
|
|
||||||
# The Kernel::sprintf format specification is used.
|
|
||||||
# Default pattern is "_%s.liquid".
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
#
|
|
||||||
# file_system = Liquid::LocalFileSystem.new("/some/path", "%s.html")
|
|
||||||
#
|
|
||||||
# file_system.full_path("index") # => "/some/path/index.html"
|
|
||||||
#
|
|
||||||
class LocalFileSystem
|
class LocalFileSystem
|
||||||
attr_accessor :root
|
attr_accessor :root
|
||||||
|
|
||||||
def initialize(root, pattern = "_%s.liquid".freeze)
|
def initialize(root)
|
||||||
@root = root
|
@root = root
|
||||||
@pattern = pattern
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def read_template_file(template_path)
|
def read_template_file(template_path, context)
|
||||||
full_path = full_path(template_path)
|
full_path = full_path(template_path)
|
||||||
raise FileSystemError, "No such template '#{template_path}'" unless File.exists?(full_path)
|
raise FileSystemError, "No such template '#{template_path}'" unless File.exists?(full_path)
|
||||||
|
|
||||||
@@ -57,15 +46,15 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def full_path(template_path)
|
def full_path(template_path)
|
||||||
raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /\A[^.\/][a-zA-Z0-9_\/]+\z/
|
raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /^[^.\/][a-zA-Z0-9_\/]+$/
|
||||||
|
|
||||||
full_path = if template_path.include?('/'.freeze)
|
full_path = if template_path.include?('/')
|
||||||
File.join(root, File.dirname(template_path), @pattern % File.basename(template_path))
|
File.join(root, File.dirname(template_path), "_#{File.basename(template_path)}.liquid")
|
||||||
else
|
else
|
||||||
File.join(root, @pattern % template_path)
|
File.join(root, "_#{template_path}.liquid")
|
||||||
end
|
end
|
||||||
|
|
||||||
raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path) =~ /\A#{File.expand_path(root)}/
|
raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path) =~ /^#{File.expand_path(root)}/
|
||||||
|
|
||||||
full_path
|
full_path
|
||||||
end
|
end
|
||||||
|
|||||||
119
lib/liquid/htmltags.rb
Normal file
119
lib/liquid/htmltags.rb
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
module Liquid
|
||||||
|
class TableRow < Block
|
||||||
|
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
|
||||||
|
|
||||||
|
def initialize(tag_name, markup, tokens)
|
||||||
|
if markup =~ Syntax
|
||||||
|
@variable_name = $1
|
||||||
|
@collection_name = $2
|
||||||
|
@idx_i = "#{$1}-#{$2}-i"
|
||||||
|
@idx_col = "#{$1}-#{$2}-c"
|
||||||
|
@attributes = {}
|
||||||
|
markup.scan(TagAttributes) do |key, value|
|
||||||
|
@attributes[key] = value
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise SyntaxError.new("Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3")
|
||||||
|
end
|
||||||
|
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
def render(context)
|
||||||
|
context.registers[:tablerowloop] ||= Hash.new(0)
|
||||||
|
|
||||||
|
collection = context[@collection_name] or return ''
|
||||||
|
|
||||||
|
from = @attributes['offset'] ? context[@attributes['offset']].to_i : 0
|
||||||
|
to = @attributes['limit'] ? from + context[@attributes['limit']].to_i : nil
|
||||||
|
|
||||||
|
collection = Utils.slice_collection_using_each(collection, from, to)
|
||||||
|
|
||||||
|
length = collection.length
|
||||||
|
|
||||||
|
cols = context[@attributes['cols']].to_i
|
||||||
|
|
||||||
|
row = 1
|
||||||
|
col = 0
|
||||||
|
|
||||||
|
result = ["<tr class=\"row1\">\n"]
|
||||||
|
context.stack do
|
||||||
|
|
||||||
|
context.registers[:tablerowloop][@idx]
|
||||||
|
context['tablerowloop'] = lambda { Tablerowloop.new(@idx_i, @idx_col, length) }
|
||||||
|
collection.each_with_index do |item, index|
|
||||||
|
context.registers[:tablerowloop][@idx_i] = index
|
||||||
|
context.registers[:tablerowloop][@idx_col] = col
|
||||||
|
context[@variable_name] = item
|
||||||
|
|
||||||
|
col += 1
|
||||||
|
|
||||||
|
result << "<td class=\"col#{col}\">" << render_all(@nodelist, context) << '</td>'
|
||||||
|
|
||||||
|
if col == cols and not (index == length - 1)
|
||||||
|
col = 0
|
||||||
|
row += 1
|
||||||
|
result << "</tr>\n<tr class=\"row#{row}\">"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
result << "</tr>\n"
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
class Tablerowloop < Liquid::Drop
|
||||||
|
attr_accessor :length
|
||||||
|
|
||||||
|
def initialize(idx_i, idx_col, length)
|
||||||
|
@idx_i, @idx_col, @length = idx_i, idx_col, length
|
||||||
|
end
|
||||||
|
|
||||||
|
def index
|
||||||
|
@context.registers[:tablerowloop][@idx_i] + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def index0
|
||||||
|
@context.registers[:tablerowloop][@idx_i]
|
||||||
|
end
|
||||||
|
|
||||||
|
def rindex
|
||||||
|
length - @context.registers[:tablerowloop][@idx_i]
|
||||||
|
end
|
||||||
|
|
||||||
|
def rindex0
|
||||||
|
length - @context.registers[:tablerowloop][@idx_i] - 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def first
|
||||||
|
(@context.registers[:tablerowloop][@idx_i] == 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
def last
|
||||||
|
(@context.registers[:tablerowloop][@idx_i] == length - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
def col
|
||||||
|
@context.registers[:tablerowloop][@idx_col] + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def col0
|
||||||
|
@context.registers[:tablerowloop][@idx_col]
|
||||||
|
end
|
||||||
|
|
||||||
|
def col_first
|
||||||
|
(@context.registers[:tablerowloop][@idx_col] == 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
def col_last
|
||||||
|
(@context.registers[:tablerowloop][@idx_col] == cols - 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Template.register_tag('tablerow', TableRow)
|
||||||
|
end
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
require 'yaml'
|
|
||||||
|
|
||||||
module Liquid
|
|
||||||
class I18n
|
|
||||||
DEFAULT_LOCALE = File.join(File.expand_path(File.dirname(__FILE__)), "locales", "en.yml")
|
|
||||||
|
|
||||||
class TranslationError < StandardError
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_reader :path
|
|
||||||
|
|
||||||
def initialize(path = DEFAULT_LOCALE)
|
|
||||||
@path = path
|
|
||||||
end
|
|
||||||
|
|
||||||
def translate(name, vars = {})
|
|
||||||
interpolate(deep_fetch_translation(name), vars)
|
|
||||||
end
|
|
||||||
alias_method :t, :translate
|
|
||||||
|
|
||||||
def locale
|
|
||||||
@locale ||= YAML.load_file(@path)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def interpolate(name, vars)
|
|
||||||
name.gsub(/%\{(\w+)\}/) {
|
|
||||||
# raise TranslationError, "Undefined key #{$1} for interpolation in translation #{name}" unless vars[$1.to_sym]
|
|
||||||
"#{vars[$1.to_sym]}"
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def deep_fetch_translation(name)
|
|
||||||
name.split('.'.freeze).reduce(locale) do |level, cur|
|
|
||||||
level[cur] or raise TranslationError, "Translation for #{name} does not exist in locale #{path}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -5,7 +5,7 @@ module Liquid
|
|||||||
attr_reader :message
|
attr_reader :message
|
||||||
|
|
||||||
def initialize(message=nil)
|
def initialize(message=nil)
|
||||||
@message = message || "interrupt".freeze
|
@message = message || "interrupt"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
require "strscan"
|
|
||||||
module Liquid
|
|
||||||
class Lexer
|
|
||||||
SPECIALS = {
|
|
||||||
'|'.freeze => :pipe,
|
|
||||||
'.'.freeze => :dot,
|
|
||||||
':'.freeze => :colon,
|
|
||||||
','.freeze => :comma,
|
|
||||||
'['.freeze => :open_square,
|
|
||||||
']'.freeze => :close_square,
|
|
||||||
'('.freeze => :open_round,
|
|
||||||
')'.freeze => :close_round,
|
|
||||||
'?'.freeze => :question,
|
|
||||||
'-'.freeze => :dash
|
|
||||||
}
|
|
||||||
IDENTIFIER = /[a-zA-Z_][\w-]*\??/
|
|
||||||
SINGLE_STRING_LITERAL = /'[^\']*'/
|
|
||||||
DOUBLE_STRING_LITERAL = /"[^\"]*"/
|
|
||||||
NUMBER_LITERAL = /-?\d+(\.\d+)?/
|
|
||||||
DOTDOT = /\.\./
|
|
||||||
COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains/
|
|
||||||
|
|
||||||
def initialize(input)
|
|
||||||
@ss = StringScanner.new(input.rstrip)
|
|
||||||
end
|
|
||||||
|
|
||||||
def tokenize
|
|
||||||
@output = []
|
|
||||||
|
|
||||||
while !@ss.eos?
|
|
||||||
@ss.skip(/\s*/)
|
|
||||||
tok = case
|
|
||||||
when t = @ss.scan(COMPARISON_OPERATOR) then [:comparison, t]
|
|
||||||
when t = @ss.scan(SINGLE_STRING_LITERAL) then [:string, t]
|
|
||||||
when t = @ss.scan(DOUBLE_STRING_LITERAL) then [:string, t]
|
|
||||||
when t = @ss.scan(NUMBER_LITERAL) then [:number, t]
|
|
||||||
when t = @ss.scan(IDENTIFIER) then [:id, t]
|
|
||||||
when t = @ss.scan(DOTDOT) then [:dotdot, t]
|
|
||||||
else
|
|
||||||
c = @ss.getch
|
|
||||||
if s = SPECIALS[c]
|
|
||||||
[s,c]
|
|
||||||
else
|
|
||||||
raise SyntaxError, "Unexpected character #{c}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@output << tok
|
|
||||||
end
|
|
||||||
|
|
||||||
@output << [:end_of_string]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
---
|
|
||||||
errors:
|
|
||||||
syntax:
|
|
||||||
assign: "Syntax Error in 'assign' - Valid syntax: assign [var] = [source]"
|
|
||||||
capture: "Syntax Error in 'capture' - Valid syntax: capture [var]"
|
|
||||||
case: "Syntax Error in 'case' - Valid syntax: case [condition]"
|
|
||||||
case_invalid_when: "Syntax Error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %}"
|
|
||||||
case_invalid_else: "Syntax Error in tag 'case' - Valid else condition: {% else %} (no parameters) "
|
|
||||||
cycle: "Syntax Error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]"
|
|
||||||
for: "Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]"
|
|
||||||
for_invalid_in: "For loops require an 'in' clause"
|
|
||||||
for_invalid_attribute: "Invalid attribute in for loop. Valid attributes are limit and offset"
|
|
||||||
if: "Syntax Error in tag 'if' - Valid syntax: if [expression]"
|
|
||||||
include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]"
|
|
||||||
unknown_tag: "Unknown tag '%{tag}'"
|
|
||||||
invalid_delimiter: "'end' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
|
|
||||||
unexpected_else: "%{block_name} tag does not expect 'else' tag"
|
|
||||||
unexpected_outer_tag: "Unexpected outer '%{tag}' 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,22 +2,21 @@
|
|||||||
# This library is free software. It may be used, redistributed and/or modified
|
# This library is free software. It may be used, redistributed and/or modified
|
||||||
# under the same terms as Ruby itself
|
# under the same terms as Ruby itself
|
||||||
#
|
#
|
||||||
# This extension is used in order to expose the object of the implementing class
|
# This extension is usesd in order to expose the object of the implementing class
|
||||||
# to liquid as it were a Drop. It also limits the liquid-callable methods of the instance
|
# to liquid as it were a Drop. It also limits the liquid-callable methods of the instance
|
||||||
# to the allowed method passed with the liquid_methods call
|
# to the allowed method passed with the liquid_methods call
|
||||||
# Example:
|
# Example:
|
||||||
#
|
#
|
||||||
# class SomeClass
|
# class SomeClass
|
||||||
# liquid_methods :an_allowed_method
|
# liquid_methods :an_allowed_method
|
||||||
#
|
#
|
||||||
# def an_allowed_method
|
# def an_allowed_method
|
||||||
# 'this comes from an allowed method'
|
# 'this comes from an allowed method'
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def unallowed_method
|
|
||||||
# 'this will never be an output'
|
|
||||||
# end
|
|
||||||
# end
|
# end
|
||||||
|
# def unallowed_method
|
||||||
|
# 'this will never be an output'
|
||||||
|
# end
|
||||||
|
# end
|
||||||
#
|
#
|
||||||
# if you want to extend the drop to other methods you can defines more methods
|
# if you want to extend the drop to other methods you can defines more methods
|
||||||
# in the class <YourClass>::LiquidDropClass
|
# in the class <YourClass>::LiquidDropClass
|
||||||
@@ -27,33 +26,31 @@
|
|||||||
# 'and this from another allowed method'
|
# 'and this from another allowed method'
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
#
|
# end
|
||||||
#
|
#
|
||||||
# usage:
|
# usage:
|
||||||
# @something = SomeClass.new
|
# @something = SomeClass.new
|
||||||
#
|
#
|
||||||
# template:
|
# template:
|
||||||
# {{something.an_allowed_method}}{{something.unallowed_method}} {{something.another_allowed_method}}
|
# {{something.an_allowed_method}}{{something.unallowed_method}} {{something.another_allowed_method}}
|
||||||
#
|
#
|
||||||
# output:
|
# output:
|
||||||
# 'this comes from an allowed method and this from another allowed method'
|
# 'this comes from an allowed method and this from another allowed method'
|
||||||
#
|
#
|
||||||
# You can also chain associations, by adding the liquid_method call in the
|
# You can also chain associations, by adding the liquid_method call in the
|
||||||
# association models.
|
# association models.
|
||||||
#
|
#
|
||||||
class Module
|
class Module
|
||||||
|
|
||||||
def liquid_methods(*allowed_methods)
|
def liquid_methods(*allowed_methods)
|
||||||
drop_class = eval "class #{self.to_s}::LiquidDropClass < Liquid::Drop; self; end"
|
drop_class = eval "class #{self.to_s}::LiquidDropClass < Liquid::Drop; self; end"
|
||||||
|
|
||||||
define_method :to_liquid do
|
define_method :to_liquid do
|
||||||
drop_class.new(self)
|
drop_class.new(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
drop_class.class_eval do
|
drop_class.class_eval do
|
||||||
def initialize(object)
|
def initialize(object)
|
||||||
@object = object
|
@object = object
|
||||||
end
|
end
|
||||||
|
|
||||||
allowed_methods.each do |sym|
|
allowed_methods.each do |sym|
|
||||||
define_method sym do
|
define_method sym do
|
||||||
@object.send sym
|
@object.send sym
|
||||||
@@ -61,4 +58,5 @@ class Module
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
module Liquid
|
|
||||||
class Parser
|
|
||||||
def initialize(input)
|
|
||||||
l = Lexer.new(input)
|
|
||||||
@tokens = l.tokenize
|
|
||||||
@p = 0 # pointer to current location
|
|
||||||
end
|
|
||||||
|
|
||||||
def jump(point)
|
|
||||||
@p = point
|
|
||||||
end
|
|
||||||
|
|
||||||
def consume(type = nil)
|
|
||||||
token = @tokens[@p]
|
|
||||||
if type && token[0] != type
|
|
||||||
raise SyntaxError, "Expected #{type} but found #{@tokens[@p].first}"
|
|
||||||
end
|
|
||||||
@p += 1
|
|
||||||
token[1]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Only consumes the token if it matches the type
|
|
||||||
# Returns the token's contents if it was consumed
|
|
||||||
# or false otherwise.
|
|
||||||
def consume?(type)
|
|
||||||
token = @tokens[@p]
|
|
||||||
return false unless token && token[0] == type
|
|
||||||
@p += 1
|
|
||||||
token[1]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Like consume? Except for an :id token of a certain name
|
|
||||||
def id?(str)
|
|
||||||
token = @tokens[@p]
|
|
||||||
return false unless token && token[0] == :id
|
|
||||||
return false unless token[1] == str
|
|
||||||
@p += 1
|
|
||||||
token[1]
|
|
||||||
end
|
|
||||||
|
|
||||||
def look(type, ahead = 0)
|
|
||||||
tok = @tokens[@p + ahead]
|
|
||||||
return false unless tok
|
|
||||||
tok[0] == type
|
|
||||||
end
|
|
||||||
|
|
||||||
def expression
|
|
||||||
token = @tokens[@p]
|
|
||||||
if token[0] == :id
|
|
||||||
variable_signature
|
|
||||||
elsif [:string, :number].include? token[0]
|
|
||||||
consume
|
|
||||||
elsif token.first == :open_round
|
|
||||||
consume
|
|
||||||
first = expression
|
|
||||||
consume(:dotdot)
|
|
||||||
last = expression
|
|
||||||
consume(:close_round)
|
|
||||||
"(#{first}..#{last})"
|
|
||||||
else
|
|
||||||
raise SyntaxError, "#{token} is not a valid expression"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def argument
|
|
||||||
str = ""
|
|
||||||
# might be a keyword argument (identifier: expression)
|
|
||||||
if look(:id) && look(:colon, 1)
|
|
||||||
str << consume << consume << ' '.freeze
|
|
||||||
end
|
|
||||||
|
|
||||||
str << expression
|
|
||||||
str
|
|
||||||
end
|
|
||||||
|
|
||||||
def variable_signature
|
|
||||||
str = consume(:id)
|
|
||||||
if look(:open_square)
|
|
||||||
str << consume
|
|
||||||
str << expression
|
|
||||||
str << consume(:close_square)
|
|
||||||
end
|
|
||||||
if look(:dot)
|
|
||||||
str << consume
|
|
||||||
str << variable_signature
|
|
||||||
end
|
|
||||||
str
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
module Liquid
|
|
||||||
module ParserSwitching
|
|
||||||
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
|
|
||||||
e.set_line_number_from_token(markup)
|
|
||||||
@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.markup_context = markup_context(markup)
|
|
||||||
raise e
|
|
||||||
end
|
|
||||||
|
|
||||||
def markup_context(markup)
|
|
||||||
"in \"#{markup.strip}\""
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
require 'liquid/profiler/hooks'
|
|
||||||
|
|
||||||
module Liquid
|
|
||||||
|
|
||||||
# Profiler enables support for profiling template rendering to help track down performance issues.
|
|
||||||
#
|
|
||||||
# To enable profiling, first require 'liquid/profiler'.
|
|
||||||
# Then, to profile a parse/render cycle, pass the <tt>profile: true</tt> option to <tt>Liquid::Template.parse</tt>.
|
|
||||||
# After <tt>Liquid::Template#render</tt> is called, the template object makes available an instance of this
|
|
||||||
# class via the <tt>Liquid::Template#profiler</tt> method.
|
|
||||||
#
|
|
||||||
# template = Liquid::Template.parse(template_content, profile: true)
|
|
||||||
# output = template.render
|
|
||||||
# profile = template.profiler
|
|
||||||
#
|
|
||||||
# This object contains all profiling information, containing information on what tags were rendered,
|
|
||||||
# where in the templates these tags live, and how long each tag took to render.
|
|
||||||
#
|
|
||||||
# This is a tree structure that is Enumerable all the way down, and keeps track of tags and rendering times
|
|
||||||
# inside of <tt>{% include %}</tt> tags.
|
|
||||||
#
|
|
||||||
# profile.each do |node|
|
|
||||||
# # Access to the token itself
|
|
||||||
# node.code
|
|
||||||
#
|
|
||||||
# # Which template and line number of this node.
|
|
||||||
# # If top level, this will be "<root>".
|
|
||||||
# node.partial
|
|
||||||
# node.line_number
|
|
||||||
#
|
|
||||||
# # Render time in seconds of this node
|
|
||||||
# node.render_time
|
|
||||||
#
|
|
||||||
# # If the template used {% include %}, this node will also have children.
|
|
||||||
# node.children.each do |child2|
|
|
||||||
# # ...
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# Profiler also exposes the total time of the template's render in <tt>Liquid::Profiler#total_render_time</tt>.
|
|
||||||
#
|
|
||||||
# All render times are in seconds. There is a small performance hit when profiling is enabled.
|
|
||||||
#
|
|
||||||
class Profiler
|
|
||||||
include Enumerable
|
|
||||||
|
|
||||||
class Timing
|
|
||||||
attr_reader :code, :partial, :line_number, :children
|
|
||||||
|
|
||||||
def initialize(token, partial)
|
|
||||||
@code = token.respond_to?(:raw) ? token.raw : token
|
|
||||||
@partial = partial
|
|
||||||
@line_number = token.respond_to?(:line_number) ? token.line_number : nil
|
|
||||||
@children = []
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.start(token, partial)
|
|
||||||
new(token, partial).tap do |t|
|
|
||||||
t.start
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def start
|
|
||||||
@start_time = Time.now
|
|
||||||
end
|
|
||||||
|
|
||||||
def finish
|
|
||||||
@end_time = Time.now
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_time
|
|
||||||
@end_time - @start_time
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.profile_token_render(token)
|
|
||||||
if Profiler.current_profile && token.respond_to?(:render)
|
|
||||||
Profiler.current_profile.start_token(token)
|
|
||||||
output = yield
|
|
||||||
Profiler.current_profile.end_token(token)
|
|
||||||
output
|
|
||||||
else
|
|
||||||
yield
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.profile_children(template_name)
|
|
||||||
if Profiler.current_profile
|
|
||||||
Profiler.current_profile.push_partial(template_name)
|
|
||||||
output = yield
|
|
||||||
Profiler.current_profile.pop_partial
|
|
||||||
output
|
|
||||||
else
|
|
||||||
yield
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.current_profile
|
|
||||||
Thread.current[:liquid_profiler]
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
@partial_stack = ["<root>"]
|
|
||||||
|
|
||||||
@root_timing = Timing.new("", current_partial)
|
|
||||||
@timing_stack = [@root_timing]
|
|
||||||
|
|
||||||
@render_start_at = Time.now
|
|
||||||
@render_end_at = @render_start_at
|
|
||||||
end
|
|
||||||
|
|
||||||
def start
|
|
||||||
Thread.current[:liquid_profiler] = self
|
|
||||||
@render_start_at = Time.now
|
|
||||||
end
|
|
||||||
|
|
||||||
def stop
|
|
||||||
Thread.current[:liquid_profiler] = nil
|
|
||||||
@render_end_at = Time.now
|
|
||||||
end
|
|
||||||
|
|
||||||
def total_render_time
|
|
||||||
@render_end_at - @render_start_at
|
|
||||||
end
|
|
||||||
|
|
||||||
def each(&block)
|
|
||||||
@root_timing.children.each(&block)
|
|
||||||
end
|
|
||||||
|
|
||||||
def [](idx)
|
|
||||||
@root_timing.children[idx]
|
|
||||||
end
|
|
||||||
|
|
||||||
def length
|
|
||||||
@root_timing.children.length
|
|
||||||
end
|
|
||||||
|
|
||||||
def start_token(token)
|
|
||||||
@timing_stack.push(Timing.start(token, current_partial))
|
|
||||||
end
|
|
||||||
|
|
||||||
def end_token(token)
|
|
||||||
timing = @timing_stack.pop
|
|
||||||
timing.finish
|
|
||||||
|
|
||||||
@timing_stack.last.children << timing
|
|
||||||
end
|
|
||||||
|
|
||||||
def current_partial
|
|
||||||
@partial_stack.last
|
|
||||||
end
|
|
||||||
|
|
||||||
def push_partial(partial_name)
|
|
||||||
@partial_stack.push(partial_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
def pop_partial
|
|
||||||
@partial_stack.pop
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
module Liquid
|
|
||||||
class BlockBody
|
|
||||||
def render_token_with_profiling(token, context)
|
|
||||||
Profiler.profile_token_render(token) do
|
|
||||||
render_token_without_profiling(token, context)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
alias_method :render_token_without_profiling, :render_token
|
|
||||||
alias_method :render_token, :render_token_with_profiling
|
|
||||||
end
|
|
||||||
|
|
||||||
class Include < Tag
|
|
||||||
def render_with_profiling(context)
|
|
||||||
Profiler.profile_children(context.evaluate(@template_name).to_s) do
|
|
||||||
render_without_profiling(context)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
alias_method :render_without_profiling, :render
|
|
||||||
alias_method :render, :render_with_profiling
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
module Liquid
|
|
||||||
class RangeLookup
|
|
||||||
def self.parse(start_markup, end_markup)
|
|
||||||
start_obj = Expression.parse(start_markup)
|
|
||||||
end_obj = Expression.parse(end_markup)
|
|
||||||
if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate)
|
|
||||||
new(start_obj, end_obj)
|
|
||||||
else
|
|
||||||
start_obj.to_i..end_obj.to_i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(start_obj, end_obj)
|
|
||||||
@start_obj = start_obj
|
|
||||||
@end_obj = end_obj
|
|
||||||
end
|
|
||||||
|
|
||||||
def evaluate(context)
|
|
||||||
context.evaluate(@start_obj).to_i..context.evaluate(@end_obj).to_i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
module Liquid
|
|
||||||
class ResourceLimits
|
|
||||||
attr_accessor :render_length, :render_score, :assign_score,
|
|
||||||
:render_length_limit, :render_score_limit, :assign_score_limit
|
|
||||||
|
|
||||||
def initialize(limits)
|
|
||||||
@render_length_limit = limits[:render_length_limit]
|
|
||||||
@render_score_limit = limits[:render_score_limit]
|
|
||||||
@assign_score_limit = limits[:assign_score_limit]
|
|
||||||
reset
|
|
||||||
end
|
|
||||||
|
|
||||||
def reached?
|
|
||||||
(@render_length_limit && @render_length > @render_length_limit) ||
|
|
||||||
(@render_score_limit && @render_score > @render_score_limit ) ||
|
|
||||||
(@assign_score_limit && @assign_score > @assign_score_limit )
|
|
||||||
end
|
|
||||||
|
|
||||||
def reset
|
|
||||||
@render_length = @render_score = @assign_score = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,29 +1,21 @@
|
|||||||
require 'cgi'
|
require 'cgi'
|
||||||
require 'bigdecimal'
|
|
||||||
|
|
||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
module StandardFilters
|
module StandardFilters
|
||||||
HTML_ESCAPE = {
|
|
||||||
'&'.freeze => '&'.freeze,
|
|
||||||
'>'.freeze => '>'.freeze,
|
|
||||||
'<'.freeze => '<'.freeze,
|
|
||||||
'"'.freeze => '"'.freeze,
|
|
||||||
"'".freeze => '''.freeze
|
|
||||||
}
|
|
||||||
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
|
|
||||||
|
|
||||||
# Return the size of an array or of an string
|
# Return the size of an array or of an string
|
||||||
def size(input)
|
def size(input)
|
||||||
|
|
||||||
input.respond_to?(:size) ? input.size : 0
|
input.respond_to?(:size) ? input.size : 0
|
||||||
end
|
end
|
||||||
|
|
||||||
# convert an input string to DOWNCASE
|
# convert a input string to DOWNCASE
|
||||||
def downcase(input)
|
def downcase(input)
|
||||||
input.to_s.downcase
|
input.to_s.downcase
|
||||||
end
|
end
|
||||||
|
|
||||||
# convert an input string to UPCASE
|
# convert a input string to UPCASE
|
||||||
def upcase(input)
|
def upcase(input)
|
||||||
input.to_s.upcase
|
input.to_s.upcase
|
||||||
end
|
end
|
||||||
@@ -34,43 +26,31 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def escape(input)
|
def escape(input)
|
||||||
CGI.escapeHTML(input).untaint rescue input
|
CGI.escapeHTML(input) rescue input
|
||||||
end
|
end
|
||||||
alias_method :h, :escape
|
|
||||||
|
|
||||||
def escape_once(input)
|
def escape_once(input)
|
||||||
input.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
|
ActionView::Helpers::TagHelper.escape_once(input)
|
||||||
|
rescue NameError
|
||||||
|
input
|
||||||
end
|
end
|
||||||
|
|
||||||
def url_encode(input)
|
alias_method :h, :escape
|
||||||
CGI.escape(input) rescue input
|
|
||||||
end
|
|
||||||
|
|
||||||
def slice(input, offset, length=nil)
|
|
||||||
offset = Integer(offset)
|
|
||||||
length = length ? Integer(length) : 1
|
|
||||||
|
|
||||||
if input.is_a?(Array)
|
|
||||||
input.slice(offset, length) || []
|
|
||||||
else
|
|
||||||
input.to_s.slice(offset, length) || ''
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Truncate a string down to x characters
|
# Truncate a string down to x characters
|
||||||
def truncate(input, length = 50, truncate_string = "...".freeze)
|
def truncate(input, length = 50, truncate_string = "...")
|
||||||
if input.nil? then return end
|
if input.nil? then return end
|
||||||
l = length.to_i - truncate_string.length
|
l = length.to_i - truncate_string.length
|
||||||
l = 0 if l < 0
|
l = 0 if l < 0
|
||||||
input.length > length.to_i ? input[0...l] + truncate_string : input
|
input.length > length.to_i ? input[0...l] + truncate_string : input
|
||||||
end
|
end
|
||||||
|
|
||||||
def truncatewords(input, words = 15, truncate_string = "...".freeze)
|
def truncatewords(input, words = 15, truncate_string = "...")
|
||||||
if input.nil? then return end
|
if input.nil? then return end
|
||||||
wordlist = input.to_s.split
|
wordlist = input.to_s.split
|
||||||
l = words.to_i - 1
|
l = words.to_i - 1
|
||||||
l = 0 if l < 0
|
l = 0 if l < 0
|
||||||
wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string : input
|
wordlist.length > l ? wordlist[0..l].join(" ") + truncate_string : input
|
||||||
end
|
end
|
||||||
|
|
||||||
# Split input string into an array of substrings separated by given pattern.
|
# Split input string into an array of substrings separated by given pattern.
|
||||||
@@ -79,97 +59,65 @@ module Liquid
|
|||||||
# <div class="summary">{{ post | split '//' | first }}</div>
|
# <div class="summary">{{ post | split '//' | first }}</div>
|
||||||
#
|
#
|
||||||
def split(input, pattern)
|
def split(input, pattern)
|
||||||
input.to_s.split(pattern)
|
input.split(pattern)
|
||||||
end
|
|
||||||
|
|
||||||
def strip(input)
|
|
||||||
input.to_s.strip
|
|
||||||
end
|
|
||||||
|
|
||||||
def lstrip(input)
|
|
||||||
input.to_s.lstrip
|
|
||||||
end
|
|
||||||
|
|
||||||
def rstrip(input)
|
|
||||||
input.to_s.rstrip
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def strip_html(input)
|
def strip_html(input)
|
||||||
empty = ''.freeze
|
input.to_s.gsub(/<script.*?<\/script>/, '').gsub(/<!--.*?-->/, '').gsub(/<.*?>/, '')
|
||||||
input.to_s.gsub(/<script.*?<\/script>/m, empty).gsub(/<!--.*?-->/m, empty).gsub(/<style.*?<\/style>/m, empty).gsub(/<.*?>/m, empty)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Remove all newlines from the string
|
# Remove all newlines from the string
|
||||||
def strip_newlines(input)
|
def strip_newlines(input)
|
||||||
input.to_s.gsub(/\r?\n/, ''.freeze)
|
input.to_s.gsub(/\n/, '')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# Join elements of the array with certain character between them
|
# Join elements of the array with certain character between them
|
||||||
def join(input, glue = ' '.freeze)
|
def join(input, glue = ' ')
|
||||||
InputIterator.new(input).join(glue)
|
[input].flatten.join(glue)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sort elements of the array
|
# Sort elements of the array
|
||||||
# provide optional property with which to sort an array of hashes or drops
|
# provide optional property with which to sort an array of hashes or drops
|
||||||
def sort(input, property = nil)
|
def sort(input, property = nil)
|
||||||
ary = InputIterator.new(input)
|
ary = [input].flatten
|
||||||
if property.nil?
|
if property.nil?
|
||||||
ary.sort
|
ary.sort
|
||||||
elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
|
elsif ary.first.respond_to?('[]') and !ary.first[property].nil?
|
||||||
ary.sort {|a,b| a[property] <=> b[property] }
|
ary.sort {|a,b| a[property] <=> b[property] }
|
||||||
elsif ary.first.respond_to?(property)
|
elsif ary.first.respond_to?(property)
|
||||||
ary.sort {|a,b| a.send(property) <=> b.send(property) }
|
ary.sort {|a,b| a.send(property) <=> b.send(property) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Remove duplicate elements from an array
|
|
||||||
# provide optional property with which to determine uniqueness
|
|
||||||
def uniq(input, property = nil)
|
|
||||||
ary = InputIterator.new(input)
|
|
||||||
if property.nil?
|
|
||||||
input.uniq
|
|
||||||
elsif input.first.respond_to?(:[])
|
|
||||||
input.uniq{ |a| a[property] }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Reverse the elements of an array
|
|
||||||
def reverse(input)
|
|
||||||
ary = InputIterator.new(input)
|
|
||||||
ary.reverse
|
|
||||||
end
|
|
||||||
|
|
||||||
# map/collect on a given property
|
# map/collect on a given property
|
||||||
def map(input, property)
|
def map(input, property)
|
||||||
InputIterator.new(input).map do |e|
|
ary = [input].flatten
|
||||||
e = e.call if e.is_a?(Proc)
|
if ary.first.respond_to?('[]') and !ary.first[property].nil?
|
||||||
|
ary.map {|e| e[property] }
|
||||||
if property == "to_liquid".freeze
|
elsif ary.first.respond_to?(property)
|
||||||
e
|
ary.map {|e| e.send(property) }
|
||||||
elsif e.respond_to?(:[])
|
|
||||||
e[property]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Replace occurrences of a string with another
|
# Replace occurrences of a string with another
|
||||||
def replace(input, string, replacement = ''.freeze)
|
def replace(input, string, replacement = '')
|
||||||
input.to_s.gsub(string, replacement.to_s)
|
input.to_s.gsub(string, replacement)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Replace the first occurrences of a string with another
|
# Replace the first occurrences of a string with another
|
||||||
def replace_first(input, string, replacement = ''.freeze)
|
def replace_first(input, string, replacement = '')
|
||||||
input.to_s.sub(string, replacement.to_s)
|
input.to_s.sub(string, replacement)
|
||||||
end
|
end
|
||||||
|
|
||||||
# remove a substring
|
# remove a substring
|
||||||
def remove(input, string)
|
def remove(input, string)
|
||||||
input.to_s.gsub(string, ''.freeze)
|
input.to_s.gsub(string, '')
|
||||||
end
|
end
|
||||||
|
|
||||||
# remove the first occurrences of a substring
|
# remove the first occurrences of a substring
|
||||||
def remove_first(input, string)
|
def remove_first(input, string)
|
||||||
input.to_s.sub(string, ''.freeze)
|
input.to_s.sub(string, '')
|
||||||
end
|
end
|
||||||
|
|
||||||
# add one string to another
|
# add one string to another
|
||||||
@@ -177,10 +125,6 @@ module Liquid
|
|||||||
input.to_s + string.to_s
|
input.to_s + string.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
def concat(input, array)
|
|
||||||
InputIterator.new(input).concat(array)
|
|
||||||
end
|
|
||||||
|
|
||||||
# prepend a string to another
|
# prepend a string to another
|
||||||
def prepend(input, string)
|
def prepend(input, string)
|
||||||
string.to_s + input.to_s
|
string.to_s + input.to_s
|
||||||
@@ -188,10 +132,10 @@ module Liquid
|
|||||||
|
|
||||||
# Add <br /> tags in front of all newlines in input string
|
# Add <br /> tags in front of all newlines in input string
|
||||||
def newline_to_br(input)
|
def newline_to_br(input)
|
||||||
input.to_s.gsub(/\n/, "<br />\n".freeze)
|
input.to_s.gsub(/\n/, "<br />\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
# Reformat a date using Ruby's core Time#strftime( string ) -> string
|
# Reformat a date
|
||||||
#
|
#
|
||||||
# %a - The abbreviated weekday name (``Sun'')
|
# %a - The abbreviated weekday name (``Sun'')
|
||||||
# %A - The full weekday name (``Sunday'')
|
# %A - The full weekday name (``Sunday'')
|
||||||
@@ -205,7 +149,6 @@ module Liquid
|
|||||||
# %m - Month of the year (01..12)
|
# %m - Month of the year (01..12)
|
||||||
# %M - Minute of the hour (00..59)
|
# %M - Minute of the hour (00..59)
|
||||||
# %p - Meridian indicator (``AM'' or ``PM'')
|
# %p - Meridian indicator (``AM'' or ``PM'')
|
||||||
# %s - Number of seconds since 1970-01-01 00:00:00 UTC.
|
|
||||||
# %S - Second of the minute (00..60)
|
# %S - Second of the minute (00..60)
|
||||||
# %U - Week number of the current year,
|
# %U - Week number of the current year,
|
||||||
# starting with the first Sunday as the first
|
# starting with the first Sunday as the first
|
||||||
@@ -220,14 +163,25 @@ module Liquid
|
|||||||
# %Y - Year with century
|
# %Y - Year with century
|
||||||
# %Z - Time zone name
|
# %Z - Time zone name
|
||||||
# %% - Literal ``%'' character
|
# %% - Literal ``%'' character
|
||||||
#
|
|
||||||
# See also: http://www.ruby-doc.org/core/Time.html#method-i-strftime
|
|
||||||
def date(input, format)
|
def date(input, format)
|
||||||
return input if format.to_s.empty?
|
|
||||||
|
|
||||||
return input unless date = to_date(input)
|
if format.to_s.empty?
|
||||||
|
return input.to_s
|
||||||
|
end
|
||||||
|
|
||||||
date.strftime(format.to_s)
|
if ((input.is_a?(String) && !/^\d+$/.match(input.to_s).nil?) || input.is_a?(Integer)) && input.to_i > 0
|
||||||
|
input = Time.at(input.to_i)
|
||||||
|
end
|
||||||
|
|
||||||
|
date = input.is_a?(String) ? Time.parse(input) : input
|
||||||
|
|
||||||
|
if date.respond_to?(:strftime)
|
||||||
|
date.strftime(format.to_s)
|
||||||
|
else
|
||||||
|
input
|
||||||
|
end
|
||||||
|
rescue => e
|
||||||
|
input
|
||||||
end
|
end
|
||||||
|
|
||||||
# Get the first element of the passed in array
|
# Get the first element of the passed in array
|
||||||
@@ -250,120 +204,41 @@ module Liquid
|
|||||||
|
|
||||||
# addition
|
# addition
|
||||||
def plus(input, operand)
|
def plus(input, operand)
|
||||||
apply_operation(input, operand, :+)
|
to_number(input) + to_number(operand)
|
||||||
end
|
end
|
||||||
|
|
||||||
# subtraction
|
# subtraction
|
||||||
def minus(input, operand)
|
def minus(input, operand)
|
||||||
apply_operation(input, operand, :-)
|
to_number(input) - to_number(operand)
|
||||||
end
|
end
|
||||||
|
|
||||||
# multiplication
|
# multiplication
|
||||||
def times(input, operand)
|
def times(input, operand)
|
||||||
apply_operation(input, operand, :*)
|
to_number(input) * to_number(operand)
|
||||||
end
|
end
|
||||||
|
|
||||||
# division
|
# division
|
||||||
def divided_by(input, operand)
|
def divided_by(input, operand)
|
||||||
apply_operation(input, operand, :/)
|
to_number(input) / to_number(operand)
|
||||||
end
|
end
|
||||||
|
|
||||||
def modulo(input, operand)
|
def modulo(input, operand)
|
||||||
apply_operation(input, operand, :%)
|
to_number(input) % to_number(operand)
|
||||||
end
|
|
||||||
|
|
||||||
def round(input, n = 0)
|
|
||||||
result = to_number(input).round(to_number(n))
|
|
||||||
result = result.to_f if result.is_a?(BigDecimal)
|
|
||||||
result = result.to_i if n == 0
|
|
||||||
result
|
|
||||||
end
|
|
||||||
|
|
||||||
def ceil(input)
|
|
||||||
to_number(input).ceil.to_i
|
|
||||||
end
|
|
||||||
|
|
||||||
def floor(input)
|
|
||||||
to_number(input).floor.to_i
|
|
||||||
end
|
|
||||||
|
|
||||||
def default(input, default_value = "".freeze)
|
|
||||||
is_blank = input.respond_to?(:empty?) ? input.empty? : !input
|
|
||||||
is_blank ? default_value : input
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def to_number(obj)
|
def to_number(obj)
|
||||||
case obj
|
case obj
|
||||||
when Float
|
when Numeric
|
||||||
BigDecimal.new(obj.to_s)
|
obj
|
||||||
when Numeric
|
when String
|
||||||
obj
|
(obj.strip =~ /^\d+\.\d+$/) ? obj.to_f : obj.to_i
|
||||||
when String
|
|
||||||
(obj.strip =~ /\A\d+\.\d+\z/) ? BigDecimal.new(obj) : obj.to_i
|
|
||||||
else
|
|
||||||
0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_date(obj)
|
|
||||||
return obj if obj.respond_to?(:strftime)
|
|
||||||
|
|
||||||
obj = obj.downcase if obj.is_a?(String)
|
|
||||||
|
|
||||||
case obj
|
|
||||||
when 'now'.freeze, 'today'.freeze
|
|
||||||
Time.now
|
|
||||||
when /\A\d+\z/, Integer
|
|
||||||
Time.at(obj.to_i)
|
|
||||||
when String
|
|
||||||
Time.parse(obj)
|
|
||||||
else
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
rescue ArgumentError
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def apply_operation(input, operand, operation)
|
|
||||||
result = to_number(input).send(operation, to_number(operand))
|
|
||||||
result.is_a?(BigDecimal) ? result.to_f : result
|
|
||||||
end
|
|
||||||
|
|
||||||
class InputIterator
|
|
||||||
include Enumerable
|
|
||||||
|
|
||||||
def initialize(input)
|
|
||||||
@input = if input.is_a?(Array)
|
|
||||||
input.flatten
|
|
||||||
elsif input.is_a?(Hash)
|
|
||||||
[input]
|
|
||||||
elsif input.is_a?(Enumerable)
|
|
||||||
input
|
|
||||||
else
|
else
|
||||||
Array(input)
|
0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def join(glue)
|
|
||||||
to_a.join(glue)
|
|
||||||
end
|
|
||||||
|
|
||||||
def concat(args)
|
|
||||||
to_a.concat args
|
|
||||||
end
|
|
||||||
|
|
||||||
def reverse
|
|
||||||
reverse_each.to_a
|
|
||||||
end
|
|
||||||
|
|
||||||
def each
|
|
||||||
@input.each do |e|
|
|
||||||
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_filter(StandardFilters)
|
Template.register_filter(StandardFilters)
|
||||||
|
|||||||
@@ -8,52 +8,46 @@ module Liquid
|
|||||||
# The Strainer only allows method calls defined in filters given to it via Strainer.global_filter,
|
# The Strainer only allows method calls defined in filters given to it via Strainer.global_filter,
|
||||||
# Context#add_filters or Template.register_filter
|
# Context#add_filters or Template.register_filter
|
||||||
class Strainer #:nodoc:
|
class Strainer #:nodoc:
|
||||||
@@global_strainer = Class.new(Strainer) do
|
@@filters = {}
|
||||||
@filter_methods = Set.new
|
@@known_filters = Set.new
|
||||||
end
|
@@known_methods = Set.new
|
||||||
@@strainer_class_cache = Hash.new do |hash, filters|
|
|
||||||
hash[filters] = Class.new(@@global_strainer) do
|
|
||||||
@filter_methods = @@global_strainer.filter_methods.dup
|
|
||||||
filters.each { |f| add_filter(f) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(context)
|
def initialize(context)
|
||||||
@context = context
|
@context = context
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.filter_methods
|
def self.global_filter(filter)
|
||||||
@filter_methods
|
raise ArgumentError, "Passed filter is not a module" unless filter.is_a?(Module)
|
||||||
|
add_known_filter(filter)
|
||||||
|
@@filters[filter.name] = filter
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.add_filter(filter)
|
def self.add_known_filter(filter)
|
||||||
raise ArgumentError, "Expected module but got: #{f.class}" unless filter.is_a?(Module)
|
unless @@known_filters.include?(filter)
|
||||||
unless self.class.include?(filter)
|
@@method_blacklist ||= Set.new(Strainer.instance_methods.map(&:to_s))
|
||||||
self.send(:include, filter)
|
new_methods = filter.instance_methods.map(&:to_s)
|
||||||
@filter_methods.merge(filter.public_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
|
end
|
||||||
|
|
||||||
def self.global_filter(filter)
|
def self.create(context)
|
||||||
@@global_strainer.add_filter(filter)
|
strainer = Strainer.new(context)
|
||||||
end
|
@@filters.each { |k,m| strainer.extend(m) }
|
||||||
|
strainer
|
||||||
def self.invokable?(method)
|
|
||||||
@filter_methods.include?(method.to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.create(context, filters = [])
|
|
||||||
@@strainer_class_cache[filters].new(context)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def invoke(method, *args)
|
def invoke(method, *args)
|
||||||
if self.class.invokable?(method)
|
if invokable?(method)
|
||||||
send(method, *args)
|
send(method, *args)
|
||||||
else
|
else
|
||||||
args.first
|
args.first
|
||||||
end
|
end
|
||||||
rescue ::ArgumentError => e
|
end
|
||||||
raise Liquid::ArgumentError.new(e.message)
|
|
||||||
|
def invokable?(method)
|
||||||
|
@@known_methods.include?(method.to_s) && respond_to?(method)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,42 +1,26 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
class Tag
|
class Tag
|
||||||
attr_accessor :options, :line_number
|
|
||||||
attr_reader :nodelist, :warnings
|
|
||||||
include ParserSwitching
|
|
||||||
|
|
||||||
class << self
|
attr_accessor :nodelist
|
||||||
def parse(tag_name, markup, tokens, options)
|
|
||||||
tag = new(tag_name, markup, options)
|
|
||||||
tag.parse(tokens)
|
|
||||||
tag
|
|
||||||
end
|
|
||||||
|
|
||||||
private :new
|
def initialize(tag_name, markup, tokens)
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
|
||||||
@tag_name = tag_name
|
@tag_name = tag_name
|
||||||
@markup = markup
|
@markup = markup
|
||||||
@options = options
|
parse(tokens)
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
end
|
end
|
||||||
|
|
||||||
def raw
|
|
||||||
"#{@tag_name} #{@markup}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def name
|
def name
|
||||||
self.class.name.downcase
|
self.class.name.downcase
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
''.freeze
|
''
|
||||||
end
|
end
|
||||||
|
|
||||||
def blank?
|
end # Tag
|
||||||
false
|
|
||||||
end
|
end # Tag
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -9,33 +9,25 @@ module Liquid
|
|||||||
# {{ foo }}
|
# {{ foo }}
|
||||||
#
|
#
|
||||||
class Assign < Tag
|
class Assign < Tag
|
||||||
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
|
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/o
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
super
|
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@to = $1
|
@to = $1
|
||||||
@from = Variable.new($2,options)
|
@from = Variable.new($2)
|
||||||
@from.line_number = line_number
|
|
||||||
else
|
else
|
||||||
raise SyntaxError.new options[:locale].t("errors.syntax.assign".freeze)
|
raise SyntaxError.new("Syntax Error in 'assign' - Valid syntax: assign [var] = [source]")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
val = @from.render(context)
|
context.scopes.last[@to] = @from.render(context)
|
||||||
context.scopes.last[@to] = val
|
''
|
||||||
|
|
||||||
inc = val.instance_of?(String) || val.instance_of?(Array) || val.instance_of?(Hash) ? val.length : 1
|
|
||||||
context.resource_limits.assign_score += inc
|
|
||||||
|
|
||||||
''.freeze
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def blank?
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('assign'.freeze, Assign)
|
Template.register_tag('assign', Assign)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -17,5 +17,5 @@ module Liquid
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('break'.freeze, Break)
|
Template.register_tag('break', Break)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
# Capture stores the result of a block into a variable without rendering it inplace.
|
# Capture stores the result of a block into a variable without rendering it inplace.
|
||||||
#
|
#
|
||||||
# {% capture heading %}
|
# {% capture heading %}
|
||||||
@@ -11,28 +12,24 @@ module Liquid
|
|||||||
# in a sidebar or footer.
|
# in a sidebar or footer.
|
||||||
#
|
#
|
||||||
class Capture < Block
|
class Capture < Block
|
||||||
Syntax = /(#{VariableSignature}+)/o
|
Syntax = /(\w+)/
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
super
|
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@to = $1
|
@to = $1
|
||||||
else
|
else
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.capture"))
|
raise SyntaxError.new("Syntax Error in 'capture' - Valid syntax: capture [var]")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
output = super
|
output = super
|
||||||
context.scopes.last[@to] = output
|
context.scopes.last[@to] = output
|
||||||
context.resource_limits.assign_score += output.length
|
''
|
||||||
''.freeze
|
|
||||||
end
|
|
||||||
|
|
||||||
def blank?
|
|
||||||
true
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('capture'.freeze, Capture)
|
Template.register_tag('capture', Capture)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,35 +1,26 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Case < Block
|
class Case < Block
|
||||||
Syntax = /(#{QuotedFragment})/o
|
Syntax = /(#{QuotedFragment})/o
|
||||||
WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/om
|
WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/o
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
super
|
|
||||||
@blocks = []
|
@blocks = []
|
||||||
|
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@left = Expression.parse($1)
|
@left = $1
|
||||||
else
|
else
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.case".freeze))
|
raise SyntaxError.new("Syntax Error in tag 'case' - Valid syntax: case [condition]")
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def parse(tokens)
|
super
|
||||||
body = BlockBody.new
|
|
||||||
while more = parse_body(body, tokens)
|
|
||||||
body = @blocks.last.attachment
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def nodelist
|
|
||||||
@blocks.map(&:attachment)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def unknown_tag(tag, markup, tokens)
|
def unknown_tag(tag, markup, tokens)
|
||||||
|
@nodelist = []
|
||||||
case tag
|
case tag
|
||||||
when 'when'.freeze
|
when 'when'
|
||||||
record_when_condition(markup)
|
record_when_condition(markup)
|
||||||
when 'else'.freeze
|
when 'else'
|
||||||
record_else_condition(markup)
|
record_else_condition(markup)
|
||||||
else
|
else
|
||||||
super
|
super
|
||||||
@@ -40,13 +31,13 @@ module Liquid
|
|||||||
context.stack do
|
context.stack do
|
||||||
execute_else_block = true
|
execute_else_block = true
|
||||||
|
|
||||||
output = ''
|
output = []
|
||||||
@blocks.each do |block|
|
@blocks.each do |block|
|
||||||
if block.else?
|
if block.else?
|
||||||
return block.attachment.render(context) if execute_else_block
|
return render_all(block.attachment, context) if execute_else_block
|
||||||
elsif block.evaluate(context)
|
elsif block.evaluate(context)
|
||||||
execute_else_block = false
|
execute_else_block = false
|
||||||
output << block.attachment.render(context)
|
output << render_all(block.attachment, context)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
output
|
output
|
||||||
@@ -56,31 +47,33 @@ module Liquid
|
|||||||
private
|
private
|
||||||
|
|
||||||
def record_when_condition(markup)
|
def record_when_condition(markup)
|
||||||
body = BlockBody.new
|
|
||||||
|
|
||||||
while markup
|
while markup
|
||||||
if not markup =~ WhenSyntax
|
# Create a new nodelist and assign it to the new block
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when".freeze))
|
if not markup =~ WhenSyntax
|
||||||
end
|
raise SyntaxError.new("Syntax Error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %} ")
|
||||||
|
end
|
||||||
|
|
||||||
markup = $2
|
markup = $2
|
||||||
|
|
||||||
block = Condition.new(@left, '=='.freeze, Expression.parse($1))
|
block = Condition.new(@left, '==', $1)
|
||||||
block.attach(body)
|
block.attach(@nodelist)
|
||||||
@blocks << block
|
@blocks.push(block)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def record_else_condition(markup)
|
def record_else_condition(markup)
|
||||||
|
|
||||||
if not markup.strip.empty?
|
if not markup.strip.empty?
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_else".freeze))
|
raise SyntaxError.new("Syntax Error in tag 'case' - Valid else condition: {% else %} (no parameters) ")
|
||||||
end
|
end
|
||||||
|
|
||||||
block = ElseCondition.new
|
block = ElseCondition.new
|
||||||
block.attach(BlockBody.new)
|
block.attach(@nodelist)
|
||||||
@blocks << block
|
@blocks << block
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('case'.freeze, Case)
|
Template.register_tag('case', Case)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,16 +1,9 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Comment < Block
|
class Comment < Block
|
||||||
def render(context)
|
def render(context)
|
||||||
''.freeze
|
''
|
||||||
end
|
|
||||||
|
|
||||||
def unknown_tag(tag, markup, tokens)
|
|
||||||
end
|
|
||||||
|
|
||||||
def blank?
|
|
||||||
true
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('comment'.freeze, Comment)
|
Template.register_tag('comment', Comment)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
# Continue tag to be used to break out of a for loop.
|
# Continue tag to be used to break out of a for loop.
|
||||||
#
|
#
|
||||||
# == Basic Usage:
|
# == Basic Usage:
|
||||||
@@ -9,10 +10,12 @@ module Liquid
|
|||||||
# {% endfor %}
|
# {% endfor %}
|
||||||
#
|
#
|
||||||
class Continue < Tag
|
class Continue < Tag
|
||||||
|
|
||||||
def interrupt
|
def interrupt
|
||||||
ContinueInterrupt.new
|
ContinueInterrupt.new
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('continue'.freeze, Continue)
|
Template.register_tag('continue', Continue)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
# Cycle is usually used within a loop to alternate between values, like colors or DOM classes.
|
# Cycle is usually used within a loop to alternate between values, like colors or DOM classes.
|
||||||
#
|
#
|
||||||
# {% for item in items %}
|
# {% for item in items %}
|
||||||
@@ -12,30 +13,30 @@ module Liquid
|
|||||||
# <div class="green"> Item five</div>
|
# <div class="green"> Item five</div>
|
||||||
#
|
#
|
||||||
class Cycle < Tag
|
class Cycle < Tag
|
||||||
SimpleSyntax = /\A#{QuotedFragment}+/o
|
SimpleSyntax = /^#{QuotedFragment}+/o
|
||||||
NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
|
NamedSyntax = /^(#{QuotedFragment})\s*\:\s*(.*)/o
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
super
|
|
||||||
case markup
|
case markup
|
||||||
when NamedSyntax
|
when NamedSyntax
|
||||||
@variables = variables_from_string($2)
|
@variables = variables_from_string($2)
|
||||||
@name = Expression.parse($1)
|
@name = $1
|
||||||
when SimpleSyntax
|
when SimpleSyntax
|
||||||
@variables = variables_from_string(markup)
|
@variables = variables_from_string(markup)
|
||||||
@name = @variables.to_s
|
@name = "'#{@variables.to_s}'"
|
||||||
else
|
else
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.cycle".freeze))
|
raise SyntaxError.new("Syntax Error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]")
|
||||||
end
|
end
|
||||||
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
context.registers[:cycle] ||= Hash.new(0)
|
context.registers[:cycle] ||= Hash.new(0)
|
||||||
|
|
||||||
context.stack do
|
context.stack do
|
||||||
key = context.evaluate(@name)
|
key = context[@name]
|
||||||
iteration = context.registers[:cycle][key]
|
iteration = context.registers[:cycle][key]
|
||||||
result = context.evaluate(@variables[iteration])
|
result = context[@variables[iteration]]
|
||||||
iteration += 1
|
iteration += 1
|
||||||
iteration = 0 if iteration >= @variables.size
|
iteration = 0 if iteration >= @variables.size
|
||||||
context.registers[:cycle][key] = iteration
|
context.registers[:cycle][key] = iteration
|
||||||
@@ -47,10 +48,11 @@ module Liquid
|
|||||||
|
|
||||||
def variables_from_string(markup)
|
def variables_from_string(markup)
|
||||||
markup.split(',').collect do |var|
|
markup.split(',').collect do |var|
|
||||||
var =~ /\s*(#{QuotedFragment})\s*/o
|
var =~ /\s*(#{QuotedFragment})\s*/o
|
||||||
$1 ? Expression.parse($1) : nil
|
$1 ? $1 : nil
|
||||||
end.compact
|
end.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('cycle', Cycle)
|
Template.register_tag('cycle', Cycle)
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ module Liquid
|
|||||||
# Hello: -3
|
# Hello: -3
|
||||||
#
|
#
|
||||||
class Decrement < Tag
|
class Decrement < Tag
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
super
|
|
||||||
@variable = markup.strip
|
@variable = markup.strip
|
||||||
|
|
||||||
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
@@ -34,5 +35,5 @@ module Liquid
|
|||||||
private
|
private
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('decrement'.freeze, Decrement)
|
Template.register_tag('decrement', Decrement)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -42,81 +42,72 @@ module Liquid
|
|||||||
# where 0 is the last item.
|
# where 0 is the last item.
|
||||||
# forloop.first:: Returns true if the item is the first item.
|
# forloop.first:: Returns true if the item is the first item.
|
||||||
# forloop.last:: Returns true if the item is the last item.
|
# forloop.last:: Returns true if the item is the last item.
|
||||||
# forloop.parentloop:: Provides access to the parent loop, if present.
|
|
||||||
#
|
#
|
||||||
class For < Block
|
class For < Block
|
||||||
Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
|
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
super
|
if markup =~ Syntax
|
||||||
parse_with_selected_parser(markup)
|
@variable_name = $1
|
||||||
@for_block = BlockBody.new
|
@collection_name = $2
|
||||||
end
|
@name = "#{$1}-#{$2}"
|
||||||
|
@idx = "#{@name}-i"
|
||||||
def parse(tokens)
|
@reversed = $3
|
||||||
if more = parse_body(@for_block, tokens)
|
@attributes = {}
|
||||||
parse_body(@else_block, tokens)
|
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
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def nodelist
|
@nodelist = @for_block = []
|
||||||
@else_block ? [@for_block, @else_block] : [@for_block]
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def unknown_tag(tag, markup, tokens)
|
def unknown_tag(tag, markup, tokens)
|
||||||
return super unless tag == 'else'.freeze
|
return super unless tag == 'else'
|
||||||
@else_block = BlockBody.new
|
@nodelist = @else_block = []
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
context.registers[:for] ||= Hash.new(0)
|
context.registers[:for] ||= Hash.new(0)
|
||||||
|
|
||||||
collection = context.evaluate(@collection_name)
|
collection = context[@collection_name]
|
||||||
collection = collection.to_a if collection.is_a?(Range)
|
collection = collection.to_a if collection.is_a?(Range)
|
||||||
|
|
||||||
# Maintains Ruby 1.8.7 String#each behaviour on 1.9
|
# Maintains Ruby 1.8.7 String#each behaviour on 1.9
|
||||||
return render_else(context) unless iterable?(collection)
|
return render_else(context) unless iterable?(collection)
|
||||||
|
|
||||||
from = if @from == :continue
|
from = if @attributes['offset'] == 'continue'
|
||||||
context.registers[:for][@name].to_i
|
context.registers[:for][@name].to_i
|
||||||
else
|
else
|
||||||
context.evaluate(@from).to_i
|
context[@attributes['offset']].to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
limit = context.evaluate(@limit)
|
limit = context[@attributes['limit']]
|
||||||
to = limit ? limit.to_i + from : nil
|
to = limit ? limit.to_i + from : nil
|
||||||
|
|
||||||
segment = Utils.slice_collection(collection, from, to)
|
segment = Utils.slice_collection_using_each(collection, from, to)
|
||||||
|
|
||||||
return render_else(context) if segment.empty?
|
return render_else(context) if segment.empty?
|
||||||
|
|
||||||
segment.reverse! if @reversed
|
segment.reverse! if @reversed
|
||||||
|
|
||||||
result = ''
|
result = []
|
||||||
|
|
||||||
length = segment.length
|
length = segment.length
|
||||||
|
|
||||||
# Store our progress through the collection for the continue flag
|
# Store our progress through the collection for the continue flag
|
||||||
context.registers[:for][@name] = from + segment.length
|
context.registers[:for][@name] = from + segment.length
|
||||||
|
|
||||||
parent_loop = context['forloop'.freeze]
|
|
||||||
|
|
||||||
context.stack do
|
context.stack do
|
||||||
|
context['forloop'] = lambda { Forloop.new(@name, @idx, length) }
|
||||||
segment.each_with_index do |item, index|
|
segment.each_with_index do |item, index|
|
||||||
|
context.registers[:for][@idx] = index
|
||||||
context[@variable_name] = item
|
context[@variable_name] = item
|
||||||
context['forloop'.freeze] = {
|
|
||||||
'name'.freeze => @name,
|
|
||||||
'length'.freeze => length,
|
|
||||||
'index'.freeze => index + 1,
|
|
||||||
'index0'.freeze => index,
|
|
||||||
'rindex'.freeze => length - index,
|
|
||||||
'rindex0'.freeze => length - index - 1,
|
|
||||||
'first'.freeze => (index == 0),
|
|
||||||
'last'.freeze => (index == length - 1),
|
|
||||||
'parentloop'.freeze => parent_loop
|
|
||||||
}
|
|
||||||
|
|
||||||
result << @for_block.render(context)
|
result << render_all(@for_block, context)
|
||||||
|
|
||||||
# Handle any interrupts if they exist.
|
# Handle any interrupts if they exist.
|
||||||
if context.has_interrupt?
|
if context.has_interrupt?
|
||||||
@@ -129,65 +120,49 @@ module Liquid
|
|||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
def lax_parse(markup)
|
|
||||||
if markup =~ Syntax
|
|
||||||
@variable_name = $1
|
|
||||||
collection_name = $2
|
|
||||||
@reversed = $3
|
|
||||||
@name = "#{@variable_name}-#{collection_name}"
|
|
||||||
@collection_name = Expression.parse(collection_name)
|
|
||||||
markup.scan(TagAttributes) do |key, value|
|
|
||||||
set_attribute(key, value)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.for".freeze))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def strict_parse(markup)
|
|
||||||
p = Parser.new(markup)
|
|
||||||
@variable_name = p.consume(:id)
|
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in".freeze)) unless p.id?('in'.freeze)
|
|
||||||
collection_name = p.expression
|
|
||||||
@name = "#{@variable_name}-#{collection_name}"
|
|
||||||
@collection_name = Expression.parse(collection_name)
|
|
||||||
@reversed = p.id?('reversed'.freeze)
|
|
||||||
|
|
||||||
while p.look(:id) && p.look(:colon, 1)
|
|
||||||
unless attribute = p.id?('limit'.freeze) || p.id?('offset'.freeze)
|
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute".freeze))
|
|
||||||
end
|
|
||||||
p.consume
|
|
||||||
set_attribute(attribute, p.expression)
|
|
||||||
end
|
|
||||||
p.consume(:end_of_string)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_attribute(key, expr)
|
class Forloop < Liquid::Drop
|
||||||
case key
|
attr_accessor :name, :length
|
||||||
when 'offset'.freeze
|
|
||||||
@from = if expr == 'continue'.freeze
|
def initialize(name, idx, length)
|
||||||
:continue
|
@name, @idx, @length = name, idx, length
|
||||||
else
|
end
|
||||||
Expression.parse(expr)
|
|
||||||
|
def index
|
||||||
|
@context.registers[:for][@idx] + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def index0
|
||||||
|
@context.registers[:for][@idx]
|
||||||
|
end
|
||||||
|
|
||||||
|
def rindex
|
||||||
|
length - @context.registers[:for][@idx]
|
||||||
|
end
|
||||||
|
|
||||||
|
def rindex0
|
||||||
|
length - @context.registers[:for][@idx] - 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def first
|
||||||
|
(@context.registers[:for][@idx] == 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
def last
|
||||||
|
(@context.registers[:for][@idx] == length - 1)
|
||||||
end
|
end
|
||||||
when 'limit'.freeze
|
|
||||||
@limit = Expression.parse(expr)
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def render_else(context)
|
|
||||||
@else_block ? @else_block.render(context) : ''.freeze
|
|
||||||
end
|
|
||||||
|
|
||||||
def iterable?(collection)
|
def render_else(context)
|
||||||
collection.respond_to?(:each) || Utils.non_blank_string?(collection)
|
return @else_block ? [render_all(@else_block, context)] : ''
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def iterable?(collection)
|
||||||
|
collection.respond_to?(:each) || Utils.non_blank_string?(collection)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('for'.freeze, For)
|
Template.register_tag('for', For)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
# If is the conditional block
|
# If is the conditional block
|
||||||
#
|
#
|
||||||
# {% if user.admin %}
|
# {% if user.admin %}
|
||||||
@@ -9,28 +10,22 @@ module Liquid
|
|||||||
#
|
#
|
||||||
# There are {% if count < 5 %} less {% else %} more {% endif %} items than you need.
|
# There are {% if count < 5 %} less {% else %} more {% endif %} items than you need.
|
||||||
#
|
#
|
||||||
|
#
|
||||||
class If < Block
|
class If < Block
|
||||||
|
SyntaxHelp = "Syntax Error in tag 'if' - Valid syntax: if [expression]"
|
||||||
Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
|
Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
|
||||||
ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
|
ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
|
||||||
BOOLEAN_OPERATORS = %w(and or)
|
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
super
|
|
||||||
@blocks = []
|
@blocks = []
|
||||||
push_block('if'.freeze, markup)
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse(tokens)
|
push_block('if', markup)
|
||||||
while more = parse_body(@blocks.last.attachment, tokens)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def nodelist
|
super
|
||||||
@blocks.map(&:attachment)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def unknown_tag(tag, markup, tokens)
|
def unknown_tag(tag, markup, tokens)
|
||||||
if ['elsif'.freeze, 'else'.freeze].include?(tag)
|
if ['elsif', 'else'].include?(tag)
|
||||||
push_block(tag, markup)
|
push_block(tag, markup)
|
||||||
else
|
else
|
||||||
super
|
super
|
||||||
@@ -41,71 +36,44 @@ module Liquid
|
|||||||
context.stack do
|
context.stack do
|
||||||
@blocks.each do |block|
|
@blocks.each do |block|
|
||||||
if block.evaluate(context)
|
if block.evaluate(context)
|
||||||
return block.attachment.render(context)
|
return render_all(block.attachment, context)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
''.freeze
|
''
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def push_block(tag, markup)
|
def push_block(tag, markup)
|
||||||
block = if tag == 'else'.freeze
|
block = if tag == 'else'
|
||||||
ElseCondition.new
|
ElseCondition.new
|
||||||
else
|
else
|
||||||
parse_with_selected_parser(markup)
|
|
||||||
|
expressions = markup.scan(ExpressionsAndOperators).reverse
|
||||||
|
raise(SyntaxError, SyntaxHelp) unless expressions.shift =~ Syntax
|
||||||
|
|
||||||
|
condition = Condition.new($1, $2, $3)
|
||||||
|
|
||||||
|
while not expressions.empty?
|
||||||
|
operator = (expressions.shift).to_s.strip
|
||||||
|
|
||||||
|
raise(SyntaxError, SyntaxHelp) unless expressions.shift.to_s =~ Syntax
|
||||||
|
|
||||||
|
new_condition = Condition.new($1, $2, $3)
|
||||||
|
new_condition.send(operator.to_sym, condition)
|
||||||
|
condition = new_condition
|
||||||
|
end
|
||||||
|
|
||||||
|
condition
|
||||||
end
|
end
|
||||||
|
|
||||||
@blocks.push(block)
|
@blocks.push(block)
|
||||||
block.attach(BlockBody.new)
|
@nodelist = block.attach(Array.new)
|
||||||
end
|
end
|
||||||
|
|
||||||
def lax_parse(markup)
|
|
||||||
expressions = markup.scan(ExpressionsAndOperators)
|
|
||||||
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop =~ Syntax
|
|
||||||
|
|
||||||
condition = Condition.new(Expression.parse($1), $2, Expression.parse($3))
|
|
||||||
|
|
||||||
while not expressions.empty?
|
|
||||||
operator = expressions.pop.to_s.strip
|
|
||||||
|
|
||||||
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop.to_s =~ Syntax
|
|
||||||
|
|
||||||
new_condition = Condition.new(Expression.parse($1), $2, Expression.parse($3))
|
|
||||||
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless BOOLEAN_OPERATORS.include?(operator)
|
|
||||||
new_condition.send(operator, condition)
|
|
||||||
condition = new_condition
|
|
||||||
end
|
|
||||||
|
|
||||||
condition
|
|
||||||
end
|
|
||||||
|
|
||||||
def strict_parse(markup)
|
|
||||||
p = Parser.new(markup)
|
|
||||||
|
|
||||||
condition = parse_comparison(p)
|
|
||||||
|
|
||||||
while op = (p.id?('and'.freeze) || p.id?('or'.freeze))
|
|
||||||
new_cond = parse_comparison(p)
|
|
||||||
new_cond.send(op, condition)
|
|
||||||
condition = new_cond
|
|
||||||
end
|
|
||||||
p.consume(:end_of_string)
|
|
||||||
|
|
||||||
condition
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_comparison(p)
|
|
||||||
a = Expression.parse(p.expression)
|
|
||||||
if op = p.consume?(:comparison)
|
|
||||||
b = Expression.parse(p.expression)
|
|
||||||
Condition.new(a, op, b)
|
|
||||||
else
|
|
||||||
Condition.new(a)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('if'.freeze, If)
|
Template.register_tag('if', If)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,17 +4,17 @@ module Liquid
|
|||||||
def render(context)
|
def render(context)
|
||||||
context.stack do
|
context.stack do
|
||||||
|
|
||||||
output = super
|
output = render_all(@nodelist, context)
|
||||||
|
|
||||||
if output != context.registers[:ifchanged]
|
if output != context.registers[:ifchanged]
|
||||||
context.registers[:ifchanged] = output
|
context.registers[:ifchanged] = output
|
||||||
output
|
output
|
||||||
else
|
else
|
||||||
''.freeze
|
''
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('ifchanged'.freeze, Ifchanged)
|
Template.register_tag('ifchanged', Ifchanged)
|
||||||
end
|
end
|
||||||
@@ -1,99 +1,65 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
# Include allows templates to relate with other templates
|
|
||||||
#
|
|
||||||
# Simply include another template:
|
|
||||||
#
|
|
||||||
# {% include 'product' %}
|
|
||||||
#
|
|
||||||
# Include a template with a local variable:
|
|
||||||
#
|
|
||||||
# {% include 'product' with products[0] %}
|
|
||||||
#
|
|
||||||
# Include a template for a collection:
|
|
||||||
#
|
|
||||||
# {% include 'product' for products %}
|
|
||||||
#
|
|
||||||
class Include < Tag
|
class Include < Tag
|
||||||
Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
|
Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
super
|
|
||||||
|
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
|
|
||||||
template_name = $1
|
@template_name = $1
|
||||||
variable_name = $3
|
@variable_name = $3
|
||||||
|
|
||||||
@variable_name = Expression.parse(variable_name || template_name[1..-2])
|
|
||||||
@context_variable_name = template_name[1..-2].split('/'.freeze).last
|
|
||||||
@template_name = Expression.parse(template_name)
|
|
||||||
@attributes = {}
|
@attributes = {}
|
||||||
|
|
||||||
markup.scan(TagAttributes) do |key, value|
|
markup.scan(TagAttributes) do |key, value|
|
||||||
@attributes[key] = Expression.parse(value)
|
@attributes[key] = value
|
||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.include".freeze))
|
raise SyntaxError.new("Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
partial = load_cached_partial(context)
|
source = _read_template_from_file_system(context)
|
||||||
variable = context.evaluate(@variable_name)
|
partial = Liquid::Template.parse(source)
|
||||||
|
variable = context[@variable_name || @template_name[1..-2]]
|
||||||
|
|
||||||
context.stack do
|
context.stack do
|
||||||
@attributes.each do |key, value|
|
@attributes.each do |key, value|
|
||||||
context[key] = context.evaluate(value)
|
context[key] = context[value]
|
||||||
end
|
end
|
||||||
|
|
||||||
if variable.is_a?(Array)
|
if variable.is_a?(Array)
|
||||||
variable.collect do |var|
|
variable.collect do |variable|
|
||||||
context[@context_variable_name] = var
|
context[@template_name[1..-2]] = variable
|
||||||
partial.render(context)
|
partial.render(context)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
context[@context_variable_name] = variable
|
context[@template_name[1..-2]] = variable
|
||||||
partial.render(context)
|
partial.render(context)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def load_cached_partial(context)
|
def _read_template_from_file_system(context)
|
||||||
cached_partials = context.registers[:cached_partials] || {}
|
|
||||||
template_name = context.evaluate(@template_name)
|
|
||||||
|
|
||||||
if cached = cached_partials[template_name]
|
|
||||||
return cached
|
|
||||||
end
|
|
||||||
source = read_template_from_file_system(context)
|
|
||||||
partial = Liquid::Template.parse(source, pass_options)
|
|
||||||
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
|
||||||
|
|
||||||
file_system.read_template_file(context.evaluate(@template_name))
|
# make read_template_file call backwards-compatible.
|
||||||
end
|
case file_system.method(:read_template_file).arity
|
||||||
|
when 1
|
||||||
def pass_options
|
file_system.read_template_file(context[@template_name])
|
||||||
dont_pass = @options[:include_options_blacklist]
|
when 2
|
||||||
return {locale: @options[:locale]} if dont_pass == true
|
file_system.read_template_file(context[@template_name], context)
|
||||||
opts = @options.merge(included: true, include_options_blacklist: false)
|
else
|
||||||
if dont_pass.is_a?(Array)
|
raise ArgumentError, "file_system.read_template_file expects two parameters: (template_name, context)"
|
||||||
dont_pass.each {|o| opts.delete(o)}
|
|
||||||
end
|
end
|
||||||
opts
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('include'.freeze, Include)
|
Template.register_tag('include', Include)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
# increment is used in a place where one needs to insert a counter
|
# increment is used in a place where one needs to insert a counter
|
||||||
# into a template, and needs the counter to survive across
|
# into a template, and needs the counter to survive across
|
||||||
# multiple instantiations of the template.
|
# multiple instantiations of the template.
|
||||||
# (To achieve the survival, the application must keep the context)
|
# (To achieve the survival, the application must keep the context)
|
||||||
#
|
#
|
||||||
# if the variable does not exist, it is created with value 0.
|
# if the variable does not exist, it is created with value 0.
|
||||||
#
|
|
||||||
# Hello: {% increment variable %}
|
# Hello: {% increment variable %}
|
||||||
#
|
#
|
||||||
# gives you:
|
# gives you:
|
||||||
@@ -15,9 +16,10 @@ module Liquid
|
|||||||
# Hello: 2
|
# Hello: 2
|
||||||
#
|
#
|
||||||
class Increment < Tag
|
class Increment < Tag
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
super
|
|
||||||
@variable = markup.strip
|
@variable = markup.strip
|
||||||
|
|
||||||
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
@@ -25,7 +27,9 @@ module Liquid
|
|||||||
context.environments.first[@variable] = value + 1
|
context.environments.first[@variable] = value + 1
|
||||||
value.to_s
|
value.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('increment'.freeze, Increment)
|
Template.register_tag('increment', Increment)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,30 +1,21 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Raw < Block
|
class Raw < Block
|
||||||
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
|
|
||||||
|
|
||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
@body = ''
|
@nodelist ||= []
|
||||||
|
@nodelist.clear
|
||||||
|
|
||||||
while token = tokens.shift
|
while token = tokens.shift
|
||||||
if token =~ FullTokenPossiblyInvalid
|
if token =~ FullToken
|
||||||
@body << $1 if $1 != "".freeze
|
if block_delimiter == $1
|
||||||
return if block_delimiter == $2
|
end_tag
|
||||||
|
return
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@body << token if not token.empty?
|
@nodelist << token if not token.empty?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
|
||||||
@body
|
|
||||||
end
|
|
||||||
|
|
||||||
def nodelist
|
|
||||||
[@body]
|
|
||||||
end
|
|
||||||
|
|
||||||
def blank?
|
|
||||||
@body.empty?
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('raw'.freeze, Raw)
|
Template.register_tag('raw', Raw)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
module Liquid
|
|
||||||
class TableRow < Block
|
|
||||||
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
|
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
|
||||||
super
|
|
||||||
if markup =~ Syntax
|
|
||||||
@variable_name = $1
|
|
||||||
@collection_name = Expression.parse($2)
|
|
||||||
@attributes = {}
|
|
||||||
markup.scan(TagAttributes) do |key, value|
|
|
||||||
@attributes[key] = Expression.parse(value)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.table_row".freeze))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def render(context)
|
|
||||||
collection = context.evaluate(@collection_name) or return ''.freeze
|
|
||||||
|
|
||||||
from = @attributes.key?('offset'.freeze) ? context.evaluate(@attributes['offset'.freeze]).to_i : 0
|
|
||||||
to = @attributes.key?('limit'.freeze) ? from + context.evaluate(@attributes['limit'.freeze]).to_i : nil
|
|
||||||
|
|
||||||
collection = Utils.slice_collection(collection, from, to)
|
|
||||||
|
|
||||||
length = collection.length
|
|
||||||
|
|
||||||
cols = context.evaluate(@attributes['cols'.freeze]).to_i
|
|
||||||
|
|
||||||
row = 1
|
|
||||||
col = 0
|
|
||||||
|
|
||||||
result = "<tr class=\"row1\">\n"
|
|
||||||
context.stack do
|
|
||||||
|
|
||||||
collection.each_with_index do |item, index|
|
|
||||||
context[@variable_name] = item
|
|
||||||
context['tablerowloop'.freeze] = {
|
|
||||||
'length'.freeze => length,
|
|
||||||
'index'.freeze => index + 1,
|
|
||||||
'index0'.freeze => index,
|
|
||||||
'col'.freeze => col + 1,
|
|
||||||
'col0'.freeze => col,
|
|
||||||
'rindex'.freeze => length - index,
|
|
||||||
'rindex0'.freeze => length - index - 1,
|
|
||||||
'first'.freeze => (index == 0),
|
|
||||||
'last'.freeze => (index == length - 1),
|
|
||||||
'col_first'.freeze => (col == 0),
|
|
||||||
'col_last'.freeze => (col == cols - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
col += 1
|
|
||||||
|
|
||||||
result << "<td class=\"col#{col}\">" << super << '</td>'
|
|
||||||
|
|
||||||
if col == cols and (index != length - 1)
|
|
||||||
col = 0
|
|
||||||
row += 1
|
|
||||||
result << "</tr>\n<tr class=\"row#{row}\">"
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
result << "</tr>\n"
|
|
||||||
result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Template.register_tag('tablerow'.freeze, TableRow)
|
|
||||||
end
|
|
||||||
@@ -1,31 +1,33 @@
|
|||||||
require File.dirname(__FILE__) + '/if'
|
require File.dirname(__FILE__) + '/if'
|
||||||
|
|
||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
# Unless is a conditional just like 'if' but works on the inverse logic.
|
# Unless is a conditional just like 'if' but works on the inverse logic.
|
||||||
#
|
#
|
||||||
# {% unless x < 0 %} x is greater than zero {% endunless %}
|
# {% unless x < 0 %} x is greater than zero {% end %}
|
||||||
#
|
#
|
||||||
class Unless < If
|
class Unless < If
|
||||||
def render(context)
|
def render(context)
|
||||||
context.stack do
|
context.stack do
|
||||||
|
|
||||||
# First condition is interpreted backwards ( if not )
|
# First condition is interpreted backwards ( if not )
|
||||||
first_block = @blocks.first
|
block = @blocks.first
|
||||||
unless first_block.evaluate(context)
|
unless block.evaluate(context)
|
||||||
return first_block.attachment.render(context)
|
return render_all(block.attachment, context)
|
||||||
end
|
end
|
||||||
|
|
||||||
# After the first condition unless works just like if
|
# After the first condition unless works just like if
|
||||||
@blocks[1..-1].each do |block|
|
@blocks[1..-1].each do |block|
|
||||||
if block.evaluate(context)
|
if block.evaluate(context)
|
||||||
return block.attachment.render(context)
|
return render_all(block.attachment, context)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
''.freeze
|
''
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('unless'.freeze, Unless)
|
|
||||||
|
Template.register_tag('unless', Unless)
|
||||||
end
|
end
|
||||||
@@ -14,60 +14,10 @@ module Liquid
|
|||||||
# template.render('user_name' => 'bob')
|
# template.render('user_name' => 'bob')
|
||||||
#
|
#
|
||||||
class Template
|
class Template
|
||||||
DEFAULT_OPTIONS = {
|
|
||||||
:locale => I18n.new
|
|
||||||
}
|
|
||||||
|
|
||||||
attr_accessor :root
|
attr_accessor :root
|
||||||
attr_reader :resource_limits
|
|
||||||
|
|
||||||
@@file_system = BlankFileSystem.new
|
@@file_system = BlankFileSystem.new
|
||||||
|
|
||||||
class TagRegistry
|
|
||||||
def initialize
|
|
||||||
@tags = {}
|
|
||||||
@cache = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def [](tag_name)
|
|
||||||
return nil unless @tags.has_key?(tag_name)
|
|
||||||
return @cache[tag_name] if Liquid.cache_classes
|
|
||||||
|
|
||||||
lookup_class(@tags[tag_name]).tap { |o| @cache[tag_name] = o }
|
|
||||||
end
|
|
||||||
|
|
||||||
def []=(tag_name, klass)
|
|
||||||
@tags[tag_name] = klass.name
|
|
||||||
@cache[tag_name] = klass
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(tag_name)
|
|
||||||
@tags.delete(tag_name)
|
|
||||||
@cache.delete(tag_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def lookup_class(name)
|
|
||||||
name.split("::").reject(&:empty?).reduce(Object) { |scope, const| scope.const_get(const) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_reader :profiler
|
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
# Sets how strict the parser should be.
|
|
||||||
# :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
|
|
||||||
# :warn is the default and will give deprecation warnings when invalid syntax is used.
|
|
||||||
# :strict will enforce correct syntax.
|
|
||||||
attr_writer :error_mode
|
|
||||||
|
|
||||||
# Sets how strict the taint checker should be.
|
|
||||||
# :lax is the default, and ignores the taint flag completely
|
|
||||||
# :warn adds a warning, but does not interrupt the rendering
|
|
||||||
# :error raises an error when tainted output is used
|
|
||||||
attr_writer :taint_mode
|
|
||||||
|
|
||||||
def file_system
|
def file_system
|
||||||
@@file_system
|
@@file_system
|
||||||
end
|
end
|
||||||
@@ -81,15 +31,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def tags
|
def tags
|
||||||
@tags ||= TagRegistry.new
|
@tags ||= {}
|
||||||
end
|
|
||||||
|
|
||||||
def error_mode
|
|
||||||
@error_mode || :lax
|
|
||||||
end
|
|
||||||
|
|
||||||
def taint_mode
|
|
||||||
@taint_mode || :lax
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Pass a module with filter methods which should be available
|
# Pass a module with filter methods which should be available
|
||||||
@@ -98,39 +40,25 @@ module Liquid
|
|||||||
Strainer.global_filter(mod)
|
Strainer.global_filter(mod)
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_resource_limits
|
|
||||||
@default_resource_limits ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
# creates a new <tt>Template</tt> object from liquid source code
|
# creates a new <tt>Template</tt> object from liquid source code
|
||||||
# To enable profiling, pass in <tt>profile: true</tt> as an option.
|
def parse(source)
|
||||||
# See Liquid::Profiler for more information
|
|
||||||
def parse(source, options = {})
|
|
||||||
template = Template.new
|
template = Template.new
|
||||||
template.parse(source, options)
|
template.parse(source)
|
||||||
|
template
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# creates a new <tt>Template</tt> from an array of tokens. Use <tt>Template.parse</tt> instead
|
||||||
def initialize
|
def initialize
|
||||||
@resource_limits = ResourceLimits.new(self.class.default_resource_limits)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Parse source code.
|
# Parse source code.
|
||||||
# Returns self for easy chaining
|
# Returns self for easy chaining
|
||||||
def parse(source, options = {})
|
def parse(source)
|
||||||
@options = options
|
@root = Document.new(tokenize(source))
|
||||||
@profiling = options[:profile]
|
|
||||||
@line_numbers = options[:line_numbers] || @profiling
|
|
||||||
@root = Document.parse(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
|
||||||
@@ -152,9 +80,6 @@ module Liquid
|
|||||||
# if you use the same filters over and over again consider registering them globally
|
# if you use the same filters over and over again consider registering them globally
|
||||||
# with <tt>Template.register_filter</tt>
|
# with <tt>Template.register_filter</tt>
|
||||||
#
|
#
|
||||||
# if profiling was enabled in <tt>Template#parse</tt> then the resulting profiling information
|
|
||||||
# will be available via <tt>Template#profiler</tt>
|
|
||||||
#
|
|
||||||
# Following options can be passed:
|
# Following options can be passed:
|
||||||
#
|
#
|
||||||
# * <tt>filters</tt> : array with local filters
|
# * <tt>filters</tt> : array with local filters
|
||||||
@@ -162,26 +87,17 @@ module Liquid
|
|||||||
# filters and tags and might be useful to integrate liquid more with its host application
|
# filters and tags and might be useful to integrate liquid more with its host application
|
||||||
#
|
#
|
||||||
def render(*args)
|
def render(*args)
|
||||||
return ''.freeze if @root.nil?
|
return '' if @root.nil?
|
||||||
|
|
||||||
context = case args.first
|
context = case args.first
|
||||||
when Liquid::Context
|
when Liquid::Context
|
||||||
c = args.shift
|
args.shift
|
||||||
|
|
||||||
if @rethrow_errors
|
|
||||||
c.exception_handler = ->(e) { true }
|
|
||||||
end
|
|
||||||
|
|
||||||
c
|
|
||||||
when Liquid::Drop
|
|
||||||
drop = args.shift
|
|
||||||
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
|
|
||||||
when Hash
|
when Hash
|
||||||
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
|
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors)
|
||||||
when nil
|
when nil
|
||||||
Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits)
|
Context.new(assigns, instance_assigns, registers, @rethrow_errors)
|
||||||
else
|
else
|
||||||
raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
|
raise ArgumentError, "Expect Hash or Liquid::Context as parameter"
|
||||||
end
|
end
|
||||||
|
|
||||||
case args.last
|
case args.last
|
||||||
@@ -196,35 +112,23 @@ module Liquid
|
|||||||
context.add_filters(options[:filters])
|
context.add_filters(options[:filters])
|
||||||
end
|
end
|
||||||
|
|
||||||
if options[:exception_handler]
|
|
||||||
context.exception_handler = options[:exception_handler]
|
|
||||||
end
|
|
||||||
when Module
|
when Module
|
||||||
context.add_filters(args.pop)
|
context.add_filters(args.pop)
|
||||||
when Array
|
when Array
|
||||||
context.add_filters(args.pop)
|
context.add_filters(args.pop)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Retrying a render resets resource usage
|
|
||||||
context.resource_limits.reset
|
|
||||||
|
|
||||||
begin
|
begin
|
||||||
# render the nodelist.
|
# render the nodelist.
|
||||||
# for performance reasons we get an array back here. join will make a string out of it.
|
# for performance reasons we get a array back here. join will make a string out of it
|
||||||
result = with_profiling do
|
@root.render(context).join
|
||||||
@root.render(context)
|
|
||||||
end
|
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
||||||
def render!(*args)
|
def render!(*args)
|
||||||
@rethrow_errors = true
|
@rethrow_errors = true; render(*args)
|
||||||
render(*args)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -233,8 +137,7 @@ module Liquid
|
|||||||
def tokenize(source)
|
def tokenize(source)
|
||||||
source = source.source if source.respond_to?(:source)
|
source = source.source if source.respond_to?(:source)
|
||||||
return [] if source.to_s.empty?
|
return [] if source.to_s.empty?
|
||||||
|
tokens = source.split(TemplateParser)
|
||||||
tokens = calculate_line_numbers(source.split(TemplateParser))
|
|
||||||
|
|
||||||
# removes the rogue empty element at the beginning of the array
|
# removes the rogue empty element at the beginning of the array
|
||||||
tokens.shift if tokens[0] and tokens[0].empty?
|
tokens.shift if tokens[0] and tokens[0].empty?
|
||||||
@@ -242,32 +145,5 @@ module Liquid
|
|||||||
tokens
|
tokens
|
||||||
end
|
end
|
||||||
|
|
||||||
def calculate_line_numbers(raw_tokens)
|
|
||||||
return raw_tokens unless @line_numbers
|
|
||||||
|
|
||||||
current_line = 1
|
|
||||||
raw_tokens.map do |token|
|
|
||||||
Token.new(token, current_line).tap do
|
|
||||||
current_line += token.count("\n")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def with_profiling
|
|
||||||
if @profiling && !@options[:included]
|
|
||||||
raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
|
|
||||||
|
|
||||||
@profiler = Profiler.new
|
|
||||||
@profiler.start
|
|
||||||
|
|
||||||
begin
|
|
||||||
yield
|
|
||||||
ensure
|
|
||||||
@profiler.stop
|
|
||||||
end
|
|
||||||
else
|
|
||||||
yield
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
module Liquid
|
|
||||||
class Token < String
|
|
||||||
attr_reader :line_number
|
|
||||||
|
|
||||||
def initialize(content, line_number)
|
|
||||||
super(content)
|
|
||||||
@line_number = line_number
|
|
||||||
end
|
|
||||||
|
|
||||||
def raw
|
|
||||||
"<raw>"
|
|
||||||
end
|
|
||||||
|
|
||||||
def child(string)
|
|
||||||
Token.new(string, @line_number)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,21 +1,9 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
module Utils
|
module Utils
|
||||||
|
|
||||||
def self.slice_collection(collection, from, to)
|
|
||||||
if (from != 0 || to != nil) && collection.respond_to?(:load_slice)
|
|
||||||
collection.load_slice(from, to)
|
|
||||||
else
|
|
||||||
slice_collection_using_each(collection, from, to)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.non_blank_string?(collection)
|
|
||||||
collection.is_a?(String) && collection != ''.freeze
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.slice_collection_using_each(collection, from, to)
|
def self.slice_collection_using_each(collection, from, to)
|
||||||
segments = []
|
segments = []
|
||||||
index = 0
|
index = 0
|
||||||
|
yielded = 0
|
||||||
|
|
||||||
# Maintains Ruby 1.8.7 String#each behaviour on 1.9
|
# Maintains Ruby 1.8.7 String#each behaviour on 1.9
|
||||||
return [collection] if non_blank_string?(collection)
|
return [collection] if non_blank_string?(collection)
|
||||||
@@ -35,5 +23,9 @@ module Liquid
|
|||||||
|
|
||||||
segments
|
segments
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.non_blank_string?(collection)
|
||||||
|
collection.is_a?(String) && collection != ''
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -11,117 +11,45 @@ module Liquid
|
|||||||
# {{ user | link }}
|
# {{ user | link }}
|
||||||
#
|
#
|
||||||
class Variable
|
class Variable
|
||||||
FilterParser = /(?:\s+|#{QuotedFragment}|#{ArgumentSeparator})+/o
|
FilterParser = /(?:#{FilterSeparator}|(?:\s*(?:#{QuotedFragment}|#{ArgumentSeparator})\s*)+)/o
|
||||||
attr_accessor :filters, :name, :warnings
|
attr_accessor :filters, :name
|
||||||
attr_accessor :line_number
|
|
||||||
include ParserSwitching
|
|
||||||
|
|
||||||
def initialize(markup, options = {})
|
def initialize(markup)
|
||||||
@markup = markup
|
@markup = markup
|
||||||
@name = nil
|
@name = nil
|
||||||
@options = options || {}
|
@filters = []
|
||||||
|
if match = markup.match(/\s*(#{QuotedFragment})(.*)/o)
|
||||||
parse_with_selected_parser(markup)
|
@name = match[1]
|
||||||
end
|
if match[2].match(/#{FilterSeparator}\s*(.*)/o)
|
||||||
|
filters = Regexp.last_match(1).scan(FilterParser)
|
||||||
def raw
|
|
||||||
@markup
|
|
||||||
end
|
|
||||||
|
|
||||||
def markup_context(markup)
|
|
||||||
"in \"{{#{markup}}}\""
|
|
||||||
end
|
|
||||||
|
|
||||||
def lax_parse(markup)
|
|
||||||
@filters ||= []
|
|
||||||
@filters.clear
|
|
||||||
if markup =~ /(#{QuotedFragment})(.*)/om
|
|
||||||
name_markup = $1
|
|
||||||
filter_markup = $2
|
|
||||||
@name = Expression.parse(name_markup)
|
|
||||||
if filter_markup =~ /#{FilterSeparator}\s*(.*)/om
|
|
||||||
filters = $1.scan(FilterParser)
|
|
||||||
filters.each do |f|
|
filters.each do |f|
|
||||||
if f =~ /\w+/
|
if matches = f.match(/\s*(\w+)(?:\s*#{FilterArgumentSeparator}(.*))?/)
|
||||||
filtername = Regexp.last_match(0)
|
filtername = matches[1]
|
||||||
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
|
filterargs = matches[2].to_s.scan(/(?:\A|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
|
||||||
@filters << parse_filter_expressions(filtername, filterargs)
|
@filters << [filtername, filterargs]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def strict_parse(markup)
|
|
||||||
@filters ||= []
|
|
||||||
@filters.clear
|
|
||||||
p = Parser.new(markup)
|
|
||||||
|
|
||||||
@name = Expression.parse(p.expression)
|
|
||||||
while p.consume?(:pipe)
|
|
||||||
filtername = p.consume(:id)
|
|
||||||
filterargs = p.consume?(:colon) ? parse_filterargs(p) : []
|
|
||||||
@filters << parse_filter_expressions(filtername, filterargs)
|
|
||||||
end
|
|
||||||
p.consume(:end_of_string)
|
|
||||||
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)
|
||||||
@filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)|
|
return '' if @name.nil?
|
||||||
filter_args = evaluate_filter_expressions(context, filter_args, filter_kwargs)
|
@filters.inject(context[@name]) do |output, filter|
|
||||||
output = context.invoke(filter_name, output, *filter_args)
|
filterargs = []
|
||||||
end.tap{ |obj| taint_check(obj) }
|
keyword_args = {}
|
||||||
end
|
filter[1].to_a.each do |a|
|
||||||
|
if matches = a.match(/\A#{TagAttributes}\z/o)
|
||||||
private
|
keyword_args[matches[1]] = context[matches[2]]
|
||||||
|
else
|
||||||
def parse_filter_expressions(filter_name, unparsed_args)
|
filterargs << context[a]
|
||||||
filter_args = []
|
end
|
||||||
keyword_args = {}
|
|
||||||
unparsed_args.each do |a|
|
|
||||||
if matches = a.match(/\A#{TagAttributes}\z/o)
|
|
||||||
keyword_args[matches[1]] = Expression.parse(matches[2])
|
|
||||||
else
|
|
||||||
filter_args << Expression.parse(a)
|
|
||||||
end
|
end
|
||||||
end
|
filterargs << keyword_args unless keyword_args.empty?
|
||||||
result = [filter_name, filter_args]
|
begin
|
||||||
result << keyword_args unless keyword_args.empty?
|
output = context.invoke(filter[0], output, *filterargs)
|
||||||
result
|
rescue FilterNotFound
|
||||||
end
|
raise FilterNotFound, "Error - filter '#{filter[0]}' in '#{@markup.strip}' could not be found."
|
||||||
|
|
||||||
def evaluate_filter_expressions(context, filter_args, filter_kwargs)
|
|
||||||
parsed_args = filter_args.map{ |expr| context.evaluate(expr) }
|
|
||||||
if filter_kwargs
|
|
||||||
parsed_kwargs = {}
|
|
||||||
filter_kwargs.each do |key, expr|
|
|
||||||
parsed_kwargs[key] = context.evaluate(expr)
|
|
||||||
end
|
|
||||||
parsed_args << parsed_kwargs
|
|
||||||
end
|
|
||||||
parsed_args
|
|
||||||
end
|
|
||||||
|
|
||||||
def taint_check(obj)
|
|
||||||
if obj.tainted?
|
|
||||||
@markup =~ QuotedFragment
|
|
||||||
name = Regexp.last_match(0)
|
|
||||||
case Template.taint_mode
|
|
||||||
when :warn
|
|
||||||
@warnings ||= []
|
|
||||||
@warnings << "variable '#{name}' is tainted and was not escaped"
|
|
||||||
when :error
|
|
||||||
raise TaintedError, "Error - variable '#{name}' is tainted and was not escaped"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
module Liquid
|
|
||||||
class VariableLookup
|
|
||||||
SQUARE_BRACKETED = /\A\[(.*)\]\z/m
|
|
||||||
COMMAND_METHODS = ['size'.freeze, 'first'.freeze, 'last'.freeze]
|
|
||||||
|
|
||||||
attr_reader :name, :lookups
|
|
||||||
|
|
||||||
def self.parse(markup)
|
|
||||||
new(markup)
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(markup)
|
|
||||||
@lookups = markup.scan(VariableParser)
|
|
||||||
|
|
||||||
name = @lookups.shift
|
|
||||||
if name =~ SQUARE_BRACKETED
|
|
||||||
name = Expression.parse($1)
|
|
||||||
end
|
|
||||||
@name = name
|
|
||||||
|
|
||||||
@command_flags = 0
|
|
||||||
|
|
||||||
@lookups.each_index do |i|
|
|
||||||
lookup = @lookups[i]
|
|
||||||
if lookup =~ SQUARE_BRACKETED
|
|
||||||
@lookups[i] = Expression.parse($1)
|
|
||||||
elsif COMMAND_METHODS.include?(lookup)
|
|
||||||
@command_flags |= 1 << i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def evaluate(context)
|
|
||||||
name = context.evaluate(@name)
|
|
||||||
object = context.find_variable(name)
|
|
||||||
|
|
||||||
@lookups.each_index do |i|
|
|
||||||
key = context.evaluate(@lookups[i])
|
|
||||||
|
|
||||||
# If object is a hash- or array-like object we look for the
|
|
||||||
# presence of the key and if its available we return it
|
|
||||||
if object.respond_to?(:[]) &&
|
|
||||||
((object.respond_to?(:has_key?) && object.has_key?(key)) ||
|
|
||||||
(object.respond_to?(:fetch) && key.is_a?(Integer)))
|
|
||||||
|
|
||||||
# if its a proc we will replace the entry with the proc
|
|
||||||
res = context.lookup_and_evaluate(object, key)
|
|
||||||
object = res.to_liquid
|
|
||||||
|
|
||||||
# Some special cases. If the part wasn't in square brackets and
|
|
||||||
# no key with the same name was found we interpret following calls
|
|
||||||
# as commands and call them on the current object
|
|
||||||
elsif @command_flags & (1 << i) != 0 && object.respond_to?(key)
|
|
||||||
object = object.send(key).to_liquid
|
|
||||||
|
|
||||||
# No key was present with the desired value and it wasn't one of the directly supported
|
|
||||||
# keywords either. The only thing we got left is to return nil
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# If we are dealing with a drop here we have to
|
|
||||||
object.context = context if object.respond_to?(:context=)
|
|
||||||
end
|
|
||||||
|
|
||||||
object
|
|
||||||
end
|
|
||||||
|
|
||||||
def ==(other)
|
|
||||||
self.class == other.class && self.state == other.state
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
def state
|
|
||||||
[@name, @lookups, @command_flags]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
# encoding: utf-8
|
|
||||||
module Liquid
|
|
||||||
VERSION = "4.0.0.alpha"
|
|
||||||
end
|
|
||||||
@@ -1,29 +1,24 @@
|
|||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
lib = File.expand_path('../lib/', __FILE__)
|
|
||||||
$:.unshift lib unless $:.include?(lib)
|
|
||||||
|
|
||||||
require "liquid/version"
|
|
||||||
|
|
||||||
Gem::Specification.new do |s|
|
Gem::Specification.new do |s|
|
||||||
s.name = "liquid"
|
s.name = "liquid"
|
||||||
s.version = Liquid::VERSION
|
s.version = "2.5.0"
|
||||||
s.platform = Gem::Platform::RUBY
|
s.platform = Gem::Platform::RUBY
|
||||||
s.summary = "A secure, non-evaling end user template engine with aesthetic markup."
|
s.summary = "A secure, non-evaling end user template engine with aesthetic markup."
|
||||||
s.authors = ["Tobias Lütke"]
|
s.authors = ["Tobias Luetke"]
|
||||||
s.email = ["tobi@leetsoft.com"]
|
s.email = ["tobi@leetsoft.com"]
|
||||||
s.homepage = "http://www.liquidmarkup.org"
|
s.homepage = "http://www.liquidmarkup.org"
|
||||||
s.license = "MIT"
|
|
||||||
#s.description = "A secure, non-evaling end user template engine with aesthetic markup."
|
#s.description = "A secure, non-evaling end user template engine with aesthetic markup."
|
||||||
|
|
||||||
s.required_rubygems_version = ">= 1.3.7"
|
s.required_rubygems_version = ">= 1.3.7"
|
||||||
|
|
||||||
s.test_files = Dir.glob("{test}/**/*")
|
s.test_files = Dir.glob("{test}/**/*")
|
||||||
s.files = Dir.glob("{lib}/**/*") + %w(MIT-LICENSE README.md)
|
s.files = Dir.glob("{lib}/**/*") +
|
||||||
|
Dir.glob("{ext}/**/*") + %w(MIT-LICENSE README.md)
|
||||||
|
|
||||||
|
s.extensions = ['ext/liquid/extconf.rb']
|
||||||
|
|
||||||
s.extra_rdoc_files = ["History.md", "README.md"]
|
s.extra_rdoc_files = ["History.md", "README.md"]
|
||||||
|
|
||||||
s.require_path = "lib"
|
s.require_paths = ["lib", "ext"]
|
||||||
|
|
||||||
s.add_development_dependency 'rake'
|
|
||||||
s.add_development_dependency 'minitest'
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
require 'benchmark/ips'
|
require 'rubygems'
|
||||||
|
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.ips do |x|
|
Benchmark.bm do |x|
|
||||||
x.time = 60
|
# x.report("parse:") { 100.times { profiler.compile } }
|
||||||
x.warmup = 5
|
x.report("parse & run:") { 100.times { profiler.run } }
|
||||||
|
|
||||||
puts
|
|
||||||
puts "Running benchmark for #{x.time} seconds (with #{x.warmup} seconds warmup)."
|
|
||||||
puts
|
|
||||||
|
|
||||||
x.report("parse:") { profiler.compile }
|
|
||||||
x.report("parse & run:") { profiler.run }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
at_exit do
|
|
||||||
p 'Objects distribution:'
|
|
||||||
require 'pp'
|
|
||||||
pp ObjectSpace.count_objects
|
|
||||||
end
|
|
||||||
|
|
||||||
require 'allocation_tracer' rescue fail("install allocation_tracer extension/gem")
|
|
||||||
require File.dirname(__FILE__) + '/theme_runner'
|
|
||||||
|
|
||||||
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
|
|
||||||
profiler = ThemeRunner.new
|
|
||||||
|
|
||||||
require 'allocation_tracer/trace'
|
|
||||||
|
|
||||||
puts "Profiling memory usage..."
|
|
||||||
|
|
||||||
200.times do
|
|
||||||
profiler.run
|
|
||||||
end
|
|
||||||
@@ -1,24 +1,17 @@
|
|||||||
require 'stackprof' rescue fail("install stackprof extension/gem")
|
require 'rubygems'
|
||||||
|
require 'ruby-prof' rescue fail("install ruby-prof extension/gem")
|
||||||
require File.dirname(__FILE__) + '/theme_runner'
|
require File.dirname(__FILE__) + '/theme_runner'
|
||||||
|
|
||||||
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
|
|
||||||
profiler = ThemeRunner.new
|
profiler = ThemeRunner.new
|
||||||
profiler.run
|
|
||||||
|
|
||||||
[:cpu, :object].each do |profile_type|
|
puts 'Running profiler...'
|
||||||
puts "Profiling in #{profile_type.to_s} mode..."
|
|
||||||
results = StackProf.run(mode: profile_type) do
|
|
||||||
200.times do
|
|
||||||
profiler.run
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if profile_type == :cpu && graph_filename = ENV['GRAPH_FILENAME']
|
results = profiler.run_profile
|
||||||
File.open(graph_filename, 'w') do |f|
|
|
||||||
StackProf::Report.new(results).print_graphviz(nil, f)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
StackProf::Report.new(results).print_text(false, 20)
|
puts 'Success'
|
||||||
File.write(ENV['FILENAME'] + "." + profile_type.to_s, Marshal.dump(results)) if ENV['FILENAME']
|
|
||||||
|
filename = (ENV['TMP'] || '/tmp') + "/callgrind.liquid.txt"
|
||||||
|
File.open(filename, "w+") do |fp|
|
||||||
|
RubyProf::CallTreePrinter.new(results).print(fp, :print_file => true)
|
||||||
end
|
end
|
||||||
|
$stderr.puts "wrote RubyProf::CallTreePrinter output to #{filename}"
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
class CommentForm < Liquid::Block
|
class CommentForm < Liquid::Block
|
||||||
Syntax = /(#{Liquid::VariableSignature}+)/
|
Syntax = /(#{Liquid::VariableSignature}+)/
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
super
|
|
||||||
|
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@variable_name = $1
|
@variable_name = $1
|
||||||
@attributes = {}
|
@attributes = {}
|
||||||
else
|
else
|
||||||
raise SyntaxError.new("Syntax Error in 'comment_form' - Valid syntax: comment_form [article]")
|
raise SyntaxError.new("Syntax Error in 'comment_form' - Valid syntax: comment_form [article]")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'yaml'
|
require 'yaml'
|
||||||
|
|
||||||
module Database
|
module Database
|
||||||
|
|
||||||
# Load the standard vision toolkit database and re-arrage it to be simply exportable
|
# Load the standard vision toolkit database and re-arrage it to be simply exportable
|
||||||
# to liquid as assigns. All this is based on Shopify
|
# to liquid as assigns. All this is based on Shopify
|
||||||
def self.tables
|
def self.tables
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
require 'json'
|
|
||||||
|
|
||||||
module JsonFilter
|
module JsonFilter
|
||||||
|
|
||||||
def json(object)
|
def json(object)
|
||||||
JSON.dump(object.reject {|k,v| k == "collections" })
|
object.reject {|k,v| k == "collections" }.to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
class Paginate < Liquid::Block
|
class Paginate < Liquid::Block
|
||||||
Syntax = /(#{Liquid::QuotedFragment})\s*(by\s*(\d+))?/
|
Syntax = /(#{Liquid::QuotedFragment})\s*(by\s*(\d+))?/
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
super
|
@nodelist = []
|
||||||
|
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@collection_name = $1
|
@collection_name = $1
|
||||||
@@ -19,6 +19,8 @@ class Paginate < Liquid::Block
|
|||||||
else
|
else
|
||||||
raise SyntaxError.new("Syntax Error in tag 'paginate' - Valid syntax: paginate [collection] by number")
|
raise SyntaxError.new("Syntax Error in tag 'paginate' - Valid syntax: paginate [collection] by number")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
@@ -71,7 +73,7 @@ class Paginate < Liquid::Block
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
super
|
render_all(@nodelist, context)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -45,16 +45,16 @@ module ShopFilter
|
|||||||
end
|
end
|
||||||
|
|
||||||
def url_for_vendor(vendor_title)
|
def url_for_vendor(vendor_title)
|
||||||
"/collections/#{to_handle(vendor_title)}"
|
"/collections/#{vendor_title.to_handle}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def url_for_type(type_title)
|
def url_for_type(type_title)
|
||||||
"/collections/#{to_handle(type_title)}"
|
"/collections/#{type_title.to_handle}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def product_img_url(url, style = 'small')
|
def product_img_url(url, style = 'small')
|
||||||
|
|
||||||
unless url =~ /\Aproducts\/([\w\-\_]+)\.(\w{2,4})/
|
unless url =~ /^products\/([\w\-\_]+)\.(\w{2,4})/
|
||||||
raise ArgumentError, 'filter "size" can only be called on product images'
|
raise ArgumentError, 'filter "size" can only be called on product images'
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -95,16 +95,4 @@ module ShopFilter
|
|||||||
input == 1 ? singular : plural
|
input == 1 ? singular : plural
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def to_handle(str)
|
|
||||||
result = str.dup
|
|
||||||
result.downcase!
|
|
||||||
result.delete!("'\"()[]")
|
|
||||||
result.gsub!(/\W+/, '-')
|
|
||||||
result.gsub!(/-+\z/, '') if result[-1] == '-'
|
|
||||||
result.gsub!(/\A-+/, '') if result[0] == '-'
|
|
||||||
result
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="article-body textile">
|
<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 />
|
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" }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<br style="clear: both;" />
|
<br style="clear: both;" />
|
||||||
<div id="articles">
|
<div id="articles">
|
||||||
{% for article in blogs.news.articles offset:1 %}
|
{% for article in blogs.news.articles offset:1 %}
|
||||||
<div class="article">
|
<div class="article">
|
||||||
<h2>{{ article.title }}</h2>
|
<h2>{{ article.title }}</h2>
|
||||||
<div class="article-body textile">
|
<div class="article-body textile">
|
||||||
{{ article.content }}
|
{{ article.content }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -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 }}
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user