Dzen is a general purpose messaging, notification and menuing program for X11 written by Robert Manea. In order to increase my experience with programming I have started rewriting portions of the source code. I started with the following 148 lines within the main function that parsed the command-line arguments.
/* cmdline args */
for(i = 1; i < argc; i++)
if(!strncmp(argv[i], "-l", 3)){
if(++i < argc) {
.slave_win.max_lines = atoi(argv[i]);
dzenif(dzen.slave_win.max_lines)
();
init_input_buffer}
}
else if(!strncmp(argv[i], "-geometry", 10)) {
if(++i < argc) {
int t;
int tx, ty;
unsigned int tw, th;
= XParseGeometry(argv[i], &tx, &ty, &tw, &th);
t
if(t & XValue)
.title_win.x = tx;
dzenif(t & YValue) {
.title_win.y = ty;
dzenif(!ty && (t & YNegative))
/* -0 != +0 */
.title_win.y = -1;
dzen}
if(t & WidthValue)
.title_win.width = (signed int) tw;
dzenif(t & HeightValue)
.line_height = (signed int) th;
dzen}
}
else if(!strncmp(argv[i], "-u", 3)){
.tsupdate = True;
dzen}
else if(!strncmp(argv[i], "-expand", 8)){
if(++i < argc) {
switch(argv[i][0]){
case 'l':
.title_win.expand = left;
dzenbreak;
case 'c':
.title_win.expand = both;
dzenbreak;
case 'r':
.title_win.expand = right;
dzenbreak;
default:
.title_win.expand = noexpand;
dzen}
}
}
else if(!strncmp(argv[i], "-p", 3)) {
.ispersistent = True;
dzenif (i+1 < argc) {
.timeout = strtoul(argv[i+1], &endptr, 10);
dzenif(*endptr)
.timeout = 0;
dzenelse
++;
i}
}
else if(!strncmp(argv[i], "-ta", 4)) {
if(++i < argc) dzen.title_win.alignment = alignment_from_char(argv[i][0]);
}
else if(!strncmp(argv[i], "-sa", 4)) {
if(++i < argc) dzen.slave_win.alignment = alignment_from_char(argv[i][0]);
}
else if(!strncmp(argv[i], "-m", 3)) {
.slave_win.ismenu = True;
dzenif(i+1 < argc) {
if( argv[i+1][0] == 'v') {
++i;
break;
}
.slave_win.ishmenu = (argv[i+1][0] == 'h') ? ++i, True : False;
dzen}
}
else if(!strncmp(argv[i], "-fn", 4)) {
if(++i < argc) dzen.fnt = argv[i];
}
else if(!strncmp(argv[i], "-e", 3)) {
if(++i < argc) action_string = argv[i];
}
else if(!strncmp(argv[i], "-title-name", 12)) {
if(++i < argc) dzen.title_win.name = argv[i];
}
else if(!strncmp(argv[i], "-slave-name", 12)) {
if(++i < argc) dzen.slave_win.name = argv[i];
}
else if(!strncmp(argv[i], "-bg", 4)) {
if(++i < argc) dzen.bg = argv[i];
}
else if(!strncmp(argv[i], "-fg", 4)) {
if(++i < argc) dzen.fg = argv[i];
}
else if(!strncmp(argv[i], "-x", 3)) {
if(++i < argc) dzen.title_win.x = dzen.slave_win.x = atoi(argv[i]);
}
else if(!strncmp(argv[i], "-y", 3)) {
if(++i < argc) dzen.title_win.y = atoi(argv[i]);
}
else if(!strncmp(argv[i], "-w", 3)) {
if(++i < argc) dzen.slave_win.width = atoi(argv[i]);
}
else if(!strncmp(argv[i], "-h", 3)) {
if(++i < argc) dzen.line_height= atoi(argv[i]);
}
else if(!strncmp(argv[i], "-tw", 4)) {
if(++i < argc) dzen.title_win.width = atoi(argv[i]);
}
else if(!strncmp(argv[i], "-fn-preload", 12)) {
if(++i < argc) {
= estrdup(argv[i]);
fnpre }
}
#ifdef DZEN_XINERAMA
else if(!strncmp(argv[i], "-xs", 4)) {
if(++i < argc) dzen.xinescreen = atoi(argv[i]);
}
#endif
else if(!strncmp(argv[i], "-dock", 6))
= 1;
use_ewmh_dock else if(!strncmp(argv[i], "-v", 3)) {
("dzen-"VERSION", (C)opyright 2007-2009 Robert Manea\n");
printf(
printf"Enabled optional features: "
#ifdef DZEN_XMP
" XPM "
#endif
#ifdef DZEN_XFT
" XFT"
#endif
#ifdef DZEN_XINERAMA
" XINERAMA "
#endif
"\n"
);
return EXIT_SUCCESS;
}
else
("usage: dzen2 [-v] [-p [seconds]] [-m [v|h]] [-ta <l|c|r>] [-sa <l|c|r>]\n"
eprint" [-x <pixel>] [-y <pixel>] [-w <pixel>] [-h <pixel>] [-tw <pixel>] [-u]\n"
" [-e <string>] [-l <lines>] [-fn <font>] [-bg <color>] [-fg <color>]\n"
" [-geometry <geometry string>] [-expand <left|right>] [-dock]\n"
" [-title-name <string>] [-slave-name <string>]\n"
#ifdef DZEN_XINERAMA
" [-xs <screen>]\n"
#endif
);
Not only does this lead to an unnecessarily long function, but it also includes a great deal of repetitive code. To reduce the repetition, and hopefully decrease the likelihood of a introducing a bug when adding a flag, I set about writing a table driven approach (see: opt.c). The core function and data structure of this design are copied below:
struct option
{
char *name;
int len;
int has_arg; // 0 false, 1 true, 2 optional
void (*setter)(Dzen *, char *);
} static opts[] = {
{ "-l", 2, 1, set_lines },
{ "-geometry", 9, 1, set_geometry },
{ "-u", 2, 0, set_update },
{ "-expand", 7, 1, set_expand },
{ "-p", 2, 2, set_persist },
{ "-ta", 3, 1, set_title_align },
{ "-sa", 4, 1, set_slave_align },
{ "-m", 2, 2, set_menu },
{ "-fn", 3, 1, set_font },
{ "-e", 2, 1, set_event },
{ "-title-name", 11, 1, set_title_name },
{ "-slave-name", 11, 1, set_slave_name },
{ "-bg", 3, 1, set_bg },
{ "-fg", 2, 1, set_fg },
{ "-y", 2, 1, set_y },
{ "-x", 2, 1, set_x },
{ "-w", 2, 1, set_width },
{ "-h", 2, 1, set_height },
{ "-tw", 3, 1, set_title_width },
{ "-fn-preload", 11, 1, set_font_preload },
#ifdef DZEN_XINERAMA
{ "-xs", 4, 1, set_xin_screen },
#endif
{ "-dock", 6, 0, set_dock },
{ "-v", 3, 0, print_version },
{ NULL, 0, 0, NULL }
};
void parse_opts( int ac, char **av, Dzen *dzen )
/*
* Loop through the command line arguments, and then the built-in flags, calling
* the appropriate function
*/
{
int i, j;
for (i = 1; i < ac; i++) { // Check command line arguments
for (j = 0; opts[j].name != NULL; j++) { // Compare built-in options
if (!strncmp(av[i], opts[j].name, opts[j].len)) {
if (opts[j].has_arg == 1) { // Required argument
if (++i < ac) {
[j].setter(dzen, av[i]);
opts}
else {
(stderr, "Missing argument: %s\n", opts[j].name);
fprintf(EXIT_FAILURE);
exit}
}
else if (opts[j].has_arg == 0) { // Not required argument
[j].setter(dzen, NULL);
opts++;
i}
else if (opts[j].has_arg == 2) { // Optional argument
if (i + 1 > ac) // Last option, no argument
[j].setter(dzen, NULL);
optselse if (av[i + 1][0] == '-') // Followed by option
[j].setter(dzen, NULL);
optselse
[j].setter(dzen, av[++i]);
opts}
break;
}
}
}
}
With a basic table driven implementation finished I am left with two
issues that are preventing me from moving forward. The first is whether
the level of branching in the parse_opts
function is too
high.
The second is how to deal with set_font_preload
and
set_event
functions, which assign a command-line argument
to a static character pointer, so that they may be processed later. If I
move the command-line parsing functions from main.c
into a
separate source file (i.e. opt.c
)
in order to increase modularity, then I still need to pass the character
pointers used by set_font_preload
and
set_event
back to main.c
, so that their
contents may be processed once the command-line parsing is complete.