boxen

Create boxes in the terminal
git clone https://noulin.net/git/boxen.git
Log | Files | Refs | README | LICENSE

boxen.c (15195B)


      1 
      2 
      3 /* Libsheepy documentation: http://spartatek.se/libsheepy/ */
      4 
      5 #include "libsheepyObject.h"
      6 #include "boxen.h"
      7 #include "boxenInternal.h"
      8 
      9 #include <stdlib.h>
     10 #include <string.h>
     11 #include <stdio.h>
     12 
     13 void initiateBoxen(boxent *self);
     14 void registerMethodsBoxen(boxenFunctionst *f);
     15 void initiateAllocateBoxen(boxent **self);
     16 void finalizeBoxen(void);
     17 boxent* allocBoxen(void);
     18 internal void freeBoxen(boxent *self);
     19 internal void terminateBoxen(boxent **self);
     20 internal char* toStringBoxen(boxent *self);
     21 internal boxent* duplicateBoxen(boxent *self);
     22 internal void smashBoxen(boxent **self);
     23 internal void finishBoxen(boxent **self);
     24 internal const char* helpBoxen(boxent *self);
     25 internal char *boxBoxen(boxent *self, char *input, char *opts);
     26 
     27 /* enable/disable logging */
     28 /* #undef pLog */
     29 /* #define pLog(...) */
     30 
     31 internal void stripAnsiSequences(char *string);
     32 internal size_t widestLine(char **text);
     33 typ struct {u16 rows; u16 cols;} winSizet;
     34 internal winSizet wsize (void);
     35 
     36 // single
     37 internal borderStyleBoxent singleBorderStyle = {
     38 .topLeft= "┌",
     39 .topRight= "┐",
     40 .bottomLeft= "└",
     41 .bottomRight= "┘",
     42 .horizontal= "─",
     43 .vertical= "│"
     44 };
     45 
     46 // double
     47 internal borderStyleBoxent doubleBorderStyle = {
     48 .topLeft= "╔",
     49 .topRight= "╗",
     50 .bottomLeft= "╚",
     51 .bottomRight= "╝",
     52 .horizontal= "═",
     53 .vertical= "║"
     54 };
     55 
     56 // single-double
     57 internal borderStyleBoxent singleDoubleBorderStyle = {
     58 .topLeft= "╓",
     59 .topRight= "╖",
     60 .bottomLeft= "╙",
     61 .bottomRight= "╜",
     62 .horizontal= "─",
     63 .vertical= "║"
     64 };
     65 
     66 // double-single
     67 internal borderStyleBoxent doubleSingleBorderStyle = {
     68 .topLeft= "╒",
     69 .topRight= "╕",
     70 .bottomLeft= "╘",
     71 .bottomRight= "╛",
     72 .horizontal= "═",
     73 .vertical= "│"
     74 };
     75 
     76 
     77 // classic
     78 internal borderStyleBoxent classicBorderStyle = {
     79 .topLeft= "+",
     80 .topRight= "+",
     81 .bottomLeft= "+",
     82 .bottomRight= "+",
     83 .horizontal= "-",
     84 .vertical= "|"
     85 };
     86 
     87 void initiateBoxen(boxent *self) {
     88 
     89   self->type = "boxen";
     90   if (!boxenF) {
     91     boxenF            = malloc(sizeof(boxenFunctionst));
     92     registerMethodsBoxen(boxenF);
     93     pErrorNot0(atexit(finalizeBoxen));
     94   }
     95   self->f = boxenF;
     96 
     97   self->borderColor        = "";
     98   self->borderColorHex     = 0;
     99   self->borderStyle        = singleBorderStyle;
    100   self->dimBorder          = "";
    101   self->padding.top        = 0;
    102   self->padding.left       = 0;
    103   self->padding.right      = 0;
    104   self->padding.bottom     = 0;
    105   self->margin.top         = 0;
    106   self->margin.left        = 0;
    107   self->margin.right       = 0;
    108   self->margin.bottom      = 0;
    109   self->boxFloat           = "left";
    110   self->backgroundColor    = "";
    111   self->backgroundColorHex = 0;
    112   self->align              = "left";
    113 }
    114 
    115 void registerMethodsBoxen(boxenFunctionst *f) {
    116 
    117   f->free      = freeBoxen;
    118   f->terminate = terminateBoxen;
    119   f->toString  = toStringBoxen;
    120   f->duplicate = duplicateBoxen;
    121   f->smash     = smashBoxen;
    122   f->finish    = finishBoxen;
    123   f->help      = helpBoxen;
    124   f->box       = boxBoxen;
    125 }
    126 
    127 void initiateAllocateBoxen(boxent **self) {
    128 
    129   if (self) {
    130     (*self) = malloc(sizeof(boxent));
    131     if (*self) {
    132       initiateBoxen(*self);
    133     }
    134   }
    135 }
    136 
    137 void finalizeBoxen(void) {
    138 
    139   if (boxenF) {
    140     free(boxenF);
    141     boxenF = NULL;
    142   }
    143 }
    144 
    145 boxent* allocBoxen(void) {
    146   boxent *r = NULL;
    147 
    148   initiateAllocateBoxen(&r);
    149   ret r;
    150 }
    151 
    152 
    153 internal void freeBoxen(boxent *self) {
    154 }
    155 
    156 internal void terminateBoxen(boxent **self) {
    157 
    158   freeBoxen(*self);
    159   finishBoxen(self);
    160 }
    161 
    162 
    163 internal char* toStringBoxen(boxent *self) {
    164 
    165   createSmallJson(j);
    166 
    167   if (isBlankG(self->borderColor))
    168     setG(&j , "borderColor"     , "default");
    169   else
    170     setG(&j , "borderColor"     , "set");
    171 
    172   if (eqG(self->borderStyle.topLeft, "┌")) {
    173     setG(&j , "borderStyle"     , "single");
    174   }
    175   elif (eqG(self->borderStyle.topLeft, "╔")) {
    176     setG(&j , "borderStyle"     , "double");
    177   }
    178   elif (eqG(self->borderStyle.topLeft, "╓")) {
    179     setG(&j , "borderStyle"     , "single-double");
    180   }
    181   elif (eqG(self->borderStyle.topLeft, "╒")) {
    182     setG(&j , "borderStyle"     , "double-single");
    183   }
    184 
    185   setG(&j , "dimBorder"       , (bool)self->dimBorder);
    186 
    187   createSmallDict(d);
    188   setG(&d , "top"     , self->padding.top);
    189   setG(&d , "left"    , self->padding.left);
    190   setG(&d , "right"   , self->padding.right);
    191   setG(&d , "bottom"  , self->padding.bottom);
    192   setG(&j , "padding" , &d);
    193   initiateG(&d);
    194   setG(&d , "top"     , self->margin.top);
    195   setG(&d , "left"    , self->margin.left);
    196   setG(&d , "right"   , self->margin.right);
    197   setG(&d , "bottom"  , self->margin.bottom);
    198   setG(&j , "margin" , &d);
    199 
    200   setG(&j , "float"           , self->boxFloat);
    201 
    202   if (isBlankG(self->backgroundColor))
    203     setG(&j , "backgroundColor"     , "default");
    204   else
    205     setG(&j , "backgroundColor"     , "set");
    206 
    207   setG(&j , "align"           , self->align);
    208 
    209   var r = toStringG(&j);
    210   freeG(&j);
    211 
    212   ret r;
    213 }
    214 
    215 internal boxent* duplicateBoxen(boxent *self) {
    216 
    217   createAllocateBoxen(dup);
    218   dup->borderColor        = self->borderColor;
    219   dup->borderColorHex     = self->borderColorHex;
    220   dup->borderStyle        = self->borderStyle;
    221   dup->dimBorder          = self->dimBorder;
    222   dup->padding            = self->padding;
    223   dup->margin             = self->margin;
    224   dup->boxFloat           = self->boxFloat;
    225   dup->backgroundColor    = self->backgroundColor;
    226   dup->backgroundColorHex = self->backgroundColorHex;
    227   dup->align              = self->align;
    228   ret dup;
    229 }
    230 
    231 internal void smashBoxen(boxent **self) {
    232 
    233   finishBoxen(self);
    234 }
    235 
    236 #if NFreeStackCheck
    237 internal void finishBoxen(boxent **self) {
    238 
    239   register u64 rsp asm("rsp");
    240   if ((u64)*self > rsp) {
    241     logW("Probably trying to free a smallArray on stack: "BLD"%p"RST" sp: "BLD"%p"RST, *self, rsp);
    242     logBtrace;
    243   }
    244   else {
    245     free(*self);
    246     *self = NULL;
    247   }
    248 }
    249 #else
    250 // #if NFreeStackCheck
    251 internal void finishBoxen(boxent **self) {
    252 
    253   free(*self);
    254   *self = NULL;
    255 }
    256 #endif
    257 // #if NFreeStackCheck
    258 
    259 internal const char* helpBoxen(boxent UNUSED *self) {
    260   ret "TODO - boxen help";
    261 }
    262 
    263 #define border(bord) self->borderStyle.bord
    264 #define oborderColor(color, value)\
    265   if (icEqG(getG(&j, rtChar, "borderColor"), color)) {\
    266     self->borderColor    = value;\
    267     self->borderColorHex = 0;\
    268   }
    269 #define oHexColor(color, opt, optHex)\
    270   if (getG(&j, rtChar, color) && getG(&j, rtChar, color)[0] == '#') {\
    271     /* hex color */\
    272     char *c  = getG(&j, rtChar, color);\
    273     /* check if color is 0 */\
    274     bool is0 = true;\
    275     size_t i = 1;\
    276     while(c[i]) if (c[i++] != '0') {is0 = false; break;}\
    277     \
    278     self->opt = BLK;\
    279     if (!is0) {\
    280       /* convert color string to int */\
    281       char *s = catS("0x", &c[1]);\
    282       self->optHex = parseHex(s);\
    283       free(s);\
    284       if (!self->optHex) {\
    285         /* invalid hex number */\
    286         self->opt = "";\
    287       }\
    288     }\
    289   }
    290 #define oborderStyle(style, value)\
    291   if (icEqG(getG(&j, rtChar, "borderStyle"), style)) {\
    292     self->borderStyle = value;\
    293   }
    294 #define opadding(where, value)\
    295   self->padding.value = getG(&j, rtU32, "\"padding\".\""where"\"");
    296 #define omargin(where, value)\
    297   self->margin.value = getG(&j, rtU32, "\"margin\".\""where"\"");
    298 #define oboxFloat(fl, value)\
    299   if (icEqG(getG(&j, rtChar, "float"), fl)) {\
    300     self->boxFloat = value;\
    301   }
    302 #define obackgroundColor(color, value)\
    303   if (icEqG(getG(&j, rtChar, "backgroundColor"), color)) {\
    304     self->backgroundColor    = value;\
    305     self->backgroundColorHex = 0;\
    306   }
    307 #define oalign(fl, value)\
    308   if (icEqG(getG(&j, rtChar, "align"), fl)) {\
    309     self->align = value;\
    310   }
    311 
    312 internal char *boxBoxen(boxent *self, char *input, char *opts) {
    313   if (!input) ret NULL;
    314 
    315   char *r = NULL;
    316 
    317 
    318   // options
    319   if (opts) {
    320     createSmallJson(j);
    321     parseG(&j, opts);
    322     oborderColor      ( "black"   , BLK)
    323     else oborderColor ( "red"     , RED)
    324     else oborderColor ( "green"   , GRN)
    325     else oborderColor ( "yellow"  , YLW)
    326     else oborderColor ( "blue"    , BLU)
    327     else oborderColor ( "magenta" , MGT)
    328     else oborderColor ( "cyan"    , CYN)
    329     else oborderColor ( "white"   , BLD WHT)
    330     else oborderColor ( "gray"    , WHT)
    331     else oHexColor("borderColor", borderColor, borderColorHex)
    332     elif (isEStringG(&j, "borderColor")) {
    333       // invalid value: reset
    334       self->borderColor = "";
    335     }
    336 
    337     oborderStyle("single"        , singleBorderStyle);
    338     oborderStyle("double"        , doubleBorderStyle);
    339     oborderStyle("single-double" , singleDoubleBorderStyle);
    340     oborderStyle("double-single" , doubleSingleBorderStyle);
    341     oborderStyle("classic"       , classicBorderStyle);
    342 
    343     if (getG(&j, rtBool, "dimBorder"))
    344       self->dimBorder = FNT;
    345     else
    346       self->dimBorder = "";
    347 
    348     if (isEIntG(&j, "padding")) {
    349       self->padding.top    = getG(&j, rtU32, "padding");
    350       self->padding.left   = self->padding.top * 3;
    351       self->padding.right  = self->padding.top * 3;
    352       self->padding.bottom = self->padding.top;
    353     }
    354     else {
    355       opadding("top"    , top);
    356       opadding("left"   , left);
    357       opadding("right"  , right);
    358       opadding("bottom" , bottom);
    359     }
    360 
    361     if (isEIntG(&j, "margin")) {
    362       self->margin.top    = getG(&j, rtU32, "margin");
    363       self->margin.left   = self->margin.top * 3;
    364       self->margin.right  = self->margin.top * 3;
    365       self->margin.bottom = self->margin.top;
    366     }
    367     else {
    368       omargin("top"    , top);
    369       omargin("left"   , left);
    370       omargin("right"  , right);
    371       omargin("bottom" , bottom);
    372     }
    373 
    374     oboxFloat("left"   , "left");
    375     oboxFloat("center" , "center");
    376     oboxFloat("right"  , "right");
    377 
    378     obackgroundColor      ( "black"   , BGBLK)
    379     else obackgroundColor ( "red"     , BGRED)
    380     else obackgroundColor ( "green"   , BGGRN)
    381     else obackgroundColor ( "yellow"  , BGYLW)
    382     else obackgroundColor ( "blue"    , BGBLU)
    383     else obackgroundColor ( "magenta" , BGMGT)
    384     else obackgroundColor ( "cyan"    , BGCYN)
    385     else obackgroundColor ( "white"   , BGWHT) // TODO RGB?
    386     else obackgroundColor ( "gray"    , BGWHT)
    387     else oHexColor("backgroundColor", backgroundColor, backgroundColorHex)
    388     elif (isEStringG(&j, "backgroundColor")) {
    389       // invalid value: reset
    390       self->backgroundColor = "";
    391     }
    392 
    393     oalign("left"   , "left");
    394     oalign("center" , "center");
    395     oalign("right"  , "right");
    396 
    397     freeG(&j);
    398   }
    399 
    400   var lines  = splitG(input, '\n');
    401 
    402   var noansi = dupG(lines);
    403   forEachS(noansi, l) {
    404     stripAnsiSequences(l);
    405   }
    406   //var widest = widestLine(lines);
    407   var widest = widestLine(noansi);
    408 
    409   // align text
    410   if (eqG(self->align, "center")) {
    411     enumerateCharP(lines, l, i) {
    412       var algWidth = (widest - lenUTF8(noansi[i])) / 2;
    413       prependNFreeG(l, repeatS(" ", algWidth));
    414       prependNFreeG(&noansi[i], repeatS(" ", algWidth));
    415     }
    416   }
    417   elif (eqG(self->align, "right")) {
    418     enumerateCharP(lines, l, i) {
    419       var algWidth = widest - lenUTF8(noansi[i]);
    420       prependNFreeG(l, repeatS(" ", algWidth));
    421       prependNFreeG(&noansi[i], repeatS(" ", algWidth));
    422     }
    423   }
    424   // align left - no-op
    425 
    426   // padding top
    427   range(i, self->padding.top) {
    428     prependG(&lines, "");
    429   }
    430   // padding bottom
    431   range(i, self->padding.bottom) {
    432     pushG(&lines, "");
    433   }
    434 
    435   var contentWidth  = widest + self->padding.left + self->padding.right;
    436   char *paddingLeft = NULL;
    437   pushNFreeG(&paddingLeft, repeatS(" ", self->padding.left));
    438 
    439   winSizet sz = wsize(); // window cols
    440 
    441   char *marginLeft = NULL;
    442 
    443   // float
    444   if (eqG(self->boxFloat, "center")) {
    445     var padWidth = maxV((sz.cols - contentWidth) / 2, 0);
    446     pushNFreeG(&marginLeft, repeatS(" ", padWidth));
    447   }
    448   elif (eqG(self->boxFloat, "right")) {
    449     var padWidth = maxV(sz.cols - contentWidth - self->margin.right - 2, 0);
    450     pushNFreeG(&marginLeft, repeatS(" ", padWidth));
    451   }
    452   else {
    453     // left
    454     pushNFreeG(&marginLeft, repeatS(" ", self->margin.left));
    455   }
    456 
    457   // colors
    458   char *borderColorS = NULL;
    459   if (!self->borderColor[0]) {
    460     emptyS(borderColorS);
    461   }
    462   elif (self->borderColorHex) {
    463     u32 c = self->borderColorHex;
    464     borderColorS = formatS("\x1b[38;2;%d;%d;%dm", c>>16, (c&0xFF00)>>8, c&0xFF);
    465   }
    466   else {
    467     borderColorS = dupG(self->borderColor);
    468   }
    469   char *backgroundColorS = NULL;
    470   if (!self->backgroundColor[0]) {
    471     emptyS(backgroundColorS);
    472   }
    473   elif (self->backgroundColorHex) {
    474     u32 c = self->backgroundColorHex;
    475     backgroundColorS = formatS("\x1b[48;2;%d;%d;%dm", c>>16, (c&0xFF00)>>8, c&0xFF);
    476   }
    477   else {
    478     backgroundColorS = dupG(self->backgroundColor);
    479   }
    480 
    481   // top
    482   pushNFreeG(&r, repeatS("\n", self->margin.top));
    483   pushG(&r, marginLeft);
    484   pushG(&r, self->dimBorder);
    485   pushG(&r, borderColorS);
    486   pushG(&r, border(topLeft));
    487   pushNFreeG(&r, repeatS(border(horizontal), contentWidth));
    488   pushG(&r, border(topRight));
    489   pushG(&r, RST"\n");
    490 
    491   // middle
    492   enumerateS(lines, l, i) {
    493     pushG(&r, marginLeft);
    494     pushG(&r, self->dimBorder);
    495     pushG(&r, borderColorS);
    496     pushG(&r, border(vertical));
    497     pushG(&r, RST);
    498     pushG(&r, backgroundColorS);
    499     pushG(&r, paddingLeft);
    500     pushG(&r, l);
    501     // restore background color after text
    502     pushG(&r, backgroundColorS);
    503     // paddingRight
    504     i64 idx = i - self->padding.top;
    505     if (idx >= 0 and idx < lenG(noansi)) {
    506       pushNFreeG(&r, repeatS(" ", contentWidth - lenUTF8(noansi[idx]) - self->padding.left));
    507     }
    508     else {
    509       pushNFreeG(&r, repeatS(" ", contentWidth - lenUTF8(l) - self->padding.left));
    510     }
    511     pushG(&r, RST);
    512     pushG(&r, self->dimBorder);
    513     pushG(&r, borderColorS);
    514     pushG(&r, border(vertical));
    515     pushG(&r, RST);
    516     pushG(&r, '\n');
    517   }
    518 
    519   // bottom
    520   pushG(&r, marginLeft);
    521   pushG(&r, self->dimBorder);
    522   pushG(&r, borderColorS);
    523   pushG(&r, border(bottomLeft));
    524   pushNFreeG(&r, repeatS(border(horizontal), contentWidth));
    525   pushG(&r, border(bottomRight));
    526   pushG(&r, RST);
    527   pushNFreeG(&r, repeatS("\n", self->margin.bottom));
    528 
    529   // free
    530   freeG(noansi);
    531   freeG(lines);
    532   freeManyS(paddingLeft, marginLeft, borderColorS, backgroundColorS);
    533 
    534   ret r;
    535 }
    536 
    537 // https://en.wikipedia.org/wiki/ANSI_escape_code#Escape_sequences
    538 // [\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\\u0007)
    539 // (?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))
    540 internal void stripAnsiSequences(char *string) {
    541   size_t i = 0,j = 0;
    542   enum {SEARCH_SEQ, CTRL_CODE};
    543   u8 status = SEARCH_SEQ;
    544   while(string[i]) {
    545     if (status == SEARCH_SEQ) {
    546       if (string[i] == '\x1b') {
    547         // dont copy sequence
    548         status = CTRL_CODE;
    549       }
    550       else {
    551         // copy other char
    552         string[j++] = string[i];
    553       }
    554     }
    555     if (status == CTRL_CODE and string[i] == 'm') {
    556       // all libsheepy codes end with 'm'
    557       status = SEARCH_SEQ;
    558     }
    559     i++;
    560   }
    561   string[j] = 0;
    562 }
    563 
    564 internal size_t widestLine(char **text) {
    565   size_t r = 0;
    566   if (!text) ret 0;
    567   forEachS(text, l) {
    568     r = MAX(r, lenUTF8(l));
    569   }
    570   ret r;
    571 }
    572 
    573 /* terminal window size */
    574 #include <sys/ioctl.h>
    575 #include <unistd.h>
    576 
    577 internal winSizet wsize (void)
    578 {
    579     struct winsize w;
    580     ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
    581 
    582     return (winSizet){w.ws_row, w.ws_col};
    583 }
    584 //-----------------------
    585 
    586 // vim: set expandtab ts=2 sw=2:
    587 
    588 bool checkLibsheepyVersionBoxen(const char *currentLibsheepyVersion) {
    589   return eqG(currentLibsheepyVersion, LIBSHEEPY_VERSION);
    590 }
    591