Compare commits

...
Author SHA1 Message Date
Tobi Lutke 544b512e7b more profile cleanups 2025-07-29 20:15:22 -04:00
Tobi Lutke 14e70cf5ba memory-profiler update 2025-07-29 18:55:13 -04:00
2420853020 Apply suggestions from code review
Co-authored-by: Copilot <[email protected]>
2025-07-29 18:53:00 -04:00
Tobi Lutke baee70ae1f also fix profiler 2025-07-29 18:07:31 -04:00
Tobi Lutke 41d0d6d51a the performance runner wasn't actually working before 2025-07-29 17:39:38 -04:00
Tobi Lutke 1b1a40c606 make theme_runner actually useful outside of the performance benchmarks 2025-07-29 17:32:31 -04:00
iainandGitHub 9bb7fbf123 Merge pull request #1968 from Shopify/shopify-dev-docs-formatting
Inline some information that previously lived at category level
2025-07-03 10:59:58 -04:00
Iain Campbell 8555fd8a20 inline warning previuosly at category level 2025-06-27 16:57:00 -04:00
James MengandGitHub 9bd408f5d0 Merge pull request #1965 from Shopify/jm/bump_liquid
Bump Liquid to 5.8.7
2025-06-09 12:42:27 -07:00
James Meng 79b831d96c Bump Liquid to 5.8.7 2025-06-09 12:38:04 -07:00
James MengandGitHub aebd75e5e8 Merge pull request #1954 from Shopify/jm/doc_body
Expose tag body in the Doc tag
2025-06-09 12:25:19 -07:00
James Meng 7f2f8a226b Add tests for new public methods 2025-06-09 12:24:59 -07:00
James Meng 7b2b25fda1 Fix Doc tag blank? method to check body content
Previously the blank? method always returned true. Now it properly checks
if the body is empty, making the tag behavior consistent with other tags.

Also updated test to use whitespace control for cleaner assertions.
2025-06-09 11:36:18 -07:00
James Meng 8548b96a97 Remove body attr_reader and initiliaze @body instance variable in parse method 2025-06-06 13:08:06 -07:00
upgrade-umpire[bot]andGitHub fc96e66e14 Merge pull request #1953 from Shopify/actions-commit
Applying Merge
2025-06-05 20:15:14 +00:00
James Meng 79a771d724 Add test for doc tag capturing token before enddoc 2025-06-04 19:30:47 -07:00
James Meng 65b1dedac5 Expose tag body in the Doc tag 2025-06-04 12:04:41 -07:00
Ian Ker-SeymerandGitHub f375d7b3aa Add unit test for custom Liquid tag registration (#1960)
Adds EnvironmentTest to verify custom tag registration and rendering.
2025-05-22 11:38:10 -04:00
Brian Chen 6b3f6c6fb4 update github actions to commits 2025-04-29 14:04:22 -04:00
17 changed files with 328 additions and 136 deletions
+6
View File
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
+4 -4
View File
@@ -31,8 +31,8 @@ jobs:
- { ruby: ruby-head, allowed-failure: false, rubyopt: "--yjit" }
name: Test Ruby ${{ matrix.entry.ruby }}
steps:
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: ruby/setup-ruby@dffc446db9ba5a0c4446edb5bca1c5c473a806c5 # v1.235.0
with:
ruby-version: ${{ matrix.entry.ruby }}
bundler-cache: true
@@ -45,8 +45,8 @@ jobs:
memory_profile:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: ruby/setup-ruby@dffc446db9ba5a0c4446edb5bca1c5c473a806c5 # v1.235.0
with:
bundler-cache: true
- run: bundle exec rake memory_profile:run
+2 -1
View File
@@ -1,6 +1,7 @@
# Liquid Change Log
## 5.8.1 (unreleased)
## 5.8.7
* Expose body content in the `Doc` tag [James Meng]
## 5.8.1
+4
View File
@@ -9,6 +9,10 @@ module Liquid
# Creates a new variable.
# @liquid_description
# You can create variables of any [basic type](/docs/api/liquid/basics#types), [object](/docs/api/liquid/objects), or object property.
#
# > Caution:
# > Predefined Liquid objects can be overridden by variables with the same name.
# > To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.
# @liquid_syntax
# {% assign variable_name = value %}
# @liquid_syntax_keyword variable_name The name of the variable being created.
+4
View File
@@ -9,6 +9,10 @@ module Liquid
# Creates a new variable with a string value.
# @liquid_description
# You can create complex strings with Liquid logic and variables.
#
# > Caution:
# > Predefined Liquid objects can be overridden by variables with the same name.
# > To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.
# @liquid_syntax
# {% capture variable %}
# value
+4
View File
@@ -7,6 +7,10 @@ module Liquid
# @liquid_name decrement
# @liquid_summary
# Creates a new variable, with a default value of -1, that's decreased by 1 with each subsequent call.
#
# > Caution:
# > Predefined Liquid objects can be overridden by variables with the same name.
# > To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.
# @liquid_description
# Variables that are declared with `decrement` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates),
# or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
+6 -2
View File
@@ -36,6 +36,8 @@ module Liquid
end
def parse(tokens)
@body = +""
while (token = tokens.shift)
tag_name = token =~ BlockBody::FullTokenPossiblyInvalid && Regexp.last_match(2)
@@ -43,8 +45,10 @@ module Liquid
if tag_name == block_delimiter
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
@body << Regexp.last_match(1) if Regexp.last_match(1) != ""
return
end
@body << token unless token.empty?
end
raise_tag_never_closed(block_name)
@@ -55,11 +59,11 @@ module Liquid
end
def blank?
true
@body.empty?
end
def nodelist
[]
[@body]
end
private
+4
View File
@@ -7,6 +7,10 @@ module Liquid
# @liquid_name increment
# @liquid_summary
# Creates a new variable, with a default value of 0, that's increased by 1 with each subsequent call.
#
# > Caution:
# > Predefined Liquid objects can be overridden by variables with the same name.
# > To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.
# @liquid_description
# Variables that are declared with `increment` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates),
# or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
+1 -1
View File
@@ -2,5 +2,5 @@
# frozen_string_literal: true
module Liquid
VERSION = "5.8.6"
VERSION = "5.8.7"
end
+11 -5
View File
@@ -3,7 +3,13 @@
require 'benchmark/ips'
require_relative 'theme_runner'
RubyVM::YJIT.enable if defined?(RubyVM::YJIT)
if defined?(RubyVM::YJIT)
RubyVM::YJIT.enable
puts "* YJIT enabled"
else
puts "* YJIT not enabled"
end
Liquid::Environment.default.error_mode = ARGV.first.to_sym if ARGV.first
profiler = ThemeRunner.new
@@ -18,8 +24,8 @@ Benchmark.ips do |x|
phase = ENV["PHASE"] || "all"
x.report("tokenize:") { profiler.tokenize } if phase == "all" || phase == "tokenize"
x.report("parse:") { profiler.compile } if phase == "all" || phase == "parse"
x.report("render:") { profiler.render } if phase == "all" || phase == "render"
x.report("parse & render:") { profiler.run } if phase == "all" || phase == "run"
x.report("tokenize:") { profiler.tokenize_all } if phase == "all" || phase == "tokenize"
x.report("parse:") { profiler.compile_all } if phase == "all" || phase == "parse"
x.report("render:") { profiler.render_all } if phase == "all" || phase == "render"
x.report("parse & render:") { profiler.run_all } if phase == "all" || phase == "run"
end
+2 -2
View File
@@ -57,7 +57,7 @@ Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
runner = ThemeRunner.new
Profiler.run do |x|
x.profile('parse') { runner.compile }
x.profile('render') { runner.render }
x.profile('parse') { runner.compile_all }
x.profile('render') { runner.render_all }
x.tabulate
end
+26 -10
View File
@@ -1,26 +1,42 @@
# frozen_string_literal: true
require 'stackprof'
require 'fileutils'
require_relative 'theme_runner'
output_dir = ENV['OUTPUT_DIR'] || "/tmp/liquid-performance"
FileUtils.mkdir_p(output_dir)
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
profiler = ThemeRunner.new
profiler.run
profiler.run_all # warmup
[:cpu, :object].each do |profile_type|
puts "Profiling in #{profile_type} mode..."
results = StackProf.run(mode: profile_type) do
[:cpu, :object].each do |mode|
puts
puts "Profiling in #{mode} mode..."
puts "writing to #{output_dir}/#{mode}.profile:"
puts
StackProf.run(mode: mode, raw: true, out: "#{output_dir}/#{mode}.profile") do
200.times do
profiler.run
profiler.run_all
end
end
if profile_type == :cpu && (graph_filename = ENV['GRAPH_FILENAME'])
File.open(graph_filename, 'w') do |f|
StackProf::Report.new(results).print_graphviz(nil, f)
result = StackProf.run(mode: mode) do
100.times do
profiler.run_all
end
end
StackProf::Report.new(results).print_text(false, 20)
File.write(ENV['FILENAME'] + "." + profile_type.to_s, Marshal.dump(results)) if ENV['FILENAME']
StackProf::Report.new(result).print_text(false, 30)
end
puts
puts "files in #{output_dir}:"
Dir.glob("#{output_dir}/*").each do |file|
puts " #{file}"
end
puts "Recommended:"
puts " stackprof --d3-flamegraph #{output_dir}/cpu.profile > #{output_dir}/flame.html"
puts " stackprof --method #{output_dir}/cpu.profile"
puts " etc"
+21 -4
View File
@@ -32,19 +32,36 @@ module Database
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'] = {
# Some standard direct accessors so that the specialized templates
# render correctly
db['collection'] = db['collections'].values.first
db['collection']['tags'] = db['collection']['products'].map { |product| product['tags'] }.flatten.uniq.sort
db['tags'] = db['collection']['tags'][0..1]
db['all_tags'] = db['products'].values.map { |product| product['tags'] }.flatten.uniq.sort
db['current_tags'] = db['collection']['tags'][0..1]
db['handle'] = db['collection']['handle']
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['linklists'] = db['link_lists']
db['shop'] = {
'name' => 'Snowdevil',
'currency' => 'USD',
'money_format' => '${{amount}}',
'money_with_currency_format' => '${{amount}} USD',
'money_format_with_currency' => 'USD ${{amount}}',
}
db
end
end
+27 -41
View File
@@ -345,8 +345,7 @@ products:
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.
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
@@ -377,8 +376,7 @@ products:
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.
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
@@ -411,8 +409,7 @@ products:
- 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.
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
@@ -446,8 +443,7 @@ products:
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.
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
@@ -478,8 +474,7 @@ products:
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.
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
@@ -516,8 +511,7 @@ products:
featured_image: products/ducati.jpg
images:
- products/ducati.jpg
description:
<h3>S PERFORMANCE</h3>
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:
@@ -636,7 +630,6 @@ products:
- *product-9-var-2
- *product-9-var-3
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# Line Items
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
@@ -644,8 +637,8 @@ products:
line_items:
- &line_item-1
id: 1
title: 'Arbor Draft'
subtitle: '151cm'
title: "Arbor Draft"
subtitle: "151cm"
price: 29900
line_price: 29900
quantity: 1
@@ -654,8 +647,8 @@ line_items:
- &line_item-2
id: 2
title: 'Comic ~ Orange'
subtitle: '159cm'
title: "Comic ~ Orange"
subtitle: "159cm"
price: 19900
line_price: 39800
quantity: 2
@@ -681,7 +674,7 @@ links:
- &link-4
id: 4
title: Powered by Shopify
url: 'http://shopify.com'
url: "http://shopify.com"
- &link-5
id: 5
title: About Us
@@ -715,8 +708,6 @@ links:
title: Catalog
url: /collections/all
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# Link Lists
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
@@ -724,8 +715,8 @@ links:
link_lists:
- &link-list-1
id: 1
title: 'Main Menu'
handle: 'main-menu'
title: "Main Menu"
handle: "main-menu"
links:
- *link-12
- *link-5
@@ -733,8 +724,8 @@ link_lists:
- *link-8
- &link-list-2
id: 1
title: 'Footer Menu'
handle: 'footer'
title: "Footer Menu"
handle: "footer"
links:
- *link-5
- *link-6
@@ -768,8 +759,7 @@ collections:
title: Snowboards
handle: snowboards
url: /collections/snowboards
description:
<p>This is a description for my <strong>Snowboards</strong> collection.</p>
description: <p>This is a description for my <strong>Snowboards</strong> collection.</p>
products:
- *product-1
- *product-2
@@ -787,8 +777,8 @@ collections:
- &collection-5
id: 5
title: Paginated Sale
handle: 'paginated-sale'
url: '/collections/paginated-sale'
handle: "paginated-sale"
url: "/collections/paginated-sale"
products:
- *product-1
- *product-2
@@ -799,8 +789,8 @@ collections:
- &collection-6
id: 6
title: All products
handle: 'all'
url: '/collections/all'
handle: "all"
url: "/collections/all"
products:
- *product-7
- *product-8
@@ -812,7 +802,6 @@ collections:
- *product-4
- *product-5
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# Pages and Blogs
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
@@ -823,8 +812,7 @@ pages:
handle: contact
url: /pages/contact
author: Tobi
content:
"<p>You can contact us via phone under (555) 567-2222.</p>
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
@@ -874,12 +862,12 @@ blogs:
url: /blogs/news
articles:
- id: 3
title: 'Welcome to the new Foo Shop'
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 &amp; 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'
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
@@ -891,13 +879,12 @@ blogs:
url: /blogs/bigcheese-blog
articles:
- id: 1
title: 'One thing you probably did not know yet...'
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
- id: 1
author: John Smith
email: [email protected]
content: Wow...great article man.
@@ -905,8 +892,7 @@ blogs:
created_at: 2009-01-01 12:00
updated_at: 2009-02-01 12:00
url: ""
-
id: 2
- id: 2
author: John Jones
email: [email protected]
content: I really enjoyed this article. And I love your shop! It's awesome. Shopify rocks!
@@ -932,7 +918,7 @@ blogs:
url: /blogs/paginated-blog
articles:
- id: 6
title: 'One thing you probably did not know yet...'
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
+73 -61
View File
@@ -25,23 +25,41 @@ class ThemeRunner
# Initialize a new liquid ThemeRunner instance
# Will load all templates into memory, do this now so that we don't profile IO.
def initialize
@tests = Dir[__dir__ + '/tests/**/*.liquid'].collect do |test|
def initialize(strictness: {})
@strictness = strictness
@tests = []
Dir[__dir__ + '/tests/**/*.liquid'].each do |test|
next if File.basename(test) == 'theme.liquid'
theme_path = File.dirname(test) + '/theme.liquid'
{
liquid: File.read(test),
layout: (File.file?(theme_path) ? File.read(theme_path) : nil),
template_name: test,
}
end.compact
theme_path = File.realpath(File.dirname(test))
theme_name = File.basename(theme_path)
test_name = theme_name + "/" + File.basename(test)
template_name = File.basename(test, '.liquid')
layout_path = theme_path + '/theme.liquid'
compile_all_tests
test = {
test_name: test_name,
liquid: File.read(test),
layout: File.file?(layout_path) ? File.read(layout_path) : nil,
template_name: template_name,
theme_name: theme_name,
theme_path: theme_path,
}
@tests << test
end
end
def find_test(test_name)
@tests.find do |test_hash|
test_hash[:test_name] == test_name
end
end
attr_reader :tests
# `compile` will test just the compilation portion of liquid without any templates
def compile
def compile_all
@tests.each do |test_hash|
Liquid::Template.new.parse(test_hash[:liquid])
Liquid::Template.new.parse(test_hash[:layout])
@@ -49,7 +67,7 @@ class ThemeRunner
end
# `tokenize` will just test the tokenizen portion of liquid without any templates
def tokenize
def tokenize_all
ss = StringScanner.new("")
@tests.each do |test_hash|
tokenizer = Liquid::Tokenizer.new(
@@ -62,78 +80,72 @@ class ThemeRunner
end
# `run` is called to benchmark rendering and compiling at the same time
def run
each_test do |liquid, layout, assigns, page_template, template_name|
compile_and_render(liquid, layout, assigns, page_template, template_name)
def run_all
@tests.each do |test|
compile_and_render(test)
end
end
# `render` is called to benchmark just the render portion of liquid
def render
def render_all
@compiled_tests ||= compile_all_tests
@compiled_tests.each do |test|
tmpl = test[:tmpl]
assigns = test[:assigns]
layout = test[:layout]
if layout
assigns['content_for_layout'] = tmpl.render!(assigns)
layout.render!(assigns)
else
tmpl.render!(assigns)
end
render_template(test)
end
end
def run_one_test(test_name)
test = find_test(test_name)
compile_and_render(test)
end
private
def render_layout(template, layout, assigns)
assigns['content_for_layout'] = template.render!(assigns)
layout&.render!(assigns)
def render_template(compiled_test)
tmpl, layout, assigns = compiled_test.values_at(:tmpl, :layout, :assigns)
if layout
assigns['content_for_layout'] = tmpl.render!(assigns, @strictness)
rendered_layout = layout.render!(assigns, @strictness)
rendered_layout
else
tmpl.render!(assigns, @strictness)
end
end
def compile_and_render(template, layout, assigns, page_template, template_file)
compiled_test = compile_test(template, layout, assigns, page_template, template_file)
render_layout(compiled_test[:tmpl], compiled_test[:layout], compiled_test[:assigns])
def compile_and_render(test)
compiled_test = compile_test(test)
render_template(compiled_test)
end
def compile_all_tests
@compiled_tests = []
each_test do |liquid, layout, assigns, page_template, template_name|
@compiled_tests << compile_test(liquid, layout, assigns, page_template, template_name)
@tests.each do |test_hash|
@compiled_tests << compile_test(test_hash)
end
@compiled_tests
end
def compile_test(template, layout, assigns, page_template, template_file)
tmpl = init_template(page_template, template_file)
parsed_template = tmpl.parse(template).dup
def compile_test(test_hash)
theme_path, template_name, layout, liquid = test_hash.values_at(:theme_path, :template_name, :layout, :liquid)
assigns = Database.tables.dup
assigns.merge!({
'title' => 'Page title',
'page_title' => 'Page title',
'content_for_header' => '',
'template' => template_name,
})
fs = ThemeRunner::FileSystem.new(theme_path)
result = {}
result[:assigns] = assigns
result[:tmpl] = Liquid::Template.parse(liquid, registers: { file_system: fs })
if layout
parsed_layout = tmpl.parse(layout)
{ tmpl: parsed_template, assigns: assigns, layout: parsed_layout }
else
{ tmpl: parsed_template, assigns: assigns }
result[:layout] = Liquid::Template.parse(layout, registers: { file_system: fs })
end
end
# utility method with similar functionality needed in `compile_all_tests` and `run`
def each_test
# Dup assigns because will make some changes to them
assigns = Database.tables.dup
@tests.each do |test_hash|
# Compute page_template outside of profiler run, uninteresting to profiler
page_template = File.basename(test_hash[:template_name], File.extname(test_hash[:template_name]))
yield(test_hash[:liquid], test_hash[:layout], assigns, page_template, test_hash[:template_name])
end
end
# set up a new Liquid::Template object for use in `compile_and_render` and `compile_test`
def init_template(page_template, template_file)
tmpl = Liquid::Template.new
tmpl.assigns['page_title'] = 'Page title'
tmpl.assigns['template'] = page_template
tmpl.registers[:file_system] = ThemeRunner::FileSystem.new(File.dirname(template_file))
tmpl
result
end
end
+25
View File
@@ -0,0 +1,25 @@
# frozen_string_literal: true
require 'test_helper'
class EnvironmentTest < Minitest::Test
include Liquid
class UnsubscribeFooter < Liquid::Tag
def render(_context)
'Unsubscribe Footer'
end
end
def test_custom_tag
email_environment = Liquid::Environment.build do |environment|
environment.register_tag("unsubscribe_footer", UnsubscribeFooter)
end
assert(email_environment.tags["unsubscribe_footer"])
assert(email_environment.tag_for_name("unsubscribe_footer"))
template = Liquid::Template.parse("{% unsubscribe_footer %}", environment: email_environment)
assert_equal('Unsubscribe Footer', template.render)
end
end
+108 -5
View File
@@ -20,6 +20,21 @@ class DocTagUnitTest < Minitest::Test
assert_template_result('', template)
end
def test_doc_tag_body_content
doc_content = " Documentation content\n @param {string} foo - test\n"
template_source = "{% doc %}#{doc_content}{% enddoc %}"
doc_tag = nil
ParseTreeVisitor
.for(Template.parse(template_source).root)
.add_callback_for(Liquid::Doc) do |tag|
doc_tag = tag
end
.visit
assert_equal(doc_content, doc_tag.nodelist.first.to_s)
end
def test_doc_tag_does_not_support_extra_arguments
error = assert_raises(Liquid::SyntaxError) do
template = <<~LIQUID.chomp
@@ -116,6 +131,20 @@ class DocTagUnitTest < Minitest::Test
assert_template_result('', template)
end
def test_doc_tag_captures_token_before_enddoc
template_source = "{% doc %}{{ incomplete{% enddoc %}"
doc_tag = nil
ParseTreeVisitor
.for(Template.parse(template_source).root)
.add_callback_for(Liquid::Doc) do |tag|
doc_tag = tag
end
.visit
assert_equal("{{ incomplete", doc_tag.nodelist.first.to_s)
end
def test_doc_tag_preserves_error_line_numbers
template = Liquid::Template.parse(<<~LIQUID.chomp, line_numbers: true)
{% doc %}
@@ -145,11 +174,11 @@ class DocTagUnitTest < Minitest::Test
def test_doc_tag_delimiter_handling
assert_template_result('', <<~LIQUID.chomp)
{% if true %}
{% doc %}
{% docEXTRA %}wut{% enddocEXTRA %}xyz
{% enddoc %}
{% endif %}
{%- if true -%}
{%- doc -%}
{%- docEXTRA -%}wut{% enddocEXTRA -%}xyz
{%- enddoc -%}
{%- endif -%}
LIQUID
assert_template_result('', "{% doc %}123{% enddoc xyz %}")
@@ -167,6 +196,80 @@ class DocTagUnitTest < Minitest::Test
)
end
def test_doc_tag_blank_with_empty_content
template_source = "{% doc %}{% enddoc %}"
doc_tag = nil
ParseTreeVisitor
.for(Template.parse(template_source).root)
.add_callback_for(Liquid::Doc) do |tag|
doc_tag = tag
end
.visit
assert_equal(true, doc_tag.blank?)
end
def test_doc_tag_blank_with_content
template_source = "{% doc %}Some documentation{% enddoc %}"
doc_tag = nil
ParseTreeVisitor
.for(Template.parse(template_source).root)
.add_callback_for(Liquid::Doc) do |tag|
doc_tag = tag
end
.visit
assert_equal(false, doc_tag.blank?)
end
def test_doc_tag_blank_with_whitespace_only
template_source = "{% doc %} {% enddoc %}"
doc_tag = nil
ParseTreeVisitor
.for(Template.parse(template_source).root)
.add_callback_for(Liquid::Doc) do |tag|
doc_tag = tag
end
.visit
assert_equal(false, doc_tag.blank?)
end
def test_doc_tag_nodelist_returns_array_with_body
doc_content = "Documentation content\n@param {string} foo"
template_source = "{% doc %}#{doc_content}{% enddoc %}"
doc_tag = nil
ParseTreeVisitor
.for(Template.parse(template_source).root)
.add_callback_for(Liquid::Doc) do |tag|
doc_tag = tag
end
.visit
assert_equal([doc_content], doc_tag.nodelist)
assert_equal(1, doc_tag.nodelist.length)
assert_equal(doc_content, doc_tag.nodelist.first)
end
def test_doc_tag_nodelist_with_empty_content
template_source = "{% doc %}{% enddoc %}"
doc_tag = nil
ParseTreeVisitor
.for(Template.parse(template_source).root)
.add_callback_for(Liquid::Doc) do |tag|
doc_tag = tag
end
.visit
assert_equal([""], doc_tag.nodelist)
assert_equal(1, doc_tag.nodelist.length)
end
private
def traversal(template)