import gleam/bit_array import gleam/string import gleam/list import gleam/dict.{type Dict} import gleam/uri import gleam/dynamic import kirala/bbmarkdown/sub.{fmt} import kirala/bbmarkdown/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, 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: BitArray, acc: BitArray) -> BitArray { case src { <<>> -> acc _ -> { let #(newacc, rest) = case src { <<"&":utf8, rest:bits>> -> #(<>, rest) <<"<":utf8, rest:bits>> -> #(<>, rest) <<">":utf8, rest:bits>> -> #(<>, rest) //<<160, rest:bit_array>> -> #(<>, rest) <> -> #(<>, rest) unhandable -> { #(<>, <<>>) } } html_encode_(rest, newacc) } } } pub fn html_encode(src: String) -> String { html_encode_(bit_array.from_string(src), <<>>) |> ret_string //let bits = html_encode_(bit_array.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(fmt("~ts", [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 -> "
" _ -> fmt("~ts", [str]) } } Url(UrlFootNote(url)) -> fmt("~ts", [url]) Url(url) -> fmt("~ts", [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(fmt("~ts", #(text))) Bold(t) -> fmt("~ts", #(emit(symbols, t))) Italic(t) -> fmt("~ts", #(emit(symbols, t))) StrikeThrough(t) -> fmt("~ts", #(emit(symbols, t))) MarkedText(t) -> fmt("~ts", #(emit(symbols, t))) InsertedText(t) -> fmt("~ts", #(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 -> "
" _ -> fmt("~ts", [str]) } } H(id, level, title) -> fmt("~ts\n", #(level, id, emit(symbols, title), level)) Url(UrlFootNote(url)) -> fmt("~ts", #(url, url)) Url(url) -> fmt("~ts", #(url_of(symbols, url), 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 = ~ts", [tcaption]) fmt("~ts", #(url_of(symbols, url), tcaption)) } ImgLink(caption, alt, url) -> fmt("~ts~ts", [ url_of(symbols, url), alt, caption, ]) FootNote(id, t) -> fmt("
※ ~ts
", [id, emit(symbols, t)]) FootNoteUrlDef(id, url, alt) -> fmt("[~ts]:~ts \"~ts\"
", [id, url, alt]) CodeLine(code) -> fmt("~ts\n", [code]) CodeSpan(code) -> fmt("~ts", [code]) CodeBlock(syntax, filename, code) -> case syntax { "csv" | "tsv" -> fmt( "
~ts
\n
", [filename, syntax, code], ) _ -> fmt( "
~ts
\n
", [filename, syntax, code], ) } DefinitionIs(obj, verb) -> fmt("
~ts
~ts
", [ obj, emit(symbols, verb), ]) Note(title, t) -> fmt("
~ts
", [title, emit(symbols, t)]) BlockQuote(level, code) -> emit(symbols, code) ListItem(StdList, level, t) -> fmt("
  • ~ts
  • \n", [emit(symbols, t)]) ListItem(CheckedList, check, t) -> fmt("
  • ~ts
  • \n", [ emit(symbols, t), ]) ListItem(UncheckedList, check, t) -> fmt("
  • ~ts
  • \n", [emit(symbols, t)]) ListItem(OrderedList, level, t) -> fmt("
  • ~ts
  • \n", [emit(symbols, t)]) Table(header, align, rows) -> { let theader = list.map(header, fn(t) { fmt("~ts", [emit(symbols, t)]) }) |> string.concat let trows = list.map(rows, fn(row) { let cols = row let trow = list.map(cols, fn(col) { fmt("~ts", [emit(symbols, col)]) }) fmt("~ts", [trow]) }) |> string.concat fmt( "
    ~ts~ts
    ", [theader, trows], ) } HR -> "
    " _ -> fmt("~p", [t]) } } pub fn convert(src: String) -> String { convert_bytes(bit_array.from_string(src)) } pub fn convert_outline(src: String) -> String { convert_bytes_outline(bit_array.from_string(src)) } pub fn convert_digest(src: String) -> String { convert_bytes_digest(bit_array.from_string(src)) } fn convert_( lineno: Int, bytes: BitArray, 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: BitArray) -> 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(_, indent1, t1), _) -> #(t, [ [ntimes(indent1, "
    "), emit(symbols, t)], ..acc ]) #(_, ListItem(_, indent2, t2)) -> #(t, [ [ntimes(indent2, "
      "), emit(symbols, t)], ..acc ]) // BlockQuote #(BlockQuote(indent1, t1), BlockQuote(indent2, t2)) if indent1 == indent2 -> #(t, [[emit(symbols, t)], ..acc]) #(BlockQuote(indent1, t1), BlockQuote(indent2, t2)) if indent1 > indent2 -> #( t, [ [ntimes(indent1 - indent2, ""), emit(symbols, t)], ..acc ], ) #(BlockQuote(indent1, t1), BlockQuote(indent2, t2)) if indent1 < indent2 -> #( t, [[ntimes(indent2 - indent1, "
      "), emit(symbols, t)], ..acc], ) #(BlockQuote(indent1, t1), _) -> #(t, [ [ntimes(indent1, "
      "), emit(symbols, t)], ..acc ]) #(_, BlockQuote(indent2, t2)) -> #(t, [ [ntimes(indent2, "
      "), emit(symbols, t)], ..acc ]) // CodeLine #(CodeLine(t1), CodeLine(t2)) -> #(t, [[emit(symbols, t)], ..acc]) #(CodeLine(t1), _) -> #(t, [["", emit(symbols, t)], ..acc]) #(_, CodeLine(t2)) -> #(t, [["
      ", emit(symbols, t)], ..acc])
      
              // Definition
              #(_, DefinitionOf(t2)) -> #(t, [
                ["
      ", emit(symbols, t2), "
      "], ..acc ]) #(_, Definition(t2)) -> { let tlines = list.map(t2, fn(e) { [emit(symbols, e), "
      "] }) |> list.flatten |> string.concat #(t, [["
      ", tlines, "
      "], ..acc]) } #(Definition(t1), _) -> #(t, [["
      ", emit(symbols, t)], ..acc]) // _ -> #(t, [[emit(symbols, t)], ..acc]) } }) strlist |> list.reverse |> list.flatten |> string.concat } pub fn convert_bytes_outline(bytes: BitArray) -> String { let #(tokens, symbols) = convert_(0, bytes, [], dict.new()) let filteredtokens = list.fold(tokens, [], fn(acc: List(Token), t) -> List(Token) { case t { H(id, level, title) -> [ ListItem( StdList, level, UrlLink(emit_text(symbols, title), UrlPlain(fmt("#H~w", [id]))), ), ..acc ] _ -> acc } }) |> list.reverse convert_tokens_(filteredtokens, symbols) } pub fn convert_bytes_digest(bytes: BitArray) -> String { let #(tokens, symbols) = convert_(0, bytes, [], dict.new()) let filteredtokens: List(String) = list.fold(tokens, [], fn(acc: List(List(String)), t) -> List(List(String)) { case t { H(id, level, title) -> [[emit_text(symbols, title), "
      \n"], ..acc] Line(_) -> [[emit_text(symbols, t), "
      \n"], ..acc] //_ -> [emit_text(symbols, t), ..acc] _ -> acc } }) |> list.reverse |> list.flatten //convert_tokens_(filteredtokens, symbols) string.concat(filteredtokens) } // ------------------------------------------------------------------------------------------------------------------------------------