-module(lustre@ssg@djot). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([frontmatter/1, metadata/1, content/1, default_renderer/0, render/2, render_with_metadata/2]). -export_type([renderer/1]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -type renderer(SNJ) :: {renderer, fun((gleam@dict:dict(binary(), binary()), gleam@option:option(binary()), binary()) -> SNJ), fun((list(SNJ)) -> SNJ), fun((gleam@dict:dict(binary(), binary()), integer(), list(SNJ)) -> SNJ), fun((jot:destination(), gleam@dict:dict(binary(), binary()), list(SNJ)) -> SNJ), fun((gleam@dict:dict(binary(), binary()), list(SNJ)) -> SNJ), fun((jot:list_layout(), binary(), list(list(SNJ))) -> SNJ), fun((binary()) -> SNJ), fun((list(SNJ)) -> SNJ), fun((binary()) -> SNJ), fun((binary()) -> SNJ), fun((jot:destination(), binary()) -> SNJ), SNJ, SNJ}. -file("src/lustre/ssg/djot.gleam", 155). ?DOC( " Extract the frontmatter string from a djot document. Frontmatter is anything\n" " between two lines of three dashes, like this:\n" "\n" " ```djot\n" " ---\n" " title = \"My Document\"\n" " ---\n" "\n" " # My Document\n" "\n" " ...\n" " ```\n" "\n" " The document **must** start with exactly three dashes and a newline for there\n" " to be any frontmatter. If there is no frontmatter, this function returns\n" " `Error(Nil)`,\n" ). -spec frontmatter(binary()) -> {ok, binary()} | {error, nil}. frontmatter(Document) -> gleam@bool:guard( not gleam_stdlib:string_starts_with(Document, <<"---"/utf8>>), {error, nil}, fun() -> Options = {options, false, true}, _assert_subject = gleam@regexp:compile( <<"^---\\n[\\s\\S]*?\\n---"/utf8>>, Options ), {ok, Re} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, value => _assert_fail, module => <<"lustre/ssg/djot"/utf8>>, function => <<"frontmatter"/utf8>>, line => 158}) end, case gleam@regexp:scan(Re, Document) of [{match, Frontmatter, _} | _] -> {ok, begin _pipe = Frontmatter, _pipe@1 = gleam@string:drop_start(_pipe, 4), gleam@string:drop_end(_pipe@1, 4) end}; _ -> {error, nil} end end ). -file("src/lustre/ssg/djot.gleam", 177). ?DOC( " Extract the TOML metadata from a djot document. This takes the [`frontmatter`](#frontmatter)\n" " and parses it as TOML. If there is *no* frontmatter, this function returns\n" " an empty dictionary.\n" "\n" " If the frontmatter is invalid TOML, this function returns a TOML parse error.\n" ). -spec metadata(binary()) -> {ok, gleam@dict:dict(binary(), tom:toml())} | {error, tom:parse_error()}. metadata(Document) -> case frontmatter(Document) of {ok, Frontmatter} -> tom:parse(Frontmatter); {error, _} -> {ok, maps:new()} end. -file("src/lustre/ssg/djot.gleam", 187). ?DOC( " Extract the djot content from a document with optional frontmatter. If the\n" " document does not have frontmatter, this acts as an identity function.\n" ). -spec content(binary()) -> binary(). content(Document) -> Toml = frontmatter(Document), case Toml of {ok, Toml@1} -> gleam@string:replace( Document, <<<<"---\n"/utf8, Toml@1/binary>>/binary, "\n---"/utf8>>, <<""/utf8>> ); {error, _} -> Document end. -file("src/lustre/ssg/djot.gleam", 328). -spec linkify(binary()) -> binary(). linkify(Text) -> _assert_subject = gleam@regexp:from_string(<<" +"/utf8>>), {ok, Re} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, value => _assert_fail, module => <<"lustre/ssg/djot"/utf8>>, function => <<"linkify"/utf8>>, line => 329}) end, _pipe = Text, _pipe@1 = gleam@regexp:split(Re, _pipe), gleam@string:join(_pipe@1, <<"-"/utf8>>). -file("src/lustre/ssg/djot.gleam", 60). ?DOC( " The default renderer generates some sensible Lustre elements from a djot\n" " document. You can use this if you need a quick drop-in renderer for some\n" " markup in a Lustre project.\n" ). -spec default_renderer() -> renderer(lustre@vdom@vnode:element(any())). default_renderer() -> To_attributes = fun(Attrs) -> gleam@dict:fold( Attrs, [], fun(Attrs@1, Key, Val) -> [lustre@attribute:attribute(Key, Val) | Attrs@1] end ) end, {renderer, fun(Attrs@2, Lang, Code) -> Lang@1 = gleam@option:unwrap(Lang, <<"text"/utf8>>), lustre@element@html:pre( To_attributes(Attrs@2), [lustre@element@html:code( [lustre@attribute:attribute( <<"data-lang"/utf8>>, Lang@1 )], [lustre@element@html:text(Code)] )] ) end, fun(Content) -> lustre@element@html:em([], Content) end, fun(Attrs@3, Level, Content@1) -> case Level of 1 -> lustre@element@html:h1(To_attributes(Attrs@3), Content@1); 2 -> lustre@element@html:h2(To_attributes(Attrs@3), Content@1); 3 -> lustre@element@html:h3(To_attributes(Attrs@3), Content@1); 4 -> lustre@element@html:h4(To_attributes(Attrs@3), Content@1); 5 -> lustre@element@html:h5(To_attributes(Attrs@3), Content@1); 6 -> lustre@element@html:h6(To_attributes(Attrs@3), Content@1); _ -> lustre@element@html:p(To_attributes(Attrs@3), Content@1) end end, fun(Destination, References, Content@2) -> case Destination of {reference, Ref} -> case gleam_stdlib:map_get(References, Ref) of {ok, Url} -> lustre@element@html:a( [lustre@attribute:href(Url)], Content@2 ); {error, _} -> lustre@element@html:a( [lustre@attribute:href( <<"#"/utf8, (linkify(Ref))/binary>> ), lustre@attribute:id( linkify(<<"back-to-"/utf8, Ref/binary>>) )], Content@2 ) end; {url, Url@1} -> lustre@element@html:a( [lustre@attribute:attribute(<<"href"/utf8>>, Url@1)], Content@2 ) end end, fun(Attrs@4, Content@3) -> lustre@element@html:p(To_attributes(Attrs@4), Content@3) end, fun(Layout, Style, Items) -> List_style_type = lustre@attribute:style( <<"list-style-type"/utf8>>, case Style of <<"-"/utf8>> -> <<"'-'"/utf8>>; <<"*"/utf8>> -> <<"disc"/utf8>>; _ -> <<"circle"/utf8>> end ), lustre@element@html:ul( [List_style_type], (gleam@list:map(Items, fun(Item) -> case Layout of tight -> lustre@element@html:li([], Item); loose -> lustre@element@html:li( [], [lustre@element@html:p([], Item)] ) end end)) ) end, fun(Content@4) -> lustre@element:unsafe_raw_html( <<""/utf8>>, <<"div"/utf8>>, [], Content@4 ) end, fun(Content@5) -> lustre@element@html:strong([], Content@5) end, fun(Text) -> lustre@element@html:text(Text) end, fun(Content@6) -> lustre@element@html:code([], [lustre@element@html:text(Content@6)]) end, fun(Destination@1, Alt) -> case Destination@1 of {reference, Ref@1} -> lustre@element@html:img( [lustre@attribute:src( <<"#"/utf8, (linkify(Ref@1))/binary>> ), lustre@attribute:alt(Alt)] ); {url, Url@2} -> lustre@element@html:img( [lustre@attribute:src(Url@2), lustre@attribute:alt(Alt)] ) end end, lustre@element@html:br([]), lustre@element@html:hr([])}. -file("src/lustre/ssg/djot.gleam", 336). -spec text_content(list(jot:inline())) -> binary(). text_content(Segments) -> gleam@list:fold(Segments, <<""/utf8>>, fun(Text, Inline) -> case Inline of {text, Content} -> <>; non_breaking_space -> <>; {link, Content@1, _} -> <>; {emphasis, Content@2} -> <>; {strong, Content@3} -> <>; {code, Content@4} -> <>; {image, _, _} -> Text; linebreak -> Text; {footnote, _} -> Text end end). -file("src/lustre/ssg/djot.gleam", 278). -spec render_inline( jot:inline(), gleam@dict:dict(binary(), binary()), renderer(SOJ) ) -> SOJ. render_inline(Inline, References, Renderer) -> case Inline of {text, Text} -> (erlang:element(10, Renderer))(Text); non_breaking_space -> (erlang:element(10, Renderer))(<<" "/utf8>>); {link, Content, Destination} -> (erlang:element(5, Renderer))( Destination, References, gleam@list:map( Content, fun(_capture) -> render_inline(_capture, References, Renderer) end ) ); {emphasis, Content@1} -> (erlang:element(3, Renderer))( gleam@list:map( Content@1, fun(_capture@1) -> render_inline(_capture@1, References, Renderer) end ) ); {strong, Content@2} -> (erlang:element(9, Renderer))( gleam@list:map( Content@2, fun(_capture@2) -> render_inline(_capture@2, References, Renderer) end ) ); {code, Content@3} -> (erlang:element(11, Renderer))(Content@3); {image, Content@4, Destination@1} -> (erlang:element(12, Renderer))( Destination@1, text_content(Content@4) ); linebreak -> erlang:element(13, Renderer); {footnote, _} -> (erlang:element(10, Renderer))(<<""/utf8>>) end. -file("src/lustre/ssg/djot.gleam", 235). -spec render_block( jot:container(), gleam@dict:dict(binary(), binary()), renderer(SOF) ) -> SOF. render_block(Block, References, Renderer) -> case Block of {paragraph, Attrs, Inline} -> (erlang:element(6, Renderer))( Attrs, gleam@list:map( Inline, fun(_capture) -> render_inline(_capture, References, Renderer) end ) ); {heading, Attrs@1, Level, Inline@1} -> (erlang:element(4, Renderer))( Attrs@1, Level, gleam@list:map( Inline@1, fun(_capture@1) -> render_inline(_capture@1, References, Renderer) end ) ); {codeblock, Attrs@2, Language, Code} -> (erlang:element(2, Renderer))(Attrs@2, Language, Code); thematic_break -> erlang:element(14, Renderer); {raw_block, Content} -> (erlang:element(8, Renderer))(Content); {bullet_list, Layout, Style, Items} -> (erlang:element(7, Renderer))( Layout, Style, gleam@list:map( Items, fun(_capture@2) -> gleam@list:map( _capture@2, fun(_capture@3) -> render_block(_capture@3, References, Renderer) end ) end ) ) end. -file("src/lustre/ssg/djot.gleam", 201). ?DOC( " Render a djot document using the given renderer. If the document contains\n" " [`frontmatter`](#frontmatter) it is stripped out before rendering.\n" ). -spec render(binary(), renderer(SNT)) -> list(SNT). render(Document, Renderer) -> Content = content(Document), {document, Content@1, References, _} = jot:parse(Content), _pipe = Content@1, gleam@list:map( _pipe, fun(_capture) -> render_block(_capture, References, Renderer) end ). -file("src/lustre/ssg/djot.gleam", 215). ?DOC( " Render a djot document using the given renderer. TOML metadata is extracted\n" " from the document's frontmatter and passed to the renderer. If the frontmatter\n" " is invalid TOML this function will return the TOML parse error, but if there\n" " is no frontmatter to parse this function will succeed and just pass an empty\n" " dictionary to the renderer.\n" ). -spec render_with_metadata( binary(), fun((gleam@dict:dict(binary(), tom:toml())) -> renderer(SNY)) ) -> {ok, list(SNY)} | {error, tom:parse_error()}. render_with_metadata(Document, Renderer) -> Toml = frontmatter(Document), gleam@result:'try'( begin _pipe = Toml, _pipe@1 = gleam@result:unwrap(_pipe, <<""/utf8>>), tom:parse(_pipe@1) end, fun(Metadata) -> Content = content(Document), Renderer@1 = Renderer(Metadata), {document, Content@1, References, _} = jot:parse(Content), _pipe@2 = Content@1, _pipe@3 = gleam@list:map( _pipe@2, fun(_capture) -> render_block(_capture, References, Renderer@1) end ), {ok, _pipe@3} end ).