image thumbnail

tcprintf : ANSI colored output in terminal

by

 

tcprintf provides colored output when using MATLAB from a terminal using ANSI escape codes.

tcprintf(style, fmatString, varargin)
function tcprintf(style, fmatString, varargin)
    % Uses ANSI escape codes to print colored output when using MATLAB
    % from a terminal. If not running in a terminal, or if called by MATLAB's
    % datatipinfo function, tcprintf reverts to standard printf. The latter is
    % desirable if tcprintf is used within an object's disp() method to avoid
    % seeing the ANSI characters here.
    %
    % The first argument is an style description that consists of space-separated
    % words. These words may include: 
    %
    % one of the following colors:
    %   black, red, green, yellow, blue, purple, cyan, darkGray, lightGray, white
    %
    % one of the following background colors:
    %   onBlack, onRed, onGreen, onYellow, onBlue, onPurple, onCyan, onWhite
    %
    % and any of the following modifiers:
    %   bright : use the bright (or bold) form of the color, not applicable for
    %       black, darkGray, lightGray, or white
    %   underline : draw an underline under each character
    %   blink : This is a mistake. Please don't use this ever.
    %
    % Example:
    %   tcprintf('lightGray onRed underline', 'Message: %20s\n', msg);
    %
    % Author: Dan O'Shea dan at djoshea.com (c) 2012
    %
    % Released under the open source BSD license 
    %   opensource.org/licenses/bsd-license.php

    if nargin < 2 || ~ischar(style) || ~ischar(fmatString)
        error('Usage: tcprintf(style, fmatString, ...)');
    end

    % determine if we're using 
    usingTerminal = ~usejava('desktop');

    % determine if datatipinfo is higher on the stack. If tcprintf
    % is used within an object's disp() method, we don't want to
    % use color in the hover datatip or all you'll see are ANSI codes.
    stack = dbstack;
    inDataTip = ismember('datatipinfo', {stack.name});

    if ~usingTerminal || inDataTip
        % print the message without color and return
        fprintf(fmatString, varargin{:});
        return;
    end

    bright = 1;
    [colorName backColorName bright underline blink] = parseStyle(style);
    colorCodes = getColorCode(colorName, bright);
    backColorCodes = getBackColorCode(backColorName);

    codes = [colorCodes; backColorCodes];

    if underline
        codes = [codes; 4];
    end

    if blink
        codes = [codes; 5];
    end

    codeStr = strjoin(codes, ';');

    % evaluate the printf style message
    contents = sprintf(fmatString, varargin{:});

    % if the message ends with a newline, we should turn off
    % formatting before the newline to avoid issues with 
    % background colors
    if ~isempty(contents) && contents(end) == char(10)
        contents = contents(1:end-1);
        endOfLine = char(10);
    else
        endOfLine = '';
    end
        
    str = ['\033[' codeStr 'm' contents '\033[0m' endOfLine];
    fprintf(str);
end

function [colorName backColorName bright underline blink] = parseStyle(style)
    defaultColor = 'white';
    defaultBackColor = 'onDefault';

    tokens = regexp(style, '(?<value>\S+)[\s]?', 'names');
    
    values = {tokens.value};

    if ismember('bright', values)
        bright = true;
    else
        bright = false;
    end

    if ismember('underline', values)
        underline = true;
    else
        underline = false;
    end
    
    if ismember('blink', values)
        blink = true;
    else
        blink = false;
    end

    % find foreground color
    colorList = {'black', 'darkGray', 'lightGray', 'red', 'green', 'yellow', ...
        'blue', 'purple', 'cyan', 'lightGray', 'white', 'default'};
    idxColor = find(ismember(colorList, values), 1);
    if ~isempty(idxColor)
        colorName = colorList{idxColor}; 
    else
        colorName = defaultColor;
    end

    % find background color
    backColorList = {'onBlack', 'onRed', 'onGreen', 'onYellow', 'onBlue', ...
        'onPurple', 'onCyan', 'onWhite', 'onDefault'};
    idxBackColor = find(ismember(backColorList, values), 1);
    if ~isempty(idxBackColor)
        backColorName = backColorList{idxBackColor}; 
    else
        backColorName = defaultBackColor;
    end

end

function colorCodes = getColorCode(colorName, bright)

    switch colorName
        case 'black'
            code = 30;
            bright = 0;
        case 'darkGray';
            code = 30;
            bright = 1;
        case 'red'
            code = 31;
        case 'green'
            code = 32;
        case 'yellow'
            code = 33;
        case 'blue'
            code = 34;
        case 'purple'
            code = 35;
        case 'cyan'
            code = 36;
        case 'lightGray'
            code = 37;
            bright = 0;
        case 'white'
            code = 37;
            bright = 1;
        case 'default'
            code = 39;
    end

    if bright
        colorCodes = [1; code];
    else
        colorCodes = [code];
    end

end

function colorCodes = getBackColorCode(colorName)

    switch colorName
        case 'onBlack'
            code = 40;
        case 'onRed'
            code = 41;
        case 'onGreen'
            code = 42;
        case 'onYellow'
            code = 43;
        case 'onBlue'
            code = 44;
        case 'onPurple'
            code = 45;
        case 'onCyan'
            code = 46;
        case 'onWhite'
            code = 47;
        case 'onDefault'
            code = 49;
    end

    colorCodes = code;
end

function str = strjoin(strCell, join)
    % str = strjoin(strCell, join)
    % creates a string by concatenating the elements of strCell, separated by the string
    % in join (default = ', ')
    %
    % e.g. strCell = {'a','b'}, join = ', ' [ default ] --> str = 'a, b'

    if nargin < 2
        join = ', ';
    end

    if isempty(strCell)
        str = '';
    else
        if isnumeric(strCell) || islogical(strCell)
            % convert numeric vectors to strings
            strCell = arrayfun(@num2str, strCell, 'UniformOutput', false);
        end

        str = cellfun(@(str) [str join], strCell, ...
            'UniformOutput', false);
        str = [str{:}]; 
        str = str(1:end-length(join));
    end
end

Contact us