import gleam/string import gleam/list import gleam/dict.{type Dict} import gleam/uri import gleam/dynamic import gleam/io import kirala/markdown/strfmt.{str as s, text} import kirala/markdown/parser.{ type Token, type UrlData, BlockQuote, Bold, CheckedList, CodeBlock, CodeLine, CodeSpan, Definition, DefinitionIs, DefinitionOf, FootNote, FootNoteUrlDef, H, HR, ImgLink, InsertedText, Italic, Line, LineIndent, ListItem, MarkedText, Note, OrderedList, StdList, StrikeThrough, Table, Text, TokenRes, UncheckedList, Url, UrlFootNote, UrlLink, UrlPlain, parse, pop_grapheme, ret_string, ret_string_trim, } fn url_of(symbols: Dict(String, String), url: UrlData) -> String { case url { UrlPlain(str) -> str UrlFootNote(id) -> case dict.get(symbols, id) { Ok(str) -> str _ -> id } } } pub fn html_encode_(src: String, acc: String) -> String { case src { "" -> acc _ -> { let #(newacc, rest) = case src { "&" <> rest -> #(acc <> "&", rest) "<" <> rest -> #(acc <> "<", rest) ">" <> rest -> #(acc <> ">", rest) _ -> { let #(chr, rest) = parser.pop_grapheme(src) #(acc <> chr, rest) } } html_encode_(rest, newacc) } } } pub fn html_encode(src: String) -> String { html_encode_(src, "") |> ret_string //let bits = html_encode_(bit_string.from_string(src), <<>>) //log("~p", [bits]) //bits //|> ret_string } pub fn emit_text(symbols: Dict(String, String), t: Token) -> String { case t { Text(text) -> html_encode(text) Bold(t) | Italic(t) | StrikeThrough(t) | MarkedText(t) | InsertedText(t) -> emit(symbols, t) Line(ts) -> { let str = list.map(ts, fn(e) { emit(symbols, e) }) |> string.concat case string.length(string.trim(str)) == 0 { True -> "
" _ -> str } } Url(UrlFootNote(url)) -> url Url(UrlPlain(url)) -> url UrlLink(caption, url) -> { let clen = string.length(caption) - 1 let tcaption = case string.starts_with(caption, "\\") { True -> html_encode(string.slice(caption, 1, clen)) _ -> caption } tcaption } _ -> "" } } pub fn emit(symbols: Dict(String, String), t: Token) -> String { case t { Text(text) -> html_encode(text) Bold(t) -> text(["", emit(symbols, t), ""]) Italic(t) -> text(["", emit(symbols, t), ""]) StrikeThrough(t) -> text(["", emit(symbols, t), ""]) MarkedText(t) -> text(["", emit(symbols, t), ""]) InsertedText(t) -> text(["", emit(symbols, t), ""]) LineIndent(_, ts) | Line(ts) -> { let str = list.map(ts, fn(e) { emit(symbols, e) }) |> string.concat case string.length(string.trim(str)) == 0 { True -> "
" _ -> str } } H(id, level, title) -> text([ "", emit(symbols, title), "\n", ]) Url(UrlFootNote(url)) -> text(["", url, ""]) Url(url) -> text([ "", url_of(symbols, url), "", ]) UrlLink(caption, url) -> { let clen = string.length(caption) - 1 let tcaption = case string.starts_with(caption, "\\") { True -> html_encode(string.slice(caption, 1, clen)) _ -> caption } //log("tcaption = ",,"", [tcaption]) text(["", tcaption, ""]) } ImgLink(caption, alt, url) -> text([ "",
        alt,
        "", caption, "", ]) FootNote(id, t) -> text(["
※ ", emit(symbols, t), "
"]) FootNoteUrlDef(id, url, alt) -> text(["[", id, "]:", url, " \"", alt, "\"
"]) CodeLine(code) -> text([code, "\n"]) CodeSpan(code) -> text(["", code, ""]) CodeBlock(syntax, filename, code) -> case syntax { "csv" | "tsv" -> text([ "
",
            code,
            "
\n
", ]) _ -> text([ "
",
            code,
            "
\n
", ]) } DefinitionIs(obj, verb) -> text([ "
", obj, "
", emit(symbols, verb), "
", ]) Note(title, t) -> text(["
", emit(symbols, t), "
"]) BlockQuote(level, code) -> emit(symbols, code) ListItem(StdList, level, t) -> text(["
  • ", emit(symbols, t), "
  • \n"]) ListItem(CheckedList, check, t) -> text([ "
  • ", emit(symbols, t), "
  • \n", ]) ListItem(UncheckedList, check, t) -> text(["
  • ", emit(symbols, t), "
  • \n"]) ListItem(OrderedList, level, t) -> text(["
  • ", emit(symbols, t), "
  • \n"]) Table(header, align, rows) -> { let theader = list.map(header, fn(t) { text(["", emit(symbols, t), ""]) }) |> string.concat let trows = list.map(rows, fn(row) { let cols = row let trow = list.map(cols, fn(col) { text(["", emit(symbols, col), ""]) }) |> string.join("\n") text(["", s(trow), ""]) }) |> string.concat text([ "
    ", theader, "", trows, "
    ", ]) } HR -> "
    " _ -> text([s(t)]) } } pub fn convert(src: String) -> String { convert_bytes(src) } pub fn convert_outline(src: String) -> String { convert_bytes_outline(src) } pub fn convert_digest(src: String) -> String { convert_bytes_digest(src) } fn convert_( lineno: Int, bytes: String, acc: List(Token), symbols: Dict(String, String), ) { case string.length(ret_string_trim(bytes)) { len if len == 0 -> #(list.reverse(acc), symbols) _ -> { let TokenRes(t, rest) = parse(lineno, bytes) let new_symbols = case t { FootNoteUrlDef(id, url, alt) -> dict.insert(symbols, id, url) _ -> symbols } convert_(lineno + 1, rest, [t, ..acc], new_symbols) } } } fn ntimes(n: Int, str: String) { list.map(list.range(0, n), fn(e) { str }) |> string.concat } pub fn convert_bytes(bytes: String) -> String { let #(tokens, symbols) = convert_(0, bytes, [], dict.new()) convert_tokens_(tokens, symbols) } pub fn convert_tokens_( tokens: List(Token), symbols: Dict(String, String), ) -> String { let #(_, strlist) = list.fold(tokens, #(HR, []), fn(acx, t) -> #(Token, List(List(String))) { //log("-- ~p", [t]) let #(prev, acc) = acx case #(prev, t) { // List #(ListItem(_, indent1, t1), ListItem(_, indent2, t2)) if indent1 == indent2 -> #( t, [[emit(symbols, t)], ..acc], ) #(ListItem(_, indent1, t1), ListItem(_, indent2, t2)) if indent1 > indent2 -> #( t, [[ntimes(indent1 - indent2, ""), emit(symbols, t)], ..acc], ) #(ListItem(_, indent1, t1), ListItem(_, indent2, t2)) if indent1 < indent2 -> #( t, [[ntimes(indent2 - indent1, ""), emit(symbols, t)], ..acc], ) #(_, ListItem(_, indent2, t2)) -> #( t, [[ntimes(indent2, "