|
| 1 | +# frozen_string_literal: true |
| 2 | + |
| 3 | +# Regenerate the committed LiquidIL-generated Ruby sample from templates.yml. |
| 4 | +# |
| 5 | +# LiquidIL (https://github.com/tobi/liquid-il) compiles a Liquid template to |
| 6 | +# standalone Ruby (Template#to_ruby), inlining static partials so the result |
| 7 | +# renders with no file_system. This script compiles each template in |
| 8 | +# templates.yml to a module under generated/, writes its render assigns to |
| 9 | +# fixtures/, and validates the standalone module reproduces the template's |
| 10 | +# expected output. benchmark.rb then renders those committed modules in a hot |
| 11 | +# loop, so the exact Ruby the JIT compiles lives in the repo and does not drift |
| 12 | +# with the compiler. |
| 13 | +# |
| 14 | +# BUNDLE_GEMFILE=$PWD/Gemfile bundle exec ruby extract.rb |
| 15 | +# |
| 16 | +# The generated/ and fixtures/ output is committed; you only need to re-run |
| 17 | +# this when templates.yml or the pinned LiquidIL version changes. |
| 18 | + |
| 19 | +require "json" |
| 20 | +require "yaml" |
| 21 | +require "liquid_il" |
| 22 | + |
| 23 | +HERE = __dir__ |
| 24 | +GEN_DIR = File.join(HERE, "generated") |
| 25 | +FIX_DIR = File.join(HERE, "fixtures") |
| 26 | + |
| 27 | +# Compile-time file system for the templates' inline partials. Static partials |
| 28 | +# are inlined into the caller, so the generated Ruby holds no reference to it. |
| 29 | +class SpecFileSystem |
| 30 | + def initialize(files) = @files = files || {} |
| 31 | + def read_template_file(name, _context = nil) |
| 32 | + @files.fetch(name.to_s) { raise LiquidIL::FileSystemError, "no such partial: #{name}" } |
| 33 | + end |
| 34 | +end |
| 35 | + |
| 36 | +def module_name_for(name) |
| 37 | + "LiquidILBench" + name.split(/[^a-zA-Z0-9]/).reject(&:empty?).map(&:capitalize).join |
| 38 | +end |
| 39 | + |
| 40 | +def deep_dup(obj) |
| 41 | + case obj |
| 42 | + when Hash then obj.each_with_object({}) { |(k, v), h| h[k] = deep_dup(v) } |
| 43 | + when Array then obj.map { |v| deep_dup(v) } |
| 44 | + else obj |
| 45 | + end |
| 46 | +end |
| 47 | + |
| 48 | +Dir.mkdir(GEN_DIR) unless Dir.exist?(GEN_DIR) |
| 49 | +Dir.mkdir(FIX_DIR) unless Dir.exist?(FIX_DIR) |
| 50 | + |
| 51 | +specs = YAML.safe_load(File.read(File.join(HERE, "templates.yml")), aliases: true).fetch("specs") |
| 52 | +manifest = [] |
| 53 | + |
| 54 | +specs.each do |spec| |
| 55 | + name = spec.fetch("name") |
| 56 | + mod = module_name_for(name) |
| 57 | + |
| 58 | + ctx = LiquidIL::Context.new(file_system: SpecFileSystem.new(spec["filesystem"])) |
| 59 | + template = ctx.parse(spec.fetch("template")) |
| 60 | + ruby = template.to_ruby(mod) |
| 61 | + |
| 62 | + # Validate the standalone module in an isolated namespace, no file_system. |
| 63 | + probe = Module.new |
| 64 | + probe.module_eval(ruby, "generated/#{name}.rb") |
| 65 | + produced = probe.const_get(mod).render(deep_dup(spec["environment"] || {})) |
| 66 | + expected = spec["expected"] |
| 67 | + if expected && produced != expected |
| 68 | + raise "#{name}: generated Ruby output does not match expected" |
| 69 | + end |
| 70 | + |
| 71 | + File.write(File.join(GEN_DIR, "#{name}.rb"), ruby) |
| 72 | + File.write(File.join(FIX_DIR, "#{name}.json"), JSON.pretty_generate(spec["environment"] || {}) + "\n") |
| 73 | + manifest << { "spec" => name, "module" => mod, "ruby_bytes" => ruby.bytesize } |
| 74 | + printf(" ok %-32s %6d B\n", name, ruby.bytesize) |
| 75 | +end |
| 76 | + |
| 77 | +File.write(File.join(HERE, "manifest.json"), JSON.pretty_generate(manifest) + "\n") |
| 78 | +puts "Extracted #{manifest.size} module(s); #{manifest.sum { |m| m["ruby_bytes"] }} B generated Ruby." |
0 commit comments