Skip to content

Instantly share code, notes, and snippets.

@activestylus
Created September 6, 2025 18:12
Show Gist options
  • Save activestylus/7fb6acd539d3775c748713ef8d901c82 to your computer and use it in GitHub Desktop.
Save activestylus/7fb6acd539d3775c748713ef8d901c82 to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby
require 'cgi'
require 'set'
require 'benchmark'
module Joys
TAGS = %w[style div h1 h2 h3 h4 h5 h6 p span a img button html head title body meta link ul ol li nav header main footer section article form input textarea select option].freeze
VOID_TAGS = %w[img meta link br hr input].freeze
BOOLEAN_ATTRS = %w[disabled readonly multiple checked selected autofocus novalidate formnovalidate hidden required open reversed scoped seamless muted autoplay controls loop default inert itemscope].to_set.freeze
@cache = {}
@layouts = {}
@components = {}
@pages = {}
@compiled_styles = {}
@consolidated_cache = {}
class << self
attr_reader :cache, :layouts, :components, :pages, :compiled_styles, :consolidated_cache
end
def self.cache(cache_id, *args, &block)
cache_key = [cache_id, args.hash]
@cache[cache_key] ||= block.call
end
def self.clear_cache!
@cache.clear
end
module Styles
@compiled_styles = {}
@consolidated_cache = {}
class << self
attr_reader :compiled_styles, :consolidated_cache
end
def self.compile_component_styles(comp_name, base_css, media_queries, scoped)
scope_prefix = scoped ? ".#{comp_name.tr('_', '-')} " : ""
processed_base_css = base_css.map { |css| scope_css(css, scope_prefix) }
processed_media_queries = {}
media_queries.each do |key, css_rules|
scoped_rules = css_rules.map { |css| scope_css(css, scope_prefix) }
processed_media_queries[key] = scoped_rules
end
@compiled_styles[comp_name] = {
base_css: processed_base_css,
media_queries: processed_media_queries
}.freeze
nil
end
def self.render_consolidated_styles(used_components)
return "" if used_components.empty?
cache_key = used_components.to_a.sort.join(',')
return @consolidated_cache[cache_key] if @consolidated_cache.key?(cache_key)
buffer = String.new(capacity: 4096)
min_queries = Hash.new { |h,k| h[k] = [] }
max_queries = Hash.new { |h,k| h[k] = [] }
seen_base = Set.new
used_components.each do |comp|
styles = @compiled_styles[comp] || next
styles[:base_css].each do |rule|
next if seen_base.include?(rule)
buffer << rule
seen_base << rule
end
styles[:media_queries].each do |key, rules|
case key
when /^min-(\d+)$/
min_queries[$1.to_i].concat(rules)
when /^max-(\d+)$/
max_queries[$1.to_i].concat(rules)
end
end
end
max_queries.sort.reverse.each do |bp,rules|
buffer << "@media (max-width: #{bp}px){"
rules.uniq.each { |r| buffer << r }
buffer << "}"
end
min_queries.sort.each do |bp,rules|
buffer << "@media (min-width: #{bp}px){"
rules.uniq.each { |r| buffer << r }
buffer << "}"
end
@consolidated_cache[cache_key] = buffer.freeze
end
def self.render_external_styles(used_components)
"<link rel=\"stylesheet\" href=\"/css/compiled.css\">"
end
private
def self.scope_css(css, scope_prefix)
return css if scope_prefix.empty?
scope_prefix + css
end
end
class MockParams
def initialize
@accesses = Set.new
end
def [](key)
@accesses << key
MockValue.new(key)
end
attr_reader :accesses
end
class MockValue
def initialize(param_name)
@param_name = param_name
end
def [](key)
MockValue.new("#{@param_name}[#{key.inspect}]")
end
def first
MockValue.new("#{@param_name}.first")
end
def last
MockValue.new("#{@param_name}.last")
end
def length; MockValue.new("#{@param_name}.length"); end
def size; MockValue.new("#{@param_name}.size"); end
def count(&block); MockValue.new("#{@param_name}.count"); end
def empty?; MockValue.new("#{@param_name}.empty?"); end
def join(separator = "")
if separator == ""
MockValue.new("#{@param_name}.join")
else
MockValue.new("#{@param_name}.join(QUOTE#{separator}QUOTE)")
end
end
def keys; MockArray.new("#{@param_name}.keys"); end
def values; MockArray.new("#{@param_name}.values"); end
def key?(key); MockValue.new("#{@param_name}.key?(QUOTE#{key}QUOTE)"); end
def split(delimiter = " ")
MockArray.new("#{@param_name}.split(QUOTE#{delimiter}QUOTE)")
end
def strip; MockValue.new("#{@param_name}.strip"); end
def upcase; MockValue.new("#{@param_name}.upcase"); end
def downcase; MockValue.new("#{@param_name}.downcase"); end
def select(&block)
if block
MockArray.new("#{@param_name}.select")
else
MockValue.new("#{@param_name}.select")
end
end
def map(&block)
if block
MockArray.new("#{@param_name}.map")
else
MockValue.new("#{@param_name}.map")
end
end
def each(&block)
if block
mock_item = MockItem.new
yield(mock_item)
end
self
end
def to_s
":PARAM_#{@param_name}"
end
end
class MockArray
def initialize(expression)
@expression = expression
end
def join(separator = "")
if separator == ""
MockValue.new("#{@expression}.join")
else
MockValue.new("#{@expression}.join(QUOTE#{separator}QUOTE)")
end
end
def map(&block)
if block
MockArray.new("#{@expression}.map")
else
MockValue.new("#{@expression}.map")
end
end
def select(&block)
if block
MockArray.new("#{@expression}.select")
else
MockValue.new("#{@expression}.select")
end
end
def first; MockValue.new("#{@expression}.first"); end
def last; MockValue.new("#{@expression}.last"); end
def length; MockValue.new("#{@expression}.length"); end
def size; MockValue.new("#{@expression}.size"); end
def count; MockValue.new("#{@expression}.count"); end
def empty?; MockValue.new("#{@expression}.empty?"); end
def [](index)
MockValue.new("#{@expression}[#{index.inspect}]")
end
def each(&block)
if block
mock_item = MockItem.new
yield(mock_item)
end
self
end
def to_s
":PARAM_#{@expression}"
end
end
class MockItem
def [](key)
":ITEM_#{key}"
end
end
class DSLCapturer
def initialize
@operations = []
@tag_stack = []
@params = MockParams.new
end
attr_reader :operations, :params
def capture(&block)
instance_eval(&block)
@operations
end
TAGS.each do |tag|
define_method(tag) do |content = nil, cs: nil, raw: false, **attrs, &block|
if block
@operations << [:tag_open, tag, cs, attrs]
instance_eval(&block)
@operations << [:tag_close, tag]
else
@operations << [:tag_complete, tag, content, cs, attrs, raw]
end
end
end
VOID_TAGS.each do |tag|
define_method(tag) do |cs: nil, **attrs|
@operations << [:void_tag, tag, cs, attrs]
end
end
def txt(content)
@operations << [:text, content, true]
end
def raw(content)
@operations << [:text, content, false]
end
def comp(name, **kwargs)
processed_kwargs = {}
kwargs.each do |key, value|
if value.respond_to?(:to_s)
processed_kwargs[key] = value.to_s
else
processed_kwargs[key] = value
end
end
@operations << [:component, name, processed_kwargs]
end
def layout(name, &block)
@operations << [:layout_start, name]
instance_eval(&block) if block
@operations << [:layout_end]
end
def push(slot_name, &block)
@operations << [:slot_start, slot_name]
instance_eval(&block) if block
@operations << [:slot_end]
end
def pull(slot_name = :main)
@operations << [:slot_pull, slot_name]
end
def styles(scoped: false, &block)
@operations << [:styles_start, scoped]
@style_context = { scoped: scoped, base_css: [], media_queries: {} }
instance_eval(&block) if block
@operations << [:styles_end, @style_context[:base_css], @style_context[:media_queries], scoped]
@style_context = nil
end
def css(rules)
return unless @style_context
rules = [rules] unless rules.is_a?(Array)
@style_context[:base_css].concat(rules)
end
def min_media(breakpoint, rules)
return unless @style_context
key = "min-#{breakpoint}"
@style_context[:media_queries][key] ||= []
@style_context[:media_queries][key] << rules
end
def max_media(breakpoint, rules)
return unless @style_context
key = "max-#{breakpoint}"
@style_context[:media_queries][key] ||= []
@style_context[:media_queries][key] << rules
end
def minmax_media(min_bp, max_bp, rules)
return unless @style_context
key = "minmax-#{min_bp}-#{max_bp}"
@style_context[:media_queries][key] ||= []
@style_context[:media_queries][key] << rules
end
def pull_styles
@operations << [:pull_styles]
end
def pull_external_styles
@operations << [:pull_external_styles]
end
def doctype
@operations << [:doctype]
end
def method_missing(method_name, *args, &block)
if block
@operations << [:iteration_start, method_name]
instance_eval(&block)
@operations << [:iteration_end]
else
@operations << [:unknown_call, method_name, args]
end
end
end
class CodeGenerator
def self.generate_layout(operations)
html_content = generate_html_content(operations, [])
<<~RUBY
lambda do |**slots|
<<~HTML
#{indent_content(html_content)}
HTML
end
RUBY
end
def self.generate_component(operations, param_accesses, comp_name)
if param_accesses.any?
param_sig = param_accesses.map { |p| "#{p}:" }.join(', ') + ", **kwargs"
else
param_sig = "**kwargs"
end
html_content = generate_html_content(operations, param_accesses, comp_name)
<<~RUBY
lambda do |#{param_sig}|
<<~HTML
#{indent_content(html_content)}
HTML
end
RUBY
end
def self.generate_page(operations, param_accesses)
if param_accesses.any?
param_sig = param_accesses.map { |p| "#{p}:" }.join(', ') + ", **kwargs"
else
param_sig = "**kwargs"
end
layout_name, slots = parse_page_structure(operations, param_accesses)
slot_code = slots.map do |slot_name, slot_ops|
slot_html = generate_html_content(slot_ops, param_accesses)
" #{slot_name}: (<<~HTML\n#{indent_content(slot_html, 6)}\n HTML\n )"
end.join(",\n")
layout_line = layout_name ? "layout: :#{layout_name}," : ""
<<~RUBY
lambda do |#{param_sig}|
@used_components = Set.new
result = {
#{layout_line}
#{slot_code}
}
result
end
RUBY
end
def self.generate_html_content(operations, param_accesses, comp_name = 'component')
lines = []
# Find operations that contain :ITEM_ markers for iteration
item_indices = Set.new
operations.each_with_index do |op, i|
if op.any? { |part| part.to_s.include?(":ITEM_") }
item_indices << i
end
end
# Process operations
operations.each_with_index do |op, i|
case op[0]
when :tag_complete
_, tag, content, cs, attrs, raw = op
if item_indices.include?(i)
# This is an iteration item - process the entire iteration block
iteration_lines = [generate_complete_tag(tag, content, cs, attrs, raw, param_accesses)]
collection_param = param_accesses.first || :items
# Process iteration content to replace :ITEM_ placeholders
processed_iteration_content = iteration_lines.map do |iter_line|
iter_line.gsub(/:ITEM_(\w+)/) do |match|
key = $1
"\#{CGI.escapeHTML(item[:#{key}].to_s)}"
end
end
lines << "\#{#{collection_param}.map do |item|"
lines << " <<~ITEM_HTML"
processed_iteration_content.each { |ic| lines << " #{ic}" }
lines << " ITEM_HTML"
lines << "end.join}"
else
lines << generate_complete_tag(tag, content, cs, attrs, raw, param_accesses)
end
when :tag_open
_, tag, cs, attrs = op
lines << generate_opening_tag(tag, cs, attrs)
when :tag_close
_, tag = op
lines << "</#{tag}>"
when :text
_, content, escaped = op
lines << generate_text_content(content, escaped, param_accesses)
when :component
_, name, kwargs = op
lines << generate_component_call(name, kwargs, param_accesses)
when :slot_pull
_, slot_name = op
lines << "\#{slots[:#{slot_name}] || ''}"
when :styles_end
_, base_css, media_queries, scoped = op
lines << "\#{Joys::Styles.compile_component_styles('#{comp_name}', #{base_css.inspect}, #{media_queries.inspect}, #{scoped}) && ''}"
when :pull_styles
lines << "\#{Joys::Styles.render_consolidated_styles(@used_components || Set.new)}"
when :pull_external_styles
lines << "\#{Joys::Styles.render_external_styles(@used_components || Set.new)}"
end
end
lines.join("\n")
end
def self.generate_complete_tag(tag, content, cs, attrs, raw, param_accesses)
attr_str = generate_attributes(cs, attrs)
if content
content_str = generate_text_content(content, !raw, param_accesses)
"<#{tag}#{attr_str}>#{content_str}</#{tag}>"
else
"<#{tag}#{attr_str}></#{tag}>"
end
end
def self.generate_opening_tag(tag, cs, attrs)
attr_str = generate_attributes(cs, attrs)
"<#{tag}#{attr_str}>"
end
def self.generate_attributes(cs, attrs)
parts = []
if cs
parts << " class=\"#{cs}\""
end
attrs&.each do |k, v|
next if v.nil? || v == false
if v.is_a?(Hash) && (k.to_s == "data" || k.to_s.start_with?("aria"))
v.each { |sk, sv| parts << " #{k}-#{sk.to_s.tr('_', '-')}=\"#{CGI.escapeHTML(sv.to_s)}\"" }
elsif v == true && BOOLEAN_ATTRS.include?(k.to_s)
parts << " #{k}"
else
parts << " #{k}=\"#{CGI.escapeHTML(v.to_s)}\""
end
end
parts.join
end
def self.generate_text_content(content, escaped, param_accesses)
return "" unless content
content_str = content.to_s
# Step 1: Replace QUOTE markers with actual quotes
content_str = content_str.gsub(/QUOTE(.*?)QUOTE/) { "\"#{$1}\"" }
# Step 2: Handle complex parameter expressions with comprehensive regex
# This regex captures method calls, array access, and combinations
content_str = content_str.gsub(/:PARAM_([a-zA-Z_]\w*(?:(?:\[[^\]]*\]|\.[a-zA-Z_]\w*|\([^)]*\))*)*)/) do |match|
expression = $1
base_param = expression.split(/[\.\[\(]/).first
if param_accesses.include?(base_param.to_sym)
escaped ? "\#{CGI.escapeHTML((#{expression}).to_s)}" : "\#{#{expression}}"
else
match
end
end
content_str
end
def self.generate_component_call(name, kwargs, param_accesses)
if kwargs.any?
kwargs_str = kwargs.map do |k, v|
v_str = v.to_s
if v_str.start_with?(":ITEM_")
key = v_str.sub(":ITEM_", "")
"#{k}: item[:#{key}]"
elsif v_str.start_with?(":PARAM_")
param = v_str.sub(":PARAM_", "")
"#{k}: #{param}"
else
"#{k}: #{v.inspect}"
end
end.join(", ")
"\#{(@used_components ||= Set.new; @used_components << '#{name}'; Joys.comp(:#{name}, #{kwargs_str}))}"
else
"\#{Joys.comp(:#{name})}"
end
end
def self.parse_page_structure(operations, param_accesses)
layout_name = nil
slots = {}
current_slot = nil
slot_operations = []
operations.each do |op|
case op[0]
when :layout_start
layout_name = op[1]
when :slot_start
if current_slot
slots[current_slot] = slot_operations.dup
slot_operations.clear
end
current_slot = op[1].to_sym
when :slot_end
if current_slot
slots[current_slot] = slot_operations.dup
slot_operations.clear
current_slot = nil
end
else
slot_operations << op if current_slot
end
end
[layout_name, slots]
end
def self.indent_content(content, spaces = 4)
content.split("\n").map { |line|
line.empty? ? "" : "#{' ' * spaces}#{line}"
}.join("\n")
end
end
def self.register(type, name, &template)
begin
capturer = DSLCapturer.new
operations = capturer.capture(&template)
param_accesses = capturer.params.accesses
style_ops = operations.select { |op| op[0] == :styles_end }
style_ops.each do |op|
_, base_css, media_queries, scoped = op
Joys::Styles.compile_component_styles(name.to_s, base_css, media_queries, scoped)
end
case type
when :comp
code = CodeGenerator.generate_component(operations, param_accesses, name)
@components[name] = eval(code)
when :layout
code = CodeGenerator.generate_layout(operations)
@layouts[name] = eval(code)
when :page
code = CodeGenerator.generate_page(operations, param_accesses)
@pages[name] = eval(code)
end
rescue => e
raise "Registration failed for #{type}:#{name} - #{e.message}"
end
end
def self.comp(name, **kwargs)
comp_func = @components[name]
return "Component :#{name} not found" unless comp_func
cache("comp_#{name}", kwargs.hash) { comp_func.call(**kwargs) }
end
def self.page(name, **locals)
page_func = @pages[name]
return "Page :#{name} not found" unless page_func
result = cache("page_#{name}", locals.hash) do
page_result = page_func.call(**locals)
if page_result.is_a?(Hash) && page_result[:layout]
layout_name = page_result.delete(:layout)
layout_func = @layouts[layout_name]
layout_func ? layout_func.call(**page_result) : "Layout not found"
else
page_result.to_s
end
end
result.freeze
end
def self.html(&block)
context = Object.new
def context.comp(name, **kwargs)
comp_func = Joys.components[name]
return "Component :#{name} not found" unless comp_func
result = comp_func.call(**kwargs)
@bf << result if @bf
nil
end
TAGS.each do |tag|
context.define_singleton_method(tag) do |content = nil, cs: nil, raw: false, **attrs, &block|
if block
attr_str = build_tag_attributes(cs, attrs)
@bf << "<#{tag}#{attr_str}>"
instance_eval(&block)
@bf << "</#{tag}>"
else
attr_str = build_tag_attributes(cs, attrs)
content_html = raw ? content.to_s : CGI.escapeHTML(content.to_s)
@bf << "<#{tag}#{attr_str}>#{content_html}</#{tag}>"
end
end
end
def context.build_tag_attributes(cs, attrs)
parts = []
parts << " class=\"#{cs}\"" if cs
attrs&.each do |k, v|
next if v.nil? || v == false
if v == true && BOOLEAN_ATTRS.include?(k.to_s)
parts << " #{k}"
else
parts << " #{k}=\"#{CGI.escapeHTML(v.to_s)}\""
end
end
parts.join
end
context.instance_variable_set(:@bf, String.new(capacity: 8192))
context.instance_eval(&block)
context.instance_variable_get(:@bf).freeze
end
end
# Test framework
Joys.register(:comp, :greeting) do
div(cs: "greeting") do
h1("Hello #{params[:name]}")
p("Welcome!")
end
end
Joys.register(:comp, :user_card) do
styles do
css ".card { background: #f9f9f9; padding: 1rem; margin: 0.5rem; border-radius: 4px; }"
css ".card h3 { color: #333; margin: 0 0 0.5rem 0; }"
css ".card p { color: #666; margin: 0; }"
end
div(cs: "card") do
h3(params[:name])
p("Email: #{params[:email]}")
end
end
Joys.register(:layout, :basic) do
html do
head do
title { pull(:title) }
style {pull_styles}
end
body do
pull(:content)
end
end
end
Joys.register(:page, :users) do
styles do
css "body {color:red}"
end
layout(:basic) do
push(:title) do
txt("User List")
end
push(:content) do
h1("Our Users")
params[:users].each do |user|
comp(:user_card, name: user[:name], email: user[:email])
end
end
end
end
Joys.register(:comp, :advanced_demo) do
div do
h3("User Stats")
p("Total users: #{params[:users].length}")
p("First user: #{params[:users].first[:name]}")
p("Last user: #{params[:users].last[:name]}")
p("Second user: #{params[:users][1][:name]}")
h3("Metadata")
p("Keys: #{params[:metadata].keys.join(', ')}")
h3("Tags")
p("Uppercase: #{params[:title].upcase}")
h3("User List")
ul do
params[:users].each do |user|
li("#{user[:name]} - #{user[:active] ? 'Active' : 'Inactive'}")
end
end
end
end
test_users = [
{ name: "Alice", email: "[email protected]", active: false },
{ name: "Bob", email: "[email protected]", active: true }
]
puts "=== Joys Framework Test ==="
puts "\nComponent test:"
puts Joys.comp(:greeting, name: "World")
puts "\nUser card test:"
puts Joys.comp(:user_card, name: "John", email: "[email protected]")
puts "\nPage test:"
puts Joys.page(:users, users: test_users)
puts "\nAdvanced demo test:"
puts Joys.comp(:advanced_demo,
users: test_users,
tags: ['hot','cold'],
title: "Yo",
metadata: { title: "hello", what: "that" })
puts "\nPerformance test:"
n = 1000
time = Benchmark.realtime { n.times { Joys.comp(:greeting, name: "Test") } }
puts "#{n} renders: #{(time * 1000 / n).round(3)}ms per render"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment