💾 Archived View for gmi.noulin.net › gitRepositories › md › file › md.c.gmi captured on 2023-07-10 at 18:08:07. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2023-01-29)

🚧 View Differences

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

md

Log

Files

Refs

README

LICENSE

md.c (28045B)

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