md

cat markdown files with syntax highlighting
git clone https://noulin.net/git/md.git
Log | Files | Refs | README | LICENSE

md.c (28036B)


      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=1};
    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