Use FEP-c16b: Formatting MFM functions #2
11 changed files with 363 additions and 1398 deletions
88
README.md
88
README.md
|
@ -1,85 +1,91 @@
|
||||||
# Akkoma-MFMParser
|
|
||||||
|
|
||||||
extremely simple modification to [the original parser](https://codeberg.org/ilja/mfm_parser) that just doesn't include the CSS
|
|
||||||
|
|
||||||
# MfmParser
|
# 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.
|
It only parses the MFM specific syntax of the form `$[name.attributes content]`.
|
||||||
That means that it doesn't parse links, usernames, HTML, Markdown or Katex.
|
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{
|
%MfmParser.Node.Text{
|
||||||
props: %{
|
content: "it's not chocolatine, it's "
|
||||||
text: "it's not chocolatine, it's "
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
%MfmParser.MFM.Twitch{
|
%MfmParser.Node.MFM{
|
||||||
props: %{
|
name: "twitch",
|
||||||
speed: "0.2s"
|
attributes: [
|
||||||
},
|
[{"alternate"}, {"speed", "0.5s"}]
|
||||||
children: [
|
],
|
||||||
%MfmParser.Text{
|
content: [
|
||||||
props: %{
|
%MfmParser.Node.Text{
|
||||||
text: "pain au chocolat"
|
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 <span class=\"mfm-spin\" data-mfm-alternate data-mfm-speed=\"0.5s\">pain au chocolat</span>
|
||||||
|
|
||||||
## Examples
|
## 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{
|
%MfmParser.Node.MFM{
|
||||||
children: [%MfmParser.Node.Text{props: %{text: "🍮"}}],
|
name: "twitch",
|
||||||
props: %{speed: "5s"}
|
attributes: [{"speed", "0.5s"}],
|
||||||
|
content: [%MfmParser.Node.Text{content: "pain au chocolat"}]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
iex> MfmParser.Parser.parse("$[twitch.speed=5s 🍮]") |> MfmParser.to_html()
|
|
||||||
"<span style=\\"display: inline-block; animation: 5s ease 0s infinite normal none running mfm-twitch;\\">🍮</span><style>@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) }}</style>"
|
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()
|
||||||
|
"<span class="mfm-twitch" data-mfm-speed="0.5s">🍮</span>"
|
||||||
|
|
||||||
|
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()
|
||||||
|
"<span class="mfm-twitch" data-mfm-speed="0.5s">🍮</span>"
|
||||||
|
|
||||||
## Reading
|
## Reading
|
||||||
### The Parser
|
### 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 parser typically consists of three parts
|
||||||
* a Reader
|
* a Reader
|
||||||
* a Lexer (aka Tokeniser)
|
* a Lexer (aka Tokeniser)
|
||||||
* the Parser
|
* the Parser
|
||||||
|
|
||||||
A Reader typically has a `next` function which takes the next character out of the input and returns it.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|
### The Encoder
|
||||||
|
|
||||||
Once we have a good data structure, we can process this and do things with it.
|
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.
|
E.g. an Encoder encodes the tree into a different format.
|
||||||
|
|
||||||
### The code
|
### 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
|
## License
|
||||||
|
|
||||||
A parser/encoder for Misskey Flavoured Markdown.
|
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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|
210
lib/encoder.ex
210
lib/encoder.ex
|
@ -1,198 +1,50 @@
|
||||||
defmodule MfmParser.Encoder do
|
defmodule MfmParser.Encoder do
|
||||||
alias MfmParser.Parser
|
|
||||||
alias MfmParser.Node
|
|
||||||
|
|
||||||
@moduledoc """
|
@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].
|
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.
|
||||||
|
|
||||||
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`.
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> [
|
iex> [
|
||||||
...> %MfmParser.Node.MFM.Twitch{
|
...> %MfmParser.Node.MFM{
|
||||||
...> children: [%MfmParser.Node.Text{props: %{text: "🍮"}}],
|
...> name: "twitch",
|
||||||
...> props: %{speed: "5s"}
|
...> content: [%MfmParser.Node.Text{content: "🍮"}],
|
||||||
|
...> attributes: [{"speed", "5s"}]
|
||||||
...> }
|
...> }
|
||||||
...> ]
|
...> ]
|
||||||
...> |> MfmParser.Encoder.to_html()
|
...> |> MfmParser.Encoder.to_html()
|
||||||
"<span class=\\"mfm\\" style=\\"display: inline-block; animation: 5s ease 0s infinite normal none running mfm-twitch;\\">🍮</span>"
|
~S[<span class="mfm-twitch" data-mfm-speed="5s">🍮</span>]
|
||||||
|
|
||||||
iex> MfmParser.Parser.parse("$[twitch.speed=5s 🍮]") |> MfmParser.Encoder.to_html()
|
iex> "$[twitch.speed=5s 🍮]" |> MfmParser.Encoder.to_html()
|
||||||
"<span class=\\"mfm\\" style=\\"display: inline-block; animation: 5s ease 0s infinite normal none running mfm-twitch;\\">🍮</span>"
|
~S[<span class="mfm-twitch" data-mfm-speed="5s">🍮</span>]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def to_html(tree) when is_list(tree) do
|
def to_html([node | rest]) do
|
||||||
{html, _styles} = to_html_styles(tree)
|
node_html =
|
||||||
|
|
||||||
html
|
|
||||||
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} ->
|
|
||||||
case node do
|
case node do
|
||||||
%Node.Text{} ->
|
%MfmParser.Node.Text{content: content} ->
|
||||||
{html <> node.props.text, styles}
|
content
|
||||||
|
|
||||||
%Node.Newline{} ->
|
%MfmParser.Node.MFM{name: name, attributes: attributes, content: content} ->
|
||||||
{html <> node.props.text, styles}
|
attributes_string =
|
||||||
|
attributes
|
||||||
|
|> Enum.reduce("", fn
|
||||||
|
{name}, acc -> acc <> " data-mfm-#{name}"
|
||||||
|
{name, value}, acc -> acc <> " data-mfm-#{name}=\"#{value}\""
|
||||||
|
end)
|
||||||
|
|
||||||
%Node.MFM.Flip{} ->
|
"<span class=\"mfm-#{name}\"#{attributes_string}>#{to_html(content)}</span>"
|
||||||
{html_child, styles_child} = to_html_styles(node.children)
|
|
||||||
|
|
||||||
case node.props do
|
|
||||||
%{v: true, h: true} ->
|
|
||||||
{html <>
|
|
||||||
"<span class=\"mfm\" style=\"display: inline-block; transform: scale(-1);\">#{html_child}</span>",
|
|
||||||
styles}
|
|
||||||
|
|
||||||
%{v: true} ->
|
|
||||||
{html <>
|
|
||||||
"<span class=\"mfm\" style=\"display: inline-block; transform: scaleY(-1);\">#{html_child}</span>",
|
|
||||||
styles}
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
{html <>
|
|
||||||
"<span class=\"mfm\" style=\"display: inline-block; transform: scaleX(-1);\">#{html_child}</span>",
|
|
||||||
styles ++ styles_child}
|
|
||||||
end
|
|
||||||
|
|
||||||
%Node.MFM.Font{} ->
|
|
||||||
{html_child, styles_child} = to_html_styles(node.children)
|
|
||||||
|
|
||||||
{html <>
|
|
||||||
"<span class=\"mfm\" style=\"display: inline-block; font-family: #{node.props.font};\">#{html_child}</span>",
|
|
||||||
styles ++ styles_child}
|
|
||||||
|
|
||||||
%Node.MFM.X{} ->
|
|
||||||
prop_map = %{"200%" => "2", "400%" => "3", "600%" => "4"}
|
|
||||||
{html_child, styles_child} = to_html_styles(node.children)
|
|
||||||
|
|
||||||
{html <>
|
|
||||||
"<span style=\"font-size: #{node.props.size}\" class=\"mfm _mfm_x#{prop_map[node.props.size]}_\">#{html_child}</span>",
|
|
||||||
styles ++ styles_child}
|
|
||||||
|
|
||||||
%Node.MFM.Blur{} ->
|
|
||||||
{html_child, styles_child} = to_html_styles(node.children)
|
|
||||||
|
|
||||||
{html <> "<span class=\"_mfm_blur_\">#{html_child}</span>",
|
|
||||||
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 <> "<span class=\"mfm _mfm_jelly_\" style=\"display: inline-block; animation: #{node.props.speed} linear 0s infinite normal both running mfm-rubberBand;\">#{html_child}</span>", styles_child}
|
|
||||||
|
|
||||||
%Node.MFM.Tada{} ->
|
|
||||||
{html_child, styles_child} = to_html_styles(node.children)
|
|
||||||
|
|
||||||
{html <>
|
|
||||||
"<span class=\"mfm _mfm_tada_\" style=\"display: inline-block; font-size: 150%; animation: #{node.props.speed} linear 0s infinite normal both running mfm-tada;\">#{html_child}</span>", styles_child}
|
|
||||||
|
|
||||||
%Node.MFM.Jump{} ->
|
|
||||||
{html_child, styles_child} = to_html_styles(node.children)
|
|
||||||
|
|
||||||
{html <> "<span class=\"mfm _mfm_jump_\" style=\"display: inline-block; animation: #{node.props.speed} linear 0s infinite normal none running mfm-jump;\">#{html_child}</span>", styles_child}
|
|
||||||
|
|
||||||
%Node.MFM.Bounce{} ->
|
|
||||||
{html_child, styles_child} = to_html_styles(node.children)
|
|
||||||
|
|
||||||
{html <> "<span class=\"mfm _mfm_bounce_\" style=\"display: inline-block; animation: #{node.props.speed} linear 0s infinite normal none running mfm-bounce; transform-origin: center bottom 0px;\">#{html_child}</span>", 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 <>
|
|
||||||
"<span class=\"mfm _mfm_spin_\" style=\"display: inline-block; animation: #{node.props.speed} linear 0s infinite #{Map.get(directions_map, node.props.direction, node.props.direction)} none running #{Map.get(keyframe_names_map, node.props.axis, "")};\">#{html_child}</span>",
|
|
||||||
styles ++ [Map.get(styles_map, node.props.axis, "")] ++ styles_child}
|
|
||||||
|
|
||||||
%Node.MFM.Shake{} ->
|
|
||||||
{html_child, styles_child} = to_html_styles(node.children)
|
|
||||||
|
|
||||||
{html <>
|
|
||||||
"<span class=\"mfm\" style=\"display: inline-block; animation: #{node.props.speed} ease 0s infinite normal none running mfm-shake;\">#{html_child}</span>",
|
|
||||||
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 <>
|
|
||||||
"<span class=\"mfm\" style=\"display: inline-block; animation: #{node.props.speed} ease 0s infinite normal none running mfm-twitch;\">#{html_child}</span>",
|
|
||||||
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 <>
|
|
||||||
"<span class=\"mfm\" style=\"display: inline-block; animation: #{node.props.speed} linear 0s infinite normal none running mfm-rainbow;\">#{html_child}</span>",
|
|
||||||
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 <>
|
|
||||||
"<span class=\"mfm\" style=\"display: inline-block; animation: 1s linear 0s infinite normal none running mfm-sparkle;\">#{html_child}</span>",
|
|
||||||
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 <>
|
|
||||||
"<span class=\"mfm\" style=\"display: inline-block; transform: rotate(90deg); transform-origin: center center 0px;\">#{html_child}</span>",
|
|
||||||
styles ++ styles_child}
|
|
||||||
|
|
||||||
%Node.MFM.Undefined{} ->
|
|
||||||
{html_child, styles_child} = to_html_styles(node.children)
|
|
||||||
|
|
||||||
{html <>
|
|
||||||
"<span>#{html_child}</span>", styles ++ styles_child}
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
{html, styles}
|
|
||||||
end
|
end
|
||||||
end)
|
|
||||||
|
node_html <> to_html(rest)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_html([]) do
|
||||||
|
""
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_html(mfm_string) when is_binary(mfm_string) do
|
||||||
|
MfmParser.Parser.parse(mfm_string) |> to_html()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,7 +3,6 @@ defmodule MfmParser.Lexer do
|
||||||
|
|
||||||
alias MfmParser.Token
|
alias MfmParser.Token
|
||||||
alias MfmParser.Token.MFM
|
alias MfmParser.Token.MFM
|
||||||
alias MfmParser.Token.Newline
|
|
||||||
alias MfmParser.Token.Text
|
alias MfmParser.Token.Text
|
||||||
|
|
||||||
def peek(input) do
|
def peek(input) do
|
||||||
|
@ -44,9 +43,6 @@ defmodule MfmParser.Lexer do
|
||||||
"]" ->
|
"]" ->
|
||||||
%MFM.Close{}
|
%MFM.Close{}
|
||||||
|
|
||||||
"\n" ->
|
|
||||||
%Newline{}
|
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
%Text{}
|
%Text{}
|
||||||
end
|
end
|
||||||
|
@ -60,10 +56,6 @@ defmodule MfmParser.Lexer do
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
defp is_last_char_of_token?(_, _, %Newline{}) do
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
defp is_last_char_of_token?(_, rest, %Text{}) do
|
defp is_last_char_of_token?(_, rest, %Text{}) do
|
||||||
case Reader.next(rest) do
|
case Reader.next(rest) do
|
||||||
:eof -> true
|
:eof -> true
|
||||||
|
|
66
lib/node.ex
66
lib/node.ex
|
@ -1,67 +1,7 @@
|
||||||
defmodule MfmParser.Node.Text do
|
defmodule MfmParser.Node.Text do
|
||||||
defstruct props: %{text: ""}
|
defstruct content: ""
|
||||||
end
|
end
|
||||||
|
|
||||||
defmodule MfmParser.Node.Newline do
|
defmodule MfmParser.Node.MFM do
|
||||||
defstruct props: %{text: "\n"}
|
defstruct name: "", attributes: %{}, content: []
|
||||||
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: []
|
|
||||||
end
|
end
|
||||||
|
|
214
lib/parser.ex
214
lib/parser.ex
|
@ -4,164 +4,98 @@ defmodule MfmParser.Parser do
|
||||||
alias MfmParser.Lexer
|
alias MfmParser.Lexer
|
||||||
|
|
||||||
@moduledoc """
|
@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
|
## Examples
|
||||||
|
|
||||||
iex> MfmParser.Parser.parse("$[twitch.speed=5s 🍮]")
|
iex> MfmParser.Parser.parse("$[twitch.speed=0.5s 🍮]")
|
||||||
[
|
[
|
||||||
%MfmParser.Node.MFM.Twitch{
|
%MfmParser.Node.MFM{
|
||||||
children: [%MfmParser.Node.Text{props: %{text: "🍮"}}],
|
name: "twitch",
|
||||||
props: %{speed: "5s"}
|
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
|
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 ->
|
:eof ->
|
||||||
tree
|
tree
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
{token, rest} ->
|
defp get_mfm_node(token) do
|
||||||
if is_end_token.(token) do
|
{name, attributes} =
|
||||||
{tree, rest}
|
case token.content
|
||||||
else
|
|> String.trim()
|
||||||
case token do
|
|> String.replace("$[", "")
|
||||||
%Token.MFM.Open{} ->
|
|> String.split(".", parts: 2) do
|
||||||
{children, rest} =
|
[name] -> {name, []}
|
||||||
case parse(rest, [], &is_mfm_close_token?/1) do
|
[name, attributes_string] -> {name, build_attributes_list(attributes_string)}
|
||||||
{children, rest} ->
|
end
|
||||||
{children, rest}
|
|
||||||
|
|
||||||
_ ->
|
%Node.MFM{name: name, attributes: attributes, content: []}
|
||||||
{[], rest}
|
end
|
||||||
end
|
|
||||||
|
|
||||||
parse(
|
defp build_attributes_list(attributes_string) do
|
||||||
rest,
|
attributes_string
|
||||||
tree ++ [token |> get_node() |> Map.put(:children, children)],
|
|> String.split(",")
|
||||||
is_end_token
|
|> Enum.reduce([], fn attribute_string, acc ->
|
||||||
)
|
attribute =
|
||||||
|
case attribute_string |> String.split("=") do
|
||||||
%Token.Text{} ->
|
[name] -> {name}
|
||||||
parse(
|
[name, value] -> {name, value}
|
||||||
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
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp is_mfm_close_token?(token) do
|
acc ++ [attribute]
|
||||||
case token do
|
end)
|
||||||
%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
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,10 +8,6 @@ defmodule MfmParser.Token.Text do
|
||||||
defstruct content: ""
|
defstruct content: ""
|
||||||
end
|
end
|
||||||
|
|
||||||
defmodule MfmParser.Token.Newline do
|
|
||||||
defstruct content: ""
|
|
||||||
end
|
|
||||||
|
|
||||||
defmodule MfmParser.Token.MFM.Open do
|
defmodule MfmParser.Token.MFM.Open do
|
||||||
defstruct content: ""
|
defstruct content: ""
|
||||||
end
|
end
|
||||||
|
|
9
mix.exs
9
mix.exs
|
@ -4,7 +4,7 @@ defmodule MfmParser.MixProject do
|
||||||
def project do
|
def project do
|
||||||
[
|
[
|
||||||
app: :mfm_parser,
|
app: :mfm_parser,
|
||||||
version: "0.1.0",
|
version: "0.2.0",
|
||||||
elixir: "~> 1.13",
|
elixir: "~> 1.13",
|
||||||
start_permanent: Mix.env() == :prod,
|
start_permanent: Mix.env() == :prod,
|
||||||
deps: deps()
|
deps: deps()
|
||||||
|
@ -14,12 +14,15 @@ defmodule MfmParser.MixProject do
|
||||||
# Run "mix help compile.app" to learn about applications.
|
# Run "mix help compile.app" to learn about applications.
|
||||||
def application do
|
def application do
|
||||||
[
|
[
|
||||||
extra_applications: [:logger]
|
# extra_applications: [:logger]
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Run "mix help deps" to learn about dependencies.
|
# Run "mix help deps" to learn about dependencies.
|
||||||
defp deps do
|
defp deps do
|
||||||
[]
|
[
|
||||||
|
# {:dep_from_hexpm, "~> 0.3.0"},
|
||||||
|
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
|
||||||
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
4
mix.lock
4
mix.lock
|
@ -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, "https://akkoma.dev/AkkomaGang/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]},
|
|
||||||
}
|
|
|
@ -8,425 +8,156 @@ defmodule MfmParser.EncoderTest do
|
||||||
|
|
||||||
describe "to_html" do
|
describe "to_html" do
|
||||||
test "it handles text" do
|
test "it handles text" do
|
||||||
input_tree = [%Node.Text{props: %{text: "chocolatine"}}]
|
input_tree = [%Node.Text{content: "chocolatine"}]
|
||||||
|
|
||||||
expected = "chocolatine"
|
expected = "chocolatine"
|
||||||
|
|
||||||
assert Encoder.to_html(input_tree) == expected
|
assert Encoder.to_html(input_tree) == expected
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it handles newlines" do
|
test "it handles a node without attributes" do
|
||||||
input_tree = [%Node.Newline{props: %{text: "\n"}}]
|
input = [%Node.MFM{name: "flip", attributes: %{}, content: []}]
|
||||||
|
|
||||||
expected = "\n"
|
expected = "<span class=\"mfm-flip\"></span>"
|
||||||
|
|
||||||
assert Encoder.to_html(input_tree) == expected
|
assert Encoder.to_html(input) == expected
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it handles flip" do
|
test "it handles a node with a non-value attribute" do
|
||||||
input_tree = [
|
input = [%Node.MFM{name: "font", attributes: [{"cursive"}], content: []}]
|
||||||
%Node.MFM.Flip{
|
|
||||||
children: [%Node.Text{props: %{text: "Misskey expands the world of the Fediverse"}}]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
input_tree_v = [
|
expected = "<span class=\"mfm-font\" data-mfm-cursive></span>"
|
||||||
%Node.MFM.Flip{
|
|
||||||
children: [%Node.Text{props: %{text: "Misskey expands the world of the Fediverse"}}],
|
|
||||||
props: %{v: true}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
input_tree_h_v = [
|
assert Encoder.to_html(input) == expected
|
||||||
%Node.MFM.Flip{
|
|
||||||
children: [%Node.Text{props: %{text: "Misskey expands the world of the Fediverse"}}],
|
|
||||||
props: %{v: true, h: true}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
expected =
|
|
||||||
~s[<span class="mfm" style="display: inline-block; transform: scaleX(-1);">Misskey expands the world of the Fediverse</span>]
|
|
||||||
|
|
||||||
expected_v =
|
|
||||||
~s[<span class="mfm" style="display: inline-block; transform: scaleY(-1);">Misskey expands the world of the Fediverse</span>]
|
|
||||||
|
|
||||||
expected_h_v =
|
|
||||||
~s[<span class="mfm" style="display: inline-block; transform: scale(-1);">Misskey expands the world of the Fediverse</span>]
|
|
||||||
|
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it handles font" do
|
test "it handles a node with one value attribute" do
|
||||||
input_tree = [
|
input = [%Node.MFM{name: "jelly", attributes: [{"speed", "2s"}], content: []}]
|
||||||
%Node.MFM.Font{
|
|
||||||
children: [%Node.Text{props: %{text: "Misskey expands the world of the Fediverse"}}],
|
|
||||||
props: %{font: "fantasy"}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
expected =
|
expected = "<span class=\"mfm-jelly\" data-mfm-speed=\"2s\"></span>"
|
||||||
~s[<span class="mfm" style="display: inline-block; font-family: fantasy;">Misskey expands the world of the Fediverse</span>]
|
|
||||||
|
|
||||||
assert Encoder.to_html(input_tree) == expected
|
assert Encoder.to_html(input) == expected
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it handles x" do
|
test "it handles a node with multiple attributes" do
|
||||||
input_tree = [
|
input = [
|
||||||
%Node.MFM.X{
|
%Node.MFM{
|
||||||
children: [%Node.Text{props: %{text: "🍮"}}],
|
name: "spin",
|
||||||
props: %{size: "400%"}
|
attributes: [{"alternate"}, {"speed", "0.5s"}],
|
||||||
|
content: []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
expected = ~s[<span style="font-size: 400%" class="mfm _mfm_x3_">🍮</span>]
|
expected = "<span class=\"mfm-spin\" data-mfm-alternate data-mfm-speed=\"0.5s\"></span>"
|
||||||
|
|
||||||
assert Encoder.to_html(input_tree) == expected
|
assert Encoder.to_html(input) == 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[<span class="_mfm_blur_">Misskey expands the world of the Fediverse</span>]
|
|
||||||
|
|
||||||
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[<span class="mfm _mfm_jelly_" style="display: inline-block; animation: 1s linear 0s infinite normal both running mfm-rubberBand;">🍮</span>]
|
|
||||||
|
|
||||||
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[<span class="mfm _mfm_tada_" style="display: inline-block; font-size: 150%; animation: 1s linear 0s infinite normal both running mfm-tada;">🍮</span>]
|
|
||||||
|
|
||||||
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[<span class="mfm _mfm_jump_" style="display: inline-block; animation: 0.75s linear 0s infinite normal none running mfm-jump;">🍮</span>]
|
|
||||||
|
|
||||||
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[<span class="mfm _mfm_bounce_" style="display: inline-block; animation: 0.75s linear 0s infinite normal none running mfm-bounce; transform-origin: center bottom 0px;">🍮</span>]
|
|
||||||
|
|
||||||
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[<span class="mfm _mfm_spin_" style="display: inline-block; animation: 1.5s linear 0s infinite reverse none running mfm-spin;">🍮</span>]
|
|
||||||
|
|
||||||
expected_tree_x_left =
|
|
||||||
~s[<span class="mfm _mfm_spin_" style="display: inline-block; animation: 1.5s linear 0s infinite reverse none running mfm-spinX;">🍮</span>]
|
|
||||||
|
|
||||||
expected_tree_y_left =
|
|
||||||
~s[<span class="mfm _mfm_spin_" style="display: inline-block; animation: 1.5s linear 0s infinite reverse none running mfm-spinY;">🍮</span>]
|
|
||||||
|
|
||||||
expected_tree_z_alternate =
|
|
||||||
~s[<span class="mfm _mfm_spin_" style="display: inline-block; animation: 1.5s linear 0s infinite alternate none running mfm-spin;">🍮</span>]
|
|
||||||
|
|
||||||
expected_tree_x_alternate =
|
|
||||||
~s[<span class="mfm _mfm_spin_" style="display: inline-block; animation: 1.5s linear 0s infinite alternate none running mfm-spinX;">🍮</span>]
|
|
||||||
|
|
||||||
expected_tree_y_alternate =
|
|
||||||
~s[<span class="mfm _mfm_spin_" style="display: inline-block; animation: 1.5s linear 0s infinite alternate none running mfm-spinY;">🍮</span>]
|
|
||||||
|
|
||||||
expected_tree_z_normal =
|
|
||||||
~s[<span class="mfm _mfm_spin_" style="display: inline-block; animation: 1.5s linear 0s infinite normal none running mfm-spin;">🍮</span>]
|
|
||||||
|
|
||||||
expected_tree_x_normal =
|
|
||||||
~s[<span class="mfm _mfm_spin_" style="display: inline-block; animation: 1.5s linear 0s infinite normal none running mfm-spinX;">🍮</span>]
|
|
||||||
|
|
||||||
expected_tree_y_normal =
|
|
||||||
~s[<span class="mfm _mfm_spin_" style="display: inline-block; animation: 1.5s linear 0s infinite normal none running mfm-spinY;">🍮</span>]
|
|
||||||
|
|
||||||
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[<span class="mfm" style="display: inline-block; animation: 0.5s ease 0s infinite normal none running mfm-shake;">🍮</span>]
|
|
||||||
|
|
||||||
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[<span class="mfm" style="display: inline-block; animation: 0.5s ease 0s infinite normal none running mfm-twitch;">🍮</span>]
|
|
||||||
|
|
||||||
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[<span class="mfm" style="display: inline-block; animation: 1s linear 0s infinite normal none running mfm-rainbow;">🍮</span>]
|
|
||||||
|
|
||||||
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[<span class="mfm" style="display: inline-block; animation: 1s linear 0s infinite normal none running mfm-sparkle;">🍮</span>]
|
|
||||||
|
|
||||||
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[<span class="mfm" style="display: inline-block; transform: rotate(90deg); transform-origin: center center 0px;">🍮</span>]
|
|
||||||
|
|
||||||
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[<span>🍮</span>]
|
|
||||||
|
|
||||||
assert Encoder.to_html(input_tree) == expected
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it handles multpile nodes on the same level" do
|
test "it handles multpile nodes on the same level" do
|
||||||
input_tree = [
|
input = [
|
||||||
%Node.MFM.Rotate{
|
%Node.MFM{name: "twitch", attributes: [], content: []},
|
||||||
children: [%Node.Text{props: %{text: "🍮"}}]
|
%Node.Text{content: "chocolatine"},
|
||||||
},
|
%Node.MFM{name: "blabla", attributes: [], content: []}
|
||||||
%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"}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
expected =
|
expected = "<span class=\"mfm-twitch\"></span>chocolatine<span class=\"mfm-blabla\"></span>"
|
||||||
~s[<span class="mfm" style="display: inline-block; transform: rotate(90deg); transform-origin: center center 0px;">🍮</span>pain au chocolat<span class="mfm" style="display: inline-block; font-family: fantasy;">Misskey expands the world of the Fediverse</span>]
|
|
||||||
|
|
||||||
assert Encoder.to_html(input_tree) == expected
|
assert Encoder.to_html(input) == expected
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it handles nesting" do
|
test "it handles nesting" do
|
||||||
input_tree = [
|
input = [
|
||||||
%Node.MFM.Rotate{
|
%Node.MFM{
|
||||||
children: [
|
name: "twitch",
|
||||||
%Node.MFM.Font{
|
attributes: [],
|
||||||
children: [%Node.Text{props: %{text: "🍮"}}],
|
content: [%Node.Text{content: "chocolatine"}]
|
||||||
props: %{font: "fantasy"}
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
expected = "<span class=\"mfm-twitch\">chocolatine</span>"
|
||||||
|
|
||||||
|
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 =
|
expected =
|
||||||
~s[<span class="mfm" style="display: inline-block; transform: rotate(90deg); transform-origin: center center 0px;"><span class="mfm" style="display: inline-block; font-family: fantasy;">🍮</span></span>]
|
"It's not <span class=\"mfm-twitch\">chocolatine</span>\nit's <span class=\"mfm-x4\"><span class=\"mfm-spin\" data-mfm-alternate data-mfm-speed=\"0.2s\">pain</span> <span class=\"mfm-rainbow\">au</span> <span class=\"mfm-jump\" data-mfm-speed=\"0.5s\">chocolat</span></span>"
|
||||||
|
|
||||||
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[<span class="mfm" style="display: inline-block; animation: 1s linear 0s infinite normal none running mfm-sparkle;">🍮</span><span class="mfm" style="display: inline-block; animation: 1s linear 0s infinite normal none running mfm-sparkle;">🍮</span>]
|
|
||||||
|
|
||||||
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 <span class=\"mfm\" style=\"display: inline-block; animation: 0.2s ease 0s infinite normal none running mfm-twitch;\">chocolatine</span>\nit's <span style=\"font-size: 600%\" class=\"mfm _mfm_x4_\"><span class=\"mfm _mfm_spin_\" style=\"display: inline-block; animation: 1s linear 0s infinite normal none running mfm-spin;\">pain</span> <span class=\"mfm\" style=\"display: inline-block; animation: 2s linear 0s infinite normal none running mfm-rainbow;\">au</span> <span class=\"mfm _mfm_jump_\" style=\"display: inline-block; animation: 0.5s linear 0s infinite normal none running mfm-jump;\">chocolat</span></span>"
|
|
||||||
|
|
||||||
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 <span class=\"mfm\" style=\"display: inline-block; animation: 0.2s ease 0s infinite normal none running mfm-twitch;\">chocolatine</span>\nit's <span style=\"font-size: 600%\" class=\"mfm _mfm_x4_\"><span class=\"mfm _mfm_spin_\" style=\"display: inline-block; animation: 1s linear 0s infinite normal none running mfm-spin;\">pain</span> <span class=\"mfm\" style=\"display: inline-block; animation: 2s linear 0s infinite normal none running mfm-rainbow;\">au</span> <span class=\"mfm _mfm_jump_\" style=\"display: inline-block; animation: 0.5s linear 0s infinite normal none running mfm-jump;\">chocolat</span></span>"
|
|
||||||
|
|
||||||
assert Encoder.to_html(input) == expected
|
assert Encoder.to_html(input) == expected
|
||||||
end
|
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 <span class=\"mfm-twitch\">chocolatine</span>\nit's <span class=\"mfm-x4\"><span class=\"mfm-spin\" data-mfm-alternate data-mfm-speed=\"0.2s\">pain</span> <span class=\"mfm-rainbow\">au</span> <span class=\"mfm-jump\" data-mfm-speed=\"0.5s\">chocolat</span></span>"
|
||||||
|
|
||||||
|
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() ==
|
||||||
|
"$<span class=\"mfm-spin\">beep</span>$"
|
||||||
|
|
||||||
|
assert MfmParser.Parser.parse("$[spin boop]]") |> Encoder.to_html() ==
|
||||||
|
"<span class=\"mfm-spin\">boop</span>]"
|
||||||
|
|
||||||
|
# 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
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,6 @@ defmodule MfmParser.LexerTest do
|
||||||
alias MfmParser.Lexer
|
alias MfmParser.Lexer
|
||||||
|
|
||||||
alias MfmParser.Token.MFM
|
alias MfmParser.Token.MFM
|
||||||
alias MfmParser.Token.Newline
|
|
||||||
alias MfmParser.Token.Text
|
alias MfmParser.Token.Text
|
||||||
|
|
||||||
describe "eof" do
|
describe "eof" do
|
||||||
|
@ -82,16 +81,4 @@ defmodule MfmParser.LexerTest do
|
||||||
{%Text{content: "Tu abuela ve anime y no se lava el culo"}, ""}
|
{%Text{content: "Tu abuela ve anime y no se lava el culo"}, ""}
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
@ -12,611 +12,139 @@ defmodule MfmParser.ParserTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it can handle text as input" do
|
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
|
assert Parser.parse(input) == output
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it can handle a newline as input" do
|
test "it can handle an element without attributes" do
|
||||||
input = "\n"
|
input = "$[flip ]"
|
||||||
|
|
||||||
output = [%MfmParser.Node.Newline{props: %{text: "\n"}}]
|
output = [%MfmParser.Node.MFM{name: "flip", attributes: [], content: []}]
|
||||||
|
|
||||||
assert Parser.parse(input) == output
|
assert Parser.parse(input) == output
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it can handle a flip element" do
|
test "it can handle an element with one non-value attribute" do
|
||||||
input_default = "$[flip ]"
|
input = "$[font.cursive ]"
|
||||||
input_v = "$[flip.v ]"
|
|
||||||
input_hv = "$[flip.h,v ]"
|
|
||||||
|
|
||||||
output_default = [
|
output = [%MfmParser.Node.MFM{name: "font", attributes: [{"cursive"}], content: []}]
|
||||||
%MfmParser.Node.MFM.Flip{
|
|
||||||
props: %{
|
|
||||||
v: false,
|
|
||||||
h: false
|
|
||||||
},
|
|
||||||
children: []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
output_v = [
|
assert Parser.parse(input) == output
|
||||||
%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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it can handle a font element" do
|
test "it can handle an element with one value attribute" do
|
||||||
input = "$[font.serif ]"
|
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 = [
|
output = [
|
||||||
%MfmParser.Node.MFM.Font{
|
%MfmParser.Node.MFM{
|
||||||
props: %{
|
name: "spin",
|
||||||
font: "serif"
|
attributes: [{"alternate"}, {"speed", "0.5s"}],
|
||||||
},
|
content: []
|
||||||
children: []
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
assert Parser.parse(input) == output
|
assert Parser.parse(input) == output
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe "multiple element input" do
|
describe "multiple element input" do
|
||||||
test "it can handle multiple elements as input" do
|
test "it can handle multiple elements as input" do
|
||||||
input = "$[twitch ]chocolatine$[blabla ]\n$[jump ]"
|
input = "$[twitch ]chocolatine$[blabla ]\n$[jump ]"
|
||||||
|
|
||||||
assert Parser.parse(input) == [
|
output = [
|
||||||
%MfmParser.Node.MFM.Twitch{children: [], props: %{speed: "0.5s"}},
|
%MfmParser.Node.MFM{name: "twitch", attributes: [], content: []},
|
||||||
%MfmParser.Node.Text{props: %{text: "chocolatine"}},
|
%MfmParser.Node.Text{content: "chocolatine"},
|
||||||
%MfmParser.Node.MFM.Undefined{children: [], props: %{}},
|
%MfmParser.Node.MFM{name: "blabla", attributes: [], content: []},
|
||||||
%MfmParser.Node.Newline{props: %{text: "\n"}},
|
%MfmParser.Node.Text{content: "\n"},
|
||||||
%MfmParser.Node.MFM.Jump{children: [], props: %{speed: "0.75s"}}
|
%MfmParser.Node.MFM{name: "jump", attributes: [], content: []}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
assert Parser.parse(input) == output
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it can handle nesting" do
|
test "it can handle nesting" do
|
||||||
input = "$[twitch chocolatine]"
|
input = "$[twitch chocolatine]"
|
||||||
|
|
||||||
assert Parser.parse(input) == [
|
output = [
|
||||||
%MfmParser.Node.MFM.Twitch{
|
%MfmParser.Node.MFM{
|
||||||
children: [%MfmParser.Node.Text{props: %{text: "chocolatine"}}],
|
name: "twitch",
|
||||||
props: %{speed: "0.5s"}
|
attributes: [],
|
||||||
}
|
content: [%MfmParser.Node.Text{content: "chocolatine"}]
|
||||||
]
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
assert Parser.parse(input) == output
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it can handle multiple nesting" do
|
test "it can handle multiple nestings" do
|
||||||
input = "$[twitch $[spin chocolatine]]"
|
input = "$[twitch $[spin chocolatine]]"
|
||||||
|
|
||||||
assert Parser.parse(input) == [
|
output = [
|
||||||
%MfmParser.Node.MFM.Twitch{
|
%MfmParser.Node.MFM{
|
||||||
children: [
|
name: "twitch",
|
||||||
%MfmParser.Node.MFM.Spin{
|
attributes: [],
|
||||||
children: [%MfmParser.Node.Text{props: %{text: "chocolatine"}}],
|
content: [
|
||||||
props: %{direction: "normal", axis: "z", speed: "1.5s"}
|
%MfmParser.Node.MFM{
|
||||||
}
|
name: "spin",
|
||||||
],
|
attributes: [],
|
||||||
props: %{speed: "0.5s"}
|
content: [%MfmParser.Node.Text{content: "chocolatine"}]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
assert Parser.parse(input) == output
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it can handle a complex structure of multiple elements and nesting" do
|
test "it can handle a complex structure of multiple elements and nesting" do
|
||||||
input =
|
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) == [
|
output = [
|
||||||
%MfmParser.Node.Text{props: %{text: "It's not "}},
|
%MfmParser.Node.Text{content: "It's not "},
|
||||||
%MfmParser.Node.MFM.Twitch{
|
%MfmParser.Node.MFM{
|
||||||
children: [%MfmParser.Node.Text{props: %{text: "chocolatine"}}],
|
name: "twitch",
|
||||||
props: %{speed: "0.5s"}
|
attributes: [],
|
||||||
},
|
content: [%MfmParser.Node.Text{content: "chocolatine"}]
|
||||||
%MfmParser.Node.Newline{props: %{text: "\n"}},
|
},
|
||||||
%MfmParser.Node.Text{props: %{text: "it's "}},
|
%MfmParser.Node.Text{content: "\nit's "},
|
||||||
%MfmParser.Node.MFM.X{
|
%MfmParser.Node.MFM{
|
||||||
children: [
|
name: "x4",
|
||||||
%MfmParser.Node.MFM.Spin{
|
attributes: [],
|
||||||
children: [%MfmParser.Node.Text{props: %{text: "pain"}}],
|
content: [
|
||||||
props: %{direction: "normal", axis: "z", speed: "1.5s"}
|
%MfmParser.Node.MFM{
|
||||||
},
|
name: "spin",
|
||||||
%MfmParser.Node.Text{props: %{text: " "}},
|
attributes: [{"alternate"}, {"speed", "0.2s"}],
|
||||||
%MfmParser.Node.MFM.Rainbow{
|
content: [%MfmParser.Node.Text{content: "pain"}]
|
||||||
children: [%MfmParser.Node.Text{props: %{text: "au"}}],
|
},
|
||||||
props: %{speed: "1s"}
|
%MfmParser.Node.Text{content: " "},
|
||||||
},
|
%MfmParser.Node.MFM{
|
||||||
%MfmParser.Node.Text{props: %{text: " "}},
|
name: "rainbow",
|
||||||
%MfmParser.Node.MFM.Jump{
|
attributes: [],
|
||||||
children: [%MfmParser.Node.Text{props: %{text: "chocolat"}}],
|
content: [%MfmParser.Node.Text{content: "au"}]
|
||||||
props: %{speed: "0.75s"}
|
},
|
||||||
}
|
%MfmParser.Node.Text{content: " "},
|
||||||
],
|
%MfmParser.Node.MFM{
|
||||||
props: %{size: "600%"}
|
name: "jump",
|
||||||
}
|
attributes: [{"speed", "0.5s"}],
|
||||||
]
|
content: [%MfmParser.Node.Text{content: "chocolat"}]
|
||||||
end
|
}
|
||||||
end
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
describe "to_props/1" do
|
assert Parser.parse(input) == output
|
||||||
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")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue