From 5563d3ed9ba0a7c0c22d31b08e904b5a074de971 Mon Sep 17 00:00:00 2001 From: ilja Date: Sat, 10 Aug 2024 19:54:30 +0200 Subject: [PATCH 1/9] Revert "revert usage of temple" This reverts commit b21ab7754024af096f2d14247574f55f0063295b. --- lib/encoder.ex | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/encoder.ex b/lib/encoder.ex index 3e8c4c4..b510168 100644 --- a/lib/encoder.ex +++ b/lib/encoder.ex @@ -78,7 +78,7 @@ defmodule MfmParser.Encoder do {html_child, styles_child} = to_html_styles(node.children) {html <> - "#{html_child}", + "#{html_child}", styles ++ styles_child} %Node.MFM.Blur{} -> @@ -93,23 +93,23 @@ defmodule MfmParser.Encoder do %Node.MFM.Jelly{} -> {html_child, styles_child} = to_html_styles(node.children) - {html <> "#{html_child}", styles_child} + "#{html_child}" %Node.MFM.Tada{} -> {html_child, styles_child} = to_html_styles(node.children) {html <> - "#{html_child}", styles_child} + "#{html_child}"} %Node.MFM.Jump{} -> {html_child, styles_child} = to_html_styles(node.children) - {html <> "#{html_child}", styles_child} + "#{html_child}" %Node.MFM.Bounce{} -> {html_child, styles_child} = to_html_styles(node.children) - {html <> "#{html_child}", styles_child} + "#{html_child}" %Node.MFM.Spin{} -> {html_child, styles_child} = to_html_styles(node.children) @@ -128,12 +128,11 @@ defmodule MfmParser.Encoder do "y" => "mfm-spinY", "z" => "mfm-spin" } - directions_map = %{ - "left" => "reverse" - } + + "#{html_child}" {html <> - "#{html_child}", + "#{html_child}", styles ++ [Map.get(styles_map, node.props.axis, "")] ++ styles_child} %Node.MFM.Shake{} -> -- 2.43.0 From db79aa92dd85d0fbfd37569b3636dc9b2fecd2f8 Mon Sep 17 00:00:00 2001 From: ilja Date: Sat, 10 Aug 2024 19:55:01 +0200 Subject: [PATCH 2/9] Revert "Revert "use html generator for some things, fix x(x) styling"" This reverts commit f124e2812ef70fa76868e01137ae648302459f2c. --- lib/encoder.ex | 224 ++++++++++++++++++++---------------------- mix.exs | 6 +- test/encoder_test.exs | 2 +- 3 files changed, 110 insertions(+), 122 deletions(-) diff --git a/lib/encoder.ex b/lib/encoder.ex index b510168..43fe995 100644 --- a/lib/encoder.ex +++ b/lib/encoder.ex @@ -1,6 +1,7 @@ defmodule MfmParser.Encoder do alias MfmParser.Parser alias MfmParser.Node + import Temple @moduledoc """ An encoder who can turn a tree into HTML. @@ -27,171 +28,156 @@ defmodule MfmParser.Encoder do """ def to_html(tree) when is_list(tree) do - {html, _styles} = to_html_styles(tree) - - html + to_html_styles(tree) end def to_html(input) when is_binary(input) do Parser.parse(input) |> to_html() end - defp to_html_styles(tree, _style \\ []) do + defp to_html_styles(tree) do tree - |> Enum.reduce({"", []}, fn node, {html, styles} -> - case node do - %Node.Text{} -> - {html <> node.props.text, styles} + |> Enum.reduce("", fn node, html -> + html <> + case node do + %Node.Text{} -> + node.props.text - %Node.Newline{} -> - {html <> node.props.text, styles} + %Node.Newline{} -> + node.props.text - %Node.MFM.Flip{} -> - {html_child, styles_child} = to_html_styles(node.children) + %Node.MFM.Flip{} -> + html_child = to_html_styles(node.children) - case node.props do - %{v: true, h: true} -> - {html <> - "#{html_child}", - styles} + case node.props do + %{v: true, h: true} -> + temple do + span class: "mfm", style: "display: inline-block; transform: scale(-1);" do + html_child + end + end - %{v: true} -> - {html <> - "#{html_child}", - styles} + %{v: true} -> + "#{html_child}" - _ -> - {html <> - "#{html_child}", - styles ++ styles_child} - end + _ -> + "#{html_child}" + end - %Node.MFM.Font{} -> - {html_child, styles_child} = to_html_styles(node.children) + %Node.MFM.Font{} -> + html_child = to_html_styles(node.children) - {html <> - "#{html_child}", - styles ++ styles_child} + temple do + span class: "mfm", + style: "display: inline-block; font-family: #{node.props.font};" do + html_child + end + end - %Node.MFM.X{} -> - prop_map = %{"200%" => "2", "400%" => "3", "600%" => "4"} - {html_child, styles_child} = to_html_styles(node.children) + %Node.MFM.X{} -> + html_child = to_html_styles(node.children) + prop_map = %{"200%" => "2", "400%" => "3", "600%" => "4"} - {html <> - "#{html_child}", - styles ++ styles_child} + temple do + span style: "font-size: #{node.props.size}", class: "mfm _mfm_x#{prop_map[node.props.size]}_" do + html_child + end + end - %Node.MFM.Blur{} -> - {html_child, styles_child} = to_html_styles(node.children) + %Node.MFM.Blur{} -> + html_child = to_html_styles(node.children) - {html <> "#{html_child}", - styles ++ - [ - "._mfm_blur_ { filter: blur(6px); transition: filter .3s; } ._mfm_blur_:hover { filter: blur(0px); }" - ] ++ styles_child} + temple do + span(class: "mfm _mfm_blur_", do: html_child) + end - %Node.MFM.Jelly{} -> - {html_child, styles_child} = to_html_styles(node.children) + %Node.MFM.Jelly{} -> + html_child = to_html_styles(node.children) - "#{html_child}" + "#{html_child}" - %Node.MFM.Tada{} -> - {html_child, styles_child} = to_html_styles(node.children) + %Node.MFM.Tada{} -> + html_child = to_html_styles(node.children) - {html <> - "#{html_child}"} + temple do + span class: "mfm _mfm_tada_", + style: + "display: inline-block; font-size: 150%; animation: #{node.props.speed} linear 0s infinite normal both running mfm-tada;" do + html_child + end + end - %Node.MFM.Jump{} -> - {html_child, styles_child} = to_html_styles(node.children) + %Node.MFM.Jump{} -> + html_child = to_html_styles(node.children) "#{html_child}" - %Node.MFM.Bounce{} -> - {html_child, styles_child} = to_html_styles(node.children) + %Node.MFM.Bounce{} -> + html_child = to_html_styles(node.children) "#{html_child}" - %Node.MFM.Spin{} -> - {html_child, styles_child} = to_html_styles(node.children) + %Node.MFM.Spin{} -> + html_child = to_html_styles(node.children) - styles_map = %{ - "x" => - "@keyframes mfm-spinX { 0% { transform:perspective(128px) rotateX(0) } to { transform:perspective(128px) rotateX(360deg) }}", - "y" => - "@keyframes mfm-spinY { 0% { transform:perspective(128px) rotateY(0) } to { transform:perspective(128px) rotateY(360deg) }}", - "z" => - "@keyframes mfm-spin { 0% { transform:rotate(0) } to { transform:rotate(360deg) }}" - } + keyframe_names_map = %{ + "x" => "mfm-spinX", + "y" => "mfm-spinY", + "z" => "mfm-spin" + } - keyframe_names_map = %{ - "x" => "mfm-spinX", - "y" => "mfm-spinY", - "z" => "mfm-spin" - } + directions_map = %{ + "left" => "reverse" + } "#{html_child}" - {html <> - "#{html_child}", - styles ++ [Map.get(styles_map, node.props.axis, "")] ++ styles_child} + %Node.MFM.Shake{} -> + html_child = to_html_styles(node.children) - %Node.MFM.Shake{} -> - {html_child, styles_child} = to_html_styles(node.children) + temple do + span class: "mfm", + style: + "display: inline-block; animation: #{node.props.speed} ease 0s infinite normal none running mfm-shake;" do + html_child + end + end - {html <> - "#{html_child}", - styles ++ - [ - "@keyframes mfm-shake { 0% { transform:translate(-3px,-1px) rotate(-8deg) } 5% { transform:translateY(-1px) rotate(-10deg) } 10% { transform:translate(1px,-3px) rotate(0) } 15% { transform:translate(1px,1px) rotate(11deg) } 20% { transform:translate(-2px,1px) rotate(1deg) } 25% { transform:translate(-1px,-2px) rotate(-2deg) } 30% { transform:translate(-1px,2px) rotate(-3deg) } 35% { transform:translate(2px,1px) rotate(6deg) } 40% { transform:translate(-2px,-3px) rotate(-9deg) } 45% { transform:translateY(-1px) rotate(-12deg) } 50% { transform:translate(1px,2px) rotate(10deg) } 55% { transform:translateY(-3px) rotate(8deg) } 60% { transform:translate(1px,-1px) rotate(8deg) } 65% { transform:translateY(-1px) rotate(-7deg) } 70% { transform:translate(-1px,-3px) rotate(6deg) } 75% { transform:translateY(-2px) rotate(4deg) } 80% { transform:translate(-2px,-1px) rotate(3deg) } 85% { transform:translate(1px,-3px) rotate(-10deg) } 90% { transform:translate(1px) rotate(3deg) } 95% { transform:translate(-2px) rotate(-3deg) } to { transform:translate(2px,1px) rotate(2deg) }}" - ] ++ styles_child} + %Node.MFM.Twitch{} -> + html_child = to_html_styles(node.children) - %Node.MFM.Twitch{} -> - {html_child, styles_child} = to_html_styles(node.children) + temple do + span class: "mfm", + style: + "display: inline-block; animation: #{node.props.speed} ease 0s infinite normal none running mfm-twitch;" do + html_child + end + end - {html <> - "#{html_child}", - styles ++ - [ - "@keyframes mfm-twitch { 0% { transform:translate(7px,-2px) } 5% { transform:translate(-3px,1px) } 10% { transform:translate(-7px,-1px) } 15% { transform:translateY(-1px) } 20% { transform:translate(-8px,6px) } 25% { transform:translate(-4px,-3px) } 30% { transform:translate(-4px,-6px) } 35% { transform:translate(-8px,-8px) } 40% { transform:translate(4px,6px) } 45% { transform:translate(-3px,1px) } 50% { transform:translate(2px,-10px) } 55% { transform:translate(-7px) } 60% { transform:translate(-2px,4px) } 65% { transform:translate(3px,-8px) } 70% { transform:translate(6px,7px) } 75% { transform:translate(-7px,-2px) } 80% { transform:translate(-7px,-8px) } 85% { transform:translate(9px,3px) } 90% { transform:translate(-3px,-2px) } 95% { transform:translate(-10px,2px) } to { transform:translate(-2px,-6px) }}" - ] ++ styles_child} + %Node.MFM.Rainbow{} -> + html_child = to_html_styles(node.children) - %Node.MFM.Rainbow{} -> - {html_child, styles_child} = to_html_styles(node.children) + "#{html_child}" - {html <> - "#{html_child}", - styles ++ - [ - "@keyframes mfm-rainbow { 0% { filter:hue-rotate(0deg) contrast(150%) saturate(150%) } to { filter:hue-rotate(360deg) contrast(150%) saturate(150%) }}" - ] ++ styles_child} + %Node.MFM.Sparkle{} -> + # TODO: This is not how Misskey does it and should be changed to make it work like Misskey. + html_child = to_html_styles(node.children) - %Node.MFM.Sparkle{} -> - # TODO: This is not how Misskey does it and should be changed to make it work like Misskey. - {html_child, styles_child} = to_html_styles(node.children) + "#{html_child}" - {html <> - "#{html_child}", - styles ++ - [ - "@keyframes mfm-sparkle { 0% { filter: brightness(100%) } to { filter: brightness(300%) }}" - ] ++ styles_child} + %Node.MFM.Rotate{} -> + html_child = to_html_styles(node.children) - %Node.MFM.Rotate{} -> - {html_child, styles_child} = to_html_styles(node.children) + "#{html_child}" - {html <> - "#{html_child}", - styles ++ styles_child} + %Node.MFM.Undefined{} -> + html_child = to_html_styles(node.children) - %Node.MFM.Undefined{} -> - {html_child, styles_child} = to_html_styles(node.children) + "#{html_child}" - {html <> - "#{html_child}", styles ++ styles_child} - - _ -> - {html, styles} - end + _ -> + html + end end) end end diff --git a/mix.exs b/mix.exs index 7bc337a..6c201bd 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule MfmParser.MixProject do def project do [ app: :mfm_parser, - version: "0.1.0", + version: "0.1.1", elixir: "~> 1.13", start_permanent: Mix.env() == :prod, deps: deps() @@ -20,6 +20,8 @@ defmodule MfmParser.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do - [] + [ + {:temple, git: "https://akkoma.dev/AkkomaGang/temple.git", ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"} + ] end end diff --git a/test/encoder_test.exs b/test/encoder_test.exs index 6557e36..974b107 100644 --- a/test/encoder_test.exs +++ b/test/encoder_test.exs @@ -92,7 +92,7 @@ defmodule MfmParser.EncoderTest do } ] - expected = ~s[Misskey expands the world of the Fediverse] + expected = ~s[Misskey expands the world of the Fediverse] assert Encoder.to_html(input_tree) == expected end -- 2.43.0 From 6a8445612b7032514f91b4300f7c6bab4a718832 Mon Sep 17 00:00:00 2001 From: ilja Date: Sat, 10 Aug 2024 19:55:07 +0200 Subject: [PATCH 3/9] Revert "add classes to a bunch of things" This reverts commit 912fba81152d4d572e457fd5427f9875b2bc3dbe. --- lib/encoder.ex | 13 ++++++------- test/encoder_test.exs | 32 ++++++++++++++++---------------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/lib/encoder.ex b/lib/encoder.ex index 43fe995..a8e7be2 100644 --- a/lib/encoder.ex +++ b/lib/encoder.ex @@ -76,10 +76,9 @@ defmodule MfmParser.Encoder do %Node.MFM.X{} -> html_child = to_html_styles(node.children) - prop_map = %{"200%" => "2", "400%" => "3", "600%" => "4"} temple do - span style: "font-size: #{node.props.size}", class: "mfm _mfm_x#{prop_map[node.props.size]}_" do + span style: "font-size: #{node.props.size}" do html_child end end @@ -94,13 +93,13 @@ defmodule MfmParser.Encoder do %Node.MFM.Jelly{} -> html_child = to_html_styles(node.children) - "#{html_child}" + "#{html_child}" %Node.MFM.Tada{} -> html_child = to_html_styles(node.children) temple do - span class: "mfm _mfm_tada_", + span class: "mfm", style: "display: inline-block; font-size: 150%; animation: #{node.props.speed} linear 0s infinite normal both running mfm-tada;" do html_child @@ -110,12 +109,12 @@ defmodule MfmParser.Encoder do %Node.MFM.Jump{} -> html_child = to_html_styles(node.children) - "#{html_child}" + "#{html_child}" %Node.MFM.Bounce{} -> html_child = to_html_styles(node.children) - "#{html_child}" + "#{html_child}" %Node.MFM.Spin{} -> html_child = to_html_styles(node.children) @@ -130,7 +129,7 @@ defmodule MfmParser.Encoder do "left" => "reverse" } - "#{html_child}" + "#{html_child}" %Node.MFM.Shake{} -> html_child = to_html_styles(node.children) diff --git a/test/encoder_test.exs b/test/encoder_test.exs index 974b107..7623f56 100644 --- a/test/encoder_test.exs +++ b/test/encoder_test.exs @@ -80,7 +80,7 @@ defmodule MfmParser.EncoderTest do } ] - expected = ~s[🍮] + expected = ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -105,7 +105,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -118,7 +118,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -131,7 +131,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -144,7 +144,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -214,31 +214,31 @@ defmodule MfmParser.EncoderTest do ] expected_tree_z_left = - ~s[🍮] + ~s[🍮] expected_tree_x_left = - ~s[🍮] + ~s[🍮] expected_tree_y_left = - ~s[🍮] + ~s[🍮] expected_tree_z_alternate = - ~s[🍮] + ~s[🍮] expected_tree_x_alternate = - ~s[🍮] + ~s[🍮] expected_tree_y_alternate = - ~s[🍮] + ~s[🍮] expected_tree_z_normal = - ~s[🍮] + ~s[🍮] expected_tree_x_normal = - ~s[🍮] + ~s[🍮] expected_tree_y_normal = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree_z_left) == expected_tree_z_left assert Encoder.to_html(input_tree_x_left) == expected_tree_x_left @@ -414,7 +414,7 @@ defmodule MfmParser.EncoderTest do ] expected = - "It's not chocolatine\nit's pain au chocolat" + "It's not chocolatine\nit's pain au chocolat" assert Encoder.to_html(input_tree) == expected end @@ -424,7 +424,7 @@ defmodule MfmParser.EncoderTest do "It's not $[twitch.speed=0.2s chocolatine]\nit's $[x4 $[spin.speed=1s pain] $[rainbow.speed=2s au] $[jump.speed=0.5s chocolat]]" expected = - "It's not chocolatine\nit's pain au chocolat" + "It's not chocolatine\nit's pain au chocolat" assert Encoder.to_html(input) == expected end -- 2.43.0 From 61f503a1bf99ce3c8cb63c8e12e76336ca67177f Mon Sep 17 00:00:00 2001 From: ilja Date: Sat, 10 Aug 2024 19:55:08 +0200 Subject: [PATCH 4/9] Revert "update git URL" This reverts commit 51282dd6a784f4e75d6987ae3ceb91671e46dcfb. --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 6c201bd..07fd0e6 100644 --- a/mix.exs +++ b/mix.exs @@ -21,7 +21,7 @@ defmodule MfmParser.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:temple, git: "https://akkoma.dev/AkkomaGang/temple.git", ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"} + {:temple, git: "git@akkoma.dev:floatingghost/temple.git", ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"} ] end end diff --git a/mix.lock b/mix.lock index 874ba53..9e0949a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,4 +1,4 @@ %{ "phoenix_view": {:hex, :phoenix_view, "1.1.2", "1b82764a065fb41051637872c7bd07ed2fdb6f5c3bd89684d4dca6e10115c95a", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "7ae90ad27b09091266f6adbb61e1d2516a7c3d7062c6789d46a7554ec40f3a56"}, - "temple": {:git, "https://akkoma.dev/AkkomaGang/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]}, + "temple": {:git, "git@akkoma.dev:floatingghost/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]}, } -- 2.43.0 From 74f7f5964d783910ce2338da9a634928b3c61107 Mon Sep 17 00:00:00 2001 From: ilja Date: Sat, 10 Aug 2024 19:55:10 +0200 Subject: [PATCH 5/9] Revert "use html generator for some things, fix x(x) styling" This reverts commit 48d0da81e060fdfb161696fc4e7e8e19190c8746. --- lib/encoder.ex | 249 ++++++++++++++++++++++++------------------ mix.exs | 5 +- mix.lock | 4 - test/encoder_test.exs | 8 +- 4 files changed, 149 insertions(+), 117 deletions(-) delete mode 100644 mix.lock diff --git a/lib/encoder.ex b/lib/encoder.ex index a8e7be2..26f123a 100644 --- a/lib/encoder.ex +++ b/lib/encoder.ex @@ -1,7 +1,6 @@ defmodule MfmParser.Encoder do alias MfmParser.Parser alias MfmParser.Node - import Temple @moduledoc """ An encoder who can turn a tree into HTML. @@ -28,155 +27,191 @@ defmodule MfmParser.Encoder do """ def to_html(tree) when is_list(tree) do - to_html_styles(tree) + {html, _styles} = to_html_styles(tree) + + html end def to_html(input) when is_binary(input) do Parser.parse(input) |> to_html() end - defp to_html_styles(tree) do + defp to_html_styles(tree, _style \\ []) do tree - |> Enum.reduce("", fn node, html -> - html <> - case node do - %Node.Text{} -> - node.props.text + |> Enum.reduce({"", []}, fn node, {html, styles} -> + case node do + %Node.Text{} -> + {html <> node.props.text, styles} - %Node.Newline{} -> - node.props.text + %Node.Newline{} -> + {html <> node.props.text, styles} - %Node.MFM.Flip{} -> - html_child = to_html_styles(node.children) + %Node.MFM.Flip{} -> + {html_child, styles_child} = to_html_styles(node.children) - case node.props do - %{v: true, h: true} -> - temple do - span class: "mfm", style: "display: inline-block; transform: scale(-1);" do - html_child - end - end + case node.props do + %{v: true, h: true} -> + {html <> + "#{html_child}", + styles} - %{v: true} -> - "#{html_child}" + %{v: true} -> + {html <> + "#{html_child}", + styles} - _ -> - "#{html_child}" - end + _ -> + {html <> + "#{html_child}", + styles ++ styles_child} + end - %Node.MFM.Font{} -> - html_child = to_html_styles(node.children) + %Node.MFM.Font{} -> + {html_child, styles_child} = to_html_styles(node.children) - temple do - span class: "mfm", - style: "display: inline-block; font-family: #{node.props.font};" do - html_child - end - end + {html <> + "#{html_child}", + styles ++ styles_child} - %Node.MFM.X{} -> - html_child = to_html_styles(node.children) + %Node.MFM.X{} -> + {html_child, styles_child} = to_html_styles(node.children) - temple do - span style: "font-size: #{node.props.size}" do - html_child - end - end + {html <> + "#{html_child}", + styles ++ styles_child} - %Node.MFM.Blur{} -> - html_child = to_html_styles(node.children) + %Node.MFM.Blur{} -> + {html_child, styles_child} = to_html_styles(node.children) - temple do - span(class: "mfm _mfm_blur_", do: html_child) - end + {html <> "#{html_child}", + styles ++ + [ + "._mfm_blur_ { filter: blur(6px); transition: filter .3s; } ._mfm_blur_:hover { filter: blur(0px); }" + ] ++ styles_child} - %Node.MFM.Jelly{} -> - html_child = to_html_styles(node.children) + %Node.MFM.Jelly{} -> + {html_child, styles_child} = to_html_styles(node.children) - "#{html_child}" + {html <> + "#{html_child}", + styles ++ + [ + "@keyframes mfm-rubberBand { 0% { transform:scaleZ(1) } 30% { transform:scale3d(1.25,.75,1) } 40% { transform:scale3d(.75,1.25,1) } 50% { transform:scale3d(1.15,.85,1) } 65% { transform:scale3d(.95,1.05,1) } 75% { transform:scale3d(1.05,.95,1) } to { transform:scaleZ(1) }}" + ] ++ styles_child} - %Node.MFM.Tada{} -> - html_child = to_html_styles(node.children) + %Node.MFM.Tada{} -> + {html_child, styles_child} = to_html_styles(node.children) - temple do - span class: "mfm", - style: - "display: inline-block; font-size: 150%; animation: #{node.props.speed} linear 0s infinite normal both running mfm-tada;" do - html_child - end - end + {html <> + "#{html_child}", + styles ++ + [ + "@keyframes tada { 0% { transform: scaleZ(1); } 10%, 20% { transform: scale3d(.9,.9,.9) rotate3d(0,0,1,-3deg); } 30%, 50%, 70%, 90% { transform: scale3d(1.1,1.1,1.1) rotate3d(0,0,1,3deg); } 40%, 60%, 80% { transform: scale3d(1.1,1.1,1.1) rotate3d(0,0,1,-3deg); } 100% { transform: scaleZ(1); }}" + ] ++ styles_child} - %Node.MFM.Jump{} -> - html_child = to_html_styles(node.children) + %Node.MFM.Jump{} -> + {html_child, styles_child} = to_html_styles(node.children) - "#{html_child}" + {html <> + "#{html_child}", + styles ++ + [ + "@keyframes mfm-jump { 0% { transform:translateY(0) } 25% { transform:translateY(-16px) } 50% { transform:translateY(0) } 75% { transform:translateY(-8px) } to { transform:translateY(0) }}" + ] ++ styles_child} - %Node.MFM.Bounce{} -> - html_child = to_html_styles(node.children) + %Node.MFM.Bounce{} -> + {html_child, styles_child} = to_html_styles(node.children) - "#{html_child}" + {html <> + "#{html_child}", + styles ++ + [ + "@keyframes mfm-bounce { 0% { transform:translateY(0) scale(1) } 25% { transform:translateY(-16px) scale(1) } 50% { transform:translateY(0) scale(1) } 75% { transform:translateY(0) scale(1.5,.75) } to { transform:translateY(0) scale(1) }}" + ] ++ styles_child} - %Node.MFM.Spin{} -> - html_child = to_html_styles(node.children) + %Node.MFM.Spin{} -> + {html_child, styles_child} = to_html_styles(node.children) - keyframe_names_map = %{ - "x" => "mfm-spinX", - "y" => "mfm-spinY", - "z" => "mfm-spin" - } + styles_map = %{ + "x" => + "@keyframes mfm-spinX { 0% { transform:perspective(128px) rotateX(0) } to { transform:perspective(128px) rotateX(360deg) }}", + "y" => + "@keyframes mfm-spinY { 0% { transform:perspective(128px) rotateY(0) } to { transform:perspective(128px) rotateY(360deg) }}", + "z" => + "@keyframes mfm-spin { 0% { transform:rotate(0) } to { transform:rotate(360deg) }}" + } - directions_map = %{ - "left" => "reverse" - } + keyframe_names_map = %{ + "x" => "mfm-spinX", + "y" => "mfm-spinY", + "z" => "mfm-spin" + } - "#{html_child}" + directions_map = %{ + "left" => "reverse" + } - %Node.MFM.Shake{} -> - html_child = to_html_styles(node.children) + {html <> + "#{html_child}", + styles ++ [Map.get(styles_map, node.props.axis, "")] ++ styles_child} - temple do - span class: "mfm", - style: - "display: inline-block; animation: #{node.props.speed} ease 0s infinite normal none running mfm-shake;" do - html_child - end - end + %Node.MFM.Shake{} -> + {html_child, styles_child} = to_html_styles(node.children) - %Node.MFM.Twitch{} -> - html_child = to_html_styles(node.children) + {html <> + "#{html_child}", + styles ++ + [ + "@keyframes mfm-shake { 0% { transform:translate(-3px,-1px) rotate(-8deg) } 5% { transform:translateY(-1px) rotate(-10deg) } 10% { transform:translate(1px,-3px) rotate(0) } 15% { transform:translate(1px,1px) rotate(11deg) } 20% { transform:translate(-2px,1px) rotate(1deg) } 25% { transform:translate(-1px,-2px) rotate(-2deg) } 30% { transform:translate(-1px,2px) rotate(-3deg) } 35% { transform:translate(2px,1px) rotate(6deg) } 40% { transform:translate(-2px,-3px) rotate(-9deg) } 45% { transform:translateY(-1px) rotate(-12deg) } 50% { transform:translate(1px,2px) rotate(10deg) } 55% { transform:translateY(-3px) rotate(8deg) } 60% { transform:translate(1px,-1px) rotate(8deg) } 65% { transform:translateY(-1px) rotate(-7deg) } 70% { transform:translate(-1px,-3px) rotate(6deg) } 75% { transform:translateY(-2px) rotate(4deg) } 80% { transform:translate(-2px,-1px) rotate(3deg) } 85% { transform:translate(1px,-3px) rotate(-10deg) } 90% { transform:translate(1px) rotate(3deg) } 95% { transform:translate(-2px) rotate(-3deg) } to { transform:translate(2px,1px) rotate(2deg) }}" + ] ++ styles_child} - temple do - span class: "mfm", - style: - "display: inline-block; animation: #{node.props.speed} ease 0s infinite normal none running mfm-twitch;" do - html_child - end - end + %Node.MFM.Twitch{} -> + {html_child, styles_child} = to_html_styles(node.children) - %Node.MFM.Rainbow{} -> - html_child = to_html_styles(node.children) + {html <> + "#{html_child}", + styles ++ + [ + "@keyframes mfm-twitch { 0% { transform:translate(7px,-2px) } 5% { transform:translate(-3px,1px) } 10% { transform:translate(-7px,-1px) } 15% { transform:translateY(-1px) } 20% { transform:translate(-8px,6px) } 25% { transform:translate(-4px,-3px) } 30% { transform:translate(-4px,-6px) } 35% { transform:translate(-8px,-8px) } 40% { transform:translate(4px,6px) } 45% { transform:translate(-3px,1px) } 50% { transform:translate(2px,-10px) } 55% { transform:translate(-7px) } 60% { transform:translate(-2px,4px) } 65% { transform:translate(3px,-8px) } 70% { transform:translate(6px,7px) } 75% { transform:translate(-7px,-2px) } 80% { transform:translate(-7px,-8px) } 85% { transform:translate(9px,3px) } 90% { transform:translate(-3px,-2px) } 95% { transform:translate(-10px,2px) } to { transform:translate(-2px,-6px) }}" + ] ++ styles_child} - "#{html_child}" + %Node.MFM.Rainbow{} -> + {html_child, styles_child} = to_html_styles(node.children) - %Node.MFM.Sparkle{} -> - # TODO: This is not how Misskey does it and should be changed to make it work like Misskey. - html_child = to_html_styles(node.children) + {html <> + "#{html_child}", + styles ++ + [ + "@keyframes mfm-rainbow { 0% { filter:hue-rotate(0deg) contrast(150%) saturate(150%) } to { filter:hue-rotate(360deg) contrast(150%) saturate(150%) }}" + ] ++ styles_child} - "#{html_child}" + %Node.MFM.Sparkle{} -> + # TODO: This is not how Misskey does it and should be changed to make it work like Misskey. + {html_child, styles_child} = to_html_styles(node.children) - %Node.MFM.Rotate{} -> - html_child = to_html_styles(node.children) + {html <> + "#{html_child}", + styles ++ + [ + "@keyframes mfm-sparkle { 0% { filter: brightness(100%) } to { filter: brightness(300%) }}" + ] ++ styles_child} - "#{html_child}" + %Node.MFM.Rotate{} -> + {html_child, styles_child} = to_html_styles(node.children) - %Node.MFM.Undefined{} -> - html_child = to_html_styles(node.children) + {html <> + "#{html_child}", + styles ++ styles_child} - "#{html_child}" + %Node.MFM.Undefined{} -> + {html_child, styles_child} = to_html_styles(node.children) - _ -> - html - end + {html <> + "#{html_child}", styles ++ styles_child} + + _ -> + {html, styles} + end end) end end diff --git a/mix.exs b/mix.exs index 07fd0e6..31864b9 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule MfmParser.MixProject do def project do [ app: :mfm_parser, - version: "0.1.1", + version: "0.1.0", elixir: "~> 1.13", start_permanent: Mix.env() == :prod, deps: deps() @@ -21,7 +21,8 @@ defmodule MfmParser.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:temple, git: "git@akkoma.dev:floatingghost/temple.git", ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"} + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} ] end end diff --git a/mix.lock b/mix.lock deleted file mode 100644 index 9e0949a..0000000 --- a/mix.lock +++ /dev/null @@ -1,4 +0,0 @@ -%{ - "phoenix_view": {:hex, :phoenix_view, "1.1.2", "1b82764a065fb41051637872c7bd07ed2fdb6f5c3bd89684d4dca6e10115c95a", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "7ae90ad27b09091266f6adbb61e1d2516a7c3d7062c6789d46a7554ec40f3a56"}, - "temple": {:git, "git@akkoma.dev:floatingghost/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]}, -} diff --git a/test/encoder_test.exs b/test/encoder_test.exs index 7623f56..47f7c69 100644 --- a/test/encoder_test.exs +++ b/test/encoder_test.exs @@ -80,7 +80,7 @@ defmodule MfmParser.EncoderTest do } ] - expected = ~s[🍮] + expected = ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -92,7 +92,7 @@ defmodule MfmParser.EncoderTest do } ] - expected = ~s[Misskey expands the world of the Fediverse] + expected = ~s[Misskey expands the world of the Fediverse] assert Encoder.to_html(input_tree) == expected end @@ -414,7 +414,7 @@ defmodule MfmParser.EncoderTest do ] expected = - "It's not chocolatine\nit's pain au chocolat" + "It's not chocolatine\nit's pain au chocolat" assert Encoder.to_html(input_tree) == expected end @@ -424,7 +424,7 @@ defmodule MfmParser.EncoderTest do "It's not $[twitch.speed=0.2s chocolatine]\nit's $[x4 $[spin.speed=1s pain] $[rainbow.speed=2s au] $[jump.speed=0.5s chocolat]]" expected = - "It's not chocolatine\nit's pain au chocolat" + "It's not chocolatine\nit's pain au chocolat" assert Encoder.to_html(input) == expected end -- 2.43.0 From 69b8a2735907ee6d712514ff16457537188e8ffe Mon Sep 17 00:00:00 2001 From: ilja Date: Sat, 10 Aug 2024 19:55:11 +0200 Subject: [PATCH 6/9] Revert "include class on spans" This reverts commit 5054e0ba1ebcbd9a7916aec219528e3e58057241. --- lib/encoder.ex | 32 ++++++++++++------------- test/encoder_test.exs | 54 +++++++++++++++++++++---------------------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/lib/encoder.ex b/lib/encoder.ex index 26f123a..bf4c2e4 100644 --- a/lib/encoder.ex +++ b/lib/encoder.ex @@ -20,10 +20,10 @@ defmodule MfmParser.Encoder do ...> } ...> ] ...> |> MfmParser.Encoder.to_html() - "🍮" + "🍮" iex> MfmParser.Parser.parse("$[twitch.speed=5s 🍮]") |> MfmParser.Encoder.to_html() - "🍮" + "🍮" """ def to_html(tree) when is_list(tree) do @@ -52,17 +52,17 @@ defmodule MfmParser.Encoder do case node.props do %{v: true, h: true} -> {html <> - "#{html_child}", + "#{html_child}", styles} %{v: true} -> {html <> - "#{html_child}", + "#{html_child}", styles} _ -> {html <> - "#{html_child}", + "#{html_child}", styles ++ styles_child} end @@ -70,7 +70,7 @@ defmodule MfmParser.Encoder do {html_child, styles_child} = to_html_styles(node.children) {html <> - "#{html_child}", + "#{html_child}", styles ++ styles_child} %Node.MFM.X{} -> @@ -93,7 +93,7 @@ defmodule MfmParser.Encoder do {html_child, styles_child} = to_html_styles(node.children) {html <> - "#{html_child}", + "#{html_child}", styles ++ [ "@keyframes mfm-rubberBand { 0% { transform:scaleZ(1) } 30% { transform:scale3d(1.25,.75,1) } 40% { transform:scale3d(.75,1.25,1) } 50% { transform:scale3d(1.15,.85,1) } 65% { transform:scale3d(.95,1.05,1) } 75% { transform:scale3d(1.05,.95,1) } to { transform:scaleZ(1) }}" @@ -103,7 +103,7 @@ defmodule MfmParser.Encoder do {html_child, styles_child} = to_html_styles(node.children) {html <> - "#{html_child}", + "#{html_child}", styles ++ [ "@keyframes tada { 0% { transform: scaleZ(1); } 10%, 20% { transform: scale3d(.9,.9,.9) rotate3d(0,0,1,-3deg); } 30%, 50%, 70%, 90% { transform: scale3d(1.1,1.1,1.1) rotate3d(0,0,1,3deg); } 40%, 60%, 80% { transform: scale3d(1.1,1.1,1.1) rotate3d(0,0,1,-3deg); } 100% { transform: scaleZ(1); }}" @@ -113,7 +113,7 @@ defmodule MfmParser.Encoder do {html_child, styles_child} = to_html_styles(node.children) {html <> - "#{html_child}", + "#{html_child}", styles ++ [ "@keyframes mfm-jump { 0% { transform:translateY(0) } 25% { transform:translateY(-16px) } 50% { transform:translateY(0) } 75% { transform:translateY(-8px) } to { transform:translateY(0) }}" @@ -123,7 +123,7 @@ defmodule MfmParser.Encoder do {html_child, styles_child} = to_html_styles(node.children) {html <> - "#{html_child}", + "#{html_child}", styles ++ [ "@keyframes mfm-bounce { 0% { transform:translateY(0) scale(1) } 25% { transform:translateY(-16px) scale(1) } 50% { transform:translateY(0) scale(1) } 75% { transform:translateY(0) scale(1.5,.75) } to { transform:translateY(0) scale(1) }}" @@ -152,14 +152,14 @@ defmodule MfmParser.Encoder do } {html <> - "#{html_child}", + "#{html_child}", styles ++ [Map.get(styles_map, node.props.axis, "")] ++ styles_child} %Node.MFM.Shake{} -> {html_child, styles_child} = to_html_styles(node.children) {html <> - "#{html_child}", + "#{html_child}", styles ++ [ "@keyframes mfm-shake { 0% { transform:translate(-3px,-1px) rotate(-8deg) } 5% { transform:translateY(-1px) rotate(-10deg) } 10% { transform:translate(1px,-3px) rotate(0) } 15% { transform:translate(1px,1px) rotate(11deg) } 20% { transform:translate(-2px,1px) rotate(1deg) } 25% { transform:translate(-1px,-2px) rotate(-2deg) } 30% { transform:translate(-1px,2px) rotate(-3deg) } 35% { transform:translate(2px,1px) rotate(6deg) } 40% { transform:translate(-2px,-3px) rotate(-9deg) } 45% { transform:translateY(-1px) rotate(-12deg) } 50% { transform:translate(1px,2px) rotate(10deg) } 55% { transform:translateY(-3px) rotate(8deg) } 60% { transform:translate(1px,-1px) rotate(8deg) } 65% { transform:translateY(-1px) rotate(-7deg) } 70% { transform:translate(-1px,-3px) rotate(6deg) } 75% { transform:translateY(-2px) rotate(4deg) } 80% { transform:translate(-2px,-1px) rotate(3deg) } 85% { transform:translate(1px,-3px) rotate(-10deg) } 90% { transform:translate(1px) rotate(3deg) } 95% { transform:translate(-2px) rotate(-3deg) } to { transform:translate(2px,1px) rotate(2deg) }}" @@ -169,7 +169,7 @@ defmodule MfmParser.Encoder do {html_child, styles_child} = to_html_styles(node.children) {html <> - "#{html_child}", + "#{html_child}", styles ++ [ "@keyframes mfm-twitch { 0% { transform:translate(7px,-2px) } 5% { transform:translate(-3px,1px) } 10% { transform:translate(-7px,-1px) } 15% { transform:translateY(-1px) } 20% { transform:translate(-8px,6px) } 25% { transform:translate(-4px,-3px) } 30% { transform:translate(-4px,-6px) } 35% { transform:translate(-8px,-8px) } 40% { transform:translate(4px,6px) } 45% { transform:translate(-3px,1px) } 50% { transform:translate(2px,-10px) } 55% { transform:translate(-7px) } 60% { transform:translate(-2px,4px) } 65% { transform:translate(3px,-8px) } 70% { transform:translate(6px,7px) } 75% { transform:translate(-7px,-2px) } 80% { transform:translate(-7px,-8px) } 85% { transform:translate(9px,3px) } 90% { transform:translate(-3px,-2px) } 95% { transform:translate(-10px,2px) } to { transform:translate(-2px,-6px) }}" @@ -179,7 +179,7 @@ defmodule MfmParser.Encoder do {html_child, styles_child} = to_html_styles(node.children) {html <> - "#{html_child}", + "#{html_child}", styles ++ [ "@keyframes mfm-rainbow { 0% { filter:hue-rotate(0deg) contrast(150%) saturate(150%) } to { filter:hue-rotate(360deg) contrast(150%) saturate(150%) }}" @@ -190,7 +190,7 @@ defmodule MfmParser.Encoder do {html_child, styles_child} = to_html_styles(node.children) {html <> - "#{html_child}", + "#{html_child}", styles ++ [ "@keyframes mfm-sparkle { 0% { filter: brightness(100%) } to { filter: brightness(300%) }}" @@ -200,7 +200,7 @@ defmodule MfmParser.Encoder do {html_child, styles_child} = to_html_styles(node.children) {html <> - "#{html_child}", + "#{html_child}", styles ++ styles_child} %Node.MFM.Undefined{} -> diff --git a/test/encoder_test.exs b/test/encoder_test.exs index 47f7c69..7ba3789 100644 --- a/test/encoder_test.exs +++ b/test/encoder_test.exs @@ -45,13 +45,13 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[Misskey expands the world of the Fediverse] + ~s[Misskey expands the world of the Fediverse] expected_v = - ~s[Misskey expands the world of the Fediverse] + ~s[Misskey expands the world of the Fediverse] expected_h_v = - ~s[Misskey expands the world of the Fediverse] + ~s[Misskey expands the world of the Fediverse] assert Encoder.to_html(input_tree) == expected assert Encoder.to_html(input_tree_v) == expected_v @@ -67,7 +67,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[Misskey expands the world of the Fediverse] + ~s[Misskey expands the world of the Fediverse] assert Encoder.to_html(input_tree) == expected end @@ -105,7 +105,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -118,7 +118,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -131,7 +131,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -144,7 +144,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -214,31 +214,31 @@ defmodule MfmParser.EncoderTest do ] expected_tree_z_left = - ~s[🍮] + ~s[🍮] expected_tree_x_left = - ~s[🍮] + ~s[🍮] expected_tree_y_left = - ~s[🍮] + ~s[🍮] expected_tree_z_alternate = - ~s[🍮] + ~s[🍮] expected_tree_x_alternate = - ~s[🍮] + ~s[🍮] expected_tree_y_alternate = - ~s[🍮] + ~s[🍮] expected_tree_z_normal = - ~s[🍮] + ~s[🍮] expected_tree_x_normal = - ~s[🍮] + ~s[🍮] expected_tree_y_normal = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree_z_left) == expected_tree_z_left assert Encoder.to_html(input_tree_x_left) == expected_tree_x_left @@ -261,7 +261,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -274,7 +274,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -287,7 +287,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -301,7 +301,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -314,7 +314,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -344,7 +344,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮pain au chocolatMisskey expands the world of the Fediverse] + ~s[🍮pain au chocolatMisskey expands the world of the Fediverse] assert Encoder.to_html(input_tree) == expected end @@ -362,7 +362,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -378,7 +378,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮🍮] + ~s[🍮🍮] assert Encoder.to_html(input_tree) == expected end @@ -414,7 +414,7 @@ defmodule MfmParser.EncoderTest do ] expected = - "It's not chocolatine\nit's pain au chocolat" + "It's not chocolatine\nit's pain au chocolat" assert Encoder.to_html(input_tree) == expected end @@ -424,7 +424,7 @@ defmodule MfmParser.EncoderTest do "It's not $[twitch.speed=0.2s chocolatine]\nit's $[x4 $[spin.speed=1s pain] $[rainbow.speed=2s au] $[jump.speed=0.5s chocolat]]" expected = - "It's not chocolatine\nit's pain au chocolat" + "It's not chocolatine\nit's pain au chocolat" assert Encoder.to_html(input) == expected end -- 2.43.0 From 9358f483afaef1fe4742522382d8c83db528315f Mon Sep 17 00:00:00 2001 From: ilja Date: Sat, 10 Aug 2024 19:55:13 +0200 Subject: [PATCH 7/9] Revert "fix animation name" This reverts commit 03901025b0e703365325da9761a1f9cdf3f95f9f. --- lib/encoder.ex | 2 +- test/encoder_test.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/encoder.ex b/lib/encoder.ex index bf4c2e4..743ff20 100644 --- a/lib/encoder.ex +++ b/lib/encoder.ex @@ -103,7 +103,7 @@ defmodule MfmParser.Encoder do {html_child, styles_child} = to_html_styles(node.children) {html <> - "#{html_child}", + "#{html_child}", styles ++ [ "@keyframes tada { 0% { transform: scaleZ(1); } 10%, 20% { transform: scale3d(.9,.9,.9) rotate3d(0,0,1,-3deg); } 30%, 50%, 70%, 90% { transform: scale3d(1.1,1.1,1.1) rotate3d(0,0,1,3deg); } 40%, 60%, 80% { transform: scale3d(1.1,1.1,1.1) rotate3d(0,0,1,-3deg); } 100% { transform: scaleZ(1); }}" diff --git a/test/encoder_test.exs b/test/encoder_test.exs index 7ba3789..42793fb 100644 --- a/test/encoder_test.exs +++ b/test/encoder_test.exs @@ -118,7 +118,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end -- 2.43.0 From a5faf98ecd2a413eea4626d1e8af70db0253f76b Mon Sep 17 00:00:00 2001 From: ilja Date: Sat, 10 Aug 2024 19:55:14 +0200 Subject: [PATCH 8/9] Revert "don't append styles" This reverts commit 5e8723e54b06caf4f7773cc8d15696ccd0baf7af. --- README.md | 4 ---- lib/encoder.ex | 18 ++++++++++++++---- test/encoder_test.exs | 43 ++++++++++++++++++++++--------------------- 3 files changed, 36 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 9063ada..c55cda2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,3 @@ -# Akkoma-MFMParser - -extremely simple modification to [the original parser](https://codeberg.org/ilja/mfm_parser) that just doesn't include the CSS - # MfmParser A simple parser for [Misskey Flavoured Markdown](https://github.com/misskey-dev/mfm.js/). diff --git a/lib/encoder.ex b/lib/encoder.ex index 743ff20..e4cc387 100644 --- a/lib/encoder.ex +++ b/lib/encoder.ex @@ -20,16 +20,16 @@ defmodule MfmParser.Encoder do ...> } ...> ] ...> |> MfmParser.Encoder.to_html() - "🍮" + "🍮" iex> MfmParser.Parser.parse("$[twitch.speed=5s 🍮]") |> MfmParser.Encoder.to_html() - "🍮" + "🍮" """ def to_html(tree) when is_list(tree) do - {html, _styles} = to_html_styles(tree) + {html, styles} = to_html_styles(tree) - html + html |> append_styles_when_not_empty(styles) end def to_html(input) when is_binary(input) do @@ -214,4 +214,14 @@ defmodule MfmParser.Encoder do end end) end + + defp append_styles_when_not_empty(html, []) do + html + end + + defp append_styles_when_not_empty(html, styles) do + styles = styles |> Enum.uniq() |> Enum.reduce("", fn style, acc -> style <> acc end) + + html <> "" + end end diff --git a/test/encoder_test.exs b/test/encoder_test.exs index 42793fb..e8e8c1f 100644 --- a/test/encoder_test.exs +++ b/test/encoder_test.exs @@ -92,7 +92,8 @@ defmodule MfmParser.EncoderTest do } ] - expected = ~s[Misskey expands the world of the Fediverse] + expected = + ~s[Misskey expands the world of the Fediverse] assert Encoder.to_html(input_tree) == expected end @@ -105,7 +106,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -118,7 +119,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -131,7 +132,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -144,7 +145,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -214,31 +215,31 @@ defmodule MfmParser.EncoderTest do ] expected_tree_z_left = - ~s[🍮] + ~s[🍮] expected_tree_x_left = - ~s[🍮] + ~s[🍮] expected_tree_y_left = - ~s[🍮] + ~s[🍮] expected_tree_z_alternate = - ~s[🍮] + ~s[🍮] expected_tree_x_alternate = - ~s[🍮] + ~s[🍮] expected_tree_y_alternate = - ~s[🍮] + ~s[🍮] expected_tree_z_normal = - ~s[🍮] + ~s[🍮] expected_tree_x_normal = - ~s[🍮] + ~s[🍮] expected_tree_y_normal = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree_z_left) == expected_tree_z_left assert Encoder.to_html(input_tree_x_left) == expected_tree_x_left @@ -261,7 +262,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -274,7 +275,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -287,7 +288,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -301,7 +302,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮] + ~s[🍮] assert Encoder.to_html(input_tree) == expected end @@ -378,7 +379,7 @@ defmodule MfmParser.EncoderTest do ] expected = - ~s[🍮🍮] + ~s[🍮🍮] assert Encoder.to_html(input_tree) == expected end @@ -414,7 +415,7 @@ defmodule MfmParser.EncoderTest do ] expected = - "It's not chocolatine\nit's pain au chocolat" + "It's not chocolatine\nit's pain au chocolat" assert Encoder.to_html(input_tree) == expected end @@ -424,7 +425,7 @@ defmodule MfmParser.EncoderTest do "It's not $[twitch.speed=0.2s chocolatine]\nit's $[x4 $[spin.speed=1s pain] $[rainbow.speed=2s au] $[jump.speed=0.5s chocolat]]" expected = - "It's not chocolatine\nit's pain au chocolat" + "It's not chocolatine\nit's pain au chocolat" assert Encoder.to_html(input) == expected end -- 2.43.0 From 418068793fedd7cf557b6f39031d6cdb31689a00 Mon Sep 17 00:00:00 2001 From: ilja Date: Sun, 28 Jul 2024 11:56:47 +0200 Subject: [PATCH 9/9] Make parser FEP-c16b compliant This is basically a rewrite of big parts of the parser, introducing a lot of breaking changes. The parser was originally written mostly as an exercise for myself and not really aimed as-is for practical usage. An adapted version has been used in Akkoma, however, and this pointed out serious flaws in how MFM was done in general on the fediverse. This was discussed [on the Foundkey issue tracker](https://akkoma.dev/FoundKeyGang/FoundKey/issues/343) and a better way was decided. At the time of writing, this is being formalised into [FEP-c16b](https://codeberg.org/fediverse/fep/src/branch/main/fep/c16b/fep-c16b.md). This commit rewrites this parser to be FEP-c16b compliant. Previously, the parser had knowledge of the specific MFM functions. This was useful for setting default attribute values and adding specific CSS. This is not the case any more. The parser has no knowledge of specific MFM functions any more. It also had an understanding of the concept of newlines, this isn't the case any more either. It only does a "simple" translation from MFM function notation to FEP-c16b compliant HTML. Because of this, we also don't add CSS any more. It's up to the software who uses this HTML to decide what functions they want to provide and use the correct CSS. In practice the CSS from this parser was never used in Akkoma, so it's not really a loss. --- README.md | 84 +++--- lib/encoder.ex | 231 ++------------- lib/lexer.ex | 8 - lib/node.ex | 66 +---- lib/parser.ex | 214 +++++--------- lib/token.ex | 4 - mix.exs | 4 +- test/encoder_test.exs | 492 +++++++------------------------ test/lexer_test.exs | 13 - test/parser_test.exs | 654 ++++++------------------------------------ 10 files changed, 355 insertions(+), 1415 deletions(-) diff --git a/README.md b/README.md index c55cda2..5adb766 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,91 @@ # MfmParser -A simple parser for [Misskey Flavoured Markdown](https://github.com/misskey-dev/mfm.js/). +A simple [FEP-c16b](https://codeberg.org/fediverse/fep/src/branch/main/fep/c16b/fep-c16b.md) compliant parser for Misskey's [Markup language For Misskey](https://misskey-hub.net/en/docs/for-users/features/mfm/) MFM functions. -It only parses the MFM specific syntax of the form $[name.params content] and newlines. -That means that it doesn't parse links, usernames, HTML, Markdown or Katex. +It only parses the MFM specific syntax of the form `$[name.attributes content]`. +That means that it doesn't parse e.g. links, usernames, HTML, Markdown or Katex. -The Parser returns a tree, which looks like +The Parser returns a tree. For example, `it's not chocolatine, it's $[spin.alternate,speed=0.5s pain au chocolat]` will look like [ - %MfmParser.Text{ - props: %{ - text: "it's not chocolatine, it's " - } + %MfmParser.Node.Text{ + content: "it's not chocolatine, it's " }, - %MfmParser.MFM.Twitch{ - props: %{ - speed: "0.2s" - }, - children: [ - %MfmParser.Text{ - props: %{ - text: "pain au chocolat" - } + %MfmParser.Node.MFM{ + name: "twitch", + attributes: [ + [{"alternate"}, {"speed", "0.5s"}] + ], + content: [ + %MfmParser.Node.Text{ + content: "pain au chocolat" } ] } ] -You can also convert the tree into HTML. +You can also convert the tree into FEP-c16b compatible HTML. + + it's not chocolatine, it's pain au chocolat ## Examples - iex> MfmParser.Parser.parse("$[twitch.speed=5s 🍮]") +Here we turn our input into a tree + + iex> "$[twitch.speed=0.5s 🍮]" |> MfmParser.Parser.parse() [ - %MfmParser.Node.MFM.Twitch{ - children: [%MfmParser.Node.Text{props: %{text: "🍮"}}], - props: %{speed: "5s"} + %MfmParser.Node.MFM{ + name: "twitch", + attributes: [{"speed", "0.5s"}], + content: [%MfmParser.Node.Text{content: "pain au chocolat"}] } ] - iex> MfmParser.Parser.parse("$[twitch.speed=5s 🍮]") |> MfmParser.to_html() - "🍮" + +Here we pipe the MFM notation through the encoder and then the parser, turning the MFM into FEP-c16b compatible HTML. + + iex> "$[twitch.speed=0.5s 🍮]" |> MfmParser.Parser.parse() |> MfmParser.Encoder.to_html() + "🍮" + +Or we can use `MfmParser.Encoder.to_html/1` directly without having to call the parser ourselves. + + iex> "$[twitch.speed=0.5s 🍮]" |> MfmParser.Encoder.to_html() + "🍮" ## Reading ### The Parser -A [parser](https://en.wikipedia.org/wiki/Parsing#Parser) takes in structured text and outputs a so called "tree". A tree is a data structure which can be more easily worked with. +A [parser](https://en.wikipedia.org/wiki/Parsing#Parser) takes in structured text and outputs a so called "tree". A tree is a data structure which can be more easily worked with. A parser typically consists of three parts * a Reader * a Lexer (aka Tokeniser) * the Parser -A Reader typically has a `next` function which takes the next character out of the input and returns it. -A `peek` function allows it to peek at the next character without changing the input. -There's also some way of detecting if the eof (End Of File) is reached. -Depending on the needs of the parser, it may be implemented to allow asking for the nth character instead of just the next. +A Reader typically has a `next` function which takes the next character out of the input and returns it. +A `peek` function allows it to peek at the next character without changing the input. +There's also some way of detecting if the eof (End Of File) is reached. +Depending on the needs of the parser, it may be implemented to allow asking for the nth character instead of just the next. -A Lexer uses the Reader. It also has a `peek` and `next` function, but instead of returning the next (or nth) character, it returns the next (or nth) token. -E.g. if you have the MFM `$[spin some text]`, then `$[spin`, `some text`, and `]` can be considered three different tokens. +A Lexer uses the Reader. It also has a `peek` and `next` function, but instead of returning the next (or nth) character, it returns the next (or nth) token. +E.g. if you have the MFM `$[spin some text]`, then `$[spin`, `some text`, and `]` can be considered three different tokens. -The parser takes in the tokens and forms the tree. This is typically a data structure the programming language understands and can more easily work with. +The parser takes in the tokens and forms the tree. This is typically a data structure the programming language understands and can more easily work with. ### The Encoder -Once we have a good data structure, we can process this and do things with it. -E.g. an Encoder encodes the tree into a different format. +Once we have a good data structure, we can process this and do things with it. +E.g. an Encoder encodes the tree into a different format. ### The code -The code can be found in the *lib* folder. It contains, among other things, the Reader, Lexer, Parse, and Encoder modules. +The code can be found in the *lib* folder. It contains, among other things, the Reader, Lexer, Parser, and Encoder modules. -The *test* folder contains the unit tests. +The *test* folder contains the tests. ## License A parser/encoder for Misskey Flavoured Markdown. - Copyright (C) 2022 Ilja + Copyright (C) 2024 ilja.space This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as diff --git a/lib/encoder.ex b/lib/encoder.ex index e4cc387..0b99d43 100644 --- a/lib/encoder.ex +++ b/lib/encoder.ex @@ -1,227 +1,50 @@ defmodule MfmParser.Encoder do - alias MfmParser.Parser - alias MfmParser.Node - @moduledoc """ - An encoder who can turn a tree into HTML. + An encoder who can turn a String with MFM functions, or an MFM tree from `MfmParser.Parser.parse/1`, into FEP-c16b compliant HTML. - It only works for the MFM specific tags of the form $[name.opts content]. - - Other parts of MFM (html, Markdown and [KaTeX](https://katex.org/)) are out of scope for this project. - - It can directly take input from function `MfmParser.Parser.parse`. + It only works for the MFM specific tags of the form `$[name.attributes content]`. Other parts of MFM (e.g. html, Markdown and [KaTeX](https://katex.org/)) are out of scope for this project. ## Examples iex> [ - ...> %MfmParser.Node.MFM.Twitch{ - ...> children: [%MfmParser.Node.Text{props: %{text: "🍮"}}], - ...> props: %{speed: "5s"} + ...> %MfmParser.Node.MFM{ + ...> name: "twitch", + ...> content: [%MfmParser.Node.Text{content: "🍮"}], + ...> attributes: [{"speed", "5s"}] ...> } ...> ] ...> |> MfmParser.Encoder.to_html() - "🍮" + ~S[🍮] - iex> MfmParser.Parser.parse("$[twitch.speed=5s 🍮]") |> MfmParser.Encoder.to_html() - "🍮" + iex> "$[twitch.speed=5s 🍮]" |> MfmParser.Encoder.to_html() + ~S[🍮] """ - def to_html(tree) when is_list(tree) do - {html, styles} = to_html_styles(tree) - - html |> append_styles_when_not_empty(styles) - end - - def to_html(input) when is_binary(input) do - Parser.parse(input) |> to_html() - end - - defp to_html_styles(tree, _style \\ []) do - tree - |> Enum.reduce({"", []}, fn node, {html, styles} -> + def to_html([node | rest]) do + node_html = case node do - %Node.Text{} -> - {html <> node.props.text, styles} + %MfmParser.Node.Text{content: content} -> + content - %Node.Newline{} -> - {html <> node.props.text, styles} + %MfmParser.Node.MFM{name: name, attributes: attributes, content: content} -> + attributes_string = + attributes + |> Enum.reduce("", fn + {name}, acc -> acc <> " data-mfm-#{name}" + {name, value}, acc -> acc <> " data-mfm-#{name}=\"#{value}\"" + end) - %Node.MFM.Flip{} -> - {html_child, styles_child} = to_html_styles(node.children) - - case node.props do - %{v: true, h: true} -> - {html <> - "#{html_child}", - styles} - - %{v: true} -> - {html <> - "#{html_child}", - styles} - - _ -> - {html <> - "#{html_child}", - styles ++ styles_child} - end - - %Node.MFM.Font{} -> - {html_child, styles_child} = to_html_styles(node.children) - - {html <> - "#{html_child}", - styles ++ styles_child} - - %Node.MFM.X{} -> - {html_child, styles_child} = to_html_styles(node.children) - - {html <> - "#{html_child}", - styles ++ styles_child} - - %Node.MFM.Blur{} -> - {html_child, styles_child} = to_html_styles(node.children) - - {html <> "#{html_child}", - styles ++ - [ - "._mfm_blur_ { filter: blur(6px); transition: filter .3s; } ._mfm_blur_:hover { filter: blur(0px); }" - ] ++ styles_child} - - %Node.MFM.Jelly{} -> - {html_child, styles_child} = to_html_styles(node.children) - - {html <> - "#{html_child}", - styles ++ - [ - "@keyframes mfm-rubberBand { 0% { transform:scaleZ(1) } 30% { transform:scale3d(1.25,.75,1) } 40% { transform:scale3d(.75,1.25,1) } 50% { transform:scale3d(1.15,.85,1) } 65% { transform:scale3d(.95,1.05,1) } 75% { transform:scale3d(1.05,.95,1) } to { transform:scaleZ(1) }}" - ] ++ styles_child} - - %Node.MFM.Tada{} -> - {html_child, styles_child} = to_html_styles(node.children) - - {html <> - "#{html_child}", - styles ++ - [ - "@keyframes tada { 0% { transform: scaleZ(1); } 10%, 20% { transform: scale3d(.9,.9,.9) rotate3d(0,0,1,-3deg); } 30%, 50%, 70%, 90% { transform: scale3d(1.1,1.1,1.1) rotate3d(0,0,1,3deg); } 40%, 60%, 80% { transform: scale3d(1.1,1.1,1.1) rotate3d(0,0,1,-3deg); } 100% { transform: scaleZ(1); }}" - ] ++ styles_child} - - %Node.MFM.Jump{} -> - {html_child, styles_child} = to_html_styles(node.children) - - {html <> - "#{html_child}", - styles ++ - [ - "@keyframes mfm-jump { 0% { transform:translateY(0) } 25% { transform:translateY(-16px) } 50% { transform:translateY(0) } 75% { transform:translateY(-8px) } to { transform:translateY(0) }}" - ] ++ styles_child} - - %Node.MFM.Bounce{} -> - {html_child, styles_child} = to_html_styles(node.children) - - {html <> - "#{html_child}", - styles ++ - [ - "@keyframes mfm-bounce { 0% { transform:translateY(0) scale(1) } 25% { transform:translateY(-16px) scale(1) } 50% { transform:translateY(0) scale(1) } 75% { transform:translateY(0) scale(1.5,.75) } to { transform:translateY(0) scale(1) }}" - ] ++ styles_child} - - %Node.MFM.Spin{} -> - {html_child, styles_child} = to_html_styles(node.children) - - styles_map = %{ - "x" => - "@keyframes mfm-spinX { 0% { transform:perspective(128px) rotateX(0) } to { transform:perspective(128px) rotateX(360deg) }}", - "y" => - "@keyframes mfm-spinY { 0% { transform:perspective(128px) rotateY(0) } to { transform:perspective(128px) rotateY(360deg) }}", - "z" => - "@keyframes mfm-spin { 0% { transform:rotate(0) } to { transform:rotate(360deg) }}" - } - - keyframe_names_map = %{ - "x" => "mfm-spinX", - "y" => "mfm-spinY", - "z" => "mfm-spin" - } - - directions_map = %{ - "left" => "reverse" - } - - {html <> - "#{html_child}", - styles ++ [Map.get(styles_map, node.props.axis, "")] ++ styles_child} - - %Node.MFM.Shake{} -> - {html_child, styles_child} = to_html_styles(node.children) - - {html <> - "#{html_child}", - styles ++ - [ - "@keyframes mfm-shake { 0% { transform:translate(-3px,-1px) rotate(-8deg) } 5% { transform:translateY(-1px) rotate(-10deg) } 10% { transform:translate(1px,-3px) rotate(0) } 15% { transform:translate(1px,1px) rotate(11deg) } 20% { transform:translate(-2px,1px) rotate(1deg) } 25% { transform:translate(-1px,-2px) rotate(-2deg) } 30% { transform:translate(-1px,2px) rotate(-3deg) } 35% { transform:translate(2px,1px) rotate(6deg) } 40% { transform:translate(-2px,-3px) rotate(-9deg) } 45% { transform:translateY(-1px) rotate(-12deg) } 50% { transform:translate(1px,2px) rotate(10deg) } 55% { transform:translateY(-3px) rotate(8deg) } 60% { transform:translate(1px,-1px) rotate(8deg) } 65% { transform:translateY(-1px) rotate(-7deg) } 70% { transform:translate(-1px,-3px) rotate(6deg) } 75% { transform:translateY(-2px) rotate(4deg) } 80% { transform:translate(-2px,-1px) rotate(3deg) } 85% { transform:translate(1px,-3px) rotate(-10deg) } 90% { transform:translate(1px) rotate(3deg) } 95% { transform:translate(-2px) rotate(-3deg) } to { transform:translate(2px,1px) rotate(2deg) }}" - ] ++ styles_child} - - %Node.MFM.Twitch{} -> - {html_child, styles_child} = to_html_styles(node.children) - - {html <> - "#{html_child}", - styles ++ - [ - "@keyframes mfm-twitch { 0% { transform:translate(7px,-2px) } 5% { transform:translate(-3px,1px) } 10% { transform:translate(-7px,-1px) } 15% { transform:translateY(-1px) } 20% { transform:translate(-8px,6px) } 25% { transform:translate(-4px,-3px) } 30% { transform:translate(-4px,-6px) } 35% { transform:translate(-8px,-8px) } 40% { transform:translate(4px,6px) } 45% { transform:translate(-3px,1px) } 50% { transform:translate(2px,-10px) } 55% { transform:translate(-7px) } 60% { transform:translate(-2px,4px) } 65% { transform:translate(3px,-8px) } 70% { transform:translate(6px,7px) } 75% { transform:translate(-7px,-2px) } 80% { transform:translate(-7px,-8px) } 85% { transform:translate(9px,3px) } 90% { transform:translate(-3px,-2px) } 95% { transform:translate(-10px,2px) } to { transform:translate(-2px,-6px) }}" - ] ++ styles_child} - - %Node.MFM.Rainbow{} -> - {html_child, styles_child} = to_html_styles(node.children) - - {html <> - "#{html_child}", - styles ++ - [ - "@keyframes mfm-rainbow { 0% { filter:hue-rotate(0deg) contrast(150%) saturate(150%) } to { filter:hue-rotate(360deg) contrast(150%) saturate(150%) }}" - ] ++ styles_child} - - %Node.MFM.Sparkle{} -> - # TODO: This is not how Misskey does it and should be changed to make it work like Misskey. - {html_child, styles_child} = to_html_styles(node.children) - - {html <> - "#{html_child}", - styles ++ - [ - "@keyframes mfm-sparkle { 0% { filter: brightness(100%) } to { filter: brightness(300%) }}" - ] ++ styles_child} - - %Node.MFM.Rotate{} -> - {html_child, styles_child} = to_html_styles(node.children) - - {html <> - "#{html_child}", - styles ++ styles_child} - - %Node.MFM.Undefined{} -> - {html_child, styles_child} = to_html_styles(node.children) - - {html <> - "#{html_child}", styles ++ styles_child} - - _ -> - {html, styles} + "#{to_html(content)}" end - end) + + node_html <> to_html(rest) end - defp append_styles_when_not_empty(html, []) do - html + def to_html([]) do + "" end - defp append_styles_when_not_empty(html, styles) do - styles = styles |> Enum.uniq() |> Enum.reduce("", fn style, acc -> style <> acc end) - - html <> "" + def to_html(mfm_string) when is_binary(mfm_string) do + MfmParser.Parser.parse(mfm_string) |> to_html() end end diff --git a/lib/lexer.ex b/lib/lexer.ex index 54e86a9..61b0458 100644 --- a/lib/lexer.ex +++ b/lib/lexer.ex @@ -3,7 +3,6 @@ defmodule MfmParser.Lexer do alias MfmParser.Token alias MfmParser.Token.MFM - alias MfmParser.Token.Newline alias MfmParser.Token.Text def peek(input) do @@ -44,9 +43,6 @@ defmodule MfmParser.Lexer do "]" -> %MFM.Close{} - "\n" -> - %Newline{} - _ -> %Text{} end @@ -60,10 +56,6 @@ defmodule MfmParser.Lexer do true end - defp is_last_char_of_token?(_, _, %Newline{}) do - true - end - defp is_last_char_of_token?(_, rest, %Text{}) do case Reader.next(rest) do :eof -> true diff --git a/lib/node.ex b/lib/node.ex index d160362..c0fb747 100644 --- a/lib/node.ex +++ b/lib/node.ex @@ -1,67 +1,7 @@ defmodule MfmParser.Node.Text do - defstruct props: %{text: ""} + defstruct content: "" end -defmodule MfmParser.Node.Newline do - defstruct props: %{text: "\n"} -end - -defmodule MfmParser.Node.MFM.Blur do - defstruct props: %{}, children: [] -end - -defmodule MfmParser.Node.MFM.Bounce do - defstruct props: %{speed: "0.75s"}, children: [] -end - -defmodule MfmParser.Node.MFM.Flip do - defstruct props: %{v: false, h: false}, children: [] -end - -defmodule MfmParser.Node.MFM.Font do - defstruct props: %{font: nil}, children: [] -end - -defmodule MfmParser.Node.MFM.Jelly do - defstruct props: %{speed: "1s"}, children: [] -end - -defmodule MfmParser.Node.MFM.Jump do - defstruct props: %{speed: "0.75s"}, children: [] -end - -defmodule MfmParser.Node.MFM.Rainbow do - defstruct props: %{speed: "1s"}, children: [] -end - -defmodule MfmParser.Node.MFM.Rotate do - defstruct props: %{}, children: [] -end - -defmodule MfmParser.Node.MFM.Shake do - defstruct props: %{speed: "0.5s"}, children: [] -end - -defmodule MfmParser.Node.MFM.Sparkle do - defstruct props: %{}, children: [] -end - -defmodule MfmParser.Node.MFM.Spin do - defstruct props: %{axis: "z", direction: "normal", speed: "1.5s"}, children: [] -end - -defmodule MfmParser.Node.MFM.Tada do - defstruct props: %{speed: "1s"}, children: [] -end - -defmodule MfmParser.Node.MFM.Twitch do - defstruct props: %{speed: "0.5s"}, children: [] -end - -defmodule MfmParser.Node.MFM.Undefined do - defstruct props: %{}, children: [] -end - -defmodule MfmParser.Node.MFM.X do - defstruct props: %{size: nil}, children: [] +defmodule MfmParser.Node.MFM do + defstruct name: "", attributes: %{}, content: [] end diff --git a/lib/parser.ex b/lib/parser.ex index 6fe3254..0bf6f55 100644 --- a/lib/parser.ex +++ b/lib/parser.ex @@ -4,164 +4,98 @@ defmodule MfmParser.Parser do alias MfmParser.Lexer @moduledoc """ - `MfmParser` is a parser for [Misskey Flavoured Markdown](https://mk.nixnet.social/mfm-cheat-sheet). + `MfmParser` is a [FEP-c16b](https://codeberg.org/fediverse/fep/src/branch/main/fep/c16b/fep-c16b.md) compatible parser for Misskey's [Markup language For Misskey](https://misskey-hub.net/en/docs/for-users/features/mfm/) MFM functions. - It can parse MFM and return a tree. It also has an encoder who can turn a tree into HTML. + It can parse a string representing text containing MFM functions and return a tree. There's also has an encoder who can turn a tree into HTML. - It only works for the MFM specific tags of the form $[name.opts content]. + It only parses the MFM specific tags of the form $[name.opts content]. - Other parts of MFM (html, Markdown and [KaTeX](https://katex.org/)) are out of scope for this project. + Other parts of MFM (html, Markdown and [KaTeX](https://katex.org/)) are out of scope for this project. ## Examples - iex> MfmParser.Parser.parse("$[twitch.speed=5s 🍮]") + iex> MfmParser.Parser.parse("$[twitch.speed=0.5s 🍮]") [ - %MfmParser.Node.MFM.Twitch{ - children: [%MfmParser.Node.Text{props: %{text: "🍮"}}], - props: %{speed: "5s"} + %MfmParser.Node.MFM{ + name: "twitch", + attributes: [{"speed", "0.5s"}], + content: [%MfmParser.Node.Text{content: "🍮"}] } ] """ - def parse(input, tree \\ [], is_end_token \\ fn _ -> false end) do + def parse(input, tree \\ [], is_open \\ false) do case Lexer.next(input) do + {token, rest} -> + case token do + %Token.Text{} -> + parse( + rest, + tree ++ [%Node.Text{content: token.content}], + is_open + ) + + %Token.MFM.Open{} -> + # Here we go deeper in the structure + {children, rest} = + case parse(rest, [], true) do + {children, child_rest} -> {children, child_rest} + # Here we capture an edge case where an unclosed tag makes us hit :eof + # this causes the tree to be returned directly instead of part of a tuple + children -> {children, ""} + end + + # Here we went dept already, so now we are parsing the next Open token on the same level + parse( + rest, + tree ++ [token |> get_mfm_node() |> Map.put(:content, children)], + is_open + ) + + # We can either have a Close token who properly closes an Open token + # Or we can have a stray Close token, while currently not processing an Open token + # In the first case, we return what we have bc parsing of this Node is finished + # In the second case, we add it as text + %Token.MFM.Close{} -> + if is_open do + {tree, rest} + else + parse( + rest, + tree ++ [%Node.Text{content: token.content}] + ) + end + end + :eof -> tree + end + end - {token, rest} -> - if is_end_token.(token) do - {tree, rest} - else - case token do - %Token.MFM.Open{} -> - {children, rest} = - case parse(rest, [], &is_mfm_close_token?/1) do - {children, rest} -> - {children, rest} + defp get_mfm_node(token) do + {name, attributes} = + case token.content + |> String.trim() + |> String.replace("$[", "") + |> String.split(".", parts: 2) do + [name] -> {name, []} + [name, attributes_string] -> {name, build_attributes_list(attributes_string)} + end - _ -> - {[], rest} - end + %Node.MFM{name: name, attributes: attributes, content: []} + end - parse( - rest, - tree ++ [token |> get_node() |> Map.put(:children, children)], - is_end_token - ) - - %Token.Text{} -> - parse( - rest, - tree ++ [%Node.Text{props: %{text: token.content}}], - is_end_token - ) - - %Token.Newline{} -> - parse( - rest, - tree ++ [%Node.Newline{props: %{text: token.content}}], - is_end_token - ) - - %Token.MFM.Close{} -> - parse( - rest, - tree ++ [%Node.Text{props: %{text: token.content}}], - is_end_token - ) - end + defp build_attributes_list(attributes_string) do + attributes_string + |> String.split(",") + |> Enum.reduce([], fn attribute_string, acc -> + attribute = + case attribute_string |> String.split("=") do + [name] -> {name} + [name, value] -> {name, value} end - end - end - defp is_mfm_close_token?(token) do - case token do - %Token.MFM.Close{} -> true - _ -> false - end - end - - defp get_node(token = %{content: content}) do - cond do - content =~ "$[flip" -> %Node.MFM.Flip{} - content =~ "$[font" -> %Node.MFM.Font{} - content =~ "$[x" -> %Node.MFM.X{} - content =~ "$[blur" -> %Node.MFM.Blur{} - content =~ "$[jelly" -> %Node.MFM.Jelly{} - content =~ "$[tada" -> %Node.MFM.Tada{} - content =~ "$[jump" -> %Node.MFM.Jump{} - content =~ "$[bounce" -> %Node.MFM.Bounce{} - content =~ "$[spin" -> %Node.MFM.Spin{} - content =~ "$[shake" -> %Node.MFM.Shake{} - content =~ "$[twitch" -> %Node.MFM.Twitch{} - content =~ "$[rainbow" -> %Node.MFM.Rainbow{} - content =~ "$[sparkle" -> %Node.MFM.Sparkle{} - content =~ "$[rotate" -> %Node.MFM.Rotate{} - true -> %Node.MFM.Undefined{} - end - |> fill_props(token) - end - - defp fill_props(node = %{props: props}, %{content: content}) do - new_props = props |> Map.merge(to_props(content)) - - node |> Map.merge(%{props: new_props}) - end - - def to_props(opts_string) when is_binary(opts_string) do - cond do - opts_string =~ "." -> - Regex.replace(~r/^.*?\./u, opts_string, "") - |> String.trim() - |> String.split(",") - |> Enum.reduce(%{}, fn opt, acc -> - acc - |> Map.merge( - cond do - opt =~ "speed" -> - %{speed: String.replace(opt, "speed=", "")} - - opt =~ "v" -> - %{v: true} - - opt =~ "h" -> - %{h: true} - - opt =~ "x" -> - %{axis: "x"} - - opt =~ "y" -> - %{axis: "y"} - - opt =~ "left" -> - %{direction: "left"} - - opt =~ "alternate" -> - %{direction: "alternate"} - - true -> - if Regex.match?(~r/^\$\[font/, opts_string) do - %{font: opt} - else - %{} - end - end - ) - end) - - opts_string =~ "$[x" -> - %{ - size: - case opts_string |> String.replace("$[x", "") |> String.trim() do - "2" -> "200%" - "3" -> "400%" - "4" -> "600%" - _ -> "100%" - end - } - - true -> - %{} - end + acc ++ [attribute] + end) end end diff --git a/lib/token.ex b/lib/token.ex index 0efc237..80b8935 100644 --- a/lib/token.ex +++ b/lib/token.ex @@ -8,10 +8,6 @@ defmodule MfmParser.Token.Text do defstruct content: "" end -defmodule MfmParser.Token.Newline do - defstruct content: "" -end - defmodule MfmParser.Token.MFM.Open do defstruct content: "" end diff --git a/mix.exs b/mix.exs index 31864b9..a296494 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule MfmParser.MixProject do def project do [ app: :mfm_parser, - version: "0.1.0", + version: "0.2.0", elixir: "~> 1.13", start_permanent: Mix.env() == :prod, deps: deps() @@ -14,7 +14,7 @@ defmodule MfmParser.MixProject do # Run "mix help compile.app" to learn about applications. def application do [ - extra_applications: [:logger] + # extra_applications: [:logger] ] end diff --git a/test/encoder_test.exs b/test/encoder_test.exs index e8e8c1f..1c398c2 100644 --- a/test/encoder_test.exs +++ b/test/encoder_test.exs @@ -8,426 +8,156 @@ defmodule MfmParser.EncoderTest do describe "to_html" do test "it handles text" do - input_tree = [%Node.Text{props: %{text: "chocolatine"}}] + input_tree = [%Node.Text{content: "chocolatine"}] expected = "chocolatine" assert Encoder.to_html(input_tree) == expected end - test "it handles newlines" do - input_tree = [%Node.Newline{props: %{text: "\n"}}] + test "it handles a node without attributes" do + input = [%Node.MFM{name: "flip", attributes: %{}, content: []}] - expected = "\n" + expected = "" - assert Encoder.to_html(input_tree) == expected + assert Encoder.to_html(input) == expected end - test "it handles flip" do - input_tree = [ - %Node.MFM.Flip{ - children: [%Node.Text{props: %{text: "Misskey expands the world of the Fediverse"}}] - } - ] + test "it handles a node with a non-value attribute" do + input = [%Node.MFM{name: "font", attributes: [{"cursive"}], content: []}] - input_tree_v = [ - %Node.MFM.Flip{ - children: [%Node.Text{props: %{text: "Misskey expands the world of the Fediverse"}}], - props: %{v: true} - } - ] + expected = "" - input_tree_h_v = [ - %Node.MFM.Flip{ - children: [%Node.Text{props: %{text: "Misskey expands the world of the Fediverse"}}], - props: %{v: true, h: true} - } - ] - - expected = - ~s[Misskey expands the world of the Fediverse] - - expected_v = - ~s[Misskey expands the world of the Fediverse] - - expected_h_v = - ~s[Misskey expands the world of the Fediverse] - - assert Encoder.to_html(input_tree) == expected - assert Encoder.to_html(input_tree_v) == expected_v - assert Encoder.to_html(input_tree_h_v) == expected_h_v + assert Encoder.to_html(input) == expected end - test "it handles font" do - input_tree = [ - %Node.MFM.Font{ - children: [%Node.Text{props: %{text: "Misskey expands the world of the Fediverse"}}], - props: %{font: "fantasy"} - } - ] + test "it handles a node with one value attribute" do + input = [%Node.MFM{name: "jelly", attributes: [{"speed", "2s"}], content: []}] - expected = - ~s[Misskey expands the world of the Fediverse] + expected = "" - assert Encoder.to_html(input_tree) == expected + assert Encoder.to_html(input) == expected end - test "it handles x" do - input_tree = [ - %Node.MFM.X{ - children: [%Node.Text{props: %{text: "🍮"}}], - props: %{size: "400%"} + test "it handles a node with multiple attributes" do + input = [ + %Node.MFM{ + name: "spin", + attributes: [{"alternate"}, {"speed", "0.5s"}], + content: [] } ] - expected = ~s[🍮] + expected = "" - assert Encoder.to_html(input_tree) == expected - end - - test "it handles blur" do - input_tree = [ - %Node.MFM.Blur{ - children: [%Node.Text{props: %{text: "Misskey expands the world of the Fediverse"}}] - } - ] - - expected = - ~s[Misskey expands the world of the Fediverse] - - assert Encoder.to_html(input_tree) == expected - end - - test "it handles jelly" do - input_tree = [ - %Node.MFM.Jelly{ - children: [%Node.Text{props: %{text: "🍮"}}] - } - ] - - expected = - ~s[🍮] - - assert Encoder.to_html(input_tree) == expected - end - - test "it handles tada" do - input_tree = [ - %Node.MFM.Tada{ - children: [%Node.Text{props: %{text: "🍮"}}] - } - ] - - expected = - ~s[🍮] - - assert Encoder.to_html(input_tree) == expected - end - - test "it handles jump" do - input_tree = [ - %Node.MFM.Jump{ - children: [%Node.Text{props: %{text: "🍮"}}] - } - ] - - expected = - ~s[🍮] - - assert Encoder.to_html(input_tree) == expected - end - - test "it handles bounce" do - input_tree = [ - %Node.MFM.Bounce{ - children: [%Node.Text{props: %{text: "🍮"}}] - } - ] - - expected = - ~s[🍮] - - assert Encoder.to_html(input_tree) == expected - end - - test "it handles spin" do - input_tree_z_left = [ - %Node.MFM.Spin{ - props: %{axis: "z", direction: "left", speed: "1.5s"}, - children: [%Node.Text{props: %{text: "🍮"}}] - } - ] - - input_tree_x_left = [ - %Node.MFM.Spin{ - props: %{axis: "x", direction: "left", speed: "1.5s"}, - children: [%Node.Text{props: %{text: "🍮"}}] - } - ] - - input_tree_y_left = [ - %Node.MFM.Spin{ - props: %{axis: "y", direction: "left", speed: "1.5s"}, - children: [%Node.Text{props: %{text: "🍮"}}] - } - ] - - input_tree_z_alternate = [ - %Node.MFM.Spin{ - props: %{axis: "z", direction: "alternate", speed: "1.5s"}, - children: [%Node.Text{props: %{text: "🍮"}}] - } - ] - - input_tree_x_alternate = [ - %Node.MFM.Spin{ - props: %{axis: "x", direction: "alternate", speed: "1.5s"}, - children: [%Node.Text{props: %{text: "🍮"}}] - } - ] - - input_tree_y_alternate = [ - %Node.MFM.Spin{ - props: %{axis: "y", direction: "alternate", speed: "1.5s"}, - children: [%Node.Text{props: %{text: "🍮"}}] - } - ] - - input_tree_z_normal = [ - %Node.MFM.Spin{ - props: %{axis: "z", direction: "normal", speed: "1.5s"}, - children: [%Node.Text{props: %{text: "🍮"}}] - } - ] - - input_tree_x_normal = [ - %Node.MFM.Spin{ - props: %{axis: "x", direction: "normal", speed: "1.5s"}, - children: [%Node.Text{props: %{text: "🍮"}}] - } - ] - - input_tree_y_normal = [ - %Node.MFM.Spin{ - props: %{axis: "y", direction: "normal", speed: "1.5s"}, - children: [%Node.Text{props: %{text: "🍮"}}] - } - ] - - expected_tree_z_left = - ~s[🍮] - - expected_tree_x_left = - ~s[🍮] - - expected_tree_y_left = - ~s[🍮] - - expected_tree_z_alternate = - ~s[🍮] - - expected_tree_x_alternate = - ~s[🍮] - - expected_tree_y_alternate = - ~s[🍮] - - expected_tree_z_normal = - ~s[🍮] - - expected_tree_x_normal = - ~s[🍮] - - expected_tree_y_normal = - ~s[🍮] - - assert Encoder.to_html(input_tree_z_left) == expected_tree_z_left - assert Encoder.to_html(input_tree_x_left) == expected_tree_x_left - assert Encoder.to_html(input_tree_y_left) == expected_tree_y_left - - assert Encoder.to_html(input_tree_z_alternate) == expected_tree_z_alternate - assert Encoder.to_html(input_tree_x_alternate) == expected_tree_x_alternate - assert Encoder.to_html(input_tree_y_alternate) == expected_tree_y_alternate - - assert Encoder.to_html(input_tree_z_normal) == expected_tree_z_normal - assert Encoder.to_html(input_tree_x_normal) == expected_tree_x_normal - assert Encoder.to_html(input_tree_y_normal) == expected_tree_y_normal - end - - test "it handles shake" do - input_tree = [ - %Node.MFM.Shake{ - children: [%Node.Text{props: %{text: "🍮"}}] - } - ] - - expected = - ~s[🍮] - - assert Encoder.to_html(input_tree) == expected - end - - test "it handles twitch" do - input_tree = [ - %Node.MFM.Twitch{ - children: [%Node.Text{props: %{text: "🍮"}}] - } - ] - - expected = - ~s[🍮] - - assert Encoder.to_html(input_tree) == expected - end - - test "it handles rainbow" do - input_tree = [ - %Node.MFM.Rainbow{ - children: [%Node.Text{props: %{text: "🍮"}}] - } - ] - - expected = - ~s[🍮] - - assert Encoder.to_html(input_tree) == expected - end - - test "it handles sparkle" do - # TODO: This is not how Misskey does it and should be changed to make it work like Misskey. - input_tree = [ - %Node.MFM.Sparkle{ - children: [%Node.Text{props: %{text: "🍮"}}] - } - ] - - expected = - ~s[🍮] - - assert Encoder.to_html(input_tree) == expected - end - - test "it handles rotate" do - input_tree = [ - %Node.MFM.Rotate{ - children: [%Node.Text{props: %{text: "🍮"}}] - } - ] - - expected = - ~s[🍮] - - assert Encoder.to_html(input_tree) == expected - end - - test "it handles unsuported formats" do - input_tree = [ - %Node.MFM.Undefined{ - children: [%Node.Text{props: %{text: "🍮"}}] - } - ] - - expected = ~s[🍮] - - assert Encoder.to_html(input_tree) == expected + assert Encoder.to_html(input) == expected end test "it handles multpile nodes on the same level" do - input_tree = [ - %Node.MFM.Rotate{ - children: [%Node.Text{props: %{text: "🍮"}}] - }, - %Node.Text{props: %{text: "pain au chocolat"}}, - %Node.MFM.Font{ - children: [%Node.Text{props: %{text: "Misskey expands the world of the Fediverse"}}], - props: %{font: "fantasy"} - } + input = [ + %Node.MFM{name: "twitch", attributes: [], content: []}, + %Node.Text{content: "chocolatine"}, + %Node.MFM{name: "blabla", attributes: [], content: []} ] - expected = - ~s[🍮pain au chocolatMisskey expands the world of the Fediverse] + expected = "chocolatine" - assert Encoder.to_html(input_tree) == expected + assert Encoder.to_html(input) == expected end test "it handles nesting" do - input_tree = [ - %Node.MFM.Rotate{ - children: [ - %Node.MFM.Font{ - children: [%Node.Text{props: %{text: "🍮"}}], - props: %{font: "fantasy"} + input = [ + %Node.MFM{ + name: "twitch", + attributes: [], + content: [%Node.Text{content: "chocolatine"}] + } + ] + + expected = "chocolatine" + + assert Encoder.to_html(input) == expected + end + + test "it handles complex nesting of nodes" do + input = [ + %MfmParser.Node.Text{content: "It's not "}, + %MfmParser.Node.MFM{ + name: "twitch", + attributes: [], + content: [%MfmParser.Node.Text{content: "chocolatine"}] + }, + %MfmParser.Node.Text{content: "\nit's "}, + %MfmParser.Node.MFM{ + name: "x4", + attributes: [], + content: [ + %MfmParser.Node.MFM{ + name: "spin", + attributes: [{"alternate"}, {"speed", "0.2s"}], + content: [%MfmParser.Node.Text{content: "pain"}] + }, + %MfmParser.Node.Text{content: " "}, + %MfmParser.Node.MFM{ + name: "rainbow", + attributes: [], + content: [%MfmParser.Node.Text{content: "au"}] + }, + %MfmParser.Node.Text{content: " "}, + %MfmParser.Node.MFM{ + name: "jump", + attributes: [{"speed", "0.5s"}], + content: [%MfmParser.Node.Text{content: "chocolat"}] } ] } ] expected = - ~s[🍮] - - assert Encoder.to_html(input_tree) == expected - end - - test "it shouldn't have duplicate styles" do - input_tree = [ - %Node.MFM.Sparkle{ - children: [%Node.Text{props: %{text: "🍮"}}] - }, - %Node.MFM.Sparkle{ - children: [%Node.Text{props: %{text: "🍮"}}] - } - ] - - expected = - ~s[🍮🍮] - - assert Encoder.to_html(input_tree) == expected - end - - test "it handles complex nesting of nodes" do - input_tree = [ - %MfmParser.Node.Text{props: %{text: "It's not "}}, - %MfmParser.Node.MFM.Twitch{ - children: [%MfmParser.Node.Text{props: %{text: "chocolatine"}}], - props: %{speed: "0.2s"} - }, - %MfmParser.Node.Newline{props: %{text: "\n"}}, - %MfmParser.Node.Text{props: %{text: "it's "}}, - %MfmParser.Node.MFM.X{ - children: [ - %MfmParser.Node.MFM.Spin{ - children: [%MfmParser.Node.Text{props: %{text: "pain"}}], - props: %{direction: "normal", axis: "z", speed: "1s"} - }, - %MfmParser.Node.Text{props: %{text: " "}}, - %MfmParser.Node.MFM.Rainbow{ - children: [%MfmParser.Node.Text{props: %{text: "au"}}], - props: %{speed: "2s"} - }, - %MfmParser.Node.Text{props: %{text: " "}}, - %MfmParser.Node.MFM.Jump{ - children: [%MfmParser.Node.Text{props: %{text: "chocolat"}}], - props: %{speed: "0.5s"} - } - ], - props: %{size: "600%"} - } - ] - - expected = - "It's not chocolatine\nit's pain au chocolat" - - assert Encoder.to_html(input_tree) == expected - end - - test "it should be able to go from mfm-text input to html output" do - input = - "It's not $[twitch.speed=0.2s chocolatine]\nit's $[x4 $[spin.speed=1s pain] $[rainbow.speed=2s au] $[jump.speed=0.5s chocolat]]" - - expected = - "It's not chocolatine\nit's pain au chocolat" + "It's not chocolatine\nit's pain au chocolat" assert Encoder.to_html(input) == expected end + + test "it should be able to go from mfm text input to html output" do + input = + "It's not $[twitch chocolatine]\nit's $[x4 $[spin.alternate,speed=0.2s pain] $[rainbow au] $[jump.speed=0.5s chocolat]]" + + expected = + "It's not chocolatine\nit's pain au chocolat" + + assert input |> MfmParser.Parser.parse() |> Encoder.to_html() == expected + assert input |> Encoder.to_html() == expected + end + + test "it handles possible edge cases" do + assert MfmParser.Parser.parse("") |> Encoder.to_html() == "" + assert MfmParser.Parser.parse("]") |> Encoder.to_html() == "]" + assert MfmParser.Parser.parse("[") |> Encoder.to_html() == "[" + assert MfmParser.Parser.parse("$") |> Encoder.to_html() == "$" + assert MfmParser.Parser.parse("[1]") |> Encoder.to_html() == "[1]" + + assert MfmParser.Parser.parse("$$[spin beep]$") |> Encoder.to_html() == + "$beep$" + + assert MfmParser.Parser.parse("$[spin boop]]") |> Encoder.to_html() == + "boop]" + + # Behaviour of these is currently undefined + # The important part is that they do not crash the whole thing + MfmParser.Parser.parse("$[") |> Encoder.to_html() + MfmParser.Parser.parse("$[]") |> Encoder.to_html() + MfmParser.Parser.parse("$[spin ]") |> Encoder.to_html() + MfmParser.Parser.parse("$[spin beep]$[") |> Encoder.to_html() + MfmParser.Parser.parse("$[spin ") |> Encoder.to_html() + MfmParser.Parser.parse("$[spin chocoretto") |> Encoder.to_html() + MfmParser.Parser.parse("$[spin. ") |> Encoder.to_html() + MfmParser.Parser.parse("$[spin. chocoretto") |> Encoder.to_html() + MfmParser.Parser.parse("$[spin.x= ") |> Encoder.to_html() + MfmParser.Parser.parse("$[spin.x= chocoretto") |> Encoder.to_html() + MfmParser.Parser.parse("$[spin. chocoretto]") |> Encoder.to_html() + MfmParser.Parser.parse("$[spin.x= chocoretto]") |> Encoder.to_html() + MfmParser.Parser.parse("$[sp") |> Encoder.to_html() + end end end diff --git a/test/lexer_test.exs b/test/lexer_test.exs index 177f261..36a1aef 100644 --- a/test/lexer_test.exs +++ b/test/lexer_test.exs @@ -4,7 +4,6 @@ defmodule MfmParser.LexerTest do alias MfmParser.Lexer alias MfmParser.Token.MFM - alias MfmParser.Token.Newline alias MfmParser.Token.Text describe "eof" do @@ -82,16 +81,4 @@ defmodule MfmParser.LexerTest do {%Text{content: "Tu abuela ve anime y no se lava el culo"}, ""} end end - - describe "newline token" do - test "it handles \n as a token" do - assert Lexer.peek("\nchocolat") == %Newline{content: "\n"} - assert Lexer.next("\nchocolat") == {%Newline{content: "\n"}, "chocolat"} - end - - test "it works at the eof" do - assert Lexer.peek("\n") == %Newline{content: "\n"} - assert Lexer.next("\n") == {%Newline{content: "\n"}, ""} - end - end end diff --git a/test/parser_test.exs b/test/parser_test.exs index 9ed8e1f..b2749ca 100644 --- a/test/parser_test.exs +++ b/test/parser_test.exs @@ -12,611 +12,139 @@ defmodule MfmParser.ParserTest do end test "it can handle text as input" do - input = "pain au chocolat" + input = "pain\nau\nchocolat" - output = [%MfmParser.Node.Text{props: %{text: "pain au chocolat"}}] + output = [%MfmParser.Node.Text{content: "pain\nau\nchocolat"}] assert Parser.parse(input) == output end - test "it can handle a newline as input" do - input = "\n" + test "it can handle an element without attributes" do + input = "$[flip ]" - output = [%MfmParser.Node.Newline{props: %{text: "\n"}}] + output = [%MfmParser.Node.MFM{name: "flip", attributes: [], content: []}] assert Parser.parse(input) == output end - test "it can handle a flip element" do - input_default = "$[flip ]" - input_v = "$[flip.v ]" - input_hv = "$[flip.h,v ]" + test "it can handle an element with one non-value attribute" do + input = "$[font.cursive ]" - output_default = [ - %MfmParser.Node.MFM.Flip{ - props: %{ - v: false, - h: false - }, - children: [] - } - ] + output = [%MfmParser.Node.MFM{name: "font", attributes: [{"cursive"}], content: []}] - output_v = [ - %MfmParser.Node.MFM.Flip{ - props: %{ - v: true, - h: false - }, - children: [] - } - ] - - output_hv = [ - %MfmParser.Node.MFM.Flip{ - props: %{ - v: true, - h: true - }, - children: [] - } - ] - - assert Parser.parse(input_default) == output_default - assert Parser.parse(input_v) == output_v - assert Parser.parse(input_hv) == output_hv + assert Parser.parse(input) == output end - test "it can handle a font element" do - input = "$[font.serif ]" + test "it can handle an element with one value attribute" do + input = "$[jelly.speed=2s ]" + + output = [%MfmParser.Node.MFM{name: "jelly", attributes: [{"speed", "2s"}], content: []}] + + assert Parser.parse(input) == output + end + + test "it can handle an element with multiple attributes" do + input = "$[spin.alternate,speed=0.5s ]" output = [ - %MfmParser.Node.MFM.Font{ - props: %{ - font: "serif" - }, - children: [] + %MfmParser.Node.MFM{ + name: "spin", + attributes: [{"alternate"}, {"speed", "0.5s"}], + content: [] } ] assert Parser.parse(input) == output end - - test "it can handle an x element" do - input2 = "$[x2 ]" - input3 = "$[x3 ]" - input4 = "$[x4 ]" - - output2 = [ - %MfmParser.Node.MFM.X{ - props: %{ - size: "200%" - }, - children: [] - } - ] - - output3 = [ - %MfmParser.Node.MFM.X{ - props: %{ - size: "400%" - }, - children: [] - } - ] - - output4 = [ - %MfmParser.Node.MFM.X{ - props: %{ - size: "600%" - }, - children: [] - } - ] - - assert Parser.parse(input2) == output2 - assert Parser.parse(input3) == output3 - assert Parser.parse(input4) == output4 - end - - test "it can handle a blur element" do - input = "$[blur ]" - - output = [ - %MfmParser.Node.MFM.Blur{ - props: %{}, - children: [] - } - ] - - assert Parser.parse(input) == output - end - - test "it can handle a jelly element" do - input_default = "$[jelly ]" - - output_default = [ - %MfmParser.Node.MFM.Jelly{ - props: %{ - speed: "1s" - }, - children: [] - } - ] - - input_speed = "$[jelly.speed=20s ]" - - output_speed = [ - %MfmParser.Node.MFM.Jelly{ - props: %{ - speed: "20s" - }, - children: [] - } - ] - - assert Parser.parse(input_default) == output_default - assert Parser.parse(input_speed) == output_speed - end - - test "it can handle a tada element" do - input_default = "$[tada ]" - - output_default = [ - %MfmParser.Node.MFM.Tada{ - props: %{ - speed: "1s" - }, - children: [] - } - ] - - input_speed = "$[tada.speed=20s ]" - - output_speed = [ - %MfmParser.Node.MFM.Tada{ - props: %{ - speed: "20s" - }, - children: [] - } - ] - - assert Parser.parse(input_default) == output_default - assert Parser.parse(input_speed) == output_speed - end - - test "it can handle a jump element" do - input_default = "$[jump ]" - - output_default = [ - %MfmParser.Node.MFM.Jump{ - props: %{ - speed: "0.75s" - }, - children: [] - } - ] - - input_speed = "$[jump.speed=20s ]" - - output_speed = [ - %MfmParser.Node.MFM.Jump{ - props: %{ - speed: "20s" - }, - children: [] - } - ] - - assert Parser.parse(input_default) == output_default - assert Parser.parse(input_speed) == output_speed - end - - test "it can handle a bounce element" do - input_default = "$[bounce ]" - - output_default = [ - %MfmParser.Node.MFM.Bounce{ - props: %{ - speed: "0.75s" - }, - children: [] - } - ] - - input_speed = "$[bounce.speed=20s ]" - - output_speed = [ - %MfmParser.Node.MFM.Bounce{ - props: %{ - speed: "20s" - }, - children: [] - } - ] - - assert Parser.parse(input_default) == output_default - assert Parser.parse(input_speed) == output_speed - end - - test "it can handle a spin element" do - input_default = "$[spin ]" - - output_default = [ - %MfmParser.Node.MFM.Spin{ - props: %{ - axis: "z", - direction: "normal", - speed: "1.5s" - }, - children: [] - } - ] - - input_left = "$[spin.left ]" - - output_left = [ - %MfmParser.Node.MFM.Spin{ - props: %{ - axis: "z", - direction: "left", - speed: "1.5s" - }, - children: [] - } - ] - - input_alternate = "$[spin.alternate ]" - - output_alternate = [ - %MfmParser.Node.MFM.Spin{ - props: %{ - axis: "z", - direction: "alternate", - speed: "1.5s" - }, - children: [] - } - ] - - input_x = "$[spin.x ]" - - output_x = [ - %MfmParser.Node.MFM.Spin{ - props: %{ - axis: "x", - direction: "normal", - speed: "1.5s" - }, - children: [] - } - ] - - input_x_left = "$[spin.x,left ]" - - output_x_left = [ - %MfmParser.Node.MFM.Spin{ - props: %{ - axis: "x", - direction: "left", - speed: "1.5s" - }, - children: [] - } - ] - - input_x_alternate = "$[spin.x,alternate ]" - - output_x_alternate = [ - %MfmParser.Node.MFM.Spin{ - props: %{ - axis: "x", - direction: "alternate", - speed: "1.5s" - }, - children: [] - } - ] - - input_y = "$[spin.y ]" - - output_y = [ - %MfmParser.Node.MFM.Spin{ - props: %{ - axis: "y", - direction: "normal", - speed: "1.5s" - }, - children: [] - } - ] - - input_y_left = "$[spin.y,left ]" - - output_y_left = [ - %MfmParser.Node.MFM.Spin{ - props: %{ - axis: "y", - direction: "left", - speed: "1.5s" - }, - children: [] - } - ] - - input_y_alternate = "$[spin.y,alternate ]" - - output_y_alternate = [ - %MfmParser.Node.MFM.Spin{ - props: %{ - axis: "y", - direction: "alternate", - speed: "1.5s" - }, - children: [] - } - ] - - input_speed = "$[spin.speed=20s ]" - - output_speed = [ - %MfmParser.Node.MFM.Spin{ - props: %{ - axis: "z", - direction: "normal", - speed: "20s" - }, - children: [] - } - ] - - assert Parser.parse(input_default) == output_default - assert Parser.parse(input_left) == output_left - assert Parser.parse(input_alternate) == output_alternate - assert Parser.parse(input_x) == output_x - assert Parser.parse(input_x_left) == output_x_left - assert Parser.parse(input_x_alternate) == output_x_alternate - assert Parser.parse(input_y) == output_y - assert Parser.parse(input_y_left) == output_y_left - assert Parser.parse(input_y_alternate) == output_y_alternate - assert Parser.parse(input_speed) == output_speed - end - - test "it can handle a shake element" do - input_default = "$[shake ]" - - output_default = [ - %MfmParser.Node.MFM.Shake{ - props: %{ - speed: "0.5s" - }, - children: [] - } - ] - - input_speed = "$[shake.speed=20s ]" - - output_speed = [ - %MfmParser.Node.MFM.Shake{ - props: %{ - speed: "20s" - }, - children: [] - } - ] - - assert Parser.parse(input_default) == output_default - assert Parser.parse(input_speed) == output_speed - end - - test "it can handle a twitch element" do - input_default = "$[twitch ]" - - output_default = [ - %MfmParser.Node.MFM.Twitch{ - props: %{ - speed: "0.5s" - }, - children: [] - } - ] - - input_speed = "$[twitch.speed=0.2s ]" - - output_speed = [ - %MfmParser.Node.MFM.Twitch{ - props: %{ - speed: "0.2s" - }, - children: [] - } - ] - - assert Parser.parse(input_default) == output_default - assert Parser.parse(input_speed) == output_speed - end - - test "it can handle a rainbow element" do - input_default = "$[rainbow ]" - - output_default = [ - %MfmParser.Node.MFM.Rainbow{ - props: %{ - speed: "1s" - }, - children: [] - } - ] - - input_speed = "$[rainbow.speed=20s ]" - - output_speed = [ - %MfmParser.Node.MFM.Rainbow{ - props: %{ - speed: "20s" - }, - children: [] - } - ] - - assert Parser.parse(input_default) == output_default - assert Parser.parse(input_speed) == output_speed - end - - test "it can handle a sparkle element" do - input = "$[sparkle ]" - - output = [ - %MfmParser.Node.MFM.Sparkle{ - props: %{}, - children: [] - } - ] - - assert Parser.parse(input) == output - end - - test "it can handle a rotate element" do - input = "$[rotate ]" - - output = [ - %MfmParser.Node.MFM.Rotate{ - props: %{}, - children: [] - } - ] - - assert Parser.parse(input) == output - end - - test "it can handle an undefined element" do - input = "$[blabla ]" - - output = [ - %MfmParser.Node.MFM.Undefined{ - props: %{}, - children: [] - } - ] - - assert Parser.parse(input) == output - end - - test "it doesn't crash on a lost end token" do - Parser.parse("]") - end - - test "it doesn't crash on a non-closed token" do - Parser.parse("$[spi") - Parser.parse("$[spin ") - Parser.parse("$[spin chocolatine") - end end describe "multiple element input" do test "it can handle multiple elements as input" do input = "$[twitch ]chocolatine$[blabla ]\n$[jump ]" - assert Parser.parse(input) == [ - %MfmParser.Node.MFM.Twitch{children: [], props: %{speed: "0.5s"}}, - %MfmParser.Node.Text{props: %{text: "chocolatine"}}, - %MfmParser.Node.MFM.Undefined{children: [], props: %{}}, - %MfmParser.Node.Newline{props: %{text: "\n"}}, - %MfmParser.Node.MFM.Jump{children: [], props: %{speed: "0.75s"}} - ] + output = [ + %MfmParser.Node.MFM{name: "twitch", attributes: [], content: []}, + %MfmParser.Node.Text{content: "chocolatine"}, + %MfmParser.Node.MFM{name: "blabla", attributes: [], content: []}, + %MfmParser.Node.Text{content: "\n"}, + %MfmParser.Node.MFM{name: "jump", attributes: [], content: []} + ] + + assert Parser.parse(input) == output end test "it can handle nesting" do input = "$[twitch chocolatine]" - assert Parser.parse(input) == [ - %MfmParser.Node.MFM.Twitch{ - children: [%MfmParser.Node.Text{props: %{text: "chocolatine"}}], - props: %{speed: "0.5s"} - } - ] + output = [ + %MfmParser.Node.MFM{ + name: "twitch", + attributes: [], + content: [%MfmParser.Node.Text{content: "chocolatine"}] + } + ] + + assert Parser.parse(input) == output end - test "it can handle multiple nesting" do + test "it can handle multiple nestings" do input = "$[twitch $[spin chocolatine]]" - assert Parser.parse(input) == [ - %MfmParser.Node.MFM.Twitch{ - children: [ - %MfmParser.Node.MFM.Spin{ - children: [%MfmParser.Node.Text{props: %{text: "chocolatine"}}], - props: %{direction: "normal", axis: "z", speed: "1.5s"} - } - ], - props: %{speed: "0.5s"} - } - ] + output = [ + %MfmParser.Node.MFM{ + name: "twitch", + attributes: [], + content: [ + %MfmParser.Node.MFM{ + name: "spin", + attributes: [], + content: [%MfmParser.Node.Text{content: "chocolatine"}] + } + ] + } + ] + + assert Parser.parse(input) == output end test "it can handle a complex structure of multiple elements and nesting" do input = - "It's not $[twitch chocolatine]\nit's $[x4 $[spin pain] $[rainbow au] $[jump chocolat]]" + "It's not $[twitch chocolatine]\nit's $[x4 $[spin.alternate,speed=0.2s pain] $[rainbow au] $[jump.speed=0.5s chocolat]]" - assert Parser.parse(input) == [ - %MfmParser.Node.Text{props: %{text: "It's not "}}, - %MfmParser.Node.MFM.Twitch{ - children: [%MfmParser.Node.Text{props: %{text: "chocolatine"}}], - props: %{speed: "0.5s"} - }, - %MfmParser.Node.Newline{props: %{text: "\n"}}, - %MfmParser.Node.Text{props: %{text: "it's "}}, - %MfmParser.Node.MFM.X{ - children: [ - %MfmParser.Node.MFM.Spin{ - children: [%MfmParser.Node.Text{props: %{text: "pain"}}], - props: %{direction: "normal", axis: "z", speed: "1.5s"} - }, - %MfmParser.Node.Text{props: %{text: " "}}, - %MfmParser.Node.MFM.Rainbow{ - children: [%MfmParser.Node.Text{props: %{text: "au"}}], - props: %{speed: "1s"} - }, - %MfmParser.Node.Text{props: %{text: " "}}, - %MfmParser.Node.MFM.Jump{ - children: [%MfmParser.Node.Text{props: %{text: "chocolat"}}], - props: %{speed: "0.75s"} - } - ], - props: %{size: "600%"} - } - ] - end - end + output = [ + %MfmParser.Node.Text{content: "It's not "}, + %MfmParser.Node.MFM{ + name: "twitch", + attributes: [], + content: [%MfmParser.Node.Text{content: "chocolatine"}] + }, + %MfmParser.Node.Text{content: "\nit's "}, + %MfmParser.Node.MFM{ + name: "x4", + attributes: [], + content: [ + %MfmParser.Node.MFM{ + name: "spin", + attributes: [{"alternate"}, {"speed", "0.2s"}], + content: [%MfmParser.Node.Text{content: "pain"}] + }, + %MfmParser.Node.Text{content: " "}, + %MfmParser.Node.MFM{ + name: "rainbow", + attributes: [], + content: [%MfmParser.Node.Text{content: "au"}] + }, + %MfmParser.Node.Text{content: " "}, + %MfmParser.Node.MFM{ + name: "jump", + attributes: [{"speed", "0.5s"}], + content: [%MfmParser.Node.Text{content: "chocolat"}] + } + ] + } + ] - describe "to_props/1" do - test "it returns speed in the list of parameters" do - assert %{speed: "5s"} = Parser.to_props("$[blabla.speed=5s") - assert %{speed: "0.5s"} = Parser.to_props("$[blabla.speed=0.5s") - end - - test "it returns v and h in the list of parameters" do - assert %{v: true} = Parser.to_props("$[blabla.v") - assert %{v: true, h: true} = Parser.to_props("$[blabla.h,v") - end - - test "it returns fonts" do - assert %{font: "some_font"} = Parser.to_props("$[font.some_font") - end - - test "it returns a size for an x element" do - assert %{size: "200%"} = Parser.to_props("$[x2") - assert %{size: "400%"} = Parser.to_props("$[x3") - assert %{size: "600%"} = Parser.to_props("$[x4") - assert %{size: "100%"} = Parser.to_props("$[xqsdfqsf") - end - - test "it returns an empty list when there are no parameters" do - assert %{} = Parser.to_props("$[blabla") - end - - test "it ignores unknown parameters" do - assert %{} = Parser.to_props("$[blabla.idk") + assert Parser.parse(input) == output end end end -- 2.43.0