Compare commits

...

21 Commits

Author SHA1 Message Date
amelia cuss 0cf9a44cb6 README: link to canonical source. 2024-01-18 17:49:57 +11:00
amelia cuss 960ebe5356 v0.1.5 2024-01-11 23:56:57 +11:00
amelia cuss ce86382ecb comrak: use akkoma branch, add akkoma_autolinks option. 2024-01-11 23:32:45 +11:00
amelia cuss 58fb1be024 README: note about docs being old. 2024-01-03 23:42:53 +11:00
amelia cuss 9d9d9bbb9e v0.1.4 2024-01-03 23:38:18 +11:00
amelia cuss 52c70744a7 lib: support native renderer, cleanup. 2024-01-03 23:20:23 +11:00
amelia cuss cbe17ff232 v0.1.3 2024-01-03 21:27:03 +11:00
amelia cuss 59f625b78b lib: implement full range of options. 2024-01-03 21:19:54 +11:00
amelia cuss 31934ba9cd lib: render: don't textify list/table/fnd contents! 2024-01-02 15:20:34 +11:00
amelia cuss 5696a00f54 all: clean up, latest versions, building. 2024-01-02 14:06:44 +11:00
amelia cuss 95e2014167 lib: support "breaks" option. 2024-01-02 04:16:55 +11:00
amelia cuss 73eb76c3c4 LICENSE: chmod -x. 2024-01-02 02:27:42 +11:00
amelia cuss c5feb6bb8c native: drop path. 2024-01-02 01:40:01 +11:00
amelia cuss 3e524a576f flake.nix: clean. 2024-01-02 01:05:39 +11:00
amelia cuss 35b7f8eb5f rustler: upgrade. 2024-01-02 01:00:12 +11:00
amelia cuss 70a7090c92 native: cargo fmt; address comrak api changes. 2024-01-02 01:00:08 +11:00
amelia cuss c8d83fae8e build half-going. 2024-01-01 22:49:51 +11:00
amelia cuss aebcb2fc39 config.exs: address deprecation. 2024-01-01 22:26:31 +11:00
amelia cuss 97bdc02f1e mix deps.get. 2024-01-01 22:26:25 +11:00
amelia cuss 16e16d6b90 Cargo.toml: update. 2024-01-01 22:24:56 +11:00
amelia cuss d1718ff86b flake.nix: init. 2024-01-01 22:20:11 +11:00
25 changed files with 2204 additions and 407 deletions

3
.gitignore vendored
View File

@ -23,3 +23,6 @@ erl_crash.dump
markdown-*.tar
/priv/native/*.so
# Nix
/result

0
LICENSE Executable file → Normal file
View File

View File

@ -1,3 +1,9 @@
> **Note**: This README is currently out-of-date, but [this fork] isn't!
[this fork]: https://hrzn.ee/kivikakk/ex-markdown
---
# Markdown
markdown renderer for [comrak](https://github.com/kivikakk/comrak) markdown parser
@ -58,7 +64,6 @@ table_cell(data, content, alignment, header)
### Inline
```
autolink(data, url, link_type)
codespan(data, code)
double_emphasis(data, text)
emphasis(data, text)

View File

@ -1,6 +1,6 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config
import Config
# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this

78
flake.lock Normal file
View File

@ -0,0 +1,78 @@
{
"nodes": {
"fenix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"rust-analyzer-src": []
},
"locked": {
"lastModified": 1704090261,
"narHash": "sha256-Vti1mv4WhmXHPNcFgUiJyt4OKLvsvLzM2eKS4bEegf0=",
"owner": "nix-community",
"repo": "fenix",
"rev": "66fc1883c34c42df188b83272445aedb26bb64b5",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"path": "/nix/store/pgid9c9xfcrbqx2giry0an0bi0df7s5c-source",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "path"
},
"original": {
"id": "flake-utils",
"type": "indirect"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1701952659,
"narHash": "sha256-TJv2srXt6fYPUjxgLAL0cy4nuf1OZD4KuA1TrCiQqg0=",
"path": "/nix/store/vdap779dm1xfpr35xvfb9a1yiqiw9n89-source",
"rev": "b4372c4924d9182034066c823df76d6eaf1f4ec4",
"type": "path"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"root": {
"inputs": {
"fenix": "fenix",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

90
flake.nix Normal file
View File

@ -0,0 +1,90 @@
{
description = "ex-markdown";
inputs.fenix = {
url = github:nix-community/fenix;
inputs.nixpkgs.follows = "nixpkgs";
inputs.rust-analyzer-src.follows = "";
};
outputs = {
self,
nixpkgs,
flake-utils,
fenix,
...
}:
flake-utils.lib.eachDefaultSystem (system: let
pkgs = nixpkgs.legacyPackages.${system};
inherit (pkgs) lib beamPackages libiconv;
mixNixDeps = import ./mix.nix {inherit beamPackages lib;};
src = ./.;
rustToolchain = fenix.packages.${system}.stable.toolchain;
nativePackage =
(pkgs.makeRustPlatform {
cargo = rustToolchain;
rustc = rustToolchain;
})
.buildRustPackage {
# TODO: parse Cargo.toml.
pname = "comrak_rustler";
version = "0.1.5";
src = ./native/comrak_rustler;
cargoLock = {
lockFile = ./native/comrak_rustler/Cargo.lock;
outputHashes = {
"comrak-0.20.0" = "sha256-ohYxmOR34NqfXyhxcYO7KBdmElEF2WasxlTEDgr/ygA=";
};
};
};
in rec {
formatter = pkgs.alejandra;
packages.default =
(beamPackages.buildMix rec {
# TODO: as above? Should these versions stay in step?
name = "markdown";
version = "0.1.5";
inherit src;
patchPhase = ''
mkdir -p priv/native
find ${nativePackage}
cp ${nativePackage}/lib/libcomrak_rustler.* priv/native/libcomrak_rustler.so
export MARKDOWN_NATIVE_SKIP_COMPILATION="1"
'';
beamDeps = with mixNixDeps; [jason rustler html_entities];
doInstallCheck = true;
installCheckPhase = ''
mix test --no-deps-check
'';
})
.overrideAttrs (prev: {
LC_ALL = "en_US.UTF-8";
# buildMix clobbers the input passthru entirely.
passthru =
prev.passthru
// {
inherit src rustToolchain nativePackage;
};
});
devShells.default = packages.default.overrideAttrs (prev: {
nativeBuildInputs =
prev.nativeBuildInputs
++ [
rustToolchain
# onig_sys
libiconv
];
});
});
}

View File

@ -1,9 +1,20 @@
defmodule Markdown do
def parse(text) do
Markdown.Native.parse(text)
def to_ast(md, opts \\ %{})
def to_ast(md, opts) when is_binary(md) do
Markdown.Native.markdown_to_ast(md, opts(opts))
end
def to_ast(md, _) do md end
def to_html(md, opts \\ %{}, renderer \\ nil) do
if renderer == nil and is_binary(md) do
Markdown.Native.markdown_to_html(md, opts(opts))
else
Markdown.Render.render(to_ast(md, opts(opts)), opts(opts), renderer || Markdown.HtmlRenderer)
end
end
def render(text, renderer \\ Markdown.HtmlRenderer, data \\ %{}) do
Markdown.Render.render(parse(text), renderer, data)
defp opts(opts) do
%Markdown.Renderer.Options{}
|> Map.merge(opts)
end
end

View File

@ -1,5 +1,8 @@
defmodule Markdown.Native do
use Rustler, otp_app: :markdown, crate: "comrak"
use Rustler, otp_app: :markdown,
crate: :comrak_rustler,
mode: if(Mix.env() == :prod, do: :release, else: :debug),
skip_compilation?: System.get_env("MARKDOWN_NATIVE_SKIP_COMPILATION") != nil
defmodule NodeList do
defstruct list_type: "",
@ -129,6 +132,6 @@ defmodule Markdown.Native do
defstruct name: ""
end
def parse(_text), do: :erlang.nif_error(:nif_not_loaded)
def html(_ast), do: :erlang.nif_error(:nif_not_loaded)
def markdown_to_ast(_md, _opts), do: :erlang.nif_error(:nif_not_loaded)
def markdown_to_html(_md, _opts), do: :erlang.nif_error(:nif_not_loaded)
end

View File

@ -3,11 +3,11 @@ defmodule Markdown.Render do
alias Markdown.Renderer
defmodule Context do
defstruct output: "", footnote_ix: 0, data: nil, renderer: Renderer
defstruct output: "", footnote_ix: 0, opts: nil, renderer: nil
end
def render({_node, _children} = root, renderer \\ Renderer, data \\ %{}) do
context = render_node(%Context{data: data, renderer: renderer}, root)
def render({_node, _children} = root, opts = %{}, renderer) do
context = render_node(%Context{opts: %Renderer.Options{} |> Map.merge(opts), renderer: renderer}, root)
context.output
end
@ -57,77 +57,78 @@ defmodule Markdown.Render do
render_children(context, children)
%Native.BlockQuote{} ->
tmp = render_children(clear(context), children)
write(context, context.renderer.block_quote(context.data, tmp.output))
kids = render_children(clear(context), children)
write(context, context.renderer.block_quote(context.opts, kids.output))
%Native.List{list: list} ->
tmp = render_children(clear(context), children)
kids = render_children(clear(context), children)
write(
context,
context.renderer.list(context.data, tmp.output, list.list_type, list.start)
context.renderer.list(context.opts, kids.output, list.list_type, list.start)
)
%Native.Item{list: list} ->
tmp = render_children_as_text(clear(context), children)
write(context, context.renderer.list_item(context.data, tmp.output, list.list_type))
# XXX(ashe): we don't do any tightness distinctions on lists.
kids = render_children(clear(context), children)
write(context, context.renderer.list_item(context.opts, kids.output, list.list_type))
%Native.Heading{heading: heading} ->
tmp = render_children(clear(context), children)
write(context, context.renderer.header(context.data, tmp.output, heading.level))
kids = render_children(clear(context), children)
write(context, context.renderer.header(context.opts, kids.output, heading.level))
%Native.CodeBlock{block: block} ->
lang = Enum.at(String.split(block.info, " "), 0)
write(context, context.renderer.block_code(context.data, block.literal, lang))
write(context, context.renderer.block_code(context.opts, block.literal, lang))
%Native.HtmlBlock{block: block} ->
write(context, context.renderer.block_html(context.data, block.literal))
write(context, context.renderer.block_html(context.opts, block.literal))
%Native.ThematicBreak{} ->
write(context, context.renderer.hrule(context.data))
write(context, context.renderer.hrule(context.opts))
%Native.Paragraph{} ->
tmp = render_children(clear(context), children)
write(context, context.renderer.paragraph(context.data, tmp.output))
kids = render_children(clear(context), children)
write(context, context.renderer.paragraph(context.opts, kids.output))
%Native.Text{text: text} ->
write(context, HtmlEntities.encode(text))
%Native.LineBreak{} ->
write(context, context.renderer.linebreak(context.data))
write(context, context.renderer.linebreak(context.opts))
%Native.SoftBreak{} ->
write(context, context.renderer.softbreak(context.data))
write(context, context.renderer.softbreak(context.opts))
%Native.Code{code: code} ->
write(context, context.renderer.codespan(context.data, code))
write(context, context.renderer.codespan(context.opts, code))
%Native.HtmlInline{html: html} ->
write(context, context.renderer.raw_html(context.data, html))
write(context, context.renderer.raw_html(context.opts, html))
%Native.Strong{} ->
tmp = render_children(clear(context), children)
write(context, context.renderer.double_emphasis(context.data, tmp.output))
kids = render_children(clear(context), children)
write(context, context.renderer.double_emphasis(context.opts, kids.output))
%Native.Emph{} ->
tmp = render_children(clear(context), children)
write(context, context.renderer.emphasis(context.data, tmp.output))
kids = render_children(clear(context), children)
write(context, context.renderer.emphasis(context.opts, kids.output))
%Native.Strikethrough{} ->
tmp = render_children(clear(context), children)
write(context, context.renderer.strikethrough(context.data, tmp.output))
kids = render_children(clear(context), children)
write(context, context.renderer.strikethrough(context.opts, kids.output))
%Native.Superscript{} ->
tmp = render_children(clear(context), children)
write(context, context.renderer.superscript(context.data, tmp.output))
kids = render_children(clear(context), children)
write(context, context.renderer.superscript(context.opts, kids.output))
%Native.Link{link: link} ->
tmp = render_children(clear(context), children)
write(context, context.renderer.link(context.data, link.url, link.title, tmp.output))
kids = render_children(clear(context), children)
write(context, context.renderer.link(context.opts, link.url, link.title, kids.output))
%Native.Image{link: link} ->
tmp = render_children_as_text(clear(context), children)
write(context, context.renderer.image(context.data, link.url, link.title, tmp.output))
kids = render_children_as_text(clear(context), children)
write(context, context.renderer.image(context.opts, link.url, link.title, kids.output))
%Native.Table{alignments: alignments} ->
render_table_node(context, alignments, children)
@ -140,15 +141,15 @@ defmodule Markdown.Render do
%Native.FootnoteDefinition{name: _name} ->
context = Map.put(context, :footnote_ix, context.footnote_ix + 1)
tmp = render_children_as_text(clear(context), children)
kids = render_children(clear(context), children)
write(
context,
context.renderer.footnote_def(context.data, tmp.output, context.footnote_ix)
context.renderer.footnote_def(context.opts, kids.output, context.footnote_ix)
)
%Native.FootnoteReference{name: name} ->
write(context, context.renderer.footnote_ref(context.data, name))
write(context, context.renderer.footnote_ref(context.opts, name))
_ ->
context
@ -190,23 +191,23 @@ defmodule Markdown.Render do
write(
context,
context.renderer.table(context.data, header_context.output, body_context.output)
context.renderer.table(context.opts, header_context.output, body_context.output)
)
end
defp render_table_row_node(%Context{} = context, alignments, children, header) do
tmp =
kids =
Enum.reduce(Enum.with_index(children), clear(context), fn {{child_node, child_children},
index},
context ->
case child_node do
%Native.TableCell{} ->
alignment = Enum.at(alignments, index, "")
tmp = render_children_as_text(clear(context), child_children)
kids = render_children(clear(context), child_children)
write(
context,
context.renderer.table_cell(context.data, tmp.output, alignment, header)
context.renderer.table_cell(context.opts, kids.output, alignment, header)
)
_ ->
@ -216,7 +217,7 @@ defmodule Markdown.Render do
write(
context,
context.renderer.table_row(context.data, tmp.output)
context.renderer.table_row(context.opts, kids.output)
)
end
end

View File

@ -1,39 +1,68 @@
defmodule Markdown.Renderer do
defmodule Options do
# extensions
defstruct strikethrough: false,
tagfilter: false,
table: false,
autolink: false,
tasklist: false,
superscript: false,
header_ids: nil,
footnotes: false,
description_lists: false,
front_matter_delimiter: nil,
# parse options
smart: false,
default_info_string: nil,
relaxed_tasklist_matching: false,
relaxed_autolinks: false,
akkoma_autolinks: false,
# render options
hardbreaks: false,
github_pre_lang: false,
full_info_string: false,
width: 0,
unsafe_: false,
escape: false,
list_style: :dash,
sourcepos: false
end
defmacro __using__(_params) do
quote do
def block_code(_data, code, lang) do
def block_code(_opts, code, lang) do
"<pre><code class=\"language-#{lang}\">#{HtmlEntities.encode(code)}</code></pre>"
end
def block_quote(_data, quote) do
def block_quote(_opts, quote) do
"<blockquote>#{quote}</blockquote>"
end
def block_html(_data, raw_html) do
def block_html(_opts, raw_html) do
raw_html
end
def footnotes(_data, content) do
def footnotes(_opts, content) do
"<div class=\"footnotes\"><hr/><ol>#{content}</ol></div>"
end
def footnote_def(_data, content, number) do
def footnote_def(_opts, content, number) do
"<li id=\"fn#{number}\">&nbsp;<a href=\"#fnref#{number}\">&#8617;</a>#{content}</li>"
end
def footnote_ref(_data, number) do
def footnote_ref(_opts, number) do
"<sup id=\"fnref#{number}\"><a href=\"#fn#{number}\">#{number}</a></sup>"
end
def header(_data, text, header_level) do
def header(_opts, text, header_level) do
"<h#{header_level}>#{text}</h#{header_level}>"
end
def hrule(_data) do
def hrule(_opts) do
"<hr/>"
end
def list(_data, contents, list_type, start) do
def list(_opts, contents, list_type, start) do
if list_type == "bullet" do
"<ul start=\"#{start}\">#{contents}</ul>"
else
@ -41,23 +70,23 @@ defmodule Markdown.Renderer do
end
end
def list_item(_data, text, _list_type) do
def list_item(_opts, text, _list_type) do
"<li>#{text}</li>"
end
def paragraph(_data, text) do
def paragraph(_opts, text) do
"<p>#{text}</p>"
end
def table(_data, header, body) do
def table(_opts, header, body) do
"<table><thead>#{header}</thead><tbody>#{body}</tbody></table>"
end
def table_row(_data, content) do
def table_row(_opts, content) do
"<tr>#{content}</tr>"
end
def table_cell(_data, content, alignment, header) do
def table_cell(_opts, content, alignment, header) do
cell =
if header do
"<th"
@ -79,32 +108,19 @@ defmodule Markdown.Renderer do
end
end
def autolink(_data, url, link_type) do
href =
case link_type do
"email" ->
"mailto:#{url}"
_ ->
url
end
"<a href=\"#{href}\">#{url}</a>"
end
def codespan(_data, code) do
def codespan(_opts, code) do
"<code>#{HtmlEntities.encode(code)}</code>"
end
def double_emphasis(_data, text) do
def double_emphasis(_opts, text) do
"<strong>#{text}</strong>"
end
def emphasis(_data, text) do
def emphasis(_opts, text) do
"<em>#{text}</em>"
end
def image(_data, url, title, alt_text) do
def image(_opts, url, title, alt_text) do
img = "<img src=\"#{url}\""
img =
@ -117,15 +133,15 @@ defmodule Markdown.Renderer do
"#{img}#{alt_text}\"/>"
end
def linebreak(_data) do
def linebreak(_opts) do
"\n"
end
def softbreak(_data) do
" "
def softbreak(%Options{} = opts) do
if opts.hardbreaks do "<br>" else " " end
end
def link(_data, url, title, content) do
def link(_opts, url, title, content) do
a = "<a href=\"#{url}\""
a =
@ -138,31 +154,31 @@ defmodule Markdown.Renderer do
"#{a}#{content}</a>"
end
def raw_html(_data, raw_html) do
def raw_html(_opts, raw_html) do
raw_html
end
def triple_emphasis(_data, text) do
def triple_emphasis(_opts, text) do
"<strong><em>#{text}</em></strong>"
end
def strikethrough(_data, text) do
def strikethrough(_opts, text) do
"<del>#{text}</del>"
end
def superscript(_data, text) do
def superscript(_opts, text) do
"<sup>#{text}</sup>"
end
def underline(_data, text) do
def underline(_opts, text) do
"<u>#{text}</u>"
end
def highlight(_data, text) do
def highlight(_opts, text) do
"<mark>#{text}</mark>"
end
def quote(_data, text) do
def quote(_opts, text) do
"<q>#{text}</q>"
end
@ -179,7 +195,6 @@ defmodule Markdown.Renderer do
table: 3,
table_row: 2,
table_cell: 4,
autolink: 3,
codespan: 2,
double_emphasis: 2,
emphasis: 2,

17
mix.exs
View File

@ -4,14 +4,12 @@ defmodule Markdown.MixProject do
def project do
[
app: :markdown,
version: "0.1.2",
version: "0.1.5",
elixir: "~> 1.4",
package: package(),
start_permanent: Mix.env() == :prod,
deps: deps(),
description: description(),
compilers: [:rustler] ++ Mix.compilers(),
rustler_crates: rustler_crates(),
source_url: "https://gitlab.com/nathanfaucett/ex-markdown"
]
end
@ -41,18 +39,9 @@ defmodule Markdown.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:html_entities, "~> 0.4"},
{:rustler, "~> 0.16"},
{:html_entities, "~> 0.5"},
{:rustler, "~> 0.30"},
{:ex_doc, ">= 0.0.0", only: :dev}
]
end
defp rustler_crates do
[
io: [
path: "native/comrak",
mode: if(Mix.env() == :prod, do: :release, else: :debug)
]
]
end
end

View File

@ -1,6 +1,8 @@
%{
"earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.18.3", "f4b0e4a2ec6f333dccf761838a4b253d75e11f714b85ae271c9ae361367897b7", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},
"html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"},
"rustler": {:hex, :rustler, "0.16.0", "9b04237d2e7b30fcae40a28edb56f59aad8f4e3c8790f4996b5f200f649964be", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm", "c57508ddad47dfb8038ca6de1e616e66e9b87313220ac5d9817bc4a4dc2257b9"},
"ex_doc": {:hex, :ex_doc, "0.18.3", "f4b0e4a2ec6f333dccf761838a4b253d75e11f714b85ae271c9ae361367897b7", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm", "33d7b70d87d45ed899180fb0413fb77c7c48843188516e15747e00fdecf572b6"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
"rustler": {:hex, :rustler, "0.30.0", "cefc49922132b072853fa9b0ca4dc2ffcb452f68fb73b779042b02d545e097fb", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "9ef1abb6a7dda35c47cfc649e6a5a61663af6cf842a55814a554a84607dee389"},
"toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"},
}

93
mix.nix Normal file
View File

@ -0,0 +1,93 @@
{
lib,
beamPackages,
overrides ? (x: y: {}),
}: let
buildRebar3 = lib.makeOverridable beamPackages.buildRebar3;
buildMix = lib.makeOverridable beamPackages.buildMix;
buildErlangMk = lib.makeOverridable beamPackages.buildErlangMk;
self = packages // (overrides self packages);
packages = with beamPackages;
with self; {
earmark = buildMix rec {
name = "earmark";
version = "1.2.5";
src = fetchHex {
pkg = "earmark";
version = "${version}";
sha256 = "c57508ddad47dfb8038ca6de1e616e66e9b87313220ac5d9817bc4a4dc2257b9";
};
beamDeps = [];
};
ex_doc = buildMix rec {
name = "ex_doc";
version = "0.18.3";
src = fetchHex {
pkg = "ex_doc";
version = "${version}";
sha256 = "33d7b70d87d45ed899180fb0413fb77c7c48843188516e15747e00fdecf572b6";
};
beamDeps = [earmark];
};
html_entities = buildMix rec {
name = "html_entities";
version = "0.5.2";
src = fetchHex {
pkg = "html_entities";
version = "${version}";
sha256 = "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc";
};
beamDeps = [];
};
jason = buildMix rec {
name = "jason";
version = "1.4.1";
src = fetchHex {
pkg = "jason";
version = "${version}";
sha256 = "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1";
};
beamDeps = [];
};
rustler = buildMix rec {
name = "rustler";
version = "0.30.0";
src = fetchHex {
pkg = "rustler";
version = "${version}";
sha256 = "9ef1abb6a7dda35c47cfc649e6a5a61663af6cf842a55814a554a84607dee389";
};
beamDeps = [jason toml];
};
toml = buildMix rec {
name = "toml";
version = "0.7.0";
src = fetchHex {
pkg = "toml";
version = "${version}";
sha256 = "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70";
};
beamDeps = [];
};
};
in
self

View File

@ -1,2 +0,0 @@
target
Cargo.lock

View File

@ -1,16 +0,0 @@
[package]
name = "comrak"
version = "0.1.0"
authors = ["HansiHE <hansihe@hansihe.com>"]
[lib]
name = "comrak"
path = "src/lib.rs"
crate-type = ["dylib"]
[dependencies]
lazy_static = "1.0"
rustler = "0.16"
rustler_codegen = "0.16"
comrak = "0.2"
typed-arena = "1.3"

View File

@ -1,216 +0,0 @@
use comrak::nodes::{self, AstNode, NodeValue};
use comrak::{parse_document, ComrakOptions};
use rustler::{NifEncoder, NifEnv, NifResult, NifTerm};
use typed_arena::Arena;
rustler_export_nifs! {
"Elixir.Markdown.Native",
[("parse", 1, parse)],
None
}
macro_rules! nifs_structs {
($($name:tt $struct:item)*) => {$(
#[derive(NifStruct)]
#[module = $name]
$struct
)*}
}
nifs_structs! {
"Markdown.Native.NodeList" pub struct NodeList {
pub list_type: String,
pub start: usize,
pub delimiter: String,
pub bullet_char: String,
pub tight: bool,
}
"Markdown.Native.NodeCodeBlock" pub struct NodeCodeBlock {
pub fenced: bool,
pub fence_char: String,
pub fence_length: usize,
pub info: String,
pub literal: String,
}
"Markdown.Native.NodeHtmlBlock" pub struct NodeHtmlBlock { pub literal: String }
"Markdown.Native.NodeHeading" pub struct NodeHeading { pub level: u32, pub setext: bool }
"Markdown.Native.NodeLink" pub struct NodeLink { pub url: String, pub title: String }
}
impl<'a> From<&'a nodes::NodeList> for NodeList {
#[inline]
fn from(list: &'a nodes::NodeList) -> Self {
NodeList {
list_type: match list.list_type {
nodes::ListType::Bullet => "bullet".into(),
nodes::ListType::Ordered => "ordered".into(),
},
start: list.start,
delimiter: match list.delimiter {
nodes::ListDelimType::Period => "period".into(),
nodes::ListDelimType::Paren => "paren".into(),
},
bullet_char: list.bullet_char.to_string(),
tight: list.tight,
}
}
}
impl<'a> From<&'a nodes::NodeCodeBlock> for NodeCodeBlock {
#[inline]
fn from(list: &'a nodes::NodeCodeBlock) -> Self {
NodeCodeBlock {
fenced: list.fenced,
fence_char: list.fence_char.to_string(),
fence_length: list.fence_length,
info: unsafe { String::from_utf8_unchecked(list.info.clone()) },
literal: unsafe { String::from_utf8_unchecked(list.literal.clone()) },
}
}
}
impl<'a> From<&'a nodes::NodeHtmlBlock> for NodeHtmlBlock {
#[inline]
fn from(list: &'a nodes::NodeHtmlBlock) -> Self {
NodeHtmlBlock {
literal: unsafe { String::from_utf8_unchecked(list.literal.clone()) },
}
}
}
impl<'a> From<&'a nodes::NodeHeading> for NodeHeading {
#[inline]
fn from(list: &'a nodes::NodeHeading) -> Self {
NodeHeading {
level: list.level,
setext: list.setext,
}
}
}
impl<'a> From<&'a nodes::NodeLink> for NodeLink {
#[inline]
fn from(list: &'a nodes::NodeLink) -> Self {
NodeLink {
url: unsafe { String::from_utf8_unchecked(list.url.clone()) },
title: unsafe { String::from_utf8_unchecked(list.title.clone()) },
}
}
}
nifs_structs! {
"Markdown.Native.Document" pub struct Document;
"Markdown.Native.BlockQuote" pub struct BlockQuote;
"Markdown.Native.List" pub struct List { pub list: NodeList }
"Markdown.Native.Item" pub struct Item { pub list: NodeList }
"Markdown.Native.CodeBlock" pub struct CodeBlock { pub block: NodeCodeBlock }
"Markdown.Native.HtmlBlock" pub struct HtmlBlock { pub block: NodeHtmlBlock }
"Markdown.Native.Paragraph" pub struct Paragraph;
"Markdown.Native.Heading" pub struct Heading { pub heading: NodeHeading }
"Markdown.Native.ThematicBreak" pub struct ThematicBreak;
"Markdown.Native.FootnoteDefinition" pub struct FootnoteDefinition { pub name: String }
"Markdown.Native.Table" pub struct Table { pub alignments: Vec<String> }
"Markdown.Native.TableRow" pub struct TableRow { pub header: bool }
"Markdown.Native.TableCell" pub struct TableCell;
"Markdown.Native.Text" pub struct Text { pub text: String }
"Markdown.Native.SoftBreak" pub struct SoftBreak;
"Markdown.Native.LineBreak" pub struct LineBreak;
"Markdown.Native.Code" pub struct Code { pub code: String }
"Markdown.Native.HtmlInline" pub struct HtmlInline { pub html: String }
"Markdown.Native.Emph" pub struct Emph;
"Markdown.Native.Strong" pub struct Strong;
"Markdown.Native.Strikethrough" pub struct Strikethrough;
"Markdown.Native.Superscript" pub struct Superscript;
"Markdown.Native.Link" pub struct Link { pub link: NodeLink }
"Markdown.Native.Image" pub struct Image { pub link: NodeLink }
"Markdown.Native.FootnoteReference" pub struct FootnoteReference { pub name: String }
}
#[inline]
pub fn encode_ast_node<'a, 'b>(env: NifEnv<'a>, node: &'b AstNode<'b>) -> NifTerm<'a> {
let ast = node.data.borrow();
let parent = match &ast.value {
&NodeValue::Document => Document.encode(env),
&NodeValue::BlockQuote => BlockQuote.encode(env),
&NodeValue::List(ref list) => List { list: list.into() }.encode(env),
&NodeValue::Item(ref list) => Item { list: list.into() }.encode(env),
&NodeValue::CodeBlock(ref block) => CodeBlock {
block: block.into(),
}.encode(env),
&NodeValue::HtmlBlock(ref block) => HtmlBlock {
block: block.into(),
}.encode(env),
&NodeValue::Paragraph => Paragraph.encode(env),
&NodeValue::Heading(ref heading) => Heading {
heading: heading.into(),
}.encode(env),
&NodeValue::ThematicBreak => ThematicBreak.encode(env),
&NodeValue::FootnoteDefinition(ref name) => FootnoteDefinition {
name: unsafe { String::from_utf8_unchecked(name.clone()) },
}.encode(env),
&NodeValue::Table(ref alignments) => Table {
alignments: alignments
.iter()
.map(|a| match a {
&nodes::TableAlignment::Left => "left".into(),
&nodes::TableAlignment::Right => "right".into(),
&nodes::TableAlignment::Center => "center".into(),
&nodes::TableAlignment::None => String::new(),
})
.collect::<Vec<String>>(),
}.encode(env),
&NodeValue::TableRow(ref header) => TableRow { header: *header }.encode(env),
&NodeValue::TableCell => TableCell.encode(env),
&NodeValue::Text(ref text) => Text {
text: unsafe { String::from_utf8_unchecked(text.clone()) },
}.encode(env),
&NodeValue::SoftBreak => SoftBreak.encode(env),
&NodeValue::LineBreak => LineBreak.encode(env),
&NodeValue::Code(ref code) => Code {
code: unsafe { String::from_utf8_unchecked(code.clone()) },
}.encode(env),
&NodeValue::HtmlInline(ref html) => HtmlInline {
html: unsafe { String::from_utf8_unchecked(html.clone()) },
}.encode(env),
&NodeValue::Emph => Emph.encode(env),
&NodeValue::Strong => Strong.encode(env),
&NodeValue::Strikethrough => Strikethrough.encode(env),
&NodeValue::Superscript => Superscript.encode(env),
&NodeValue::Link(ref link) => Link { link: link.into() }.encode(env),
&NodeValue::Image(ref link) => Image { link: link.into() }.encode(env),
&NodeValue::FootnoteReference(ref name) => FootnoteReference {
name: unsafe { String::from_utf8_unchecked(name.clone()) },
}.encode(env),
};
let children = node.children()
.map(|n| encode_ast_node(env, n))
.collect::<Vec<_>>();
(parent, children).encode(env)
}
#[inline]
pub fn parse<'a>(env: NifEnv<'a>, args: &[NifTerm<'a>]) -> NifResult<NifTerm<'a>> {
let text: &'a str = try!(args[0].decode());
let arena = Arena::new();
let options = ComrakOptions {
hardbreaks: true,
github_pre_lang: true,
width: 0,
default_info_string: None,
ext_strikethrough: true,
ext_tagfilter: true,
ext_table: true,
ext_autolink: true,
ext_tasklist: true,
ext_superscript: true,
ext_header_ids: None,
ext_footnotes: true,
};
let root = parse_document(&arena, text, &options);
Ok(encode_ast_node(env, root))
}

View File

@ -1,12 +0,0 @@
#[macro_use]
extern crate rustler;
#[macro_use]
extern crate rustler_codegen;
#[macro_use]
extern crate lazy_static;
extern crate comrak;
extern crate typed_arena;
mod elixir;
pub use self::elixir::*;

View File

@ -0,0 +1,5 @@
[target.'cfg(target_os = "macos")']
rustflags = [
"-C", "link-arg=-undefined",
"-C", "link-arg=dynamic_lookup",
]

1
native/comrak_rustler/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target

1050
native/comrak_rustler/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
[package]
name = "comrak_rustler"
version = "0.1.5"
authors = []
edition = "2021"
[lib]
name = "comrak_rustler"
path = "src/lib.rs"
crate-type = ["cdylib"]
[dependencies]
typed-arena = "2.0.1"
rustler = "0.30.0"
comrak = { git = "https://github.com/kivikakk/comrak", branch = "akkoma" }

View File

@ -0,0 +1,307 @@
use comrak::nodes::{self, AstNode, NodeValue};
use rustler::{Encoder, Env, NifStruct, Term};
#[derive(NifStruct)]
#[module = "Markdown.Native.NodeList"]
pub struct NodeList {
pub list_type: String,
pub start: usize,
pub delimiter: String,
pub bullet_char: String,
pub tight: bool,
}
#[derive(NifStruct)]
#[module = "Markdown.Native.NodeCodeBlock"]
pub struct NodeCodeBlock {
pub fenced: bool,
pub fence_char: String,
pub fence_length: usize,
pub info: String,
pub literal: String,
}
#[derive(NifStruct)]
#[module = "Markdown.Native.NodeHtmlBlock"]
pub struct NodeHtmlBlock {
pub literal: String,
}
#[derive(NifStruct)]
#[module = "Markdown.Native.NodeHeading"]
pub struct NodeHeading {
pub level: u8,
pub setext: bool,
}
#[derive(NifStruct)]
#[module = "Markdown.Native.NodeLink"]
pub struct NodeLink {
pub url: String,
pub title: String,
}
impl<'a> From<&'a nodes::NodeList> for NodeList {
#[inline]
fn from(list: &'a nodes::NodeList) -> Self {
NodeList {
list_type: match list.list_type {
nodes::ListType::Bullet => "bullet".into(),
nodes::ListType::Ordered => "ordered".into(),
},
start: list.start,
delimiter: match list.delimiter {
nodes::ListDelimType::Period => "period".into(),
nodes::ListDelimType::Paren => "paren".into(),
},
bullet_char: list.bullet_char.to_string(),
tight: list.tight,
}
}
}
impl<'a> From<&'a nodes::NodeCodeBlock> for NodeCodeBlock {
#[inline]
fn from(list: &'a nodes::NodeCodeBlock) -> Self {
NodeCodeBlock {
fenced: list.fenced,
fence_char: list.fence_char.to_string(),
fence_length: list.fence_length,
info: list.info.clone(),
literal: list.literal.clone(),
}
}
}
impl<'a> From<&'a nodes::NodeHtmlBlock> for NodeHtmlBlock {
#[inline]
fn from(list: &'a nodes::NodeHtmlBlock) -> Self {
NodeHtmlBlock {
literal: list.literal.clone(),
}
}
}
impl<'a> From<&'a nodes::NodeHeading> for NodeHeading {
#[inline]
fn from(list: &'a nodes::NodeHeading) -> Self {
NodeHeading {
level: list.level,
setext: list.setext,
}
}
}
impl<'a> From<&'a nodes::NodeLink> for NodeLink {
#[inline]
fn from(list: &'a nodes::NodeLink) -> Self {
NodeLink {
url: list.url.clone(),
title: list.title.clone(),
}
}
}
#[derive(NifStruct)]
#[module = "Markdown.Native.Document"]
pub struct Document;
#[derive(NifStruct)]
#[module = "Markdown.Native.BlockQuote"]
pub struct BlockQuote;
#[derive(NifStruct)]
#[module = "Markdown.Native.List"]
pub struct List {
pub list: NodeList,
}
#[derive(NifStruct)]
#[module = "Markdown.Native.Item"]
pub struct Item {
pub list: NodeList,
}
#[derive(NifStruct)]
#[module = "Markdown.Native.CodeBlock"]
pub struct CodeBlock {
pub block: NodeCodeBlock,
}
#[derive(NifStruct)]
#[module = "Markdown.Native.HtmlBlock"]
pub struct HtmlBlock {
pub block: NodeHtmlBlock,
}
#[derive(NifStruct)]
#[module = "Markdown.Native.Paragraph"]
pub struct Paragraph;
#[derive(NifStruct)]
#[module = "Markdown.Native.Heading"]
pub struct Heading {
pub heading: NodeHeading,
}
#[derive(NifStruct)]
#[module = "Markdown.Native.ThematicBreak"]
pub struct ThematicBreak;
#[derive(NifStruct)]
#[module = "Markdown.Native.FootnoteDefinition"]
pub struct FootnoteDefinition {
pub name: String,
}
#[derive(NifStruct)]
#[module = "Markdown.Native.Table"]
pub struct Table {
pub alignments: Vec<String>,
}
#[derive(NifStruct)]
#[module = "Markdown.Native.TableRow"]
pub struct TableRow {
pub header: bool,
}
#[derive(NifStruct)]
#[module = "Markdown.Native.TableCell"]
pub struct TableCell;
#[derive(NifStruct)]
#[module = "Markdown.Native.Text"]
pub struct Text {
pub text: String,
}
#[derive(NifStruct)]
#[module = "Markdown.Native.SoftBreak"]
pub struct SoftBreak;
#[derive(NifStruct)]
#[module = "Markdown.Native.LineBreak"]
pub struct LineBreak;
#[derive(NifStruct)]
#[module = "Markdown.Native.Code"]
pub struct Code {
pub code: String,
}
#[derive(NifStruct)]
#[module = "Markdown.Native.HtmlInline"]
pub struct HtmlInline {
pub html: String,
}
#[derive(NifStruct)]
#[module = "Markdown.Native.Emph"]
pub struct Emph;
#[derive(NifStruct)]
#[module = "Markdown.Native.Strong"]
pub struct Strong;
#[derive(NifStruct)]
#[module = "Markdown.Native.Strikethrough"]
pub struct Strikethrough;
#[derive(NifStruct)]
#[module = "Markdown.Native.Superscript"]
pub struct Superscript;