💾 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
⬅️ Previous capture (2023-01-29)
-=-=-=-=-=-=-
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