Packages

A small library for parsing Gemini's gemtext. Runs on both Erlang and JS

Current section

Files

Jump to
glemtext src glemtext.erl
Raw

src/glemtext.erl

-module(glemtext).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([parse/1]).
-export_type([gem_element/0]).
-type gem_element() :: {text, binary()} |
{link, binary(), gleam@option:option(binary())} |
{heading, integer(), binary()} |
{list_item, binary()} |
{blockquote, binary()} |
{preformatted, gleam@option:option(binary()), binary()}.
-spec until_nl(binary()) -> boolean().
until_nl(C) ->
C /= <<"\n"/utf8>>.
-spec get_line(list(binary())) -> {list(binary()), list(binary())}.
get_line(In) ->
Line = begin
_pipe = In,
gleam@list:take_while(_pipe, fun until_nl/1)
end,
Rest = begin
_pipe@1 = In,
_pipe@2 = gleam@list:drop_while(_pipe@1, fun until_nl/1),
gleam@list:drop(_pipe@2, 1)
end,
{Line, Rest}.
-spec parse_text(list(binary())) -> {gem_element(), list(binary())}.
parse_text(In) ->
{Line, Rest} = get_line(In),
Line@1 = gleam@string:join(Line, <<""/utf8>>),
{{text, Line@1}, Rest}.
-spec take_preformatted_lines(list(binary())) -> {list(binary()),
list(binary())}.
take_preformatted_lines(In) ->
{Line, Rest} = get_line(In),
case Line of
[<<"`"/utf8>>, <<"`"/utf8>>, <<"`"/utf8>> | Rest@1] ->
{_, Rest@2} = get_line(Rest@1),
{[], Rest@2};
[] ->
{[], []};
_ ->
{Next, In@1} = take_preformatted_lines(Rest),
Line@1 = begin
_pipe = Line,
gleam@string:join(_pipe, <<""/utf8>>)
end,
{[Line@1 | Next], In@1}
end.
-spec drop_lines(list(binary())) -> list(binary()).
drop_lines(In) ->
case In of
[<<"\n"/utf8>> | Rest] ->
drop_lines(Rest);
_ ->
In
end.
-spec drop_ws(list(binary())) -> list(binary()).
drop_ws(In) ->
case In of
[<<" "/utf8>> | Rest] ->
drop_ws(Rest);
[<<"\t"/utf8>> | Rest@1] ->
drop_ws(Rest@1);
_ ->
In
end.
-spec parse_blockquote(list(binary())) -> {gem_element(), list(binary())}.
parse_blockquote(In) ->
{Line, Rest} = get_line(In),
Line@1 = begin
_pipe = Line,
_pipe@1 = drop_ws(_pipe),
gleam@string:join(_pipe@1, <<""/utf8>>)
end,
{{blockquote, Line@1}, Rest}.
-spec parse_preformatted(list(binary())) -> {gem_element(), list(binary())}.
parse_preformatted(In) ->
{Line, Rest} = get_line(In),
Alt = begin
_pipe = Line,
_pipe@1 = drop_ws(_pipe),
gleam@string:join(_pipe@1, <<""/utf8>>)
end,
Alt@1 = case Alt of
<<""/utf8>> ->
none;
_ ->
{some, Alt}
end,
{Pre_lines, In@1} = take_preformatted_lines(Rest),
Pre_lines@1 = begin
_pipe@2 = Pre_lines,
gleam@string:join(_pipe@2, <<"\n"/utf8>>)
end,
{{preformatted, Alt@1, Pre_lines@1}, In@1}.
-spec parse_heading(integer(), list(binary())) -> {gem_element(),
list(binary())}.
parse_heading(Level, In) ->
{Line, Rest} = get_line(In),
Line@1 = begin
_pipe = Line,
_pipe@1 = drop_ws(_pipe),
gleam@string:join(_pipe@1, <<""/utf8>>)
end,
{{heading, Level, Line@1}, Rest}.
-spec parse_link(list(binary())) -> {gem_element(), list(binary())}.
parse_link(In) ->
{Line, Rest} = get_line(In),
{Url, Linerest} = begin
_pipe = Line,
_pipe@1 = drop_ws(_pipe),
gleam@list:split_while(
_pipe@1,
fun(C) ->
not ((C =:= <<" "/utf8>>) orelse (C =:= <<"\t"/utf8>>))
end
)
end,
Url@1 = gleam@string:join(Url, <<""/utf8>>),
Label = begin
_pipe@2 = Linerest,
_pipe@3 = drop_ws(_pipe@2),
gleam@string:join(_pipe@3, <<""/utf8>>)
end,
Label@1 = case Label of
<<""/utf8>> ->
none;
_ ->
{some, Label}
end,
{{link, Url@1, Label@1}, Rest}.
-spec parse_list(list(binary())) -> {gem_element(), list(binary())}.
parse_list(In) ->
{Line, Rest} = get_line(In),
Line@1 = begin
_pipe = Line,
_pipe@1 = drop_ws(_pipe),
gleam@string:join(_pipe@1, <<""/utf8>>)
end,
{{list_item, Line@1}, Rest}.
-spec parse_document(list(binary()), list(gem_element())) -> list(gem_element()).
parse_document(In, Ast) ->
case In of
[<<"="/utf8>>, <<">"/utf8>> | Rest] ->
{Link, In@1} = parse_link(Rest),
parse_document(In@1, [Link | Ast]);
[<<"*"/utf8>>, <<" "/utf8>> | Rest@1] ->
{Item, In@2} = parse_list(Rest@1),
parse_document(In@2, [Item | Ast]);
[<<">"/utf8>> | Rest@2] ->
{Quote, In@3} = parse_blockquote(Rest@2),
parse_document(In@3, [Quote | Ast]);
[<<"#"/utf8>>, <<" "/utf8>> | Rest@3] ->
{Heading, In@4} = parse_heading(1, Rest@3),
parse_document(In@4, [Heading | Ast]);
[<<"#"/utf8>>, <<"#"/utf8>>, <<" "/utf8>> | Rest@4] ->
{Heading@1, In@5} = parse_heading(2, Rest@4),
parse_document(In@5, [Heading@1 | Ast]);
[<<"#"/utf8>>, <<"#"/utf8>>, <<"#"/utf8>>, <<" "/utf8>> | Rest@5] ->
{Heading@2, In@6} = parse_heading(3, Rest@5),
parse_document(In@6, [Heading@2 | Ast]);
[<<"`"/utf8>>, <<"`"/utf8>>, <<"`"/utf8>> | Rest@6] ->
{Preformatted, In@7} = parse_preformatted(Rest@6),
parse_document(In@7, [Preformatted | Ast]);
[] ->
Ast;
_ ->
{Text, In@8} = parse_text(In),
parse_document(In@8, [Text | Ast])
end.
-spec parse(binary()) -> list(gem_element()).
parse(Text) ->
_pipe = Text,
_pipe@1 = gleam@string:replace(_pipe, <<"\r\n"/utf8>>, <<"\n"/utf8>>),
_pipe@2 = gleam@string:to_graphemes(_pipe@1),
_pipe@3 = parse_document(_pipe@2, []),
gleam@list:reverse(_pipe@3).