Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add optional support for math syntax #142

Merged
merged 1 commit into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
- [Pure links](#pure-links)
- [Wikilinks...](#wikilinks)
- [Sub and Sup HTML Elements](#sub-and-sup-html-elements)
- [Mathematical expressions](#mathematical-expressions)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I couldn't find the readme task, so I mirrored the changes from moduledoc manually. Let me know if/how I should generate it :)

Copy link
Owner

Choose a reason for hiding this comment

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

Done it is mix xtra I guess I should document that somewhere, maybe contribution notes/rules?

Copy link
Owner

Choose a reason for hiding this comment

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

Also done 😊

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah it comes from an external package, that makes sense, thanks!

- [Github Flavored Markdown](#github-flavored-markdown)
- [Strike Through](#strike-through)
- [GFM Tables](#gfm-tables)
Expand Down Expand Up @@ -170,6 +171,34 @@ But by specifying `sub_sup: true`
{:ok, [{"p", [], ["H", {"sub", [], ["2"], %{}}, "O or a", {"sup", [], ["n"], %{}}, " + b", {"sup", [], ["n"], %{}}, " = c", {"sup", [], ["n"], %{}}], %{}}], []}
```

### Mathematical expressions

> Note: math syntax within Markdown is not standardized, so this option is a subject to change in future releases.

This feature is not enabled by default but can be enabled with the option `math: true`.

When enabled, LaTeX formatted math can be written within Markdown. For more information, see [LaTeX/Mathematics](https://en.wikibooks.org/wiki/LaTeX/Mathematics) in Wikibooks.

#### Inline expressions

Inline-style expression can be written by surrounding the expression with dollar symbols (`$`).

```elixir
iex> EarmarkParser.as_ast("$x = 1$", math: true)
{:ok, [{"p", [], [{"code", [{"class", "math-inline"}], ["x = 1"], %{line: 1}}], %{}}], []}
```

There must be no space between `$` and the surrounded expression. If you want to use a dollar sign in the same line as a math expression, you can escape the dollar with backslash (`\\$`).

#### Expressions as blocks

Display-style expression can be written by surrounding the expression with two dollar signs (`$$`).

```elixir
iex> EarmarkParser.as_ast("$$x = 1$$", math: true)
{:ok, [{"p", [], [{"code", [{"class", "math-display"}], ["x = 1"], %{line: 1}}], %{}}], []}
```

### Github Flavored Markdown

GFM is supported by default, however as GFM is a moving target and all GFM extension do not make sense in a general context, EarmarkParser does not support all of it, here is a list of what is supported:
Expand Down
24 changes: 24 additions & 0 deletions lib/earmark_parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,30 @@ defmodule EarmarkParser do
iex(10)> EarmarkParser.as_ast("H~2~O or a^n^ + b^n^ = c^n^", sub_sup: true)
{:ok, [{"p", [], ["H", {"sub", [], ["2"], %{}}, "O or a", {"sup", [], ["n"], %{}}, " + b", {"sup", [], ["n"], %{}}, " = c", {"sup", [], ["n"], %{}}], %{}}], []}

### Mathematical expressions

> Note: math syntax within Markdown is not standardized, so this option is a subject to change in future releases.

This feature is not enabled by default but can be enabled with the option `math: true`.

When enabled, LaTeX formatted math can be written within Markdown. For more information, see [LaTeX/Mathematics](https://en.wikibooks.org/wiki/LaTeX/Mathematics) in Wikibooks.

#### Inline expressions

Inline-style expression can be written by surrounding the expression with dollar symbols (`$`).

iex> EarmarkParser.as_ast("$x = 1$", math: true)
{:ok, [{"p", [], [{"code", [{"class", "math-inline"}], ["x = 1"], %{line: 1}}], %{}}], []}

There must be no space between `$` and the surrounded expression. If you want to use a dollar sign in the same line as a math expression, you can escape the dollar with backslash (`\\$`).

#### Expressions as blocks

Display-style expression can be written by surrounding the expression with two dollar signs (`$$`).

iex> EarmarkParser.as_ast("$$x = 1$$", math: true)
{:ok, [{"p", [], [{"code", [{"class", "math-display"}], ["x = 1"], %{line: 1}}], %{}}], []}

### Github Flavored Markdown

GFM is supported by default, however as GFM is a moving target and all GFM extension do not make sense in a general context, EarmarkParser does not support all of it, here is a list of what is supported:
Expand Down
29 changes: 28 additions & 1 deletion lib/earmark_parser/ast/inline.ex
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ defmodule EarmarkParser.Ast.Inline do
converter_for_sub: &converter_for_sub/1,
converter_for_sup: &converter_for_sup/1,
#
converter_for_math_display: &converter_for_math_display/1,
converter_for_math_inline: &converter_for_math_inline/1,
#
converter_for_code: &converter_for_code/1,
converter_for_br: &converter_for_br/1,
converter_for_inline_ial: &converter_for_inline_ial/1,
Expand All @@ -78,7 +81,7 @@ defmodule EarmarkParser.Ast.Inline do
# Converters
#
######################
@escape_rule ~r{^\\([\\`*\{\}\[\]()\#+\-.!_>])}
@escape_rule ~r{^\\([\\`*\{\}\[\]()\#+\-.!_>$])}
def converter_for_escape({src, lnb, context, use_linky?}) do
if match = Regex.run(@escape_rule, src) do
[match, escaped] = match
Expand Down Expand Up @@ -227,6 +230,30 @@ defmodule EarmarkParser.Ast.Inline do

def converter_for_sup(_), do: nil

@math_inline_rgx ~r{\A\$(?=[^\s$])([\s\S]*?[^\s\\])\$}
def converter_for_math_inline({src, lnb, %{options: %{math: true}} = context, use_linky?}) do
if match = Regex.run(@math_inline_rgx, src) do
[match, content] = match
content = String.trim(content)
out = math_inline(content, lnb)
{behead(src, match), lnb, prepend(context, out), use_linky?}
end
end

def converter_for_math_inline(_), do: nil

@math_display_rgx ~r{\A\$\$([\s\S]+?)\$\$}
def converter_for_math_display({src, lnb, %{options: %{math: true}} = context, use_linky?}) do
if match = Regex.run(@math_display_rgx, src) do
[match, content] = match
content = String.trim(content)
out = math_display(content, lnb)
{behead(src, match), lnb, prepend(context, out), use_linky?}
end
end

def converter_for_math_display(_), do: nil

@squash_ws ~r{\s+}
@code ~r{^
(`+) # $1 = Opening run of `
Expand Down
10 changes: 8 additions & 2 deletions lib/earmark_parser/context.ex
Original file line number Diff line number Diff line change
Expand Up @@ -110,16 +110,22 @@ defmodule EarmarkParser.Context do
else
""
end
math =
if options.math do
"$"
else
""
end
rule_updates =
if options.gfm do
rules = [
text: ~r{^[\s\S]+?(?=~~|[\\<!\[_*`#{subsup}]|https?://| \{2,\}\n|$)}
text: ~r{^[\s\S]+?(?=~~|[\\<!\[_*`#{math}#{subsup}]|https?://| \{2,\}\n|$)}
]

if options.breaks do
break_updates = [
br: ~r{^ *\n(?!\s*$)},
text: ~r{^[\s\S]+?(?=~~|[\\<!\[_*`#{subsup}]|https?://| *\n|$)}
text: ~r{^[\s\S]+?(?=~~|[\\<!\[_*`#{math}#{subsup}]|https?://| *\n|$)}
]

Keyword.merge(rules, break_updates)
Expand Down
11 changes: 10 additions & 1 deletion lib/earmark_parser/helpers/ast_helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ defmodule EarmarkParser.Helpers.AstHelpers do
[{t, merge_attrs(a, atts), c, m}|tags]
end
def augment_tag_with_ial([], _atts) do
[]
[]
end

@doc false
Expand All @@ -41,6 +41,15 @@ defmodule EarmarkParser.Helpers.AstHelpers do
{"class", classes |> Enum.join(" ")}
end

@doc false
def math_inline(text, lnb) do
emit("code", text, [class: "math-inline"], %{line: lnb})
end

def math_display(text, lnb) do
emit("code", text, [class: "math-display"], %{line: lnb})
end

@doc false
def codespan(text, lnb) do
emit("code", text, [class: "inline"], %{line: lnb})
Expand Down
3 changes: 2 additions & 1 deletion lib/earmark_parser/options.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ defmodule EarmarkParser.Options do
messages: MapSet.new([]),
pure_links: true,
sub_sup: false,
math: false,

# deprecated
pedantic: false,
Expand Down Expand Up @@ -57,7 +58,7 @@ defmodule EarmarkParser.Options do
# deprecated
pedantic: boolean(),
smartypants: boolean(),
timeout: nil | non_neg_integer()
timeout: nil | non_neg_integer()

}

Expand Down
Loading
Loading