Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiline comments #126

Merged
merged 6 commits into from
May 23, 2017
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions lib/slime/parser/text_block.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
defmodule Slime.Parser.TextBlock do
@moduledoc "
Utilities for parsing text blocks.
"

import Slime.Parser.Transform, only: [wrap_in_quotes: 1]

@doc """
Given a text block and its declaration indentation level (see below),
produces a string (or a dynamic EEx tuple) that can be inserted into the tree.

nested
| Text block
that spans over multiple lines
---
^
declaration indent
"""
def render(lines, decl_indent, trailing_whitespace \\ "") do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be use declaration_indent as variable name? An abbreviation is a bit confusing.

[{first_line_indent, first_line, is_eex_line} | rest] = lines

text_indent = if first_line == "" do
[{indent, _, _} | _] = rest
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about single ' used for spacing, example similar to one in my work project:

p
  '
  span text
  ' .

I understand, span< could be used here, but it is a valid slim and should be supported (probably should add a test case for it)

indent
else
decl_indent + first_line_indent
end

content = [{text_indent, first_line, is_eex_line} | rest]

{text, is_eex} = Enum.reduce(content, {"", false},
fn ({line_indent, line, is_eex_line}, {text, is_eex}) ->
text = if text == "", do: text, else: text <> "\n"
leading_space = String.duplicate(" ", line_indent - text_indent)
{text <> leading_space <> line, is_eex || is_eex_line}
end)

text = text <> trailing_whitespace

if is_eex do
{:eex, content: wrap_in_quotes(text), inline: true}
else
text
end
end
end
84 changes: 31 additions & 53 deletions lib/slime/parser/transform.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ defmodule Slime.Parser.Transform do
import Slime.Parser.Preprocessor, only: [indent_size: 1]
alias Slime.Parser.AttributesKeyword
alias Slime.Parser.EmbeddedEngine
alias Slime.Parser.TextBlock
alias Slime.Doctype

@default_tag Application.get_env(:slime, :default_tag, "div")
Expand Down Expand Up @@ -72,7 +73,11 @@ defmodule Slime.Parser.Transform do
end

def transform(:html_comment, input, _index) do
{:html_comment, children: [to_string(Enum.at(input, 2))]}
indent = indent_size(input[:indent])
decl_indent = indent + String.length(input[:type])
text = TextBlock.render(input[:content], decl_indent)

{indent, {:html_comment, children: [text]}}
end

def transform(:ie_comment, input, _index) do
Expand All @@ -86,66 +91,39 @@ defmodule Slime.Parser.Transform do

def transform(:verbatim_text, input, _index) do
indent = indent_size(input[:indent])
relative_text_indent = indent_size(input[:space])
text_indent = indent + relative_text_indent + String.length(input[:type])
[{first_line, is_eex_line} | rest] = input[:content]
text_indent = text_indent + if first_line == "" && relative_text_indent == 0, do: 1, else: 0
content = [{text_indent, first_line, is_eex_line} | rest]
shift_indent = content |> Enum.map(&elem(&1, 0)) |> Enum.min
shift_indent = if text_indent == shift_indent do
relative_text_indent = if relative_text_indent == 0, do: 0, else: relative_text_indent - 1
text_indent - relative_text_indent
else
shift_indent
end
{content, is_eex} = Enum.reduce(content, {"", false},
fn ({line_indent, line, is_eex_line}, {result, is_eex}) ->
result = if result == "", do: result, else: result <> "\n"
result_line_indent = String.duplicate(" ", line_indent - shift_indent)
{
result <> result_line_indent <> line,
is_eex || is_eex_line
}
end
)

content = if input[:type] == "'", do: content <> " ", else: content
content = if is_eex do
{:eex, content: wrap_in_quotes(content), inline: true}
else
content
end
{indent, content}
decl_indent = indent + String.length(input[:type])
trailing_whitespace = if input[:type] == "'", do: " ", else: ""
text = TextBlock.render(input[:content], decl_indent, trailing_whitespace)

{indent, text}
end

def transform(:verbatim_text_lines, input, _index) do
def transform(:text_block, input, _index) do
case input do
[line, []] -> [line]
[line, nested_lines] ->
lines = nested_lines[:lines]
spaces = indent_size(nested_lines[:space])
[{first_line, is_eex} | rest] = lines
[line, {spaces, first_line, is_eex} | rest]
[line, nested_lines] -> [line | nested_lines[:lines]]
end
end

def transform(:verbatim_text_nested_lines, input, _index) do
def transform(:text_block_nested_lines, input, _index) do
case input do
[line, []] -> [line]
[line, lines] ->
lines = Enum.flat_map(lines, fn ([_, line]) ->
[{first_line, is_eex} | rest] = line[:lines]
[{indent_size(line[:space]), first_line, is_eex} | rest]
end)
[line | lines]
[line, nested_lines] ->
[line | Enum.flat_map(nested_lines, fn([_crlf, l]) ->
case l do
[_indent, nested, _dedent] -> nested
nested -> nested
end
end)]
end
end

def transform(:verbatim_text_line, input, _index) do
case input do
"" -> {"", false}
{:simple, content} -> {to_string(content), false}
{:dynamic, content} -> {to_string(content), true}
def transform(:text_block_line, input, _index) do
[space, line] = input
indent = indent_size(space)
case line do
{:simple, content} -> {indent, to_string(content), false}
{:dynamic, content} -> {indent, to_string(content), true}
end
end

Expand Down Expand Up @@ -347,6 +325,10 @@ defmodule Slime.Parser.Transform do
end
end

def wrap_in_quotes(content) do
~s("#{String.replace(content, @quote_outside_interpolation_regex, ~S(\\"))}")
end

defp expand_attr_shortcut(type, value) do
spec = Map.fetch!(@shortcut, type)
expand_shortcut(spec, value)
Expand All @@ -361,8 +343,4 @@ defmodule Slime.Parser.Transform do
final_attrs = Enum.concat(attrs, Map.get(spec, :additional_attrs, []))
{spec[:tag], final_attrs}
end

defp wrap_in_quotes(content) do
~s("#{String.replace(content, @quote_outside_interpolation_regex, ~S(\\"))}")
end
end
26 changes: 13 additions & 13 deletions src/slime_parser.peg.eex
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ doctype <- space? 'doctype' space (!eol .)+ eol;
% Also remove dedent from eol definition and modify transforms accordingly
tags_list <- tag (crlf indent? empty_lines:(space? crlf indent?)* tag:tag dedent*)*;

% NOTE: We have to track leading spaces for verbatim_text, so it handled separately
tag <- verbatim_text / space? tag_item / blank:(space? &eol);
% NOTE: Leading whitespace is handled separately for text items
tag <- text_item / space? tag_item / blank:(space? &eol);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is better to keep verbatim_text separated from comment, so here it would be comment / verbatim_text / ...


tag_item <- (embedded_engine / comment / inline_html / code / html_tag);
tag_item <- (embedded_engine / inline_html / code / html_tag);

inline_html <- &'<' text_content;

Expand Down Expand Up @@ -83,22 +83,22 @@ braces <- '{' (braces / !'}' .)* '}';

interpolation <- '#{' (string / string_with_interpolation / !'}' .)* '}';

comment <- html_comment / ie_comment / code_comment;
text_item <- verbatim_text / html_comment / space? ie_comment / space? code_comment;

html_comment <- '/!' space? (!eol .)*;
verbatim_text <- indent:space? type:[|'] content:text_block;

html_comment <- indent:space? type:'/!' content:text_block;

ie_comment <- '/[' condition:(!']' .)+ ']' space? content:(!eol .)*;

% TODO: support multi-line
code_comment <- '/' (!eol .)*;
code_comment <- '/' text_block;

verbatim_text <- indent:space? type:[|'] space:space? content:verbatim_text_lines;
verbatim_text_lines <- verbatim_text_line (crlf indent space:space lines:verbatim_text_nested_lines dedent)?;
verbatim_text_nested_lines <- verbatim_text_line (crlf (
indent space:space lines:verbatim_text_nested_lines dedent /
space:space lines:verbatim_text_nested_lines
text_block <- text_block_line (crlf
indent lines:text_block_nested_lines dedent)?;
text_block_nested_lines <- text_block_line (crlf (
indent text_block_nested_lines dedent / text_block_nested_lines
))*;
verbatim_text_line <- dynamic:text_with_interpolation / simple:text / '';
text_block_line <- space? (dynamic:text_with_interpolation / simple:text);

embedded_engine <- tag_name ':' (
crlf indent lines:embedded_engine_lines dedent / empty:('' &eol)
Expand Down
36 changes: 33 additions & 3 deletions test/rendering/comments_test.exs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
defmodule RenderCommentsTest do
use ExUnit.Case, async: true

import Slime, only: [render: 1]
import Slime, only: [render: 1, render: 2]

test "lines started with / are commented out" do
test "lines indented deeper than / are commented out" do
slime = """
/ code comment
/ Code comment
that spans over multiple lines
/
Code comment started
on another line
p.test
/ One-liner
"""
assert render(slime) == ~s(<p class="test"></p>)
end
Expand All @@ -15,6 +20,31 @@ defmodule RenderCommentsTest do
assert render(~s(/! html comment)) == ~s(<!--html comment-->)
end

test "/! can span over multiple lines" do
slime = """
div
/!
HTML comments
/! Have similar semantics
to other text blocks:
they can be nested, with indentation being converted to spaces
"""
html = """
<div><!--HTML comments--><!--Have similar semantics
to other text blocks:
they can be nested, with indentation being converted to spaces--></div>
""" |> String.strip(?\n)
assert render(slime) == html
end

test "/! renders comments with interpolation" do
slime = ~S(/! html comment with #{interpolation})
html = """
<!--html comment with a-->
""" |> String.strip(?\n)
assert render(slime, interpolation: "a") == html
end

test "/![foo] renders IE comments" do
assert render(~s(/[if IE] html comment)) == ~s(<!--[if IE]>html comment<![endif]-->)
end
Expand Down