💾 Archived View for gmi.noulin.net › gitRepositories › md › file › md.c.gmi captured on 2023-01-29 at 13:22:45. Gemini links have been rewritten to link to archived content

View Raw

More Information

➡️ Next capture (2023-07-10)

-=-=-=-=-=-=-

md

Log

Files

Refs

README

LICENSE

md.c (27969B)

     1 #! /usr/bin/env sheepy
     2 //
     3 
     4 #include "libsheepyObject.h"
     5 #include "shpPackages/md4c/md4c.h"
     6 #include "fort.h"
     7 
     8 #include "entities.h"
     9 
    10 /* enable/disable logging */
    11 #undef pLog
    12 #define pLog(...)
    13 
    14 
    15 typ struct {
    16     MD_BLOCKTYPE type;
    17     unsigned olCount;
    18 } blockt;
    19 
    20 sliceT(blockst, blockt);
    21 
    22 typedef struct {
    23     smallArrayt  *out;
    24     smallStringt *current;
    25     smallArrayt  *span;
    26     smallStringt *rspan; // recursive span string, copy to current on last leave span
    27     MD_BLOCKTYPE  blockquote;
    28     MD_BLOCKTYPE  h;
    29     MD_BLOCKTYPE  p;
    30     MD_BLOCKTYPE  blockcode;
    31     MD_BLOCKTYPE  tbl;
    32     blockst       blocks;
    33     blockst       spans;
    34     smallArrayt   table;
    35     smallArrayt   row;
    36     int           bqlevel;
    37     smallJsont   *entities;
    38     char          colorCode[40];
    39 } outt;
    40 
    41 #define BLOCKQUOTE FNT"┃"RST"  "
    42 
    43 #define ADD_BLOCKQUOTE \
    44   if(r->blockquote == MD_BLOCK_QUOTE) {\
    45     loop(r->bqlevel) {\
    46         prependG(r->current, BLOCKQUOTE);\
    47     }\
    48   }
    49 
    50 #define ADD_BLOCKCODE \
    51   if(r->blockcode == MD_BLOCK_CODE) {\
    52     pushG(r->current, BLD BGRED WHT "`````````" RST "\n" BLD MGT);\
    53     r->blockcode = 0;\
    54   }
    55 
    56 
    57 internal int leave_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata);
    58 internal int leave_all_spans(MD_SPANTYPE type, void* detail, void* userdata);
    59 
    60 char *e2text[50]; // block
    61 char *t2text[50]; // text
    62 char *s2text[50]; // span
    63 
    64 internal int
    65 enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
    66 {
    67     cast(outt *, r, userdata);
    68 
    69     logD(e2text[type]);
    70     if (not sliceIsEmpty(&r->blocks)) {
    71         logD("stack %s",e2text[sliceLast(&r->blocks).type]);
    72     }
    73 
    74 
    75     logVarG(r->current);
    76     logVarG(sliceCount(&r->blocks));
    77 
    78     switch(type) {
    79     case MD_BLOCK_DOC:      /* noop */
    80         break;
    81     case MD_BLOCK_QUOTE:
    82         r->blockquote = MD_BLOCK_QUOTE;
    83         ADD_BLOCKQUOTE;
    84         pErrorNULL(pushG(r->out, r->current));
    85         rallocG(r->current, "");
    86         inc r->bqlevel;
    87         //puts(MGT "blockquote" RST);
    88         break;
    89     case MD_BLOCK_UL:;
    90         if (not sliceIsEmpty(&r->blocks) and sliceLast(&r->blocks).type == MD_BLOCK_LI) {
    91             leave_block_callback(MD_BLOCK_LI, null, userdata);
    92         }
    93         cast(MD_BLOCK_UL_DETAIL *, uld, detail);
    94         logVarG(uld->is_tight);
    95         logD("uld->mark=%c",uld->mark);
    96         if (sliceIsEmpty(&r->blocks)) {
    97             pushG(r->out, "");
    98         }
    99         //puts(GRN "ul" RST);
   100         sliceAppend(&r->blocks, (blockt){.type=MD_BLOCK_UL});
   101         break;
   102     case MD_BLOCK_OL:;
   103         if (not sliceIsEmpty(&r->blocks) and sliceLast(&r->blocks).type == MD_BLOCK_LI) {
   104             leave_block_callback(MD_BLOCK_LI, null, userdata);
   105         }
   106         cast(MD_BLOCK_OL_DETAIL *, old, detail);
   107         logVarG(old->start);
   108         logVarG(old->is_tight);
   109         logD("old->mark_delimiter=%c",old->mark_delimiter);
   110         if (sliceIsEmpty(&r->blocks)) {
   111             pushG(r->out, "");
   112         }
   113         //puts(GRN "ol" RST);
   114         blockt t = {.type=MD_BLOCK_OL, .olCount=1};
   115         sliceAppend(&r->blocks, t);
   116         break;
   117     case MD_BLOCK_LI:;
   118         cast(MD_BLOCK_LI_DETAIL *, lid, detail);
   119         logVarG(lid->is_task);
   120         logD("lid->task_mark=%c",lid->task_mark);
   121         logVarG(lid->task_mark_offset);
   122         if(!isEmptyG(r->current)) {
   123             ADD_BLOCKQUOTE
   124             ADD_BLOCKCODE
   125             pushG(r->out, r->current);
   126             pushG(r->out,"");
   127             rallocG(r->current, "");
   128         }
   129         //puts(GRN "li" RST);
   130         sliceAppend(&r->blocks, (blockt){.type=MD_BLOCK_LI});
   131         break;
   132     case MD_BLOCK_HR:
   133         //puts(BLU "hr" RST);
   134         break;
   135     case MD_BLOCK_H:
   136         r->h = MD_BLOCK_H;
   137         if(!isEmptyG(r->current)) {
   138             ADD_BLOCKCODE
   139             pushG(r->out, r->current);
   140             rallocG(r->current, "");
   141         }
   142         //puts(BLU "hN" RST);
   143         break;
   144     case MD_BLOCK_CODE:
   145         r->blockcode = MD_BLOCK_CODE;
   146         pushG(r->out, "\n" BLD BGRED WHT "`````````" RST BLD MGT);
   147         //puts(MGT "BLOCK_CODE" RST);
   148         break;
   149     /* case MD_BLOCK_HTML:     #<{(| noop |)}># break; */
   150     case MD_BLOCK_P:
   151         if ((sliceLast(&r->blocks).type != MD_BLOCK_LI) && !isEmptyG(r->current)) {
   152             ADD_BLOCKQUOTE
   153             ADD_BLOCKCODE
   154             pushG(r->out, r->current);
   155             rallocG(r->current, "");
   156         }
   157         //puts(GRN "p" RST);
   158         break;
   159     case MD_BLOCK_TABLE:
   160         initiateG(&r->table);
   161         r->tbl = MD_BLOCK_TABLE;
   162         break;
   163         /* case MD_BLOCK_THEAD:    RENDER_LITERAL(r, "<thead>\n"); break; */
   164         /* case MD_BLOCK_TBODY:    RENDER_LITERAL(r, "<tbody>\n"); break; */
   165     case MD_BLOCK_TR:
   166         initiateG(&r->row);
   167         break;
   168     /* case MD_BLOCK_TH:       render_open_td_block(r, "th", (MD_BLOCK_TD_DETAIL*)detail); break; */
   169     /* case MD_BLOCK_TD:       render_open_td_block(r, "td", (MD_BLOCK_TD_DETAIL*)detail); break; */
   170     }
   171 
   172     return 0;
   173 }
   174 
   175 internal int
   176 leave_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
   177 {
   178     cast(outt *, r, userdata);
   179     /* MD_RENDER_HTML* r = (MD_RENDER_HTML*) userdata; */
   180 
   181     logD(e2text[type]);
   182     if (not sliceIsEmpty(&r->blocks)) {
   183         logD("stack %s",e2text[sliceLast(&r->blocks).type]);
   184     }
   185 
   186     logVarG(r->current);
   187     logVarG(sliceCount(&r->blocks));
   188 
   189     leave_all_spans(0, null, userdata);
   190 
   191     switch(type) {
   192     case MD_BLOCK_DOC:      /*noop*/
   193         break;
   194     case MD_BLOCK_QUOTE:
   195         ADD_BLOCKQUOTE;
   196         pErrorNULL(pushG(r->out, r->current));
   197         rallocG(r->current, "");
   198         dec r->bqlevel;
   199         if (!r->bqlevel)
   200             r->blockquote = 0;
   201         //puts(MGT "/blockquote" RST);
   202         break;
   203     case MD_BLOCK_UL:
   204         if (not sliceIsEmpty(&r->blocks) and sliceLast(&r->blocks).type == MD_BLOCK_UL) {
   205             sliceDelLast(&r->blocks);
   206         }
   207         if (sliceIsEmpty(&r->blocks)) {
   208             pushG(r->out, "");
   209         }
   210         //puts(GRN "/ul" RST);
   211         break;
   212     case MD_BLOCK_OL:
   213         if (not sliceIsEmpty(&r->blocks) and sliceLast(&r->blocks).type == MD_BLOCK_OL) {
   214             sliceDelLast(&r->blocks);
   215         }
   216         if (sliceIsEmpty(&r->blocks)) {
   217             pushG(r->out, "");
   218         }
   219         //puts(GRN "/ol" RST);
   220         break;
   221     case MD_BLOCK_LI:
   222         if (not sliceIsEmpty(&r->blocks) and sliceLast(&r->blocks).type == MD_BLOCK_LI) {
   223             sliceDelLast(&r->blocks);
   224         }
   225         //puts(GRN "/li" RST);
   226         //logD("'%m'", r->current);
   227         if (not isEmptyG(r->current)) {
   228             if (sliceLast(&r->blocks).type == MD_BLOCK_UL) {
   229                 prependG(r->current, "- ");
   230                 loop(sliceCount(&r->blocks)-1/*list level*/) {
   231                     prependG(r->current, "  ");
   232                 }
   233             }
   234             if (sliceLast(&r->blocks).type == MD_BLOCK_OL) {
   235                 char *s = intToS(sliceLast(&r->blocks).olCount);
   236                 pErrorNULL(pushG(&s, ". "));
   237                 prependG(r->current, s);
   238                 free(s);
   239                 loop(sliceCount(&r->blocks)-1/*list level*/) {
   240                     prependG(r->current, "  ");
   241                 }
   242                 sliceLast(&r->blocks).olCount++;
   243             }
   244             ADD_BLOCKQUOTE
   245             pushG(r->out, r->current);
   246             rallocG(r->current, "");
   247         }
   248         break;
   249     case MD_BLOCK_HR:
   250         pushG(r->out, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
   251         break;
   252     case MD_BLOCK_H:
   253         //puts(BLU "</hN>" RST);
   254         r->h = 0;
   255         pushG(r->out, "");
   256         prependG(r->current, " ");
   257         loop(((MD_BLOCK_H_DETAIL*)detail)->level) {
   258             prependG(r->current, "#");
   259         }
   260         colorG(r->current, BLD YLW);
   261         pushG(r->out, r->current);
   262         rallocG(r->current, "");
   263         break;
   264     case MD_BLOCK_CODE:
   265         if (not isEmptyG(r->current)) {
   266             pushG(r->current, RST BLD BGRED WHT "`````````" RST "\n");
   267             pushG(r->out, r->current);
   268             rallocG(r->current, "");
   269         }
   270         //puts(MGT "/BLOCK_CODE" RST);
   271         break;
   272     case MD_BLOCK_HTML:     /* noop */
   273         break;
   274     case MD_BLOCK_P:
   275         //puts(GRN "/p" RST);
   276         if (sliceLast(&r->blocks).type == MD_BLOCK_LI) {
   277             if (sliceAt(&r->blocks, sliceCount(&r->blocks)-2).type == MD_BLOCK_UL) {
   278                 prependG(r->current, "- ");
   279                 loop(sliceCount(&r->blocks)-2/*list level*/) {
   280                     prependG(r->current, "  ");
   281                 }
   282             }
   283             if (sliceAt(&r->blocks, sliceCount(&r->blocks)-2).type == MD_BLOCK_OL) {
   284                 char *s = intToS(sliceAt(&r->blocks, sliceCount(&r->blocks)-2).olCount);
   285                 pErrorNULL(pushG(&s, ". "));
   286                 prependG(r->current, s);
   287                 free(s);
   288                 loop(sliceCount(&r->blocks)-2/*list level*/) {
   289                     prependG(r->current, "  ");
   290                 }
   291                 sliceAt(&r->blocks, sliceCount(&r->blocks)-2).olCount++;
   292             }
   293         }
   294         ADD_BLOCKQUOTE
   295         pErrorNULL(pushG(r->out, r->current));
   296         rallocG(r->current, "");
   297         break;
   298     case MD_BLOCK_TABLE:;
   299         // TODO render with fort like csvFmt
   300         int rows = lenG(&r->table);
   301         int cols = 0;
   302         iter(&r->table, Row) {
   303             cast(smallArrayt*, row, Row);
   304             cols = maxV(cols, lenG(row));
   305         }
   306         char **t = malloc(rows * cols * sizeof(char*));
   307         int j = 0;
   308         iter(&r->table, Row) {
   309             cast(smallArrayt*, row, Row);
   310             int i = 0;
   311             iter(row, c) {
   312                 *(t + j * cols + i) = ssGet(c);
   313                 inc i;
   314             }
   315             if (i < cols) {
   316                 while (i < cols) {
   317                     *(t + j * cols + i) = "";
   318                     inc i;
   319                 }
   320             }
   321             inc j;
   322         }
   323         ft_table_t *table2 = ft_create_table();
   324 
   325         ft_set_border_style(table2, FT_SIMPLE_STYLE);
   326         //ft_set_border_style(table2, FT_DOUBLE_STYLE);
   327 
   328         ft_table_write(table2, rows, cols, (const char **) t);
   329 
   330         ft_set_cell_prop(table2, 0, FT_ANY_COLUMN, FT_CPROP_ROW_TYPE, FT_ROW_HEADER);
   331         range(i, cols) {
   332             ft_set_cell_prop(table2, 0, i, FT_CPROP_CONT_TEXT_STYLE, FT_TSTYLE_BOLD);
   333         }
   334         range(i, rows) {
   335             ft_set_cell_prop(table2, i, FT_ANY_COLUMN, FT_CPROP_CELL_BG_COLOR,  FT_COLOR_DEFAULT);
   336             ft_set_cell_prop(table2, i, FT_ANY_COLUMN, FT_CPROP_CELL_BG_RGBCOLOR,  i & 1 ? 0x2f2f2f : 0x1f1f1f);
   337         }
   338         ft_set_cell_prop(table2, 0, FT_ANY_COLUMN, FT_CPROP_CELL_BG_RGBCOLOR,  0x4867ff);
   339 
   340         // 0x4042f
   341         // 0x2f2f2f
   342         pErrorNULL(pushG(r->out, ft_to_string(table2)));
   343         ft_destroy_table(table2);
   344         free(t);
   345         logG(&r->table);
   346         r->tbl = 0;
   347         freeG(&r->table);
   348         break;
   349     /* case MD_BLOCK_THEAD:    RENDER_LITERAL(r, "</thead>\n"); break; */
   350     /* case MD_BLOCK_TBODY:    RENDER_LITERAL(r, "</tbody>\n"); break; */
   351     case MD_BLOCK_TR:
   352         pushG(&r->table, &r->row);
   353         break;
   354     case MD_BLOCK_TH:
   355         pErrorNULL(pushG(&r->row, r->current));
   356         rallocG(r->current, "");
   357         break;
   358     case MD_BLOCK_TD:
   359         pErrorNULL(pushG(&r->row, r->current));
   360         rallocG(r->current, "");
   361         break;
   362     }
   363 
   364     return 0;
   365 }
   366 
   367 
   368 internal int
   369 enter_span_callback(MD_SPANTYPE type, void* detail, void* userdata)
   370 {
   371     cast(outt *, r, userdata);
   372 
   373     logD(s2text[type]);
   374 
   375     // check if span a specifies a color
   376     // type is changed to MD_SPAN_COLOR
   377     if (type == MD_SPAN_A) {
   378         MD_SPAN_A_DETAIL *a = detail;
   379         /* logVarG(a->href.text); */
   380         /* logVarG(a->href.size); */
   381         // TODO detect font
   382         i32 fg = -1;
   383         i32 bg = -1;
   384         if (a->href.text[0] == '#' and a->href.size < 17 /* maximum size of hexadecimal rgb colors */) {
   385             if (a->href.size > 1) {
   386                 char s[30];
   387                 char colorString[17] = init0Var;
   388                 if (a->href.text[1] == 'x') {
   389                     if (a->href.size > 2) {
   390                         memcpy(colorString, a->href.text, a->href.size);
   391                         // search for background color
   392                         // minimum: #x?#?
   393                         i32 bgoff = -1;
   394                         if (a->href.size > 5) {
   395                             rangeFrom(i, 3, a->href.size) {
   396                                 if (a->href.text[i] == '#') {
   397                                     bgoff = i;
   398                                     break;
   399                                 }
   400                             }
   401                             if (bgoff > 0) {
   402                                 if (bgoff+2 > a->href.size)
   403                                     bgoff = -1;
   404                                 else {
   405                                     if (a->href.text[bgoff+1] < '0' and a->href.text[bgoff+1] > '9' and a->href.text[bgoff+1] != 'x') {
   406                                         bgoff = -1;
   407                                     }
   408                                     elif (a->href.text[bgoff+1] == 'x' and bgoff+3 > a->href.size)
   409                                         bgoff = -1;
   410                                 }
   411                             }
   412                         }
   413                         colorString[0] = '0';
   414                         fg = parseHex(colorString);
   415                         colorString[0] = '#';
   416                         if (bgoff < 0) {
   417                             sprintf(r->colorCode, "\x1b[38;2;%u;%u;%um", fg >> 16, (fg&0xFF00)>>8, fg&0xFF);
   418                         }
   419                         else {
   420                             if (a->href.text[bgoff+1] >= '0' and a->href.text[bgoff+1] <= '9') {
   421                                 bg = parseInt(a->href.text + bgoff);
   422                                 if (bg >= 0 and bg < 8) {
   423                                     sprintf(r->colorCode, "\x1b[38;2;%u;%u;%um\x1B[4%dm", fg >> 16, (fg&0xFF00)>>8, fg&0xFF, bg);
   424                                 }
   425                             }
   426                             elif (a->href.text[bgoff+1] == 'x') {
   427                                 colorString[bgoff] = '0';
   428                                 bg = parseHex(colorString + bgoff);
   429                                 colorString[bgoff] = '#';
   430                                 sprintf(r->colorCode, "\x1b[38;2;%u;%u;%um\x1b[48;2;%u;%u;%um", fg >> 16, (fg&0xFF00)>>8, fg&0xFF, bg >> 16, (bg&0xFF00)>>8, bg&0xFF);
   431                             }
   432                         }
   433                         type = MD_SPAN_COLOR;
   434                     }
   435                 }
   436                 elif (a->href.text[1] >= '0' and a->href.text[1] <= '9') {
   437                     // check background color
   438                     // Minimum: #?#?
   439                     i32 bgoff = -1;
   440                     if (a->href.size > 3) {
   441                         rangeFrom(i, 2, a->href.size) {
   442                             if (a->href.text[i] == '#') {
   443                                 bgoff = i;
   444                                 break;
   445                             }
   446                         }
   447                         if (bgoff > 0) {
   448                             if (bgoff+2 > a->href.size) {
   449                                 bgoff = -1;
   450                             }
   451                             else {
   452                                 if (a->href.text[bgoff+1] < '0' and a->href.text[bgoff+1] > '9' and a->href.text[bgoff+1] != 'x') {
   453                                     bgoff = -1;
   454                                 }
   455                                 elif (a->href.text[bgoff+1] == 'x' and bgoff+3 > a->href.size) {
   456                                     bgoff = -1;
   457                                 }
   458                             }
   459                         }
   460                     }
   461                     fg = parseInt(a->href.text);
   462                     char *bright = "";
   463                     if (fg >= 8 and fg < 16) {
   464                         bright = BLD;
   465                         fg -= 8;
   466                     }
   467                     if (fg >= 0 and fg < 8) {
   468                         if (bgoff < 0) {
   469                             sprintf(r->colorCode, "%s\x1B[3%dm", bright, fg);
   470                         }
   471                         else {
   472                             if (a->href.text[bgoff+1] >= '0' and a->href.text[bgoff+1] <= '9') {
   473                                 bg = parseInt(a->href.text + bgoff);
   474                                 if (bg >= 0 and bg < 8) {
   475                                     sprintf(r->colorCode, "%s\x1B[3%dm\x1B[4%dm", bright, fg, bg);
   476                                 }
   477                             }
   478                             elif (a->href.text[bgoff+1] == 'x') {
   479                                 memcpy(colorString, a->href.text, a->href.size);
   480                                 colorString[bgoff] = '0';
   481                                 bg = parseHex(colorString + bgoff);
   482                                 colorString[bgoff] = '#';
   483                                 sprintf(r->colorCode, "%s\x1B[3%dm\x1b[48;2;%u;%u;%um", bright, fg, bg >> 16, (bg&0xFF00)>>8, bg&0xFF);
   484                             }
   485                         }
   486                         type = MD_SPAN_COLOR;
   487                     }
   488                 }
   489                 elif (a->href.size > 2 and a->href.text[1] == '#') {
   490                     // ##?
   491                     // there is no foreground color
   492                     if (a->href.text[2] == 'x' and a->href.size > 3) {
   493                         // ##x?
   494                         memcpy(colorString, a->href.text, a->href.size);
   495                         colorString[1] = '0';
   496                         bg = parseHex(colorString + 1);
   497                         sprintf(r->colorCode, "\x1b[48;2;%u;%u;%um", bg >> 16, (bg&0xFF00)>>8, bg&0xFF);
   498                         type = MD_SPAN_COLOR;
   499                     }
   500                     elif (a->href.text[2] >= '0' and a->href.text[2] <= '9') {
   501                         bg = parseInt(a->href.text);
   502                         if (bg >= 0 and bg < 8) {
   503                             sprintf(r->colorCode, "\x1B[4%dm", bg);
   504                             type = MD_SPAN_COLOR;
   505                         }
   506 
   507                     }
   508                 }
   509             }
   510         }
   511     }
   512 
   513     if (not sliceIsEmpty(&r->spans)) {
   514         pushG(r->span, r->rspan);
   515         rallocG(r->rspan, "");
   516     }
   517 
   518     sliceAppend(&r->spans, (blockt){.type=type});
   519 
   520     switch(type) {
   521         case MD_SPAN_EM:
   522             prependG(r->rspan, ITL);
   523             break;
   524         case MD_SPAN_STRONG:
   525             prependG(r->rspan, BLD);
   526             break;
   527         case MD_SPAN_U:
   528             prependG(r->rspan, UDL);
   529             break;
   530         case MD_SPAN_A:
   531             prependG(r->rspan, BLD UDL BLU);
   532             break;
   533         case MD_SPAN_IMG:
   534             prependG(r->rspan, UDL RED);
   535             break;
   536         case MD_SPAN_CODE:
   537             prependG(r->rspan, BGBLU);
   538             break;
   539         case MD_SPAN_DEL:
   540             prependG(r->rspan, CRD);
   541             break;
   542         case MD_SPAN_FNT:
   543             prependG(r->rspan, FNT);
   544             break;
   545         case MD_SPAN_INV:
   546             prependG(r->rspan, INV);
   547             break;
   548         case MD_SPAN_COC:
   549             prependG(r->rspan, COC);
   550             break;
   551         case MD_SPAN_BLI:
   552             prependG(r->rspan, BLI);
   553             break;
   554         case MD_SPAN_ANCHOR:
   555             prependG(r->rspan, GRN);
   556             break;
   557         case MD_SPAN_COLOR:
   558             prependG(r->rspan, r->colorCode);
   559             break;
   560     }
   561     return 0;
   562 }
   563 
   564 internal int
   565 leave_all_spans(MD_SPANTYPE type, void* detail, void* userdata)
   566 {
   567     cast(outt *, r, userdata);
   568 
   569     if (not sliceIsEmpty(&r->spans)) {
   570         sliceEmpty(&r->spans);
   571         pushG(r->rspan, RST);
   572         pushG(r->span, r->rspan);
   573         rallocG(r->rspan, "");
   574         cleanFinishSmallStringP(s) = joinG(r->span, " ");
   575         freeG(r->span);
   576         pushG(r->current, s);
   577     }
   578 
   579     return 0;
   580 }
   581 internal int
   582 leave_span_callback(MD_SPANTYPE type, void* detail, void* userdata)
   583 {
   584     cast(outt *, r, userdata);
   585 
   586     logD(s2text[type]);
   587 
   588     if (not sliceIsEmpty(&r->spans)) {
   589         sliceDelLast(&r->spans);
   590         if (sliceIsEmpty(&r->spans)) {
   591             pushG(r->rspan, RST);
   592             pushG(r->span, r->rspan);
   593             rallocG(r->rspan, "");
   594             cleanFinishSmallStringP(s) = joinG(r->span, " ");
   595             freeG(r->span);
   596             pushG(r->current, s);
   597         }
   598         else {
   599             pushG(r->rspan, RST);
   600             sliceForEach(&r->spans, e) {
   601                 switch(e->type) {
   602                     case MD_SPAN_EM:
   603                         pushG(r->rspan, ITL);
   604                         break;
   605                     case MD_SPAN_STRONG:
   606                         pushG(r->rspan, BLD);
   607                         break;
   608                     case MD_SPAN_U:
   609                         pushG(r->rspan, UDL);
   610                         break;
   611                     case MD_SPAN_A:
   612                         pushG(r->rspan, BLD UDL BLU);
   613                         break;
   614                     case MD_SPAN_IMG:
   615                         pushG(r->rspan, UDL RED);
   616                         break;
   617                     case MD_SPAN_CODE:
   618                         pushG(r->rspan, BGBLU);
   619                         break;
   620                     case MD_SPAN_DEL:
   621                         pushG(r->rspan, CRD);
   622                         break;
   623                     case MD_SPAN_FNT:
   624                         pushG(r->rspan, FNT);
   625                         break;
   626                     case MD_SPAN_INV:
   627                         pushG(r->rspan, INV);
   628                         break;
   629                     case MD_SPAN_COC:
   630                         pushG(r->rspan, COC);
   631                         break;
   632                     case MD_SPAN_BLI:
   633                         pushG(r->rspan, BLI);
   634                         break;
   635                     case MD_SPAN_ANCHOR:
   636                         pushG(r->rspan, GRN);
   637                         break;
   638                     case MD_SPAN_COLOR:
   639                         pushG(r->rspan, r->colorCode);
   640                         break;
   641                 }
   642             }
   643             pushG(r->span, r->rspan);
   644             rallocG(r->rspan, "");
   645         }
   646     }
   647     //logG(r->span);
   648 
   649     return 0;
   650 }
   651 
   652 internal int
   653 text_callback(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, void* userdata)
   654 {
   655     cast(outt *, r, userdata);
   656     char *s         = calloc(1, size+1);
   657     smallStringt *t;
   658 
   659     logD(t2text[type]);
   660 
   661     switch(type) {
   662     case MD_TEXT_NULLCHAR:
   663         break;
   664     case MD_TEXT_BR:
   665         pushG(r->out, "");
   666         //puts(BLU "br" RST);
   667         break;
   668     case MD_TEXT_SOFTBR:
   669         pushG(r->current, " ");
   670         //puts(BLU "sbr" RST);
   671         break;
   672     case MD_TEXT_HTML:
   673         strncpy(s, text, size); /*pushG(r->out, s);*/ //puts(BLU "html" RST);
   674         break;
   675     case MD_TEXT_ENTITY:
   676         // spec https://html.spec.whatwg.org/entities.json
   677         strncpy(s, text, size);
   678         logVarG(s);
   679         char utf8Code[10] = init0Var;
   680         if (s[1] == '#') {
   681             // numeric character reference
   682             if (s[2] == 'x' or s[2] == 'X') {
   683                 // hexadecimal
   684                 s[1] = '0';
   685                 rune c = parseHex(s+1);
   686                 pError0(bRune2CodeUTF8(utf8Code, c));
   687                 s[1] = '#';
   688             }
   689             else {
   690                 // decimal
   691                 rune c = parseIntG(s);
   692                 pError0(bRune2CodeUTF8(utf8Code, c));
   693             }
   694         }
   695         else {
   696             // check entity list
   697             if (!r->entities) {
   698                 initiateG(&r->entities);
   699                 parseG(r->entities, entitiesString);
   700             }
   701             char *u = getG(r->entities, rtChar, s);
   702             if (!u) break; // not found
   703             strcpy(utf8Code, u);
   704         }
   705         pushG(r->current, utf8Code);
   706         break;
   707     default:
   708         strncpy(s, text, size);
   709         t = allocG(s);
   710         if (not sliceIsEmpty(&r->spans)) {
   711             pushNFreeG(r->rspan, t);
   712             break;
   713         }
   714         pushNFreeG(r->current, t);
   715         //puts(BLU "default" RST);
   716         //printf(">%s<\n", s);
   717         break;
   718     }
   719 
   720     logVarG(s);
   721     free(s);
   722 
   723     return 0;
   724 }
   725 
   726 internal void
   727 debug_log_callback(const char* msg, void* userdata)
   728 {
   729     shEPrintfS("md4c %s", msg);
   730 }
   731 
   732 smallArrayt *
   733 md_highlight(const char *md_source) {
   734 
   735     if (!md_source) {
   736         return NULL;
   737     }
   738 
   739     outt result = init0Var;
   740 
   741     result.out     = allocG(rtSmallArrayt);
   742     result.current = allocG("");
   743     result.span    = allocG(rtSmallArrayt);
   744     result.rspan   = allocG("");
   745     sliceInitCount(&result.blocks, 16);
   746     sliceInitCount(&result.spans, 16);
   747 
   748     unsigned parser_flags   = MD_DIALECT_GITHUB | MD_FLAG_UNDERLINE;
   749     unsigned renderer_flags = 0;
   750 
   751     MD_PARSER parser = {
   752         0,
   753         parser_flags,
   754         enter_block_callback,
   755         leave_block_callback,
   756         enter_span_callback,
   757         leave_span_callback,
   758         text_callback,
   759         debug_log_callback,
   760         null
   761     };
   762 
   763     md_parse(md_source, strlen(md_source), &parser, &result);
   764 
   765     smashG(result.current);
   766     terminateG(result.span);
   767     terminateG(result.rspan);
   768     sliceFree(&result.blocks);
   769     sliceFree(&result.spans);
   770     terminateG(result.entities);
   771     return result.out;
   772 }
   773 
   774 #ifndef LIB
   775 int main(int ARGC, char** ARGV) {
   776 
   777     initLibsheepy(ARGV[0]);
   778     setLogMode(LOG_FUNC);
   779 
   780     if (ARGC < 2) {
   781         puts(RED "Give a filename in parameter" RST);
   782         XFAILURE
   783     }
   784 
   785     e2text[MD_BLOCK_DOC]              = "MD_BLOCK_DOC";
   786     e2text[MD_BLOCK_QUOTE]            = "MD_BLOCK_QUOTE";
   787     e2text[MD_BLOCK_UL]               = "MD_BLOCK_UL";
   788     e2text[MD_BLOCK_OL]               = "MD_BLOCK_OL";
   789     e2text[MD_BLOCK_LI]               = "MD_BLOCK_LI";
   790     e2text[MD_BLOCK_HR]               = "MD_BLOCK_HR";
   791     e2text[MD_BLOCK_H]                = "MD_BLOCK_H";
   792     e2text[MD_BLOCK_CODE]             = "MD_BLOCK_CODE";
   793     e2text[MD_BLOCK_HTML]             = "MD_BLOCK_HTML";
   794     e2text[MD_BLOCK_P]                = "MD_BLOCK_P";
   795     e2text[MD_BLOCK_TABLE]            = "MD_BLOCK_TABLE";
   796     e2text[MD_BLOCK_THEAD]            = "MD_BLOCK_THEAD";
   797     e2text[MD_BLOCK_TBODY]            = "MD_BLOCK_TBODY";
   798     e2text[MD_BLOCK_TR]               = "MD_BLOCK_TR";
   799     e2text[MD_BLOCK_TH]               = "MD_BLOCK_TH";
   800     e2text[MD_BLOCK_TD]               = "MD_BLOCK_TD";
   801 
   802     t2text[MD_TEXT_NORMAL]            = "MD_TEXT_NORMAL";
   803     t2text[MD_TEXT_NULLCHAR]          = "MD_TEXT_NULLCHAR";
   804     t2text[MD_TEXT_BR]                = "MD_TEXT_BR";
   805     t2text[MD_TEXT_SOFTBR]            = "MD_TEXT_SOFTBR";
   806     t2text[MD_TEXT_ENTITY]            = "MD_TEXT_ENTITY";
   807     t2text[MD_TEXT_CODE]              = "MD_TEXT_CODE";
   808     t2text[MD_TEXT_HTML]              = "MD_TEXT_HTML";
   809     t2text[MD_TEXT_LATEXMATH]         = "MD_TEXT_LATEXMATH";
   810 
   811     s2text[MD_SPAN_EM]                = "MD_SPAN_EM";
   812     s2text[MD_SPAN_STRONG]            = "MD_SPAN_STRONG";
   813     s2text[MD_SPAN_A]                 = "MD_SPAN_A";
   814     s2text[MD_SPAN_IMG]               = "MD_SPAN_IMG";
   815     s2text[MD_SPAN_CODE]              = "MD_SPAN_CODE";
   816     s2text[MD_SPAN_DEL]               = "MD_SPAN_DEL";
   817     s2text[MD_SPAN_LATEXMATH]         = "MD_SPAN_LATEXMATH";
   818     s2text[MD_SPAN_LATEXMATH_DISPLAY] = "MD_SPAN_LATEXMATH_DISPLAY";
   819     s2text[MD_SPAN_WIKILINK]          = "MD_SPAN_WIKILINK";
   820     s2text[MD_SPAN_U]                 = "MD_SPAN_U";
   821     s2text[MD_SPAN_FNT]               = "MD_SPAN_FNT";
   822     s2text[MD_SPAN_INV]               = "MD_SPAN_INV";
   823     s2text[MD_SPAN_COC]               = "MD_SPAN_COC";
   824     s2text[MD_SPAN_BLI]               = "MD_SPAN_BLI";
   825     s2text[MD_SPAN_ANCHOR]            = "MD_SPAN_ANCHOR";
   826 
   827     char *c = readFileG(c, ARGV[1]);
   828 
   829     if (!c) {
   830         puts(RED "Error reading:" RST);
   831         puts(ARGV[1]);
   832         XFAILURE
   833     }
   834 
   835     logG(md_highlight(c));
   836 
   837     //logVarG(result.out);
   838 }
   839 #endif