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.
#if 0
for the code portion of the example. Not a big issue either way. \$\endgroup\$