mirror of
https://github.com/Shopify/liquid.git
synced 2026-09-15 00:40:40 -07:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba82f8d05e | ||
|
|
7c70b56002 | ||
|
|
8a71d29bfb | ||
|
|
f6d1b56b4e | ||
|
|
45ddc00710 | ||
|
|
c2b7b3df50 |
@@ -6,12 +6,12 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
entry:
|
||||
- { ruby: 2.7, allowed-failure: false } # minimum supported
|
||||
- { ruby: 2.5, allowed-failure: false } # minimum supported
|
||||
- { ruby: 3.1, allowed-failure: false } # latest
|
||||
- { ruby: ruby-head, allowed-failure: true }
|
||||
name: test (${{ matrix.entry.ruby }})
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v2
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: ${{ matrix.entry.ruby }}
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
memory_profile:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v2
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 2.7
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ Performance:
|
||||
Enabled: true
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.7
|
||||
TargetRubyVersion: 2.5
|
||||
NewCops: disable
|
||||
Exclude:
|
||||
- 'vendor/bundle/**/*'
|
||||
|
||||
+14
-145
@@ -1,82 +1,27 @@
|
||||
# This configuration was generated by
|
||||
# `rubocop --auto-gen-config`
|
||||
# on 2022-06-01 17:13:35 UTC using RuboCop version 1.30.0.
|
||||
# on 2020-12-11 18:53:41 UTC using RuboCop version 1.6.1.
|
||||
# The point is for the user to remove these configuration records
|
||||
# one by one as the offenses are removed from the code base.
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
# versions of RuboCop, may require this file to be generated again.
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: Include.
|
||||
# Include: **/*.gemspec
|
||||
Gemspec/DeprecatedAttributeAssignment:
|
||||
# Offense count: 2
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: runtime_error, standard_error
|
||||
Lint/InheritException:
|
||||
Exclude:
|
||||
- 'liquid.gemspec'
|
||||
- 'lib/liquid/interrupts.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include.
|
||||
# Include: **/*.gemspec
|
||||
Gemspec/OrderedDependencies:
|
||||
Exclude:
|
||||
- 'liquid.gemspec'
|
||||
|
||||
# Offense count: 6
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
Layout/ClosingHeredocIndentation:
|
||||
Exclude:
|
||||
- 'test/integration/tags/for_tag_test.rb'
|
||||
|
||||
# Offense count: 34
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
Layout/EmptyLineAfterGuardClause:
|
||||
Exclude:
|
||||
- 'lib/liquid/block.rb'
|
||||
- 'lib/liquid/block_body.rb'
|
||||
- 'lib/liquid/context.rb'
|
||||
- 'lib/liquid/drop.rb'
|
||||
- 'lib/liquid/lexer.rb'
|
||||
- 'lib/liquid/parser.rb'
|
||||
- 'lib/liquid/profiler/hooks.rb'
|
||||
- 'lib/liquid/standardfilters.rb'
|
||||
- 'lib/liquid/tags/for.rb'
|
||||
- 'lib/liquid/tags/if.rb'
|
||||
- 'lib/liquid/utils.rb'
|
||||
- 'lib/liquid/variable.rb'
|
||||
- 'lib/liquid/variable_lookup.rb'
|
||||
- 'performance/shopify/money_filter.rb'
|
||||
- 'performance/shopify/paginate.rb'
|
||||
# Offense count: 113
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
||||
# URISchemes: http, https
|
||||
Layout/LineLength:
|
||||
Max: 260
|
||||
|
||||
# Offense count: 8
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: AllowAliasSyntax, AllowedMethods.
|
||||
# AllowedMethods: alias_method, public, protected, private
|
||||
Layout/EmptyLinesAroundAttributeAccessor:
|
||||
Exclude:
|
||||
- 'lib/liquid/template.rb'
|
||||
- 'test/integration/filter_test.rb'
|
||||
- 'test/integration/tags/include_tag_test.rb'
|
||||
- 'test/unit/strainer_template_unit_test.rb'
|
||||
|
||||
# Offense count: 17
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
||||
# SupportedStyles: aligned, indented
|
||||
Layout/LineEndStringConcatenationIndentation:
|
||||
Exclude:
|
||||
- 'test/integration/tags/for_tag_test.rb'
|
||||
- 'test/integration/tags/increment_tag_test.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
||||
# SupportedStyles: aligned, indented
|
||||
Layout/MultilineOperationIndentation:
|
||||
Exclude:
|
||||
- 'lib/liquid/expression.rb'
|
||||
|
||||
# Offense count: 9
|
||||
Lint/MissingSuper:
|
||||
Exclude:
|
||||
- 'lib/liquid/forloop_drop.rb'
|
||||
@@ -88,7 +33,7 @@ Lint/MissingSuper:
|
||||
- 'test/integration/tags/for_tag_test.rb'
|
||||
- 'test/integration/tags/table_row_test.rb'
|
||||
|
||||
# Offense count: 44
|
||||
# Offense count: 43
|
||||
Naming/ConstantName:
|
||||
Exclude:
|
||||
- 'lib/liquid.rb'
|
||||
@@ -106,83 +51,7 @@ Naming/ConstantName:
|
||||
- 'performance/shopify/paginate.rb'
|
||||
- 'test/integration/tags/include_tag_test.rb'
|
||||
|
||||
# Offense count: 9
|
||||
# Configuration parameters: CheckIdentifiers, CheckConstants, CheckVariables, CheckStrings, CheckSymbols, CheckComments, CheckFilepaths, FlaggedTerms.
|
||||
Naming/InclusiveLanguage:
|
||||
Exclude:
|
||||
- 'lib/liquid/drop.rb'
|
||||
- 'lib/liquid/parse_context.rb'
|
||||
- 'test/integration/drop_test.rb'
|
||||
- 'test/integration/tags/if_else_tag_test.rb'
|
||||
|
||||
# Offense count: 2
|
||||
Style/ClassVars:
|
||||
Exclude:
|
||||
- 'lib/liquid/condition.rb'
|
||||
|
||||
# Offense count: 3
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
Style/ExplicitBlockArgument:
|
||||
Exclude:
|
||||
- 'test/integration/context_test.rb'
|
||||
- 'test/integration/tag/disableable_test.rb'
|
||||
- 'test/integration/tags/for_tag_test.rb'
|
||||
|
||||
# Offense count: 2982
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
|
||||
# SupportedStyles: single_quotes, double_quotes
|
||||
Style/StringLiterals:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 20
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: single_quotes, double_quotes
|
||||
Style/StringLiteralsInInterpolation:
|
||||
Exclude:
|
||||
- 'lib/liquid/condition.rb'
|
||||
- 'lib/liquid/strainer_template.rb'
|
||||
- 'lib/liquid/tag/disableable.rb'
|
||||
- 'performance/shopify/shop_filter.rb'
|
||||
- 'performance/shopify/tag_filter.rb'
|
||||
|
||||
# Offense count: 6
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EnforcedStyleForMultiline.
|
||||
# SupportedStylesForMultiline: comma, consistent_comma, no_comma
|
||||
Style/TrailingCommaInArrayLiteral:
|
||||
Exclude:
|
||||
- 'example/server/example_servlet.rb'
|
||||
- 'lib/liquid/condition.rb'
|
||||
- 'test/integration/context_test.rb'
|
||||
- 'test/integration/standard_filter_test.rb'
|
||||
- 'test/unit/parse_tree_visitor_test.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EnforcedStyleForMultiline.
|
||||
# SupportedStylesForMultiline: comma, consistent_comma, no_comma
|
||||
Style/TrailingCommaInHashLiteral:
|
||||
Exclude:
|
||||
- 'lib/liquid/expression.rb'
|
||||
|
||||
# Offense count: 19
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EnforcedStyle, MinSize, WordRegex.
|
||||
# SupportedStyles: percent, brackets
|
||||
Style/WordArray:
|
||||
Exclude:
|
||||
- 'lib/liquid/tags/if.rb'
|
||||
- 'liquid.gemspec'
|
||||
- 'test/integration/assign_test.rb'
|
||||
- 'test/integration/context_test.rb'
|
||||
- 'test/integration/drop_test.rb'
|
||||
- 'test/integration/standard_filter_test.rb'
|
||||
|
||||
# Offense count: 117
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, IgnoredPatterns.
|
||||
# URISchemes: http, https
|
||||
Layout/LineLength:
|
||||
Max: 260
|
||||
|
||||
@@ -11,14 +11,20 @@ group :benchmark, :test do
|
||||
gem 'benchmark-ips'
|
||||
gem 'memory_profiler'
|
||||
gem 'terminal-table'
|
||||
gem 'unicode_plot'
|
||||
|
||||
install_if -> { RUBY_PLATFORM !~ /mingw|mswin|java/ && RUBY_ENGINE != 'truffleruby' } do
|
||||
gem 'stackprof'
|
||||
end
|
||||
end
|
||||
|
||||
group :development do
|
||||
gem 'pry-byebug'
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem 'rubocop-shopify', '~> 2.6', require: false
|
||||
gem 'rubocop', '~> 1.4', require: false
|
||||
gem 'rubocop-shopify', '~> 1.0.7', require: false
|
||||
gem 'rubocop-performance', require: false
|
||||
|
||||
platform :mri, :truffleruby do
|
||||
|
||||
-15
@@ -1,20 +1,5 @@
|
||||
# Liquid Change Log
|
||||
|
||||
## 5.4.0 (unreleased)
|
||||
|
||||
### Breaking Changes
|
||||
* Drop support for end-of-life Ruby versions (2.5 and 2.6) (#1578) [Andy Waite]
|
||||
|
||||
### Features
|
||||
* Allow `#` to be used as an inline comment tag (#1498) [CP Clermont]
|
||||
|
||||
### Fixes
|
||||
* `PartialCache` now shares snippet cache with subcontexts by default (#1553) [Chris AtLee]
|
||||
* Hash registers no longer leak into subcontexts as static registers (#1564) [Chris AtLee]
|
||||
|
||||
### Changed
|
||||
* Liquid::Context#registers now always returns a Liquid::StaticRegisters object, though supports the most used Hash functions for compatibility (#1553)
|
||||
|
||||
## 5.3.0 2022-03-22
|
||||
|
||||
### Fixes
|
||||
|
||||
@@ -63,13 +63,13 @@ 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 strict errors to template.errors but continues as normal
|
||||
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)
|
||||
Liquid::Template.parse(source, :error_mode => :strict)
|
||||
```
|
||||
This is useful for doing things like enabling strict mode only in the theme editor.
|
||||
|
||||
|
||||
@@ -53,6 +53,21 @@ task :test do
|
||||
Rake::Task['integration_test'].reenable
|
||||
Rake::Task['integration_test'].invoke
|
||||
end
|
||||
|
||||
if RUBY_ENGINE == 'ruby'
|
||||
Rake::Task['test_compile'].invoke
|
||||
end
|
||||
end
|
||||
|
||||
task :test_compile do
|
||||
ENV['LIQUID_COMPILE'] = '1'
|
||||
ENV['LIQUID_PARSER_MODE'] = 'lax'
|
||||
Rake::Task['integration_test'].reenable
|
||||
Rake::Task['integration_test'].invoke
|
||||
|
||||
ENV['LIQUID_PARSER_MODE'] = 'strict'
|
||||
Rake::Task['integration_test'].reenable
|
||||
Rake::Task['integration_test'].invoke
|
||||
end
|
||||
|
||||
task(gem: :build)
|
||||
@@ -77,6 +92,22 @@ namespace :benchmark do
|
||||
ruby "./performance/benchmark.rb lax"
|
||||
end
|
||||
|
||||
desc "Compare the render performance of compiled Liquid to standard Liquid and Liquid-C"
|
||||
task :compare_render do
|
||||
ENV.delete("LIQUID_C")
|
||||
ENV.delete("RENDER_ONLY")
|
||||
|
||||
ENV["LIQUID_COMPILE"] = "1"
|
||||
ruby "./performance/benchmark.rb strict"
|
||||
|
||||
ENV.delete("LIQUID_COMPILE")
|
||||
ruby "./performance/benchmark.rb strict"
|
||||
|
||||
ENV["LIQUID_C"] = "1"
|
||||
ruby "./performance/benchmark.rb strict"
|
||||
|
||||
end
|
||||
|
||||
desc "Run the liquid benchmark with strict parsing"
|
||||
task :strict do
|
||||
ruby "./performance/benchmark.rb strict"
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# https://github.com/evanphx/benchmark-ips
|
||||
require 'liquid'
|
||||
#require 'liquid/c'
|
||||
require 'benchmark/ips'
|
||||
|
||||
require_relative '../lib/liquid/compile'
|
||||
|
||||
require_relative 'shop_filter'
|
||||
require_relative 'money_filter'
|
||||
|
||||
# Each database table is a hash
|
||||
require_relative 'database'
|
||||
tables = Database.tables
|
||||
|
||||
Liquid::Template.register_filter(MoneyFilter)
|
||||
Liquid::Template.register_filter(ShopFilter)
|
||||
@template = Liquid::Template.parse(File.read("product.liquid"))
|
||||
|
||||
context = Liquid::Context.new([tables, {}], {}, {}, false, Liquid::ResourceLimits.new(Liquid::Template.default_resource_limits))
|
||||
|
||||
Benchmark.ips do |x|
|
||||
x.report("render") { @template.render(context) }
|
||||
|
||||
# Compare the iterations per second of the various reports
|
||||
x.compare!
|
||||
end
|
||||
@@ -0,0 +1,57 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'yaml'
|
||||
require 'stackprof'
|
||||
|
||||
module Database
|
||||
DATABASE_FILE_PATH = "#{__dir__}/vision.database.yml"
|
||||
|
||||
# Load the standard vision toolkit database and re-arrage it to be simply exportable
|
||||
# to liquid as assigns. All this is based on Shopify
|
||||
def self.tables
|
||||
@tables ||= begin
|
||||
db =
|
||||
if YAML.respond_to?(:unsafe_load_file) # Only Psych 4+ can use unsafe_load_file
|
||||
# unsafe_load_file is needed for YAML references
|
||||
YAML.unsafe_load_file(DATABASE_FILE_PATH, symbolize_names: true)
|
||||
else
|
||||
YAML.load_file(DATABASE_FILE_PATH, symbolize_names: true)
|
||||
end
|
||||
|
||||
# From vision source
|
||||
db[:products].each do |product|
|
||||
collections = db[:collections].find_all do |collection|
|
||||
collection[:products].any? { |p| p[:id].to_i == product[:id].to_i }
|
||||
end
|
||||
product[:collections] = collections
|
||||
end
|
||||
|
||||
# key the tables by handles, as this is how liquid expects it.
|
||||
db = db.each_with_object({}) do |(key, values), assigns|
|
||||
assigns[key] = values.each_with_object({}) do |v, h|
|
||||
h[v[:handle]] = v
|
||||
end
|
||||
end
|
||||
|
||||
# Some standard direct accessors so that the specialized templates
|
||||
# render correctly
|
||||
db[:collection] = db[:collections].values.first
|
||||
db[:product] = db[:products].values.first
|
||||
db[:blog] = db[:blogs].values.first
|
||||
db[:article] = db[:blog][:articles].first
|
||||
|
||||
db[:cart] = {
|
||||
:total_price => db[:line_items].values.inject(0) { |sum, item| sum + item[:line_price] * item[:quantity] },
|
||||
:item_count => db[:line_items].values.inject(0) { |sum, item| sum + item[:quantity] },
|
||||
:items => db[:line_items].values,
|
||||
}
|
||||
|
||||
db
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if __FILE__ == $PROGRAM_NAME
|
||||
p(Database.tables[:collections][:frontpage].keys)
|
||||
# p Database.tables[:blog][:articles]
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module MoneyFilter
|
||||
def money_with_currency(money)
|
||||
return '' if money.nil?
|
||||
format("$ %.2f USD", money / 100.0)
|
||||
end
|
||||
|
||||
def money(money)
|
||||
return '' if money.nil?
|
||||
format("$ %.2f", money / 100.0)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def currency
|
||||
ShopDrop.new.currency
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,36 @@
|
||||
<div id="product-left">
|
||||
{% for image in product.images %}{% if forloop.first %}<div id="product-image">
|
||||
<a href="{{ image | product_img_url: 'large' }}" rel="lightbox[images]" title="{{ product.title | escape }}"><img src="{{ image | product_img_url: 'medium' }}" alt="{{ product.title | escape }}" /></a>
|
||||
</div>{% else %}
|
||||
<div class="product-images">
|
||||
<a href="{{ image | product_img_url: 'large' }}" rel="lightbox[images]" title="{{ product.title | escape }}"><img src="{{ image | product_img_url: 'small' }}" alt="{{ product.title | escape }}" /></a>
|
||||
</div>{% endif %}{% endfor %}
|
||||
</div>
|
||||
<div id="product-right">
|
||||
<h1>{{ product.title }}</h1>
|
||||
{{ product.description }}
|
||||
|
||||
{% if product.available %}
|
||||
<form action="/cart/add" method="post">
|
||||
|
||||
<div id="product-variants">
|
||||
<div id="price-field"></div>
|
||||
|
||||
<select id="product-select" name='id'>
|
||||
{% for variant in product.variants %}
|
||||
<option value="{{ variant.id }}">{{ variant.title }} - {{ variant.price | money }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<input type="image" src="{{ 'purchase.png' | asset_url }}" name="add" value="Purchase" id="purchase" />
|
||||
</form>
|
||||
{% else %}
|
||||
<p class="bold-red">This product is temporarily unavailable</p>
|
||||
{% endif %}
|
||||
|
||||
<div id="product-details">
|
||||
<strong>Continue Shopping</strong><br />
|
||||
Browse more {{ product.type | link_to_type }} or additional {{ product.vendor | link_to_vendor }} products.
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,106 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ShopFilter
|
||||
def asset_url(input)
|
||||
"/files/1/[shop_id]/[shop_id]/assets/#{input}"
|
||||
end
|
||||
|
||||
def global_asset_url(input)
|
||||
"/global/#{input}"
|
||||
end
|
||||
|
||||
def shopify_asset_url(input)
|
||||
"/shopify/#{input}"
|
||||
end
|
||||
|
||||
def script_tag(url)
|
||||
%(<script src="#{url}" type="text/javascript"></script>)
|
||||
end
|
||||
|
||||
def stylesheet_tag(url, media = "all")
|
||||
%(<link href="#{url}" rel="stylesheet" type="text/css" media="#{media}" />)
|
||||
end
|
||||
|
||||
def link_to(link, url, title = "")
|
||||
%(<a href="#{url}" title="#{title}">#{link}</a>)
|
||||
end
|
||||
|
||||
def img_tag(url, alt = "")
|
||||
%(<img src="#{url}" alt="#{alt}" />)
|
||||
end
|
||||
|
||||
def link_to_vendor(vendor)
|
||||
if vendor
|
||||
link_to(vendor, url_for_vendor(vendor), vendor)
|
||||
else
|
||||
'Unknown Vendor'
|
||||
end
|
||||
end
|
||||
|
||||
def link_to_type(type)
|
||||
if type
|
||||
link_to(type, url_for_type(type), type)
|
||||
else
|
||||
'Unknown Vendor'
|
||||
end
|
||||
end
|
||||
|
||||
def url_for_vendor(vendor_title)
|
||||
"/collections/#{to_handle(vendor_title)}"
|
||||
end
|
||||
|
||||
def url_for_type(type_title)
|
||||
"/collections/#{to_handle(type_title)}"
|
||||
end
|
||||
|
||||
def product_img_url(url, style = 'small')
|
||||
unless url =~ %r{\Aproducts/([\w\-\_]+)\.(\w{2,4})}
|
||||
raise ArgumentError, 'filter "size" can only be called on product images'
|
||||
end
|
||||
|
||||
case style
|
||||
when 'original'
|
||||
'/files/shops/random_number/' + url
|
||||
when 'grande', 'large', 'medium', 'compact', 'small', 'thumb', 'icon'
|
||||
"/files/shops/random_number/products/#{Regexp.last_match(1)}_#{style}.#{Regexp.last_match(2)}"
|
||||
else
|
||||
raise ArgumentError, 'valid parameters for filter "size" are: original, grande, large, medium, compact, small, thumb and icon '
|
||||
end
|
||||
end
|
||||
|
||||
def default_pagination(paginate)
|
||||
html = []
|
||||
html << %(<span class="prev">#{link_to(paginate['previous']['title'], paginate['previous']['url'])}</span>) if paginate['previous']
|
||||
|
||||
paginate['parts'].each do |part|
|
||||
html << if part['is_link']
|
||||
%(<span class="page">#{link_to(part['title'], part['url'])}</span>)
|
||||
elsif part['title'].to_i == paginate['current_page'].to_i
|
||||
%(<span class="page current">#{part['title']}</span>)
|
||||
else
|
||||
%(<span class="deco">#{part['title']}</span>)
|
||||
end
|
||||
end
|
||||
|
||||
html << %(<span class="next">#{link_to(paginate['next']['title'], paginate['next']['url'])}</span>) if paginate['next']
|
||||
html.join(' ')
|
||||
end
|
||||
|
||||
# Accepts a number, and two words - one for singular, one for plural
|
||||
# Returns the singular word if input equals 1, otherwise plural
|
||||
def pluralize(input, singular, plural)
|
||||
input == 1 ? singular : plural
|
||||
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
|
||||
Binary file not shown.
@@ -0,0 +1,945 @@
|
||||
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
# Variants
|
||||
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
product_variants:
|
||||
- &product-1-var-1
|
||||
id: 1
|
||||
title: 151cm / Normal
|
||||
price: 19900
|
||||
weight: 1000
|
||||
compare_at_price: 49900
|
||||
available: true
|
||||
inventory_quantity: 5
|
||||
option1: 151cm
|
||||
option2: Normal
|
||||
option3:
|
||||
- &product-1-var-2
|
||||
id: 2
|
||||
title: 155cm / Normal
|
||||
price: 31900
|
||||
weight: 1000
|
||||
compare_at_price: 50900
|
||||
available: true
|
||||
inventory_quantity: 2
|
||||
option1: 155cm
|
||||
option2: Normal
|
||||
option3:
|
||||
- &product-2-var-1
|
||||
id: 3
|
||||
title: 162cm
|
||||
price: 29900
|
||||
weight: 1000
|
||||
compare_at_price: 52900
|
||||
available: true
|
||||
inventory_quantity: 3
|
||||
option1: 162cm
|
||||
option2:
|
||||
option3:
|
||||
- &product-3-var-1
|
||||
id: 4
|
||||
title: 159cm
|
||||
price: 19900
|
||||
weight: 1000
|
||||
compare_at_price:
|
||||
available: true
|
||||
inventory_quantity: 4
|
||||
option1: 159cm
|
||||
option2:
|
||||
option3:
|
||||
- &product-4-var-1
|
||||
id: 5
|
||||
title: 159cm
|
||||
price: 19900
|
||||
weight: 1000
|
||||
compare_at_price: 32900
|
||||
available: true
|
||||
inventory_quantity: 6
|
||||
option1: 159cm
|
||||
option2:
|
||||
option3:
|
||||
- &product-1-var-3
|
||||
id: 6
|
||||
title: 158cm / Wide
|
||||
price: 23900
|
||||
weight: 1000
|
||||
compare_at_price: 99900
|
||||
available: false
|
||||
inventory_quantity: 0
|
||||
option1: 158cm
|
||||
option2: Wide
|
||||
option3:
|
||||
- &product-3-var-2
|
||||
id: 7
|
||||
title: 162cm
|
||||
price: 19900
|
||||
weight: 1000
|
||||
compare_at_price:
|
||||
available: false
|
||||
inventory_quantity: 0
|
||||
option1: 162cm
|
||||
option2:
|
||||
option3:
|
||||
- &product-3-var-3
|
||||
id: 8
|
||||
title: 165cm
|
||||
price: 22900
|
||||
weight: 1000
|
||||
compare_at_price:
|
||||
available: true
|
||||
inventory_quantity: 4
|
||||
option1: 165cm
|
||||
option2:
|
||||
option3:
|
||||
- &product-5-var-1
|
||||
id: 9
|
||||
title: black / 42
|
||||
price: 11900
|
||||
weight: 500
|
||||
compare_at_price: 22900
|
||||
available: true
|
||||
inventory_quantity: 1
|
||||
option1: black
|
||||
option2: 42
|
||||
option3:
|
||||
- &product-5-var-2
|
||||
id: 10
|
||||
title: beige / 42
|
||||
price: 11900
|
||||
weight: 500
|
||||
compare_at_price: 22900
|
||||
available: true
|
||||
inventory_quantity: 3
|
||||
option1: beige
|
||||
option2: 42
|
||||
option3:
|
||||
- &product-5-var-3
|
||||
id: 11
|
||||
title: white / 42
|
||||
price: 13900
|
||||
weight: 500
|
||||
compare_at_price: 24900
|
||||
available: true
|
||||
inventory_quantity: 1
|
||||
option1: white
|
||||
option2: 42
|
||||
option3:
|
||||
- &product-5-var-4
|
||||
id: 12
|
||||
title: black / 44
|
||||
price: 11900
|
||||
weight: 500
|
||||
compare_at_price: 22900
|
||||
available: true
|
||||
inventory_quantity: 2
|
||||
option1: black
|
||||
option2: 44
|
||||
option3:
|
||||
- &product-5-var-5
|
||||
id: 13
|
||||
title: beige / 44
|
||||
price: 11900
|
||||
weight: 500
|
||||
compare_at_price: 22900
|
||||
available: false
|
||||
inventory_quantity: 0
|
||||
option1: beige
|
||||
option2: 44
|
||||
option3:
|
||||
- &product-5-var-6
|
||||
id: 14
|
||||
title: white / 44
|
||||
price: 13900
|
||||
weight: 500
|
||||
compare_at_price: 24900
|
||||
available: false
|
||||
inventory_quantity: 0
|
||||
option1: white
|
||||
option2: 44
|
||||
option3:
|
||||
- &product-6-var-1
|
||||
id: 15
|
||||
title: red
|
||||
price: 2179500
|
||||
weight: 200000
|
||||
compare_at_price:
|
||||
available: true
|
||||
inventory_quantity: 0
|
||||
option1: red
|
||||
option2:
|
||||
option3:
|
||||
- &product-7-var-1
|
||||
id: 16
|
||||
title: black / small
|
||||
price: 1900
|
||||
weight: 200
|
||||
compare_at_price:
|
||||
available: true
|
||||
inventory_quantity: 20
|
||||
option1: black
|
||||
option2: small
|
||||
option3:
|
||||
- &product-7-var-2
|
||||
id: 17
|
||||
title: black / medium
|
||||
price: 1900
|
||||
weight: 200
|
||||
compare_at_price:
|
||||
available: false
|
||||
inventory_quantity: 0
|
||||
option1: black
|
||||
option2: medium
|
||||
option3:
|
||||
- &product-7-var-3
|
||||
id: 18
|
||||
title: black / large
|
||||
price: 1900
|
||||
weight: 200
|
||||
compare_at_price:
|
||||
available: true
|
||||
inventory_quantity: 10
|
||||
option1: black
|
||||
option2: large
|
||||
option3:
|
||||
- &product-7-var-4
|
||||
id: 19
|
||||
title: black / extra large
|
||||
price: 1900
|
||||
weight: 200
|
||||
compare_at_price:
|
||||
available: false
|
||||
inventory_quantity: 0
|
||||
option1: black
|
||||
option2: extra large
|
||||
option3:
|
||||
- &product-8-var-1
|
||||
id: 20
|
||||
title: brown / small
|
||||
price: 5900
|
||||
weight: 400
|
||||
compare_at_price: 6900
|
||||
available: true
|
||||
inventory_quantity: 5
|
||||
option1: brown
|
||||
option2: small
|
||||
option3:
|
||||
- &product-8-var-2
|
||||
id: 21
|
||||
title: brown / medium
|
||||
price: 5900
|
||||
weight: 400
|
||||
compare_at_price: 6900
|
||||
available: false
|
||||
inventory_quantity: 0
|
||||
option1: brown
|
||||
option2: medium
|
||||
option3:
|
||||
- &product-8-var-3
|
||||
id: 22
|
||||
title: brown / large
|
||||
price: 5900
|
||||
weight: 400
|
||||
compare_at_price: 6900
|
||||
available: true
|
||||
inventory_quantity: 10
|
||||
option1: brown
|
||||
option2: large
|
||||
option3:
|
||||
- &product-8-var-4
|
||||
id: 23
|
||||
title: black / small
|
||||
price: 5900
|
||||
weight: 400
|
||||
compare_at_price: 6900
|
||||
available: true
|
||||
inventory_quantity: 10
|
||||
option1: black
|
||||
option2: small
|
||||
option3:
|
||||
- &product-8-var-5
|
||||
id: 24
|
||||
title: black / medium
|
||||
price: 5900
|
||||
weight: 400
|
||||
compare_at_price: 6900
|
||||
available: true
|
||||
inventory_quantity: 10
|
||||
option1: black
|
||||
option2: medium
|
||||
option3:
|
||||
- &product-8-var-6
|
||||
id: 25
|
||||
title: black / large
|
||||
price: 5900
|
||||
weight: 400
|
||||
compare_at_price: 6900
|
||||
available: false
|
||||
inventory_quantity: 0
|
||||
option1: black
|
||||
option2: large
|
||||
option3:
|
||||
- &product-9-var-1
|
||||
id: 26
|
||||
title: Body Only
|
||||
price: 499995
|
||||
weight: 2000
|
||||
compare_at_price:
|
||||
available: true
|
||||
inventory_quantity: 3
|
||||
option1: Body Only
|
||||
option2:
|
||||
option3:
|
||||
- &product-9-var-2
|
||||
id: 27
|
||||
title: Kit with 18-55mm VR lens
|
||||
price: 523995
|
||||
weight: 2000
|
||||
compare_at_price:
|
||||
available: true
|
||||
inventory_quantity: 2
|
||||
option1: Kit with 18-55mm VR lens
|
||||
option2:
|
||||
option3:
|
||||
- &product-9-var-3
|
||||
id: 28
|
||||
title: Kit with 18-200 VR lens
|
||||
price: 552500
|
||||
weight: 2000
|
||||
compare_at_price:
|
||||
available: true
|
||||
inventory_quantity: 3
|
||||
option1: Kit with 18-200 VR lens
|
||||
option2:
|
||||
option3:
|
||||
|
||||
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
# Products
|
||||
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
products:
|
||||
- &product-1
|
||||
id: 1
|
||||
title: Arbor Draft
|
||||
handle: arbor-draft
|
||||
type: Snowboards
|
||||
vendor: Arbor
|
||||
price: 23900
|
||||
price_max: 31900
|
||||
price_min: 23900
|
||||
price_varies: true
|
||||
available: true
|
||||
tags:
|
||||
- season2005
|
||||
- pro
|
||||
- intermediate
|
||||
- wooden
|
||||
- freestyle
|
||||
options:
|
||||
- Length
|
||||
- Style
|
||||
compare_at_price: 49900
|
||||
compare_at_price_max: 50900
|
||||
compare_at_price_min: 49900
|
||||
compare_at_price_varies: true
|
||||
url: /products/arbor-draft
|
||||
featured_image: products/arbor_draft.jpg
|
||||
images:
|
||||
- products/arbor_draft.jpg
|
||||
description:
|
||||
The Arbor Draft snowboard wouldn't exist if Polynesians hadn't figured out how to surf hundreds of years ago. But the Draft does exist, and it's here to bring your urban and park riding to a new level. The board's freaky Tiki design pays homage to culture that inspired snowboarding. It's designed to spin with ease, land smoothly, lock hook-free onto rails, and take the abuse of a pavement pounding or twelve. The Draft will pop off kickers with authority and carve solidly across the pipe. The Draft features targeted Koa wood die cuts inlayed into the deck that enhance the flex pattern. Now bow down to riding's ancestors.
|
||||
variants:
|
||||
- *product-1-var-1
|
||||
- *product-1-var-2
|
||||
- *product-1-var-3
|
||||
- &product-2
|
||||
id: 2
|
||||
title: Arbor Element
|
||||
handle: arbor-element
|
||||
type: Snowboards
|
||||
vendor: Arbor
|
||||
price: 29900
|
||||
price_max: 29900
|
||||
price_min: 29900
|
||||
price_varies: false
|
||||
available: true
|
||||
tags:
|
||||
- season2005
|
||||
- pro
|
||||
- wooden
|
||||
- freestyle
|
||||
options:
|
||||
- Length
|
||||
compare_at_price: 52900
|
||||
compare_at_price_max: 52900
|
||||
compare_at_price_min: 52900
|
||||
compare_at_price_varies: false
|
||||
url: /products/arbor-element
|
||||
featured_image: products/element58.jpg
|
||||
images:
|
||||
- products/element58.jpg
|
||||
description:
|
||||
The Element is a technically advanced all-mountain board for riders who readily transition from one terrain, snow condition, or riding style to another. Its balanced design provides the versatility needed for the true ride-it-all experience. The Element is exceedingly lively, freely initiates, and holds a tight edge at speed. Its structural real-wood topsheet is made with book-matched Koa.
|
||||
variants:
|
||||
- *product-2-var-1
|
||||
|
||||
- &product-3
|
||||
id: 3
|
||||
title: Comic ~ Pastel
|
||||
handle: comic-pastel
|
||||
type: Snowboards
|
||||
vendor: Technine
|
||||
price: 19900
|
||||
price_max: 22900
|
||||
price_min: 19900
|
||||
tags:
|
||||
- season2006
|
||||
- beginner
|
||||
- intermediate
|
||||
- freestyle
|
||||
- purple
|
||||
options:
|
||||
- Length
|
||||
price_varies: true
|
||||
available: true
|
||||
compare_at_price:
|
||||
compare_at_price_max: 0
|
||||
compare_at_price_min: 0
|
||||
compare_at_price_varies: false
|
||||
url: /products/comic-pastel
|
||||
featured_image: products/technine1.jpg
|
||||
images:
|
||||
- products/technine1.jpg
|
||||
- products/technine2.jpg
|
||||
- products/technine_detail.jpg
|
||||
description:
|
||||
2005 Technine Comic Series Description The Comic series was developed to be the ultimate progressive freestyle board in the Technine line. Dependable edge control and a perfect flex pattern for jumping in the park or out of bounds. Landins and progression will come easy with this board and it will help your riding progress to the next level. Street rails, park jibs, backcountry booters and park jumps, this board will do it all.
|
||||
variants:
|
||||
- *product-3-var-1
|
||||
- *product-3-var-2
|
||||
- *product-3-var-3
|
||||
|
||||
- &product-4
|
||||
id: 4
|
||||
title: Comic ~ Orange
|
||||
handle: comic-orange
|
||||
type: Snowboards
|
||||
vendor: Technine
|
||||
price: 19900
|
||||
price_max: 19900
|
||||
price_min: 19900
|
||||
price_varies: false
|
||||
available: true
|
||||
tags:
|
||||
- season2006
|
||||
- beginner
|
||||
- intermediate
|
||||
- freestyle
|
||||
- orange
|
||||
options:
|
||||
- Length
|
||||
compare_at_price: 32900
|
||||
compare_at_price_max: 32900
|
||||
compare_at_price_min: 32900
|
||||
compare_at_price_varies: false
|
||||
url: /products/comic-orange
|
||||
featured_image: products/technine3.jpg
|
||||
images:
|
||||
- products/technine3.jpg
|
||||
- products/technine4.jpg
|
||||
description:
|
||||
2005 Technine Comic Series Description The Comic series was developed to be the ultimate progressive freestyle board in the Technine line. Dependable edge control and a perfect flex pattern for jumping in the park or out of bounds. Landins and progression will come easy with this board and it will help your riding progress to the next level. Street rails, park jibs, backcountry booters and park jumps, this board will do it all.
|
||||
variants:
|
||||
- *product-4-var-1
|
||||
|
||||
- &product-5
|
||||
id: 5
|
||||
title: Burton Boots
|
||||
handle: burton-boots
|
||||
type: Boots
|
||||
vendor: Burton
|
||||
price: 11900
|
||||
price_max: 11900
|
||||
price_min: 11900
|
||||
price_varies: false
|
||||
available: true
|
||||
tags:
|
||||
- season2006
|
||||
- beginner
|
||||
- intermediate
|
||||
- boots
|
||||
options:
|
||||
- Color
|
||||
- Shoe Size
|
||||
compare_at_price: 22900
|
||||
compare_at_price_max: 22900
|
||||
compare_at_price_min: 22900
|
||||
compare_at_price_varies: false
|
||||
url: /products/burton-boots
|
||||
featured_image: products/burton.jpg
|
||||
images:
|
||||
- products/burton.jpg
|
||||
description:
|
||||
The Burton boots are particularly well on snowboards. The very best thing about them is that the according picture is cubic. This makes testing in a Vision testing environment very easy.
|
||||
variants:
|
||||
- *product-5-var-1
|
||||
- *product-5-var-2
|
||||
- *product-5-var-3
|
||||
- *product-5-var-4
|
||||
- *product-5-var-5
|
||||
- *product-5-var-6
|
||||
|
||||
- &product-6
|
||||
id: 6
|
||||
title: Superbike 1198 S
|
||||
handle: superbike
|
||||
type: Superbike
|
||||
vendor: Ducati
|
||||
price: 2179500
|
||||
price_max: 2179500
|
||||
price_min: 2179500
|
||||
price_varies: false
|
||||
available: true
|
||||
tags:
|
||||
- ducati
|
||||
- superbike
|
||||
- bike
|
||||
- street
|
||||
- racing
|
||||
- performance
|
||||
options:
|
||||
- Color
|
||||
compare_at_price:
|
||||
compare_at_price_max: 0
|
||||
compare_at_price_min: 0
|
||||
compare_at_price_varies: false
|
||||
url: /products/superbike
|
||||
featured_image: products/ducati.jpg
|
||||
images:
|
||||
- products/ducati.jpg
|
||||
description:
|
||||
<h3>‘S’ PERFORMANCE</h3>
|
||||
<p>Producing 170hp (125kW) and with a dry weight of just 169kg (372.6lb), the new 1198 S now incorporates more World Superbike technology than ever before by taking the 1198 motor and adding top-of-the-range suspension, lightweight chassis components and a true racing-style traction control system designed for road use.</p>
|
||||
<p>The high performance, fully adjustable 43mm Öhlins forks, which sport low friction titanium nitride-treated fork sliders, respond effortlessly to every imperfection in the tarmac. Beyond their advanced engineering solutions, one of the most important characteristics of Öhlins forks is their ability to communicate the condition and quality of the tyre-to-road contact patch, a feature that puts every rider in superior control. The suspension set-up at the rear is complemented with a fully adjustable Öhlins rear shock equipped with a ride enhancing top-out spring and mounted to a single-sided swingarm for outstanding drive and traction. The front-to-rear Öhlins package is completed with a control-enhancing adjustable steering damper.</p>
|
||||
variants:
|
||||
- *product-6-var-1
|
||||
|
||||
- &product-7
|
||||
id: 7
|
||||
title: Shopify Shirt
|
||||
handle: shopify-shirt
|
||||
type: Shirt
|
||||
vendor: Shopify
|
||||
price: 1900
|
||||
price_max: 1900
|
||||
price_min: 1900
|
||||
price_varies: false
|
||||
available: true
|
||||
tags:
|
||||
- shopify
|
||||
- shirt
|
||||
- apparel
|
||||
- tshirt
|
||||
- clothing
|
||||
options:
|
||||
- Color
|
||||
- Size
|
||||
compare_at_price:
|
||||
compare_at_price_max: 0
|
||||
compare_at_price_min: 0
|
||||
compare_at_price_varies: false
|
||||
url: /products/shopify-shirt
|
||||
featured_image: products/shopify_shirt.png
|
||||
images:
|
||||
- products/shopify_shirt.png
|
||||
description:
|
||||
<p>High Quality Shopify Shirt. Wear your e-commerce solution with pride and attract attention anywhere you go.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
variants:
|
||||
- *product-7-var-1
|
||||
- *product-7-var-2
|
||||
- *product-7-var-3
|
||||
- *product-7-var-4
|
||||
|
||||
- &product-8
|
||||
id: 8
|
||||
title: Hooded Sweater
|
||||
handle: hooded-sweater
|
||||
type: Sweater
|
||||
vendor: Stormtech
|
||||
price: 5900
|
||||
price_max: 5900
|
||||
price_min: 5900
|
||||
price_varies: false
|
||||
available: true
|
||||
tags:
|
||||
- sweater
|
||||
- hooded
|
||||
- apparel
|
||||
- clothing
|
||||
options:
|
||||
- Color
|
||||
- Size
|
||||
compare_at_price: 6900
|
||||
compare_at_price_max: 6900
|
||||
compare_at_price_min: 6900
|
||||
compare_at_price_varies: false
|
||||
url: /products/hooded-sweater
|
||||
featured_image: products/hooded-sweater.jpg
|
||||
images:
|
||||
- products/hooded-sweater.jpg
|
||||
- products/hooded-sweater-b.jpg
|
||||
description:
|
||||
<p>Extra comfortable zip up sweater. Durable quality, ideal for any outdoor activities.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
variants:
|
||||
- *product-8-var-1
|
||||
- *product-8-var-2
|
||||
- *product-8-var-3
|
||||
- *product-8-var-4
|
||||
- *product-8-var-5
|
||||
- *product-8-var-6
|
||||
|
||||
- &product-9
|
||||
id: 9
|
||||
title: D3 Digital SLR Camera
|
||||
handle: d3
|
||||
type: SLR
|
||||
vendor: Nikon
|
||||
price: 499995
|
||||
price_max: 552500
|
||||
price_min: 499995
|
||||
price_varies: true
|
||||
available: true
|
||||
tags:
|
||||
- camera
|
||||
- slr
|
||||
- nikon
|
||||
- professional
|
||||
options:
|
||||
- Bundle
|
||||
compare_at_price:
|
||||
compare_at_price_max: 0
|
||||
compare_at_price_min: 0
|
||||
compare_at_price_varies: false
|
||||
url: /products/d3
|
||||
featured_image: products/d3.jpg
|
||||
images:
|
||||
- products/d3.jpg
|
||||
- products/d3_2.jpg
|
||||
- products/d3_3.jpg
|
||||
description:
|
||||
<p>Flagship pro D-SLR with a 12.1-MP FX-format CMOS sensor, blazing 9 fps shooting at full FX resolution and low-noise performance up to 6400 ISO.</p>
|
||||
<p><strong>Nikon's original 12.1-megapixel FX-format (23.9 x 36mm) CMOS sensor:</strong> Couple Nikon's exclusive digital image processing system with the 12.1-megapixel FX-format and you'll get breathtakingly rich images while also reducing noise to unprecedented levels with even higher ISOs.</p>
|
||||
<p><strong>Continuous shooting at up to 9 frames per second:</strong> At full FX resolution and up to 11fps in the DX crop mode, the D3 offers uncompromised shooting speeds for fast-action and sports photography.</p>
|
||||
variants:
|
||||
- *product-9-var-1
|
||||
- *product-9-var-2
|
||||
- *product-9-var-3
|
||||
|
||||
|
||||
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
# Line Items
|
||||
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
line_items:
|
||||
- &line_item-1
|
||||
id: 1
|
||||
title: 'Arbor Draft'
|
||||
subtitle: '151cm'
|
||||
price: 29900
|
||||
line_price: 29900
|
||||
quantity: 1
|
||||
variant: *product-1-var-1
|
||||
product: *product-1
|
||||
|
||||
- &line_item-2
|
||||
id: 2
|
||||
title: 'Comic ~ Orange'
|
||||
subtitle: '159cm'
|
||||
price: 19900
|
||||
line_price: 39800
|
||||
quantity: 2
|
||||
variant: *product-4-var-1
|
||||
product: *product-4
|
||||
|
||||
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
# Link Lists
|
||||
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
links:
|
||||
- &link-1
|
||||
id: 1
|
||||
title: Our Sale
|
||||
url: /collections/sale
|
||||
- &link-2
|
||||
id: 2
|
||||
title: Arbor Stuff
|
||||
url: /collections/arbor
|
||||
- &link-3
|
||||
id: 3
|
||||
title: All our Snowboards
|
||||
url: /collections/snowboards
|
||||
- &link-4
|
||||
id: 4
|
||||
title: Powered by Shopify
|
||||
url: 'http://shopify.com'
|
||||
- &link-5
|
||||
id: 5
|
||||
title: About Us
|
||||
url: /pages/about-us
|
||||
- &link-6
|
||||
id: 6
|
||||
title: Policies
|
||||
url: /pages/shipping
|
||||
- &link-7
|
||||
id: 7
|
||||
title: Contact Us
|
||||
url: /pages/contact
|
||||
- &link-8
|
||||
id: 8
|
||||
title: Our blog
|
||||
url: /blogs/bigcheese-blog
|
||||
- &link-9
|
||||
id: 9
|
||||
title: New Boots
|
||||
url: /products/burton-boots
|
||||
- &link-10
|
||||
id: 10
|
||||
title: Paginated Sale
|
||||
url: /collections/paginated-sale
|
||||
- &link-11
|
||||
id: 11
|
||||
title: Our Paginated blog
|
||||
url: /blogs/paginated-blog
|
||||
- &link-12
|
||||
id: 12
|
||||
title: Catalog
|
||||
url: /collections/all
|
||||
|
||||
|
||||
|
||||
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
# Link Lists
|
||||
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
link_lists:
|
||||
- &link-list-1
|
||||
id: 1
|
||||
title: 'Main Menu'
|
||||
handle: 'main-menu'
|
||||
links:
|
||||
- *link-12
|
||||
- *link-5
|
||||
- *link-7
|
||||
- *link-8
|
||||
- &link-list-2
|
||||
id: 1
|
||||
title: 'Footer Menu'
|
||||
handle: 'footer'
|
||||
links:
|
||||
- *link-5
|
||||
- *link-6
|
||||
|
||||
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
# Collections
|
||||
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
collections:
|
||||
- &collection-1
|
||||
id: 1
|
||||
title: Frontpage
|
||||
handle: frontpage
|
||||
url: /collections/frontpage
|
||||
products:
|
||||
- *product-7
|
||||
- *product-8
|
||||
- *product-9
|
||||
|
||||
- &collection-2
|
||||
id: 2
|
||||
title: Arbor
|
||||
handle: arbor
|
||||
url: /collections/arbor
|
||||
products:
|
||||
- *product-1
|
||||
- *product-2
|
||||
|
||||
- &collection-3
|
||||
id: 3
|
||||
title: Snowboards
|
||||
handle: snowboards
|
||||
url: /collections/snowboards
|
||||
description:
|
||||
<p>This is a description for my <strong>Snowboards</strong> collection.</p>
|
||||
products:
|
||||
- *product-1
|
||||
- *product-2
|
||||
- *product-3
|
||||
- *product-4
|
||||
|
||||
- &collection-4
|
||||
id: 4
|
||||
title: Items On Sale
|
||||
handle: sale
|
||||
url: /collections/sale
|
||||
products:
|
||||
- *product-1
|
||||
|
||||
- &collection-5
|
||||
id: 5
|
||||
title: Paginated Sale
|
||||
handle: 'paginated-sale'
|
||||
url: '/collections/paginated-sale'
|
||||
products:
|
||||
- *product-1
|
||||
- *product-2
|
||||
- *product-3
|
||||
- *product-4
|
||||
products_count: 210
|
||||
|
||||
- &collection-6
|
||||
id: 6
|
||||
title: All products
|
||||
handle: 'all'
|
||||
url: '/collections/all'
|
||||
products:
|
||||
- *product-7
|
||||
- *product-8
|
||||
- *product-9
|
||||
- *product-6
|
||||
- *product-1
|
||||
- *product-2
|
||||
- *product-3
|
||||
- *product-4
|
||||
- *product-5
|
||||
|
||||
|
||||
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
# Pages and Blogs
|
||||
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
pages:
|
||||
- &page-2
|
||||
id: 1
|
||||
title: Contact Us
|
||||
handle: contact
|
||||
url: /pages/contact
|
||||
author: Tobi
|
||||
content:
|
||||
"<p>You can contact us via phone under (555) 567-2222.</p>
|
||||
<p>Our retail store is located at <em>Rue d'Avignon 32, Avignon (Provence)</em>.</p>
|
||||
<p><strong>Opening Hours:</strong><br />Monday through Friday: 9am - 6pm<br />Saturday: 10am - 3pm<br />Sunday: closed</p>"
|
||||
created_at: 2005-04-04 12:00
|
||||
|
||||
- &page-3
|
||||
id: 2
|
||||
title: About Us
|
||||
handle: about-us
|
||||
url: /pages/about-us
|
||||
author: Tobi
|
||||
content:
|
||||
"<p>Our company was founded in 1894 and we are since operating out of Avignon from the beautiful Provence.</p>
|
||||
<p>We offer the highest quality products and are proud to serve our customers to their heart's content.</p>"
|
||||
created_at: 2005-04-04 12:00
|
||||
|
||||
- &page-4
|
||||
id: 3
|
||||
title: Shopping Cart
|
||||
handle: shopping-cart
|
||||
url: /pages/shopping-cart
|
||||
author: Tobi
|
||||
content: "<ul><li>Your order is safe with us. Our checkout uses industry standard security to protect your information.</li><li>Your order will be billed immediately upon checkout.</li><li><b>ALL SALES ARE FINAL:</b> Defective or damaged product will be exchanged</li><li>All orders are processed expediently: usually in under 24 hours.</li></ul>"
|
||||
created_at: 2005-04-04 12:00
|
||||
|
||||
- &page-5
|
||||
id: 4
|
||||
title: Shipping and Handling
|
||||
handle: shipping
|
||||
url: /pages/shipping
|
||||
author: Tobi
|
||||
content: <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
created_at: 2005-04-04 12:00
|
||||
|
||||
- &page-6
|
||||
id: 5
|
||||
title: Frontpage
|
||||
handle: frontpage
|
||||
url: /pages/frontpage
|
||||
author: Tobi
|
||||
content: <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
created_at: 2005-04-04 12:00
|
||||
|
||||
blogs:
|
||||
- id: 1
|
||||
handle: news
|
||||
title: News
|
||||
url: /blogs/news
|
||||
articles:
|
||||
- id: 3
|
||||
title: 'Welcome to the new Foo Shop'
|
||||
author: Daniel
|
||||
content: <p><strong>Welcome to your Shopify store! The jaded Pixel crew is really glad you decided to take Shopify for a spin.</strong></p><p>To help you get you started with Shopify, here are a couple of tips regarding what you see on this page.</p><p>The text you see here is an article. To edit this article, create new articles or create new pages you can go to the <a href="/admin/pages">Blogs & Pages</a> tab of the administration menu.</p><p>The Shopify t-shirt above is a product and selling products is what Shopify is all about. To edit this product, or create new products you can go to the <a href="/admin/products">Products Tab</a> in of the administration menu.</p><p>While you're looking around be sure to check out the <a href="/admin/collections">Collections</a> and <a href="/admin/links">Navigations</a> tabs and soon you will be well on your way to populating your site.</p><p>And of course don't forget to browse the <a href="admin/design/appearance/themes">theme gallery</a> to pick a new look for your shop!</p><p><strong>Shopify is in beta</strong><br />If you would like to make comments or suggestions please visit us in the <a href="http://forums.shopify.com/community">Shopify Forums</a> or drop us an <a href="mailto:[email protected]">email</a>.</p>
|
||||
created_at: 2005-04-04 16:00
|
||||
- id: 4
|
||||
title: 'Breaking News: Restock on all sales products'
|
||||
author: Tobi
|
||||
content: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
created_at: 2005-04-04 12:00
|
||||
articles_count: 2
|
||||
|
||||
- id: 2
|
||||
handle: bigcheese-blog
|
||||
title: Bigcheese blog
|
||||
url: /blogs/bigcheese-blog
|
||||
articles:
|
||||
- id: 1
|
||||
title: 'One thing you probably did not know yet...'
|
||||
author: Justin
|
||||
content: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
created_at: 2005-04-04 16:00
|
||||
comments:
|
||||
-
|
||||
id: 1
|
||||
author: John Smith
|
||||
email: [email protected]
|
||||
content: Wow...great article man.
|
||||
status: published
|
||||
created_at: 2009-01-01 12:00
|
||||
updated_at: 2009-02-01 12:00
|
||||
url: ""
|
||||
-
|
||||
id: 2
|
||||
author: John Jones
|
||||
email: [email protected]
|
||||
content: I really enjoyed this article. And I love your shop! It's awesome. Shopify rocks!
|
||||
status: published
|
||||
created_at: 2009-03-01 12:00
|
||||
updated_at: 2009-02-01 12:00
|
||||
url: "http://somesite.com/"
|
||||
- id: 2
|
||||
title: Fascinating
|
||||
author: Tobi
|
||||
content: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
created_at: 2005-04-06 12:00
|
||||
comments:
|
||||
articles_count: 2
|
||||
comments_enabled?: true
|
||||
comment_post_url: ""
|
||||
comments_count: 2
|
||||
moderated?: true
|
||||
|
||||
- id: 3
|
||||
handle: paginated-blog
|
||||
title: Paginated blog
|
||||
url: /blogs/paginated-blog
|
||||
articles:
|
||||
- id: 6
|
||||
title: 'One thing you probably did not know yet...'
|
||||
author: Justin
|
||||
content: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
created_at: 2005-04-04 16:00
|
||||
|
||||
- id: 7
|
||||
title: Fascinating
|
||||
author: Tobi
|
||||
content: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
created_at: 2005-04-06 12:00
|
||||
articles_count: 200
|
||||
Vendored
BIN
Binary file not shown.
@@ -29,7 +29,6 @@ module Liquid
|
||||
WhitespaceControl = '-'
|
||||
TagStart = /\{\%/
|
||||
TagEnd = /\%\}/
|
||||
TagName = /#|\w+/
|
||||
VariableSignature = /\(?[\w\-\.\[\]]\)?/
|
||||
VariableSegment = /[\w\-]/
|
||||
VariableStart = /\{\{/
|
||||
|
||||
@@ -4,8 +4,8 @@ require 'English'
|
||||
|
||||
module Liquid
|
||||
class BlockBody
|
||||
LiquidTagToken = /\A\s*(#{TagName})\s*(.*?)\z/o
|
||||
FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(#{TagName})(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
|
||||
LiquidTagToken = /\A\s*(\w+)\s*(.*?)\z/o
|
||||
FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(\w+)(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
|
||||
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
|
||||
WhitespaceOrNothing = /\A\s*\z/
|
||||
TAGSTART = "{%"
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'pry-byebug'
|
||||
require 'set'
|
||||
|
||||
module Liquid
|
||||
class Compiler
|
||||
def initialize
|
||||
@ruby = +""
|
||||
@nodes = {}
|
||||
@declared = Set.new(['__l_product'])
|
||||
end
|
||||
|
||||
def <<(line)
|
||||
@ruby << line
|
||||
end
|
||||
|
||||
def var_name(name)
|
||||
"__l_#{name}"
|
||||
end
|
||||
|
||||
def declare(name)
|
||||
@declared << name
|
||||
end
|
||||
|
||||
def declared?(name)
|
||||
@declared.member?(name)
|
||||
end
|
||||
|
||||
def to_proc
|
||||
show_ruby = ENV["SHOW_RUBY"] && ENV["SHOW_RUBY"].to_i || 0
|
||||
STDERR.puts @ruby if show_ruby >= 1
|
||||
res = RubyVM::InstructionSequence.compile(<<~RUBY).eval.call(@nodes)
|
||||
# frozen_string_literal: true
|
||||
->(__nodes) {
|
||||
->(__context, __output, __l_product) {
|
||||
__assigns = __context.scopes.last
|
||||
#{@ruby}
|
||||
__output
|
||||
}
|
||||
}
|
||||
RUBY
|
||||
|
||||
STDERR.puts RubyVM::InstructionSequence.disasm(res) if show_ruby >= 2
|
||||
res
|
||||
end
|
||||
|
||||
def compile_expr(node)
|
||||
if node.respond_to?(:compile_expr)
|
||||
node.compile_expr(self)
|
||||
else
|
||||
case node
|
||||
when Range # returned by RangeLookup when range contains only literals
|
||||
node.inspect
|
||||
when Integer, Float, nil, true, false, String
|
||||
node.inspect
|
||||
else
|
||||
raise ArgumentError, "cannot compile node #{node.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def output(node)
|
||||
compiled = compile_expr(node)
|
||||
self << if compiled.is_a?(String)
|
||||
"__output << #{compiled}\n"
|
||||
else
|
||||
"__output << #{compiled}.to_s\n"
|
||||
end
|
||||
end
|
||||
|
||||
def fallback_evaluate_expr(node)
|
||||
"__nodes[#{node.object_id}].evaluate(__context)"
|
||||
end
|
||||
|
||||
def to_integer_expr(var_name)
|
||||
<<~RUBY.strip
|
||||
(begin
|
||||
if #{var_name}.is_a?(Integer)
|
||||
#{var_name}
|
||||
else
|
||||
begin
|
||||
Integer(#{var_name}.to_s)
|
||||
rescue ::ArgumentError
|
||||
raise Liquid::ArgumentError, "invalid integer"
|
||||
end
|
||||
end
|
||||
end)
|
||||
RUBY
|
||||
end
|
||||
|
||||
def compile(node)
|
||||
if node.instance_of?(String)
|
||||
self << "__output << #{node.inspect}\n"
|
||||
else
|
||||
@nodes[node.object_id] = node
|
||||
if node.respond_to?(:compile)
|
||||
node.compile(self)
|
||||
else
|
||||
line_number = if node.respond_to?(:line_number) && node.line_number.is_a?(Integer)
|
||||
node.line_number
|
||||
else
|
||||
nil
|
||||
end
|
||||
catch_errors(line_number, show_message: !node.blank?) do
|
||||
self << "__nodes[#{node.object_id}].render_to_output_buffer(__context, __output) # #{node.inspect} \n"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def catch_errors(line_number, show_message: true)
|
||||
self << "begin\n"
|
||||
yield
|
||||
self << <<~RUBY
|
||||
rescue => __exc
|
||||
case __exc
|
||||
when Liquid::MemoryError
|
||||
raise
|
||||
when Liquid::UndefinedVariable, Liquid::UndefinedDropMethod, Liquid::UndefinedFilter
|
||||
__context.handle_error(__exc, #{line_number.inspect})
|
||||
else
|
||||
__error_message = __context.handle_error(__exc, #{line_number.inspect})
|
||||
RUBY
|
||||
self << "__output << __error_message\n" if show_message
|
||||
self << "end\nend\n"
|
||||
end
|
||||
end
|
||||
|
||||
class BlockBody
|
||||
def render_to_output_buffer(context, output)
|
||||
raise "Tried to render uncompiled block" unless @compiled
|
||||
@compiled.call(context, output, context.environments[0][:product])
|
||||
end
|
||||
|
||||
def compile_top_level
|
||||
compiler = Compiler.new
|
||||
compile(compiler)
|
||||
@compiled = compiler.to_proc
|
||||
end
|
||||
|
||||
def compile(compiler)
|
||||
nodelist.each { |node| compiler.compile(node) }
|
||||
end
|
||||
end
|
||||
|
||||
class Document
|
||||
def parse(tokenizer, parse_context)
|
||||
while parse_body(tokenizer)
|
||||
end
|
||||
@body.compile_top_level
|
||||
@body.freeze
|
||||
rescue SyntaxError => e
|
||||
e.line_number ||= parse_context.line_number
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
class VariableLookup
|
||||
def compile_expr(compiler)
|
||||
# HACK
|
||||
if @name == "forloop" && @lookups == ["first"]
|
||||
return "forloop_first"
|
||||
end
|
||||
|
||||
var_name = compiler.var_name(@name)
|
||||
root = if compiler.declared?(var_name)
|
||||
var_name
|
||||
else
|
||||
"__scope[#{@name.inspect}]"
|
||||
end
|
||||
|
||||
@lookups.reduce(root) do |prev, lookup|
|
||||
"#{prev}[:#{lookup}]"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Variable
|
||||
FILTERS = {
|
||||
"modulo" => ->(compiler, expr, args, kwargs) {
|
||||
"(#{expr} % #{args[0]})"
|
||||
},
|
||||
"product_img_url" => ->(compiler, expr, args, kwargs) {
|
||||
style = args.fetch(0, 'small')
|
||||
|
||||
rest = case style
|
||||
when 'original'
|
||||
"\"/files/shops/random_number/\#{url}\""
|
||||
when 'grande', 'large', 'medium', 'compact', 'small', 'thumb', 'icon'
|
||||
"\"/files/shops/random_number/products/\#{$1}_#{style}.\#{$2}\""
|
||||
else
|
||||
"raise ArgumentError, 'valid parameters for filter \"size\" are: original, grande, large, medium, compact, small, thumb and icon '"
|
||||
end
|
||||
|
||||
<<~RUBY.strip
|
||||
(begin
|
||||
if #{expr} =~ %r{\\Aproducts/([\\w\\-\\_]+)\\.(\\w{2,4})}
|
||||
#{rest}
|
||||
else
|
||||
raise ArgumentError, 'filter \"size\" can only be called on product images'
|
||||
end
|
||||
end)
|
||||
RUBY
|
||||
},
|
||||
"escape" => ->(compiler, expr, args, kwargs) {
|
||||
"(_t = #{expr}; CGI.escapeHTML(_t) if _t)"
|
||||
},
|
||||
"money" => ->(compiler, expr, args, kwargs) {
|
||||
"(_m = #{expr}; _m.nil? ? '' : \"$ \#{(_m / 100.0).round(2)}\")"
|
||||
},
|
||||
}
|
||||
|
||||
def compile(compiler)
|
||||
if const?
|
||||
compiler.output(name)
|
||||
else
|
||||
compiler.catch_errors(@line_number, show_message: true) do
|
||||
compiler.output(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def const?
|
||||
@filters.empty? && !name.respond_to?(:compile_expr)
|
||||
end
|
||||
|
||||
def compile_expr(compiler)
|
||||
@filters.reduce(compiler.compile_expr(name)) do |expr, (name, args, kwargs)|
|
||||
if FILTERS.key?(name)
|
||||
FILTERS[name].call(compiler, expr, args, kwargs)
|
||||
else
|
||||
expr
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Echo
|
||||
def compile(compiler)
|
||||
compiler.compile(variable)
|
||||
end
|
||||
end
|
||||
|
||||
class For
|
||||
def compile(compiler)
|
||||
compiler << "collection = #{compiler.compile_expr(@collection_name)}\n"
|
||||
|
||||
unless @from.nil?
|
||||
compiler << <<~RUBY
|
||||
from_value = #{compiler.compile_expr(@from)}
|
||||
from = if from_value.nil?
|
||||
0
|
||||
else
|
||||
#{compiler.to_integer_expr(:from_value)}
|
||||
end
|
||||
|
||||
limit_value = #{compiler.compile_expr(@limit)}
|
||||
to = if limit_value.nil?
|
||||
nil
|
||||
else
|
||||
#{compiler.to_integer_expr(:limit_value)} + from
|
||||
end
|
||||
|
||||
collection = #{compiler.slice_collection_expr(:collection, :from, :to)}
|
||||
#{@reversed ? "segment.reverse!" : "" }
|
||||
RUBY
|
||||
end
|
||||
|
||||
item_var = compiler.var_name(variable_name)
|
||||
compiler << <<~RUBY
|
||||
forloop_first = true
|
||||
for #{item_var} in collection
|
||||
RUBY
|
||||
compiler.declare(item_var)
|
||||
compiler.compile(@for_block)
|
||||
compiler << <<~RUBY
|
||||
forloop_first = false
|
||||
end
|
||||
RUBY
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
class If
|
||||
def compile(compiler)
|
||||
compiler << "if #{compiler.compile_expr(blocks[0])}\n"
|
||||
compiler.compile(blocks[0].attachment)
|
||||
blocks.drop(1).each do |block|
|
||||
if block.else?
|
||||
compiler << "else\n"
|
||||
else
|
||||
compiler << "elsif #{block.compile_expr(compiler)}\n"
|
||||
end
|
||||
compiler.compile(block.attachment)
|
||||
end
|
||||
compiler << "end\n"
|
||||
end
|
||||
end
|
||||
|
||||
class Comment
|
||||
def compile(_compiler); end
|
||||
end
|
||||
|
||||
class Condition
|
||||
def compile_expr(compiler)
|
||||
if operator
|
||||
left_expr = compiler.compile_expr(left)
|
||||
right_expr = compiler.compile_expr(right)
|
||||
if child_relation
|
||||
child_expr = compiler.compile_expr(child_condition)
|
||||
expr = "((#{left_expr} #{operator} #{right_expr}) #{child_relation} #{child_expr})"
|
||||
else
|
||||
expr = "(#{left_expr} #{operator} #{right_expr})"
|
||||
end
|
||||
expr
|
||||
else
|
||||
left_expr = compiler.compile_expr(left)
|
||||
expr = "#{left_expr}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Assign
|
||||
def compile(compiler)
|
||||
compiler.declare(compiler.var_name(@to))
|
||||
compiler << "#{compiler.var_name(@to)} = #{compiler.compile_expr(@from)}\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -28,7 +28,7 @@ module Liquid
|
||||
|
||||
@static_environments = [static_environments].flat_map(&:freeze).freeze
|
||||
@scopes = [(outer_scope || {})]
|
||||
@registers = registers.is_a?(StaticRegisters) ? registers : StaticRegisters.new(registers)
|
||||
@registers = registers
|
||||
@errors = []
|
||||
@partial = false
|
||||
@strict_variables = false
|
||||
@@ -39,10 +39,6 @@ module Liquid
|
||||
@global_filter = nil
|
||||
@disabled_tags = {}
|
||||
|
||||
@registers.static[:cached_partials] ||= {}
|
||||
@registers.static[:file_system] ||= Liquid::Template.file_system
|
||||
@registers.static[:template_factory] ||= Liquid::TemplateFactory.new
|
||||
|
||||
self.exception_renderer = Template.default_exception_renderer
|
||||
if rethrow_errors
|
||||
self.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
|
||||
|
||||
@@ -13,16 +13,15 @@
|
||||
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]"
|
||||
inline_comment_invalid: "Syntax error in tag '#' - Each line of comments must be prefixed by the '#' character"
|
||||
unknown_tag: "Unknown tag '%{tag}'"
|
||||
invalid_delimiter: "'%{tag}' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
|
||||
render: "Syntax error in tag 'render' - Template name must be a quoted string"
|
||||
table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
|
||||
tag_never_closed: "'%{block_name}' tag was never closed"
|
||||
tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
|
||||
unexpected_else: "%{block_name} tag does not expect 'else' tag"
|
||||
unexpected_outer_tag: "Unexpected outer '%{tag}' tag"
|
||||
unknown_tag: "Unknown 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"
|
||||
table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
|
||||
render: "Syntax error in tag 'render' - Template name must be a quoted string"
|
||||
argument:
|
||||
include: "Argument error in tag 'include' - Illegal template name"
|
||||
disabled:
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
module Liquid
|
||||
class PartialCache
|
||||
def self.load(template_name, context:, parse_context:)
|
||||
cached_partials = context.registers[:cached_partials]
|
||||
cached_partials = (context.registers[:cached_partials] ||= {})
|
||||
cached = cached_partials[template_name]
|
||||
return cached if cached
|
||||
|
||||
file_system = context.registers[:file_system]
|
||||
file_system = (context.registers[:file_system] ||= Liquid::Template.file_system)
|
||||
source = file_system.read_template_file(template_name)
|
||||
|
||||
parse_context.partial = true
|
||||
|
||||
template_factory = context.registers[:template_factory]
|
||||
template_factory = (context.registers[:template_factory] ||= Liquid::TemplateFactory.new)
|
||||
template = template_factory.for(template_name)
|
||||
|
||||
partial = template.parse(source, parse_context)
|
||||
|
||||
@@ -25,6 +25,10 @@ module Liquid
|
||||
start_int..end_int
|
||||
end
|
||||
|
||||
def compile_expr(compiler)
|
||||
compiler.fallback_evaluate_expr(self)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def to_integer(input)
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
class InlineComment < Tag
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
|
||||
# Semantically, a comment should only ignore everything after it on the line.
|
||||
# Currently, this implementation doesn't support mixing a comment with another tag
|
||||
# but we need to reserve future support for this and prevent the introduction
|
||||
# of inline comments from being backward incompatible change.
|
||||
#
|
||||
# As such, we're forcing users to put a # symbol on every line otherwise this
|
||||
# tag will throw an error.
|
||||
if markup.match?(/\n\s*[^#\s]/)
|
||||
raise SyntaxError, options[:locale].t("errors.syntax.inline_comment_invalid")
|
||||
end
|
||||
end
|
||||
|
||||
def render_to_output_buffer(_context, output)
|
||||
output
|
||||
end
|
||||
|
||||
def blank?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
Template.register_tag('#', InlineComment)
|
||||
end
|
||||
@@ -34,9 +34,9 @@ module Liquid
|
||||
end
|
||||
|
||||
def render_tag(context, output)
|
||||
# The expression should be a String literal, which parses to a String object
|
||||
template_name = @template_name_expr
|
||||
raise ::ArgumentError unless template_name.is_a?(String)
|
||||
# Though we evaluate this here we will only ever parse it as a string literal.
|
||||
template_name = context.evaluate(@template_name_expr)
|
||||
raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name
|
||||
|
||||
partial = PartialCache.load(
|
||||
template_name,
|
||||
|
||||
@@ -167,14 +167,15 @@ module Liquid
|
||||
|
||||
output = nil
|
||||
|
||||
context_register = context.registers.is_a?(StaticRegisters) ? context.registers.static : context.registers
|
||||
|
||||
case args.last
|
||||
when Hash
|
||||
options = args.pop
|
||||
output = options[:output] if options[:output]
|
||||
static_registers = context.registers.static
|
||||
|
||||
options[:registers]&.each do |key, register|
|
||||
static_registers[key] = register
|
||||
context_register[key] = register
|
||||
end
|
||||
|
||||
apply_options_to_context(context, options)
|
||||
|
||||
@@ -93,6 +93,10 @@ module Liquid
|
||||
context.apply_global_filter(obj)
|
||||
end
|
||||
|
||||
def compile
|
||||
"__output << #{@name}\n"
|
||||
end
|
||||
|
||||
def render_to_output_buffer(context, output)
|
||||
obj = render(context)
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
VERSION = "5.4.0.alpha"
|
||||
VERSION = "5.3.0"
|
||||
end
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
|
||||
s.license = "MIT"
|
||||
# s.description = "A secure, non-evaling end user template engine with aesthetic markup."
|
||||
|
||||
s.required_ruby_version = ">= 2.7.0"
|
||||
s.required_ruby_version = ">= 2.5.0"
|
||||
s.required_rubygems_version = ">= 1.3.7"
|
||||
|
||||
s.metadata['allowed_push_host'] = 'https://rubygems.org'
|
||||
|
||||
Executable
+224
@@ -0,0 +1,224 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
unless ENV.key?("BUNDLE_BIN_PATH")
|
||||
exec("bundle", "exec", "ruby", __FILE__, *ARGV)
|
||||
end
|
||||
|
||||
require "pry"
|
||||
require "liquid"
|
||||
require "unicode_plot"
|
||||
require "optparse"
|
||||
require "open3"
|
||||
require "csv"
|
||||
require "io/console"
|
||||
|
||||
TERM_ROWS, TERM_COLS = IO.console.winsize
|
||||
|
||||
def record
|
||||
if ARGV.count != 2
|
||||
STDERR.puts "Usage: benchmark.rb record [output_path]"
|
||||
exit(1)
|
||||
end
|
||||
output_path = ARGV[1]
|
||||
out, status = Open3.capture2("ruby", "#{__dir__}/benchmark_child.rb")
|
||||
File.write(output_path, out)
|
||||
end
|
||||
|
||||
def calc_stats(nums)
|
||||
mean = nums.reduce(:+) / nums.length
|
||||
variance = nums.map { |n| (n - mean).pow(2) }.reduce(:+) / nums.length
|
||||
stddev = Math.sqrt(variance)
|
||||
{
|
||||
mean: mean,
|
||||
variance: variance,
|
||||
stddev: stddev,
|
||||
normalized: normalize_outliers(mean, stddev, nums),
|
||||
raw: nums
|
||||
}
|
||||
end
|
||||
|
||||
def normalize_outliers(mean, stddev, nums)
|
||||
cutoff = stddev * 3
|
||||
nums.map do |n|
|
||||
if (n - mean).abs < cutoff
|
||||
n
|
||||
else
|
||||
mean
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def show
|
||||
if ARGV.count < 2
|
||||
STDERR.puts "Usage: benchmark.rb show [path1] [path2]? ..."
|
||||
exit(1)
|
||||
end
|
||||
|
||||
recordings = ARGV.drop(1).to_h do |path|
|
||||
[File.basename(path), CSV.parse(
|
||||
File.open(path),
|
||||
col_sep: "\t",
|
||||
headers: true,
|
||||
converters: :integer
|
||||
)]
|
||||
end
|
||||
|
||||
runs = (1..1000).to_a
|
||||
recordings.values.first.headers.each do |benchmark|
|
||||
colors = [:green, :blue, :red]
|
||||
|
||||
10.times { puts }
|
||||
title = "Benchmark: #{benchmark} (times in µs)"
|
||||
print " " * (TERM_COLS / 2 - title.length)
|
||||
puts title
|
||||
puts
|
||||
|
||||
all_stats = recordings.transform_values do |csv|
|
||||
stats = calc_stats(csv.map { |row| row[benchmark] })
|
||||
stats[:color] = colors.shift
|
||||
stats
|
||||
end
|
||||
|
||||
line_plots = []
|
||||
distributions = []
|
||||
shared_line_plot = nil
|
||||
|
||||
max_mean_stats = all_stats.values.max_by { |stats| stats[:mean] }
|
||||
max_y = (max_mean_stats[:mean] + max_mean_stats[:stddev] * 3).to_i
|
||||
|
||||
all_stats.each do |name, stats|
|
||||
if shared_line_plot == nil
|
||||
shared_line_plot = UnicodePlot.lineplot(
|
||||
runs,
|
||||
stats[:normalized],
|
||||
name: name,
|
||||
width: TERM_COLS - 25,
|
||||
ylim: [0, max_y],
|
||||
color: stats[:color]
|
||||
)
|
||||
else
|
||||
UnicodePlot.lineplot!(shared_line_plot, stats[:normalized], name: name, color: stats[:color])
|
||||
end
|
||||
|
||||
line_plots << render_to_s(UnicodePlot.lineplot(
|
||||
runs,
|
||||
stats[:normalized],
|
||||
name: name,
|
||||
color: stats[:color],
|
||||
width: 40
|
||||
))
|
||||
|
||||
distributions << render_to_s(UnicodePlot.histogram(
|
||||
stats[:normalized],
|
||||
title: name,
|
||||
color: stats[:color]
|
||||
))
|
||||
end
|
||||
|
||||
shared_line_plot.render
|
||||
print_columns(line_plots)
|
||||
print_columns(distributions)
|
||||
|
||||
all_times = all_stats.transform_values { |stats| stats[:normalized] }
|
||||
UnicodePlot.boxplot(data: all_times, title: "Comparison", width: TERM_COLS - 25).render
|
||||
end
|
||||
end
|
||||
|
||||
def render_to_s(plot)
|
||||
io = StringIO.new
|
||||
plot.render(io, color: true)
|
||||
io.string
|
||||
end
|
||||
|
||||
def visual_length(line)
|
||||
line.gsub(/\e\[(\d+)m/, '').length
|
||||
end
|
||||
|
||||
def print_columns(cols)
|
||||
col_lines = cols.map { |col| col.split("\n") }
|
||||
col_width = col_lines.map do |lines|
|
||||
lines.map { |line| visual_length(line) }.max
|
||||
end.max
|
||||
col_height = col_lines.map { |lines| lines.length }.max
|
||||
|
||||
(0...col_height).each do |i|
|
||||
col_lines.each do |lines|
|
||||
line = lines[i] || ""
|
||||
vis_length = visual_length(line)
|
||||
print line
|
||||
print(" " * (col_width - vis_length))
|
||||
end
|
||||
puts
|
||||
end
|
||||
end
|
||||
|
||||
ITERS = 1
|
||||
Benchmarks = Class.new do
|
||||
def define(_name, benchmark)
|
||||
@benchmark = benchmark
|
||||
end
|
||||
|
||||
def run
|
||||
start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
||||
@benchmark.compile
|
||||
parsed = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
||||
|
||||
# warmup
|
||||
ITERS.times { @benchmark.render}
|
||||
warm = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
||||
|
||||
res = nil
|
||||
ITERS.times { res = @benchmark.render }
|
||||
ran = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
||||
|
||||
if res.is_a?(String)
|
||||
puts res
|
||||
puts "Output digest: #{Digest::SHA2.hexdigest(res)}"
|
||||
end
|
||||
|
||||
if ENV['SHOW_RUBY'] == '1'
|
||||
STDERR.puts "note: SHOW_RUBY prevents gathering parse metrics"
|
||||
STDERR.puts "render: #{(ran - warm) / ITERS}µs"
|
||||
else
|
||||
STDERR.puts "parse: #{parsed - start}µs"
|
||||
STDERR.puts "render: #{(ran - warm) / ITERS}µs"
|
||||
STDERR.puts "total: #{(ran - warm) / ITERS + (parsed - start)}µs"
|
||||
end
|
||||
end
|
||||
end.new
|
||||
|
||||
def execute
|
||||
require 'digest'
|
||||
|
||||
if ARGV.count != 2
|
||||
STDERR.puts "Usage: benchmark.rb record [output_path]"
|
||||
exit(1)
|
||||
end
|
||||
|
||||
case ENV['ENGINE']
|
||||
when 'LIQUID_COMPILE'
|
||||
require_relative '../lib/liquid/compile'
|
||||
when 'LIQUID_C'
|
||||
require 'liquid/c'
|
||||
when 'LIQUID_RUBY'
|
||||
else
|
||||
raise "Invalid ENGINE: #{ENV['ENGINE'].inspect}, expected ENGINE=(LIQUID_RUBY|LIQUID_COMPILE)"
|
||||
end
|
||||
|
||||
require_relative "./benchmarks/#{ARGV[1]}.rb"
|
||||
Benchmarks.run
|
||||
end
|
||||
|
||||
case ARGV.first
|
||||
when "record", "r"
|
||||
record
|
||||
when "show", "s"
|
||||
show
|
||||
when "execute", "x"
|
||||
execute
|
||||
else
|
||||
puts "Invalid command. Expected benchmark [record|show|execute] ..."
|
||||
exit 1
|
||||
end
|
||||
@@ -1,20 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'benchmark/ips'
|
||||
require_relative 'theme_runner'
|
||||
|
||||
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
|
||||
profiler = ThemeRunner.new
|
||||
|
||||
Benchmark.ips do |x|
|
||||
x.time = 10
|
||||
x.warmup = 5
|
||||
|
||||
puts
|
||||
puts "Running benchmark for #{x.time} seconds (with #{x.warmup} seconds warmup)."
|
||||
puts
|
||||
|
||||
x.report("parse:") { profiler.compile }
|
||||
x.report("render:") { profiler.render }
|
||||
x.report("parse & render:") { profiler.run }
|
||||
end
|
||||
@@ -0,0 +1,62 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'liquid'
|
||||
|
||||
Liquid::Template.error_mode = :strict
|
||||
|
||||
case ENV['ENGINE']
|
||||
when 'LIQUID_COMPILE'
|
||||
require_relative '../lib/liquid/compile'
|
||||
when 'LIQUID_C'
|
||||
require 'liquid/c'
|
||||
when 'LIQUID_RUBY'
|
||||
else
|
||||
raise "Invalid engine: #{ENV['ENGINE'].inspect}"
|
||||
end
|
||||
|
||||
OPTIONS = {
|
||||
render_iters: 1000
|
||||
}
|
||||
|
||||
def get_time_us
|
||||
Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
||||
end
|
||||
|
||||
Benchmarks = Class.new do
|
||||
def initialize
|
||||
@by_name = {}
|
||||
end
|
||||
|
||||
def define(name, benchmark)
|
||||
@by_name[name] = benchmark
|
||||
end
|
||||
|
||||
def run
|
||||
times = {}
|
||||
@by_name.each do |name, benchmark|
|
||||
benchmark.compile
|
||||
|
||||
times[name] = OPTIONS[:render_iters].times.map do
|
||||
before = get_time_us
|
||||
benchmark.render
|
||||
get_time_us - before
|
||||
end
|
||||
end
|
||||
|
||||
puts times.keys.join("\t")
|
||||
cols = times.values
|
||||
OPTIONS[:render_iters].times do |i|
|
||||
cols.each do |values|
|
||||
print values[i]
|
||||
print "\t" unless values == cols.last
|
||||
end
|
||||
puts
|
||||
end
|
||||
end
|
||||
end.new
|
||||
|
||||
Dir[__dir__ + "/benchmarks/*.rb", base: __dir__].each do |path|
|
||||
require_relative path
|
||||
end
|
||||
|
||||
Benchmarks.run
|
||||
@@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
Benchmarks.define('fizzbuzz_10000', Class.new do
|
||||
TEMPLATE = <<~LIQUID
|
||||
{% for i in (1..#{ENV['COUNT'].to_i}) %}
|
||||
{% liquid
|
||||
assign rem_3 = i | modulo: 3
|
||||
assign rem_5 = i | modulo: 5
|
||||
if rem_3 == 0 and rem_5 == 0
|
||||
echo "Fizzbuzz"
|
||||
elsif rem_3 == 0
|
||||
echo "Fizz"
|
||||
elsif rem_5 == 0
|
||||
echo "Buzz"
|
||||
else
|
||||
echo i
|
||||
endif
|
||||
%}{% endfor %}
|
||||
LIQUID
|
||||
|
||||
def compile
|
||||
@parsed = Liquid::Template.parse(TEMPLATE)
|
||||
end
|
||||
|
||||
def render
|
||||
@parsed.render
|
||||
end
|
||||
end.new)
|
||||
@@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
Benchmarks.define('simple_loop', Class.new do
|
||||
TEMPLATE = "{% for i in (1..1000) %}{{ i }}{% endfor %}"
|
||||
|
||||
def compile
|
||||
@parsed = Liquid::Template.parse(TEMPLATE)
|
||||
end
|
||||
|
||||
def render
|
||||
@parsed.render
|
||||
end
|
||||
end.new)
|
||||
@@ -0,0 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../theme_runner'
|
||||
|
||||
#Benchmarks.define('theme_runner', ThemeRunner.new)
|
||||
@@ -3,6 +3,16 @@
|
||||
require 'stackprof'
|
||||
require_relative 'theme_runner'
|
||||
|
||||
if ENV['LIQUID_C'] == '1'
|
||||
puts "-- LIQUID C"
|
||||
require 'liquid/c'
|
||||
end
|
||||
|
||||
if ENV['LIQUID_COMPILE'] == '1'
|
||||
puts "-- COMPILED"
|
||||
require 'liquid/compile'
|
||||
end
|
||||
|
||||
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
|
||||
profiler = ThemeRunner.new
|
||||
profiler.run
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
$LOAD_PATH.unshift(__dir__ + '/../../lib')
|
||||
require_relative '../../lib/liquid'
|
||||
|
||||
require_relative 'comment_form'
|
||||
require_relative 'paginate'
|
||||
require_relative 'json_filter'
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# This profiler run simulates Shopify.
|
||||
# We are looking in the tests directory for liquid files and render them within the designated layout file.
|
||||
# We will also export a substantial database to liquid which the templates can render values of.
|
||||
# All this is to make the benchmark as non synthetic as possible. All templates and tests are lifted from
|
||||
# All this is to make the benchmark as non syntetic as possible. All templates and tests are lifted from
|
||||
# direct real-world usage and the profiler measures code that looks very similar to the way it looks in
|
||||
# Shopify which is likely the biggest user of liquid in the world which something to the tune of several
|
||||
# million Template#render calls a day.
|
||||
|
||||
@@ -618,20 +618,6 @@ class ContextTest < Minitest::Test
|
||||
end
|
||||
end
|
||||
|
||||
def test_context_always_uses_static_registers
|
||||
registers = {
|
||||
my_register: :my_value,
|
||||
}
|
||||
c = Context.new({}, {}, registers)
|
||||
assert_instance_of(StaticRegisters, c.registers)
|
||||
assert_equal(:my_value, c.registers[:my_register])
|
||||
|
||||
r = StaticRegisters.new(registers)
|
||||
c = Context.new({}, {}, r)
|
||||
assert_instance_of(StaticRegisters, c.registers)
|
||||
assert_equal(:my_value, c.registers[:my_register])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assert_no_object_allocations
|
||||
|
||||
@@ -32,7 +32,7 @@ class TestDrop < Liquid::Drop
|
||||
attr_reader :value
|
||||
|
||||
def registers
|
||||
{ @value => @context.registers[@value] }
|
||||
@context.registers
|
||||
end
|
||||
end
|
||||
|
||||
@@ -440,7 +440,7 @@ class StandardFiltersTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_map_calls_context=
|
||||
model = TestModel.new(value: :test)
|
||||
model = TestModel.new(value: "test")
|
||||
|
||||
template = Template.parse('{{ foo | map: "registers" }}')
|
||||
template.registers[:test] = 1234
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class InlineCommentTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_inline_comment_returns_nothing
|
||||
assert_template_result('', '{%- # this is an inline comment -%}')
|
||||
assert_template_result('', '{%-# this is an inline comment -%}')
|
||||
assert_template_result('', '{% # this is an inline comment %}')
|
||||
assert_template_result('', '{%# this is an inline comment %}')
|
||||
end
|
||||
|
||||
def test_inline_comment_does_not_require_a_space_after_the_pound_sign
|
||||
assert_template_result('', '{%#this is an inline comment%}')
|
||||
end
|
||||
|
||||
def test_liquid_inline_comment_returns_nothing
|
||||
assert_template_result('Hey there, how are you doing today?', <<~LIQUID)
|
||||
{%- liquid
|
||||
# This is how you'd write a block comment in a liquid tag.
|
||||
# It looks a lot like what you'd have in ruby.
|
||||
|
||||
# You can use it as inline documentation in your
|
||||
# liquid blocks to explain why you're doing something.
|
||||
echo "Hey there, "
|
||||
|
||||
# It won't affect the output.
|
||||
echo "how are you doing today?"
|
||||
-%}
|
||||
LIQUID
|
||||
end
|
||||
|
||||
def test_inline_comment_can_be_written_on_multiple_lines
|
||||
assert_template_result('', <<~LIQUID)
|
||||
{%-
|
||||
# That kind of block comment is also allowed.
|
||||
# It would only be a stylistic difference.
|
||||
|
||||
# Much like JavaScript's /* */ comments and their
|
||||
# leading * on new lines.
|
||||
-%}
|
||||
LIQUID
|
||||
end
|
||||
|
||||
def test_inline_comment_multiple_pound_signs
|
||||
assert_template_result('', <<~LIQUID)
|
||||
{%- liquid
|
||||
######################################
|
||||
# We support comments like this too. #
|
||||
######################################
|
||||
-%}
|
||||
LIQUID
|
||||
end
|
||||
|
||||
def test_inline_comments_require_the_pound_sign_on_every_new_line
|
||||
assert_match_syntax_error("Each line of comments must be prefixed by the '#' character", <<~LIQUID)
|
||||
{%-
|
||||
# some comment
|
||||
echo 'hello world'
|
||||
-%}
|
||||
LIQUID
|
||||
end
|
||||
|
||||
def test_inline_comment_does_not_support_nested_tags
|
||||
assert_template_result(' -%}', "{%- # {% echo 'hello world' %} -%}")
|
||||
end
|
||||
end
|
||||
+4
-1
@@ -15,7 +15,10 @@ if (env_mode = ENV['LIQUID_PARSER_MODE'])
|
||||
end
|
||||
Liquid::Template.error_mode = mode
|
||||
|
||||
if ENV['LIQUID_C'] == '1'
|
||||
if ENV['LIQUID_COMPILE'] == '1'
|
||||
puts "-- COMPILED"
|
||||
require 'liquid/compile'
|
||||
elsif ENV['LIQUID_C'] == '1'
|
||||
puts "-- LIQUID C"
|
||||
require 'liquid/c'
|
||||
end
|
||||
|
||||
@@ -125,35 +125,4 @@ class PartialCacheUnitTest < Minitest::Test
|
||||
assert_equal('my partial body', partial.render)
|
||||
assert_equal(1, template_factory.count)
|
||||
end
|
||||
|
||||
def test_cache_state_is_shared_for_subcontexts
|
||||
parse_context = Liquid::ParseContext.new
|
||||
shared_file_system = StubFileSystem.new(
|
||||
'my_partial' => 'my shared value'
|
||||
)
|
||||
context = Liquid::Context.build(
|
||||
registers: Liquid::StaticRegisters.new(
|
||||
file_system: shared_file_system,
|
||||
)
|
||||
)
|
||||
subcontext = context.new_isolated_subcontext
|
||||
|
||||
assert_equal(subcontext.registers[:cached_partials].object_id, context.registers[:cached_partials].object_id)
|
||||
|
||||
2.times do
|
||||
Liquid::PartialCache.load(
|
||||
'my_partial',
|
||||
context: context,
|
||||
parse_context: parse_context
|
||||
)
|
||||
|
||||
Liquid::PartialCache.load(
|
||||
'my_partial',
|
||||
context: subcontext,
|
||||
parse_context: parse_context
|
||||
)
|
||||
end
|
||||
|
||||
assert_equal(1, shared_file_system.file_read_count)
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user