root/usr.bin/top/color.c
/*
 * Copyright (c) 1984 through 2008, William LeFebvre
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 * 
 *     * Neither the name of William LeFebvre nor the names of other
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 *  Top users/processes display for Unix
 *  Version 3
 */

/*
 * This file handles color definitions and access for augmenting
 * the output with ansi color sequences.
 *
 * The definition of a color setting is as follows, separated by
 * colons:
 *
 * tag=minimum,maximum#code
 *
 * "tag" is the name of the value to display with color.
 *
 * "minimum" and "maximum" are positive integer values defining a range:
 * when the value is within this range it will be shown with the
 * specified color.  A missing value indicates that no check should be
 * made (i.e.: ",25" is n <= 25; "25,50" is 25 <= n <= 50; and "50,"
 * is 50 <= n).
 * 
 * "code" is the ansi sequence that defines the color to use with the
 * escape sequence "[m".  Semi-colons are allowed in this string to
 * combine attributes.
 */

#include "os.h"
#include "display.h"
#include "message.h"
#include "color.h"
#include "utils.h"

typedef struct color_entry {
    char *tag;
    int min;
    int max;
    char color;
    struct color_entry *next;
    struct color_entry *tagnext;
} color_entry;

static color_entry *entries = NULL;

static color_entry **bytag = NULL;
static char **bytag_names = NULL;
static int totaltags = 0;
static int tagcnt = 0;
static int color_off = 0;

static char **color_ansi = NULL;
static int num_color_ansi = 0;
static int max_color_ansi = 0;

static int
color_slot(char *str)

{
    int i;

    for (i = 0; i < num_color_ansi; i++)
    {
        if (strcmp(color_ansi[i], str) == 0)
        {
            return i;
        }
    }

    /* need a new slot */
    if (num_color_ansi >= max_color_ansi)
    {
        max_color_ansi += COLOR_ANSI_SLOTS;
        color_ansi = (char **)realloc(color_ansi, max_color_ansi * sizeof(char *));
    }
    color_ansi[num_color_ansi] = strdup(str);
    return num_color_ansi++;
}

/*
 * int color_env_parse(char *env)
 *
 * Parse a color specification "env" (such as one found in the environment) and
 * add them to the list of entries.  Always returns 0.  Should only be called
 * once.
 */

int
color_env_parse(char *env)

{
    char *p;
    char *min;
    char *max;
    char *str;
    int len;
    color_entry *ce;

    /* initialization */
    color_ansi = (char **)malloc(COLOR_ANSI_SLOTS * sizeof(char *));
    max_color_ansi = COLOR_ANSI_SLOTS;

    /* color slot 0 is always "0" */
    color_slot("0");

    if (env != NULL)
    {
        p = strtok(env, ":");
        while (p != NULL)
        {
            if ((min = strchr(p, '=')) != NULL &&
                (max = strchr(min, ',')) != NULL &&
                (str = strchr(max, '#')) != NULL)
            {
                ce = (color_entry *)malloc(sizeof(color_entry));
                len = min - p;
                ce->tag = (char *)malloc(len + 1);
                strncpy(ce->tag, p, len);
                ce->tag[len] = '\0';
                ce->min = atoi(++min);
                ce->max = atoi(++max);
                ce->color = color_slot(++str);
                ce->next = entries;
                entries = ce;
            }
            else
            {
                if (min != NULL)
                {
                    len = min - p;
                }
                else
                {
                    len = strlen(p);
                }
                message_error(" %.*s: bad color entry", len, p);
            }
            p = strtok(NULL, ":");
        }
    }
    return 0;
}

/*
 * int color_tag(char *tag)
 *
 * Declare "tag" as a color tag.  Return a tag index to use when testing
 * a value against the tests for this tag.  Should not be called before
 * color_env_parse.
 */

int
color_tag(char *tag)

{
    color_entry *entryp;
    color_entry *tp;

    /* check for absurd arguments */
    if (tag == NULL || *tag == '\0')
    {
        return -1;
    }

    dprintf("color_tag(%s)\n", tag);

    /* initial allocation */
    if (bytag == NULL)
    {
        totaltags = 10;
        bytag = (color_entry **)malloc(totaltags * sizeof(color_entry *));
        bytag_names = (char **)malloc(totaltags * sizeof(char *));
    }

    /* if we dont have enough room then reallocate */
    if (tagcnt >= totaltags)
    {
        totaltags *= 2;
        bytag = (color_entry **)realloc(bytag, totaltags * sizeof(color_entry *));
        bytag_names = (char **)realloc(bytag_names, totaltags * sizeof(char *));
    }

    /* initialize scan */
    entryp = entries;
    tp = NULL;

    /* look for tag in the list of entries */
    while (entryp != NULL)
    {
        if (strcmp(entryp->tag, tag) == 0)
        {
            entryp->tagnext = tp;
            tp = entryp;
        }
        entryp = entryp->next;
    }

    /* we track names in the array bytag */
    bytag[tagcnt] = tp;
    bytag_names[tagcnt] = strdup(tag);

    /* return this index number as a reference */
    dprintf("color_tag: returns %d\n", tagcnt);
    return (tagcnt++);
}

/*
 * int color_test(int tagidx, int value)
 *
 * Test "value" against tests for tag "tagidx", a number previously returned
 * by color_tag.  Return the correct color number to use when highlighting.
 * If there is no match, return 0 (color 0).
 */

int
color_test(int tagidx, int value)

{
    color_entry *ce;

    /* sanity check */
    if (tagidx < 0 || tagidx >= tagcnt || color_off)
    {
        return 0;
    }

    ce = bytag[tagidx];

    while (ce != NULL)
    {
        if ((!ce->min || ce->min <= value) &&
            (!ce->max || ce->max >= value))
        {
            return ce->color;
        }
        ce = ce->tagnext;
    }

    return 0;
}

/*
 * char *color_setstr(int color)
 *
 * Return ANSI string to set the terminal for color number "color".
 */

char *
color_setstr(int color)

{
    static char v[32];

    v[0] = '\0';
    if (color >= 0 && color < num_color_ansi)
    {
        snprintf(v, sizeof(v), "\033[%sm", color_ansi[color]);
    }
    return v;
}

void
color_dump(FILE *f)

{
    color_entry *ep;
    int i;
    int col;
    int len;

    fputs("These color tags are available:", f);
    col = 81;
    for (i = 0; i < tagcnt; i++)
    {
        len = strlen(bytag_names[i]) + 1;
        if (len + col > 79)
        {
            fputs("\n  ", f);
            col = 2;
        }
        fprintf(f, " %s", bytag_names[i]);
        col += len;
    }

    fputs("\n\nTop color settings:\n", f);

    for (i = 0; i < tagcnt; i++)
    {
        ep = bytag[i];
        while (ep != NULL)
        {
            fprintf(f, "   %s (%d-", ep->tag, ep->min);
            if (ep->max != 0)
            {
                fprintf(f, "%d", ep->max);
            }
            fprintf(f, "): ansi color %s, %sSample Text",
                    color_ansi[(int)ep->color],
                    color_setstr(ep->color));
            fprintf(f, "%s\n", color_setstr(0));
            ep = ep -> tagnext;
        }
    }
}

void
color_debug(FILE *f)

{
    color_entry *ep;
    int i;

    fprintf(f, "color debug dump\n");
    ep = entries;
    while (ep != NULL)
    {
        fprintf(f, "%s(%d,%d): slot %d, ansi %s, %sSample Text",
                ep->tag, ep->min, ep->max, ep->color, color_ansi[(int)ep->color],
               color_setstr(ep->color));
        fprintf(f, "%s\n", color_setstr(0));
        ep = ep -> next;
    }

    fprintf(f, "\ntags:");
    for (i = 0; i < tagcnt; i++)
    {
        fprintf(f, " %s", bytag_names[i]);
    }
    fprintf(f, "\n");
}

int
color_activate(int i)

{
    if (i == -1)
    {
        color_off = !color_off;
    }
    else
    {
        color_off = !i;
    }
    return color_off;
}