0
\$\begingroup\$

This is a followup of the last review to comply with meta's rules.

In the last, I posted a small library called colorprint and got some helpful pointers from chux in his answer.


colorprint.c

#include "colorprint.h"

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

#define CP_BUFSZ ((3 * 2) + (2 * (CP_FX_END - 1)) + 1)

enum validate_error {
    VE_OK,
    VE_FXTOOLARGE,
    VE_TOOMANYFX,
    VE_FGTOOLARGE,
    VE_BGTOOLARGE,
    VE_BADSTRING,
    VE_END
};

const char *validate_error_str[VE_END] = {
    [VE_FXTOOLARGE] = "(.fx) one or more effect values are out of bounds",
    [VE_TOOMANYFX]  = "(.fx) too many effects (the \"trap\" effect was set)",
    [VE_FGTOOLARGE] = "(.fg) foreground color out of bounds",
    [VE_BGTOOLARGE] = "(.bg) background color out of bounds",
    [VE_BADSTRING]  = "(.string) no string provided"
};

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

    strcat(out, buf);
}

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

    strcat(out, buf);
}

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

    strcat(out, buf);
}

static void
apply_fx(char *out, enum fx *fx)
{
    for(int i = 0; i < CP_FX_END - 1 && 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);
}

static enum validate_error
validate_cpo(struct cpoptions *cpo)
{
    for(int i = 0; i < CP_FX_END - 1 && cpo->fx[i]; i++)
        if(cpo->fx[i] >= CP_FX_END)
            return VE_FXTOOLARGE;

    if(cpo->fx[CP_FX_END - 1] != CP_FX_INVALID)
        return VE_TOOMANYFX;

    if(cpo->fg >= CP_COLOR_END)
        return VE_FGTOOLARGE;

    if(cpo->bg >= CP_COLOR_END)
        return VE_BGTOOLARGE;

    if(cpo->string == NULL)
        return VE_BADSTRING;

    return VE_OK;
}

int
cprint_default(struct cpoptions *cpo)
{
    char options[CP_BUFSZ] = "";
    enum validate_error ve;

    if((ve = validate_cpo(cpo)) != VE_OK){
        fprintf(stderr, "colorprint: An error occurred. Details: E%03u (%s)\n", ve, validate_error_str[ve]);
        return -1;
    }

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

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

int
cputs_default(struct cpoptions *cpo)
{
    int ret = cprint_default(cpo);

    putchar('\n');
    return ret;
}

colorprint.h

#ifndef COLORPRINT_H
#define COLORPRINT_H

#define CPFUNC(name, type, ...) \
    name(&(type){__VA_ARGS__})

enum color {
    CP_COLOR_INVALID,
    CP_COLOR_BLACK,
    CP_COLOR_RED, 
    CP_COLOR_GREEN,
    CP_COLOR_YELLOW,
    CP_COLOR_BLUE,
    CP_COLOR_PURPLE,
    CP_COLOR_AQUA, 
    CP_COLOR_WHITE,
    CP_COLOR_END
};

enum fx {
    CP_FX_INVALID,
    CP_FX_PLAIN,
    CP_FX_BOLD,
    CP_FX_DARK,
    CP_FX_ITALIC,
    CP_FX_UNDERLINE,
    CP_FX_LIGHT,
    CP_FX_LIGHT2,
    CP_FX_REVERSE,
    CP_FX_BLINK,
    CP_FX_STRIKE,
    CP_FX_END
};

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

int cprint_default(struct cpoptions *cpo);
int cputs_default(struct cpoptions *cpo);

#define cprint(...) CPFUNC(cprint_default, struct cpoptions, __VA_ARGS__)
#define cputs(...) CPFUNC(cputs_default, struct cpoptions, __VA_ARGS__)

#if 0

Example: 

    cprint("Hello World", .fg = CP_COLOR_GREEN, .fx = {CP_FX_BOLD, CP_FX_ITALIC, CP_FX_STRIKE});

Or... 

    struct cpoptions cpo = {
        .string = "Hello World",
        .fg = CP_COLOR_GREEN,
        .fx = {
            CP_FX_BOLD,
            CP_FX_ITALIC,
            CP_FX_STRIKE
        }
    };

    cprint_default(&cpo);

#endif

#endif

I will say that I am not a fan of the #if 0 style comment suggested by chux in his answer. I personally find this much easier to read:

/*
 *
 *  Example: 
 *
 *      cprint("Hello World", .fg = CP_COLOR_GREEN, .fx = {CP_FX_BOLD, CP_FX_ITALIC, CP_FX_STRIKE});
 *
 *  Or... 
 *
 *      struct cpoptions cpo = {
 *          .string = "Hello World",
 *          .fg = CP_COLOR_GREEN,
 *          .fx = {
 *              CP_FX_BOLD,
 *              CP_FX_ITALIC,
 *              CP_FX_STRIKE
 *          }
 *      };
 *
 *      cprint_default(&cpo);
 *
 */

Because the * on the left side creates a margin that makes it easy to scan vertically and see where the comment begins and ends.

So I will probably change it back at some point.

\$\endgroup\$
  • \$\begingroup\$ To be clearer, suggest using #if 0 for the code portion of the example. Not a big issue either way. \$\endgroup\$ – chux Sep 22 '16 at 20:24
3
\$\begingroup\$

I've found some problems, and have already fixed them. So I will post them here in the style of an answer rather than break the rules and edit the question.

1. fx enum values are not completely accurate

The values 5 and 6 (LIGHT, LIGHT2) actually produce a blink effect, and 8 (BLINK) produces an "invisible" effect So...

// CP_FX_LIGHT,
// CP_FX_LIGHT2,
CP_FX_BLINK,
CP_FX_BLINK2,
...
// CP_FX_BLINK,
CP_FX_HIDDEN

2. Bug: Applying only a background color breaks the escape sequence

cputs("Hello World", .bg = CP_COLOR_BLUE);
// "?[44;Hello World?[0m"

It would be prudent to check for this:

if(cpo->fg)
    apply_fg(out, cpo->fg);
else
    // terminate the escape sequence
\$\endgroup\$

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.