Skip to content

Commit

Permalink
Add nif to wrap jotdown rust crate
Browse files Browse the repository at this point in the history
  • Loading branch information
paradox460 committed Nov 9, 2023
1 parent 85348ad commit 0000001
Show file tree
Hide file tree
Showing 13 changed files with 292 additions and 14 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@ djot-*.tar

# Temporary files, for example, from tests.
/tmp/

# Rustler compiled artifacts
/priv/native/*
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,32 @@
# Djot

A fast [Djot](https://djot.net) parser and formatter for Elixir.

Djot parsing and transformations powered by the [jotdown](https://crates.io/crates/jotdown) rust crate.

## Goals
- [x] Fast Djot to HTML transforms
- [ ] Precompiled binary
- [ ] Optional sanitization
- [ ] Syntax highlighting via autumn or inkjet

## Usage

Simply call `to_html` on a string that contains Djot data, and you'll get back a html fragment.

```elixir

Djot.to_html("hello *world*!")
# => {:ok, "<p>hello <strong>world</strong>!</p>\n"}
```

## Installation

Add `:djot` as a dependency:

```elixir
def deps do
[
{:djot, "~> 0.1.0"}
]
```
24 changes: 14 additions & 10 deletions lib/djot.ex
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
defmodule Djot do
@moduledoc """
Documentation for `Djot`.
"""
Transform a Djot string input to HTML output.
@doc """
Hello world.
No sanitization is performed, the document comes directly from the Djot transformer.
"""

## Examples
alias Djot.Native

iex> Djot.hello()
:world
@spec to_html(String.t()) :: {:ok, String.t()} | {:error, :djot_transform}
def to_html(dj) do
Native.to_html(dj)
end

"""
def hello do
:world
@spec to_html!(String.t()) :: String.t()
def to_html!(dj) do
case Native.to_html(dj) do
{:ok, html} -> html
{:error, :djot_transform} -> raise Djot.DjotError
end
end
end
7 changes: 7 additions & 0 deletions lib/djot/djot_error.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
defmodule Djot.DjotError do
@moduledoc """
The djot parser/transformer does not expose many errors, it either parses and transforms a document, or fails to do so.
"""

defexception message: "An error occured transforming the Djot document"
end
5 changes: 5 additions & 0 deletions lib/djot/native.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defmodule Djot.Native do
use Rustler, otp_app: :djot, crate: :djot_nif

def to_html(_dj), do: :erlang.nif_error(:nif_not_loaded)
end
11 changes: 7 additions & 4 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ defmodule Djot.MixProject do
def project do
[
app: :djot,
version: "2023.11.0",
version: "0.1.0",
elixir: "~> 1.15",
start_permanent: Mix.env() == :prod,
source_url: @source_url,
homepage_url: @source_url,
name: "Djot",
description: "A Djot parser and formatter",
deps: deps()
description: "A Djot markup language parser and formatter",
deps: deps(),
package: package()
]
end

Expand All @@ -35,7 +36,9 @@ defmodule Djot.MixProject do

defp deps do
[
{:rustler, "~> 0.30.0"}
{:rustler, "~> 0.30.0"},

{:ex_doc, "~> 0.30.9", only: :dev, runtime: false}
]
end
end
6 changes: 6 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
%{
"earmark_parser": {:hex, :earmark_parser, "1.4.37", "2ad73550e27c8946648b06905a57e4d454e4d7229c2dafa72a0348c99d8be5f7", [:mix], [], "hexpm", "6b19783f2802f039806f375610faa22da130b8edc21209d0bff47918bb48360e"},
"ex_doc": {:hex, :ex_doc, "0.30.9", "d691453495c47434c0f2052b08dd91cc32bc4e1a218f86884563448ee2502dd2", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "d7aaaf21e95dc5cddabf89063327e96867d00013963eadf2c6ad135506a8bc10"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
"makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
"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"},
}
5 changes: 5 additions & 0 deletions native/djot_nif/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[target.'cfg(target_os = "macos")']
rustflags = [
"-C", "link-arg=-undefined",
"-C", "link-arg=dynamic_lookup",
]
1 change: 1 addition & 0 deletions native/djot_nif/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target
156 changes: 156 additions & 0 deletions native/djot_nif/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions native/djot_nif/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "djot_nif"
version = "0.1.0"
authors = ["Jeff Sandberg"]
edition = "2021"

[lib]
name = "djot_nif"
path = "src/lib.rs"
crate-type = ["cdylib"]

[dependencies]
jotdown = "0.3.2"
rustler = "0.30.0"
20 changes: 20 additions & 0 deletions native/djot_nif/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# NIF for Elixir.Djot.Native

## To build the NIF module:

- Your NIF will now build along with your project.

## To load the NIF:

```elixir
defmodule Djot.Native do
use Rustler, otp_app: :djot, crate: "djot_nif"

# When your NIF is loaded, it will override this function.
def add(_a, _b), do: :erlang.nif_error(:nif_not_loaded)
end
```

## Examples

[This](https://github.com/rusterlium/NifIo) is a complete example of a NIF written in Rust.
25 changes: 25 additions & 0 deletions native/djot_nif/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
extern crate rustler;

use jotdown::Render;
use rustler::nif;
use rustler::Encoder;
use rustler::Env;
use rustler::Error as RustlerError;
use rustler::Term;

rustler::atoms! {
ok,
djot_transform
}

#[nif(schedule = "DirtyCpu")]
pub fn to_html<'a>(env: Env<'a>, dj: &str) -> Result<Term<'a>, RustlerError> {
let events = jotdown::Parser::new(dj);
let mut html = String::new();
match jotdown::html::Renderer::default().push(events, &mut html) {
Ok(()) => Ok((ok(), html).encode(env)),
Err(_e) => Err(RustlerError::Term(Box::new(djot_transform()))),
}
}

rustler::init!("Elixir.Djot.Native", [to_html]);

0 comments on commit 0000001

Please sign in to comment.