cosmo show progress

function progress_line=cosmo_show_progress(clock_start, ratio_done, msg, prev_progress_line)
% Shows a progress bar, and time elapsed and expected to complete.
%
% progress_line=cosmo_show_progress(clock_start, progress[, msg[, prev_progress_line]])
%
% Inputs:
%   clock_start         The time the task started (from clock()).
%   ratio_done          0 <= ratio_done <= 1, where 0 means nothing
%                       completed and 1 means fully completed.
%   msg                 String with a message to be shown next to the
%                       progress bar (optional).
%   prev_progress_line  The output from the previous call to this
%                       function, if applicable (optional). If provided
%                       then invoking this function prefixes the output
%                       with numel(prev_progress_msg) backspace characters,
%                       which deletes the output from the previous call
%                       from the console. In other words, this allows for
%                       showing a progress message at a fixed location in
%                       the console window.
%
% Output:
%   progress_line       String indicating current progress using a bar,
%                       with time elapsed and expected time to complete
%                       (using linear extrapolation).
%
% Notes:
%   - As a side effect of this function, progress_msg is written to standard
%     out (the console).
%   - The use of prev_progress_line may not work properly if output is
%     written to standard out without using this function.
%
% Example:
%   % this code takes just over 3 seconds to run, and fills a progress bar.
%   prev_msg='';
%   clock_start=clock();
%   for k=1:100
%       pause(.03);
%       status=sprintf('done %.1f%%', k);
%       ratio_done=k/100;
%       prev_msg=cosmo_show_progress(clock_start,ratio_done,status,prev_msg);
%   end
%   % output:
%   > +00:00:03 [####################] -00:00:00  done 100.0%
%
% #   For CoSMoMVPA's copyright information and license terms,   #
% #   see the COPYING file distributed with CoSMoMVPA.           #

    if nargin<4 || isempty(prev_progress_line)
        delete_count=0; % nothing to delete
    elseif ischar(prev_progress_line)
        delete_count=numel(prev_progress_line); % count the characters
    end % if not a string, die ungracefully

    if nargin<3 || isempty(msg)
        msg='';
    end
    if ratio_done<0 || ratio_done>1
        error('illegal progress %d: should be between 0 and 1', ratio_done);
    end

    if ratio_done==0
        ratio_to_do=Inf;
    else
        ratio_to_do=(1-ratio_done)/ratio_done;
    end

    clock_now=clock();
    took=etime(clock_now, clock_start);
    eta=ratio_to_do*took; % 'estimated time of arrival'

    % set number of backspace characters
    delete_str=repmat(sprintf('\b'),1,delete_count);

    % define the bar
    bar_width=20;
    bar_done=round(ratio_done*bar_width);
    bar_eta=bar_width-bar_done;
    bar_str=[repmat('#',1,bar_done) repmat('-',1,bar_eta)];

    % because msg may contain the '%' character (which is not to be
    % interpolated) care is needed to ensure that neither building the
    % progress line nor printing it to standard out applies interpolation.
    progress_line=[sprintf('+%s [%s] -%s  ', secs2str(took), bar_str, ...
                                        secs2str(-eta)),...
                   msg];

    if ratio_done==1
        postfix=sprintf('\n');
    else
        postfix='';
    end

    fprintf('%s%s%s',delete_str,progress_line,postfix);

function [m,d]=moddiv(x,y)
    % helper function that does mod and div together so that m+d*y==x
    m=mod(x,y);
    d=(x-m)/y;

function str=secs2str(secs)
    % helper function that formats the number of seconds as
    % human-readable string

    % make secs positive (calling function should add '+' or '-')
    secs=abs(secs);

    if ~isfinite(secs)
        str='oo'; % attempt to look like 'infinity' symbol
        return
    end

    secs=round(secs); % do not provide sub-second precision

    % compute number of seconds, minutes, hours, and days
    [s,secs]=moddiv(secs,60);
    [m,secs]=moddiv(secs,60);
    [h,d]=moddiv(secs,24);

    % add prefix for day, if secs represents at least one day
    if d>0
        daypf=sprintf('%dd+',d);
    else
        daypf='';
    end

    str=sprintf('%s%02d:%02d:%02d', daypf, h, m, s);