3
\$\begingroup\$

I wrote this a long time ago. I came back to it and decided to clean it up a little.

It uses the preprocessor and C99 anon structs to make a flexible python-like API for calling its functions.

colorprint.c

#include <stdio.h>
#include <string.h>

#include "colorprint.h"

static const char *numch = "0123456789";

static void
apply_effect(char *out, enum fx fx)
{/*{{{*/
    char buf[3] = { numch[fx - 1], ';' };

    strcat(out, buf);
}/*}}}*/

static void
apply_fg(char *out, enum color c)
{/*{{{*/
    char buf[4] = { '3', numch[c - 1], 'm' };

    strcat(out, buf);
}/*}}}*/

static void
apply_bg(char *out, enum color c)
{/*{{{*/
    char buf[4] = { '4', numch[c - 1], ';' };

    strcat(out, buf);
}/*}}}*/

static void
apply_fx(char *out, enum fx *fx)
{/*{{{*/
    for(int i = 0; fx[i]; i++)
        apply_effect(out, fx[i]);
}/*}}}*/

static void
apply_colors(char *out, struct cpoptions *cpo)
{/*{{{*/
    if(cpo->bg)
        apply_bg(out, cpo->bg);

    if(cpo->fg)
        apply_fg(out, cpo->fg);
}/*}}}*/

void
_cprint(struct cpoptions *cpo)
{/*{{{*/
    char options[30] = "";

    apply_fx(options, cpo->fx);
    apply_colors(options, cpo);

    if(!cpo->fg && !cpo->bg && !cpo->fx[0])
         fputs(cpo->string, stdout);
    else
        printf("\e[%s%s\e[0m", options, cpo->string);
}/*}}}*/

void
_cputs(struct cpoptions *cpo)
{/*{{{*/
    _cprint(cpo);
    putchar('\n');
}/*}}}*/

colorprint.h

#define func(name, type, ...) \
    name(&(type){.header = 0, __VA_ARGS__})

enum color {
    color_invalid,
    color_black,
    color_red, 
    color_green,
    color_yellow,
    color_blue,
    color_purple,
    color_aqua, 
    color_white
};

enum fx {
    fx_invalid,
    fx_plain,
    fx_bold,
    fx_dark,
    fx_italic,
    fx_underline,
    fx_light,
    fx_light2,
    fx_reverse,
    fx_blink,
    fx_strike
};

struct cpoptions {
    char header;
    char *string;
    enum fx fx[10];
    enum color fg;
    enum color bg;
};

void _cprint(struct cpoptions *cpo);
void _cputs(struct cpoptions *cpo);

#define cprint(...) func(_cprint, struct cpoptions, __VA_ARGS__)
#define cputs(...) func(_cputs, struct cpoptions, __VA_ARGS__)

/* Example: 
 *      cprint("Hello World", .fg = color_green, .fx = {fx_bold, fx_italic, fx_strike});
 * Or: 
 *      struct cpoptions cpo = {
 *          .string = "Hello World",
 *          .fg = color_green,
 *          .fx = {
 *              fx_bold,
 *              fx_italic,
 *              fx_strike
 *          }
 *      };
 *      _cprint(&cpo);
 */

Its usage ranges in complexity from

cputs("Hello World"); // prints "Hello World\n"

to

cputs( .string = "Hello World",
       .fg = color_green,
       .bg = color_red,
       .fx = {
           fx_bold,
           fx_strike,
           fx_italic
        }
); // prints "\e[1;9;3;41;32mHello World\e[0m\n", which appears as "Hello World" with a red background, green foreground, with bold+strikethrough+italic effects

In my opinion, this method of function calling is far more type-safe and versatile than the horrid va_args. It's also nice because it allows colorful output without the overhead of ncurses or termcap.

\$\endgroup\$
1
\$\begingroup\$
  1. Architecture: IMO, the time for trusting that char * buffers will not overflow is past. Pass in a size_t size and check for valid sizes in function calls.

  2. static const char *numch = "0123456789"; serves little value and its application can cause UB on unruly input. Rather than use it convert to digit character, just add '0', offset as needed. numch[fx - 1] --> fx - 1 + '0'

  3. Global functions that being with _ are reserve for the implementation. Use a different name.

    // void _cprint(struct cpoptions *cpo)
    void cp_print(struct cpoptions *cpo)
    
  4. \e is a non-standard escape sequence. Use \033 or ...

    // printf("\e[%s%s\e[0m"
    #define ESC 27
    printf("%c[%s%s\e[0m", ESC
    
  5. colorprint.h should have a protective #ifndef to cope with re-entrantcy.

    #ifndef colorprint_h
    #define colorprint_h
       ... // existing content
    #endif
    
  6. I found useful enumerating the Nth item

    enum color {
        color_invalid,
        color_white,
        color_N  // add
    };
    
    if (value >= color_N) Handle_InvalidValue(value);
    
  7. Nice to have example usage in .h file. Suggest #if 0 bracketing instead as it does not stop syntax highlighting.

  8. Pedantic: size_t is the type to use for array indexing rather than int. Code accordingly, remember that size_t is some unsigned type.

  9. Namespace colorprint.h abuses namespace with color_..., fx_..., cp.... Suggest a more uniform prefix. cp... is not too bad, but then use cp.h.

  10. Avoid magic number - why 30? Numbers other than -1,0,1,2 and maybe 42 deserve a macro or const object.

    char options[30] = "";
    
  11. Do not include files before "colorprint.h". "colorprint.c" should do this to insure "colorprint.h" is not dependant on some <*.h> file

  12. tidy up, remove /*{{{*/, etc.

  13. Good use of static functions.

  14. Print-like functions should return int.

    /// void _cprint(
    int  _cprint(
      ...
      int i = printf("\e[%s%s\e[0m", options, cpo->string);
      return i;
    }
    
  15. func is much too common to use as a macro name

    // #define func(name, type, ...) \
    #define cp_func(name, type, ...) \
    
\$\endgroup\$
  • \$\begingroup\$ Can you elaborate more on 1 and 7? As for #10, I specifically calculated the maximum possible size of a string generated by the apply_* functions, and it came out to 27 bytes. 30 is just more round. Are you suggesting that I instead use a macro like #define CP_BUFSZ ((10 * 2) + (2 * 3) + 1)? \$\endgroup\$ – Braden Best Sep 22 '16 at 1:58
  • \$\begingroup\$ @Braden #10: Yes, #define CP_BUFSZ ... is a good idea. 10 should not be hard coded either, but some macro/constant like fx_N see #6 #1: cancel with #10 change. #7: Rather than /* Example:... */, use #if 0 ... #endif. #16: Minor: Recommend testing if (cpo->string) before using it. #17: header field usage unclear - in general a bit more documentation in *.h would be useful. \$\endgroup\$ – chux Sep 22 '16 at 2:20
  • \$\begingroup\$ @Braden As the user sets up the fields of struct cpoptions suggest validating them as able, before using them in the .c file \$\endgroup\$ – chux Sep 22 '16 at 2:25
  • \$\begingroup\$ The intent of header was to make it so that the struct doesn't get populated as {} when a function is called with no arguments (fn()). Though I've since decided to remove header and let the compiler validate the usage of the function for me, since there's supposed to at least be one argument anyway. \$\endgroup\$ – Braden Best Sep 22 '16 at 3:05
  • \$\begingroup\$ Anyways, I've made edits purporting to 1-7, 9-12, and 14-17. I went ahead and pushed the current version to a gitlab repo \$\endgroup\$ – Braden Best Sep 22 '16 at 4:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.