Created
June 30, 2020 09:39
-
-
Save luikore/da28915e4ca1c15761e41d89f1848c16 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# enable the template engine shortcuts | |
Slim::Engine.set_options shortcut: {'&' => {tag: 'input', attr: 'type'}, '#' => {attr: 'id'}, '.' => {attr: 'class'}}, sort_attrs: false | |
# enable stimulus shortcuts: | |
# - @controller="foo" | |
# - @target="bar" | |
# - @click...="#action" | |
module Slim | |
class Parser | |
# this stack works in sync with tag stack, but tracks controller context | |
class ContextStack < Array | |
def initialize elements | |
super elements | |
@stimulus_controller = [] | |
end | |
def pop | |
mark, _ = @stimulus_controller.last | |
if mark == (size - 1) | |
@stimulus_controller.pop | |
end | |
super | |
end | |
def delete_at i | |
deleted = false | |
@stimulus_controller.delete_if do |obj| | |
if deleted | |
# carefully modify the markers afterwards to ensure sync | |
obj[0] -= 1 | |
false | |
else | |
should = (obj[0] == i) | |
deleted = true if should | |
should | |
end | |
end | |
super i | |
end | |
def push_controller c | |
@stimulus_controller.push [size, c] | |
end | |
def curr_controller | |
_, c = @stimulus_controller.last | |
c | |
end | |
end | |
def reset(lines = nil, stacks = nil) | |
@indents = [] | |
@stacks = (ContextStack.new(stacks) if stacks) | |
@lineno = 0 | |
@lines = lines | |
@line = @orig_line = nil | |
end | |
def parse_attributes(attributes) | |
# Check to see if there is a delimiter right after the tag name | |
delimiter = nil | |
if @line =~ @attr_list_delims_re | |
delimiter = @attr_list_delims[$1] | |
@line = $' | |
end | |
if delimiter | |
boolean_attr_re = /#{@attr_name}(?=(\s|#{Regexp.escape delimiter}|\Z))/ | |
end_re = /\A\s*#{Regexp.escape delimiter}/ | |
end | |
while true | |
case @line | |
when @splat_attrs_regexp | |
# Splat attribute | |
@line = $' | |
attributes << [:slim, :splat, parse_ruby_code(delimiter)] | |
when /\A\s*@(\w+)\s*=\s*(["'])([\w\.\-\>\#]+)\2/ | |
attr_value = $3 | |
attr_name = $1 | |
line_rest = $' | |
if (attr_name == 'target' and !attr_value['.']) | |
if c = @stacks.curr_controller | |
attr_value = "#{c}.#{attr_value}" | |
else | |
syntax_error!('Using abbreviated data-target without declaring @controller') | |
end | |
end | |
if attr_name != 'target' and attr_name != 'controller' | |
case attr_value | |
when /\w+\#\w+/ | |
# OK, not abbreviated | |
when /\#\w+/ | |
if c = @stacks.curr_controller | |
attr_value = "#{c}#{attr_value}" | |
else | |
syntax_error!('Using abbreviated data-target without declaring @controller') | |
end | |
else | |
syntax_error!('Invalid action format, should be like `controller#action_name` or just `#action_name`') | |
end | |
end | |
if attr_value['#'] | |
@line = line_rest | |
attr_value = "#{attr_name}->#{attr_value}" | |
# look for existing data-action, if there is one, append to it | |
have_data_action = false | |
attributes.each do |(h, a, aname, rest)| | |
if [h, a, aname] == [:html, :attr, 'data-action'] | |
rest[2] << " #{attr_value}" | |
have_data_action = true | |
break | |
end | |
end | |
if !have_data_action | |
attributes << [:html, :attr, 'data-action', [:slim, :interpolate, attr_value]] | |
end | |
else | |
case attr_name | |
when 'controller' | |
@line = line_rest | |
@stacks.push_controller attr_value | |
attributes << [:html, :attr, 'data-controller', [:slim, :interpolate, attr_value]] | |
when 'target' | |
@line = line_rest | |
attributes << [:html, :attr, 'data-target', [:slim, :interpolate, attr_value]] | |
else | |
syntax_error!('Must use "@controller=" or "@target=" or assign "controller#action"') | |
end | |
end | |
when /\A\s*@/ | |
syntax_error!('Expect @controller or @target or event names') | |
when @quoted_attr_re | |
# Value is quoted (static) | |
@line = $' | |
attributes << [:html, :attr, $1, | |
[:escape, $2.empty?, [:slim, :interpolate, parse_quoted_attribute($3)]]] | |
when @code_attr_re | |
# Value is ruby code | |
@line = $' | |
name = $1 | |
escape = $2.empty? | |
value = parse_ruby_code(delimiter) | |
syntax_error!('Invalid empty attribute') if value.empty? | |
attributes << [:html, :attr, name, [:slim, :attrvalue, escape, value]] | |
else | |
break unless delimiter | |
case @line | |
when boolean_attr_re | |
# Boolean attribute | |
@line = $' | |
attributes << [:html, :attr, $1, [:multi]] | |
when end_re | |
# Find ending delimiter | |
@line = $' | |
break | |
else | |
# Found something where an attribute should be | |
@line.lstrip! | |
syntax_error!('Expected attribute') unless @line.empty? | |
# Attributes span multiple lines | |
@stacks.last << [:newline] | |
syntax_error!("Expected closing delimiter #{delimiter}") if @lines.empty? | |
next_line | |
end | |
end | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment