Skip to content

Commit

Permalink
Getting ready for 0.4.0 release
Browse files Browse the repository at this point in the history
  • Loading branch information
akoutmos committed Apr 27, 2022
1 parent 58f8b52 commit ba01eca
Show file tree
Hide file tree
Showing 8 changed files with 574 additions and 28 deletions.
209 changes: 209 additions & 0 deletions .credo.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
# This file contains the configuration for Credo and you are probably reading
# this after creating it with `mix credo.gen.config`.
#
# If you find anything wrong or unclear in this file, please report an
# issue on GitHub: https://github.com/rrrene/credo/issues
#
%{
#
# You can have as many configs as you like in the `configs:` field.
configs: [
%{
#
# Run any config using `mix credo -C <name>`. If no config name is given
# "default" is used.
#
name: "default",
#
# These are the files included in the analysis:
files: %{
#
# You can give explicit globs or simply directories.
# In the latter case `**/*.{ex,exs}` will be used.
#
included: [
"lib/",
"src/",
"test/",
"web/",
"apps/*/lib/",
"apps/*/src/",
"apps/*/test/",
"apps/*/web/"
],
excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"]
},
#
# Load and configure plugins here:
#
plugins: [],
#
# If you create your own checks, you must specify the source files for
# them here, so they can be loaded by Credo before running the analysis.
#
requires: [],
#
# If you want to enforce a style guide and need a more traditional linting
# experience, you can change `strict` to `true` below:
#
strict: false,
#
# To modify the timeout for parsing files, change this value:
#
parse_timeout: 5000,
#
# If you want to use uncolored output by default, you can change `color`
# to `false` below:
#
color: true,
#
# You can customize the parameters of any check by adding a second element
# to the tuple.
#
# To disable a check put `false` as second element:
#
# {Credo.Check.Design.DuplicatedCode, false}
#
checks: %{
enabled: [
#
## Consistency Checks
#
{Credo.Check.Consistency.ExceptionNames, []},
{Credo.Check.Consistency.LineEndings, []},
{Credo.Check.Consistency.ParameterPatternMatching, []},
{Credo.Check.Consistency.SpaceAroundOperators, []},
{Credo.Check.Consistency.SpaceInParentheses, []},
{Credo.Check.Consistency.TabsOrSpaces, []},

#
## Design Checks
#
# You can customize the priority of any check
# Priority values are: `low, normal, high, higher`
#
{Credo.Check.Design.AliasUsage, [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]},
# You can also customize the exit_status of each check.
# If you don't want TODO comments to cause `mix credo` to fail, just
# set this value to 0 (zero).
#
{Credo.Check.Design.TagTODO, [exit_status: 2]},
{Credo.Check.Design.TagFIXME, []},

#
## Readability Checks
#
{Credo.Check.Readability.AliasOrder, []},
{Credo.Check.Readability.FunctionNames, []},
{Credo.Check.Readability.LargeNumbers, []},
{Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]},
{Credo.Check.Readability.ModuleAttributeNames, []},
{Credo.Check.Readability.ModuleDoc, []},
{Credo.Check.Readability.ModuleNames, []},
{Credo.Check.Readability.ParenthesesInCondition, []},
{Credo.Check.Readability.ParenthesesOnZeroArityDefs, []},
{Credo.Check.Readability.PipeIntoAnonymousFunctions, []},
{Credo.Check.Readability.PredicateFunctionNames, []},
{Credo.Check.Readability.PreferImplicitTry, []},
{Credo.Check.Readability.RedundantBlankLines, []},
{Credo.Check.Readability.Semicolons, []},
{Credo.Check.Readability.SpaceAfterCommas, []},
{Credo.Check.Readability.StringSigils, []},
{Credo.Check.Readability.TrailingBlankLine, []},
{Credo.Check.Readability.TrailingWhiteSpace, []},
{Credo.Check.Readability.UnnecessaryAliasExpansion, []},
{Credo.Check.Readability.VariableNames, []},
{Credo.Check.Readability.WithSingleClause, []},

#
## Refactoring Opportunities
#
{Credo.Check.Refactor.Apply, []},
{Credo.Check.Refactor.CondStatements, []},
{Credo.Check.Refactor.CyclomaticComplexity, [max_complexity: 12]},
{Credo.Check.Refactor.FunctionArity, []},
{Credo.Check.Refactor.LongQuoteBlocks, []},
{Credo.Check.Refactor.MatchInCondition, []},
{Credo.Check.Refactor.MapJoin, []},
{Credo.Check.Refactor.NegatedConditionsInUnless, []},
{Credo.Check.Refactor.NegatedConditionsWithElse, []},
{Credo.Check.Refactor.Nesting, []},
{Credo.Check.Refactor.UnlessWithElse, []},
{Credo.Check.Refactor.WithClauses, []},
{Credo.Check.Refactor.FilterFilter, []},
{Credo.Check.Refactor.RejectReject, []},
{Credo.Check.Refactor.RedundantWithClauseResult, []},

#
## Warnings
#
{Credo.Check.Warning.ApplicationConfigInModuleAttribute, []},
{Credo.Check.Warning.BoolOperationOnSameValues, []},
{Credo.Check.Warning.ExpensiveEmptyEnumCheck, []},
{Credo.Check.Warning.IExPry, []},
{Credo.Check.Warning.IoInspect, []},
{Credo.Check.Warning.OperationOnSameValues, []},
{Credo.Check.Warning.OperationWithConstantResult, []},
{Credo.Check.Warning.RaiseInsideRescue, []},
{Credo.Check.Warning.SpecWithStruct, []},
{Credo.Check.Warning.WrongTestFileExtension, []},
{Credo.Check.Warning.UnusedEnumOperation, []},
{Credo.Check.Warning.UnusedFileOperation, []},
{Credo.Check.Warning.UnusedKeywordOperation, []},
{Credo.Check.Warning.UnusedListOperation, []},
{Credo.Check.Warning.UnusedPathOperation, []},
{Credo.Check.Warning.UnusedRegexOperation, []},
{Credo.Check.Warning.UnusedStringOperation, []},
{Credo.Check.Warning.UnusedTupleOperation, []},
{Credo.Check.Warning.UnsafeExec, []}
],
disabled: [
#
# Checks scheduled for next check update (opt-in for now, just replace `false` with `[]`)

#
# Controversial and experimental checks (opt-in, just move the check to `:enabled`
# and be sure to use `mix credo --strict` to see low priority checks)
#
{Credo.Check.Consistency.MultiAliasImportRequireUse, []},
{Credo.Check.Consistency.UnusedVariableNames, []},
{Credo.Check.Design.DuplicatedCode, []},
{Credo.Check.Design.SkipTestWithoutComment, []},
{Credo.Check.Readability.AliasAs, []},
{Credo.Check.Readability.BlockPipe, []},
{Credo.Check.Readability.ImplTrue, []},
{Credo.Check.Readability.MultiAlias, []},
{Credo.Check.Readability.NestedFunctionCalls, []},
{Credo.Check.Readability.SeparateAliasRequire, []},
{Credo.Check.Readability.SingleFunctionToBlockPipe, []},
{Credo.Check.Readability.SinglePipe, []},
{Credo.Check.Readability.Specs, []},
{Credo.Check.Readability.StrictModuleLayout, []},
{Credo.Check.Readability.WithCustomTaggedTuple, []},
{Credo.Check.Refactor.ABCSize, []},
{Credo.Check.Refactor.AppendSingleItem, []},
{Credo.Check.Refactor.DoubleBooleanNegation, []},
{Credo.Check.Refactor.FilterReject, []},
{Credo.Check.Refactor.IoPuts, []},
{Credo.Check.Refactor.MapMap, []},
{Credo.Check.Refactor.ModuleDependencies, []},
{Credo.Check.Refactor.NegatedIsNil, []},
{Credo.Check.Refactor.PipeChainStart, []},
{Credo.Check.Refactor.RejectFilter, []},
{Credo.Check.Refactor.VariableRebinding, []},
{Credo.Check.Warning.LazyLogging, []},
{Credo.Check.Warning.LeakyEnvironment, []},
{Credo.Check.Warning.MapGetUnsafePass, []},
{Credo.Check.Warning.MixEnv, []},
{Credo.Check.Warning.UnsafeToAtom, []}

# {Credo.Check.Refactor.MapInto, []},

#
# Custom checks can be created using `mix credo.gen.check`.
#
]
}
}
]
}
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.4.0] - 2021-04-27

### Fixed

- Calls to `render_component` now evaluate the AST aliases in the context of the `__CALLER__`
- EEx templates, components and layouts are tokenized prior to going through the MJML EEx engine as not to escape MJML content

## [0.3.0] - 2021-04-17

### Added
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ dependencies in `mix.exs`:
```elixir
def deps do
[
{:mjml_eex, "~> 0.3.0"}
{:mjml_eex, "~> 0.4.0"}
]
end
```
Expand Down Expand Up @@ -78,7 +78,7 @@ Checkout my [GitHub Sponsorship page](https://github.com/sponsors/akoutmos) if y

### Basic Usage

Add `{:mjml_eex, "~> 0.3.0"}` to your `mix.exs` file and run `mix deps.get`. After you have that in place, you
Add `{:mjml_eex, "~> 0.4.0"}` to your `mix.exs` file and run `mix deps.get`. After you have that in place, you
can go ahead and create a template module like so:

```elixir
Expand Down
35 changes: 21 additions & 14 deletions lib/engines/mjml.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ defmodule MjmlEEx.Engines.Mjml do
@behaviour EEx.Engine

@impl true
defdelegate init(opts), to: EEx.Engine
def init(opts) do
{caller, remaining_opts} = Keyword.pop!(opts, :caller)

remaining_opts
|> EEx.Engine.init()
|> Map.put(:caller, caller)
end

@impl true
defdelegate handle_body(state), to: EEx.Engine
Expand All @@ -23,31 +29,32 @@ defmodule MjmlEEx.Engines.Mjml do
defdelegate handle_text(state, meta, text), to: EEx.Engine

@impl true
def handle_expr(state, "=", {:render_component, _, [{:__aliases__, _, module}]}) do
do_render_component(state, module, [])
def handle_expr(state, "=", {:render_component, _, [{:__aliases__, _, _module} = aliases]}) do
module = Macro.expand(aliases, state.caller)

do_render_component(state, module, [], state.caller)
end

def handle_expr(state, "=", {:render_component, _, [{:__aliases__, _, module}, opts]}) do
do_render_component(state, module, opts)
def handle_expr(state, "=", {:render_component, _, [{:__aliases__, _, _module} = aliases, opts]}) do
module = Macro.expand(aliases, state.caller)

do_render_component(state, module, opts, state.caller)
end

def handle_expr(_state, _marker, {:render_component, _, _}) do
raise "render_component can only be invoked inside of an <%= ... %> expression"
end

def handle_expr(state, marker, expr) do
encoded_expression = Utils.encode_expression(marker, expr)

%{binary: binary} = state
%{state | binary: [encoded_expression | binary]}
def handle_expr(_state, marker, expr) do
raise "Unescaped expression. This should never happen and is most likely a bug in MJML EEx: <%#{marker} #{Macro.to_string(expr)} %>"
end

defp do_render_component(state, module_alias_list, opts) do
defp do_render_component(state, module, opts, caller) do
{mjml_component, _} =
module_alias_list
|> Module.concat()
module
|> apply(:render, [opts])
|> EEx.compile_string(engine: MjmlEEx.Engines.Mjml, line: 1, trim: true)
|> Utils.escape_eex_expressions()
|> EEx.compile_string(engine: MjmlEEx.Engines.Mjml, line: 1, trim: true, caller: caller)
|> Code.eval_quoted()

%{binary: binary} = state
Expand Down
26 changes: 17 additions & 9 deletions lib/mjml_eex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ defmodule MjmlEEx do

layout_module = Keyword.get(opts, :layout, false)

phoenix_html_ast =
{phoenix_html_ast, escaped_mjml_document} =
if layout_module do
layout_module = Macro.expand(layout_module, __CALLER__)

Code.ensure_compiled!(layout_module)
compile_with_layout(mjml_template, layout_module)
compile_with_layout(mjml_template, layout_module, __CALLER__)
else
compile_file(mjml_template)
compile_file(mjml_template, __CALLER__)
end

created_code =
Expand All @@ -75,6 +75,11 @@ defmodule MjmlEEx do
@external_resource unquote(layout_module).__layout_file__()
end

@doc "Returns the escaped MJML template. Useful for debugging rendering issues."
def debug_mjml_template do
unquote(escaped_mjml_document)
end

@doc "Safely render the MJML template using Phoenix.HTML"
def render(assigns) do
assigns
Expand All @@ -94,27 +99,30 @@ defmodule MjmlEEx do
created_code
end

defp compile_file(template_path) do
defp compile_file(template_path, caller) do
{mjml_document, _} =
template_path
|> EEx.compile_file(engine: MjmlEEx.Engines.Mjml, line: 1, trim: true)
|> File.read!()
|> Utils.escape_eex_expressions()
|> EEx.compile_string(engine: MjmlEEx.Engines.Mjml, line: 1, trim: true, caller: caller)
|> Code.eval_quoted()

compile_mjml_document(mjml_document)
{compile_mjml_document(mjml_document), mjml_document}
end

defp compile_with_layout(template_path, layout_module) do
defp compile_with_layout(template_path, layout_module, caller) do
template_file_contents = File.read!(template_path)
pre_inner_content = layout_module.pre_inner_content()
post_inner_content = layout_module.post_inner_content()

{mjml_document, _} =
[pre_inner_content, template_file_contents, post_inner_content]
|> Enum.join()
|> EEx.compile_string(engine: MjmlEEx.Engines.Mjml, line: 1, trim: true)
|> Utils.escape_eex_expressions()
|> EEx.compile_string(engine: MjmlEEx.Engines.Mjml, line: 1, trim: true, caller: caller)
|> Code.eval_quoted()

compile_mjml_document(mjml_document)
{compile_mjml_document(mjml_document), mjml_document}
end

defp compile_mjml_document(mjml_document) do
Expand Down
Loading

0 comments on commit ba01eca

Please sign in to comment.