💾 Archived View for gmi.noulin.net › gitRepositories › boxen › file › boxen.c.gmi captured on 2024-08-19 at 04:01:49. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2023-01-29)

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

boxen

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