cosmo publish run scripts

function is_ok=cosmo_publish_run_scripts(varargin)
% helper function to publish example scripts (for developers)
%
% cosmo_publish_build_html([force|fn])
%
% Inputs:
%   fn           filename of matlab file to publish (in the examples
%                directory)
%   '-force'     force rebuild, even if an output file is newer than the
%                corresponding output file
%   '-dry'       dry run: do not build any files, but show the output that
%                would be shown.
%   '-o', d      write output in directory d. By default d is
%                'doc/source/_static/publish/' relative to the CoSMoMVPA
%                root directory.
%
% Notes:
%  - if no filename is given, then all files are rebuilt if necessary
%  - this function is intended for developers (to build the website)
%  - whether an output file is out of date is first determined using
%    git: if git shows no changes to the last commit of the input file
%    then it is assumed it is not out of date. If the input file has
%    been changed since the last commit (or never been comitted), then
%    the modification date is used to determine whether it is out of date.
%  - requirements
%    * a Unix-like system
%    * a working installation of git
%    * CoSMoMVPA code present in a git repository
%
% #   For CoSMoMVPA's copyright information and license terms,   #
% #   see the COPYING file distributed with CoSMoMVPA.           #

    % ensure 'publish' function is available
    cosmo_check_external('!publish',true);

    [srcfn_cell,opt]=process_input(varargin{:});
    trgdir=get_output_dir(opt);

    trgext=['.' opt.format];
    summaryfn=['index.' opt.format];

    orig_path=path();
    path_resetter=onCleanup(@()path(orig_path));

    % go to mvpa dir
    orig_pwd=pwd();
    pwd_resetter=onCleanup(@()cd(orig_pwd));
    mvpa_dir=fileparts(mfilename('fullpath'));
    cd(mvpa_dir);

    if ~isdir(trgdir) && ~opt.dryrun;
        mkdir_recursively(trgdir);
    end

    nsrc=numel(srcfn_cell);
    outputs=cell(nsrc,1);

    total_time_took=0;
    output_pos=0;
    is_ok=true;

    for k=1:nsrc
        srcfn=srcfn_cell{k};
        [srcpth,srcnm]=fileparts(srcfn);

        if k==1
            addpath(srcpth);
        end

        trgfn=fullfile(trgdir,[srcnm trgext]);

        [needs_update,build_msg]=target_needs_update(srcfn,trgfn);
        if opt.force
            build_msg=sprintf('update forced: %s',srcfn);
        end

        fprintf(build_msg);

        do_update=needs_update || opt.force;

        if do_update
            fprintf('\n   building ... ');
            clock_start=clock();

            if opt.dryrun
                fprintf('<dry run>');
                is_built=true;
            else
                is_built=publish_helper(srcfn,trgfn);
            end

            clock_end=clock();
            time_took=etime(clock_end,clock_start);
            total_time_took=total_time_took+time_took;

            if is_built
                outcome_msg=' done';
            else
                outcome_msg=' !! failed';
                is_ok=false;
            end

            status_msg=sprintf('%s (%.1f sec)', outcome_msg,time_took);
        else
            status_msg='';
        end

        fprintf('%s\n', status_msg);

        output_pos=output_pos+1;
        outputs{output_pos}=srcnm;
    end

    fprintf('Processed %d files (%.1f sec)\n',output_pos,total_time_took);

    outputfn=fullfile(trgdir, summaryfn);

    if ~opt.dryrun
        write_index(outputfn,opt)
    end

function write_index(index_outputfn,opt)
    [outputdir,outputnm]=fileparts(index_outputfn);
    inputdir=fullfile(get_root_dir(),'examples');

    d=dir(fullfile(inputdir,'*.m'));


    fid=fopen(index_outputfn,'w');
    cleaner3=onCleanup(@()fclose(fid));
    fprintf(fid,['<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"'...
            '>\n']);
    fprintf(fid,['<HTML><HEAD><TITLE>Index of matlab outputs'...
                    '</TITLE></HEAD>\n<BODY>Matlab output<UL>\n']);

    for k=1:numel(d)
        [unused,nm]=fileparts(d(k).name);
        outputfn=sprintf('%s.%s',nm,opt.format);
        if exist(fullfile(outputdir,outputfn),'file')
            fprintf(fid,'<LI><A HREF="%s">%s</A></LI>\n',outputfn,nm);
        end
    end

    fprintf(fid,['</UL>Back to <A HREF="../../%s.html">index'...
                    '</A>.</BODY></HTML>\n'],outputnm);

    fprintf('Index written to %s\n', index_outputfn);


function trgdir=get_output_dir(opt)
    trgdir=opt.output_dir;
    if isempty(trgdir)
        trgdir=fullfile(get_root_dir(),'doc/source/_static/publish/');
    end

function d=get_root_dir()
    d=fileparts(fileparts(mfilename('fullpath')));

function is_built=publish_helper(srcfn,trgfn)
    is_built=false;
    try
        publish_wrapper(srcfn,trgfn);
        is_built=true;
    catch
        me=lasterror();

        if exist(trgfn,'file')
            delete(trgfn);
        end

        msg=sprintf('Unable to build %s: %s\n',srcfn,...
                                        me.message);

        s=me.stack;
        for j=1:numel(s)
            msg=sprintf('%s\n  %s:%s', msg, s(j).file, s(j).line);
        end

        cosmo_warning('%s',msg);
    end

function publish_wrapper(srcfn,trgfn)
    [srcdir,srcnm]=fileparts(srcfn);
    trgdir=fileparts(trgfn);

    orig_pwd=pwd();
    pwd_resetter=onCleanup(@()cd(orig_pwd));

    orig_fig_handles=findobj('Type','figure');
    fig_cleaner=onCleanup(@()close(setdiff(findobj('Type','figure'),...
                                                    orig_fig_handles)));


    addpath(srcdir);
    cd(trgdir);

    if cosmo_wtf('is_matlab')
        args={struct('outputDir',trgdir,'catchError',false)};
    else
        args={'format','html','imageFormat','jpg'};
    end

    publish(srcnm,args{:});



function [srcfn_cell,opt]=process_input(varargin)
    % set defaults
    srcpat='';

    opt=struct();
    opt.force=false;
    opt.dryrun=false;
    opt.format='html'; % currently not changeable
    opt.output_dir='';

    % process arguments
    n=numel(varargin);

    k=0;
    while k<n
        k=k+1;
        arg=varargin{k};
        if ischar(arg)
            if strcmp(arg,'-force')
                opt.force=true;
            elseif strcmp(arg,'-dry')
                opt.dryrun=true;
            elseif strcmp(arg,'-o')
                if k==n
                    error('Missing argument after %s',arg);
                end
                k=k+1;
                opt.output_dir=varargin{k};

            elseif ~isempty(srcpat)
                error('multiple inputs found, this is not supported');
            else
                srcpat=arg;
            end
        else
            error('Illegal argument at position %d - expected string', k);
        end
    end

    srcfn_cell=get_srcfn_cell(srcpat);

function srcfn_cell=get_srcfn_cell(srcpat)
    default_pattern={'run_*','demo_*'};

    if isdir(srcpat)
        p=srcpat;
        nms=default_pattern;
        e='.m';
    else
        [p,nm,e]=fileparts(srcpat);
        if isempty(p)
            p=fullfile(get_root_dir(),'examples');
        end

        if isempty(nm)
            nms=default_pattern;
        else
            nms={nm};
        end

        if isempty(e)
            e='.m';
        end
    end

    ds=cellfun(@(nm)dir(fullfile(p,[nm e])),nms,'UniformOutput',false);
    d=cat(1,ds{:});
    n=numel(d);
    if n==0
        error('No input files found');
    end
    srcfn_cell=cellfun(@(fn)fullfile(p,fn),{d.name},'UniformOutput',false);



function [tf,msg]=target_needs_update(srcfn,trgfn)
    % helper function to see if html is out of date
    [unused,root,srcext]=fileparts(srcfn);
    [unused,root_alt,trgext]=fileparts(trgfn);
    assert(isequal(root,root_alt));

    srcname=[root,srcext];
    trgname=[root,trgext];
    t_trg=time_last_changed(trgfn);
    if isnan(t_trg)
        % does not exist, so needs update
        tf=true;
        msg=sprintf('not found: %s', trgname);
        return;
    end

    if is_in_staging(srcfn) || is_untracked(srcfn)
        % changes since last commit, see when changes were made
        t_src=time_last_changed(srcfn);
        tf=isnan(t_src) || t_trg<t_src;
        msg=sprintf('modified: %s', srcname);
    else
        % no changes since last commit, see when last commit was made
        t_src=time_last_commit(srcfn);
        tf=isnan(t_src) || t_trg<t_src;
        msg=sprintf('recent commit: %s',srcname);
    end

    if ~tf
        msg=sprintf('up to date: %s',trgname);
    end

function t=time_last_changed(fn)
    d=dir(fn);
    if isempty(d)
        t=NaN;
        return
    end
    assert(numel(d)==1);
    t=(d.datenum-datenum(1970,1,1))*86400;

function r=run_git(args)
    prefix='export TERM=ansi; git ';
    cmd=[prefix args];

    [e,r]=unix(cmd);

    if e
        fprintf(2,'Unable to run git, or an error was produced\n');
        error(r);
    end

function tf=is_untracked(srcfn)
    untracked=run_git('ls-files . --exclude-standard --others');
    tf=cosmo_match({srcfn},untracked);


function t=time_last_commit(srcfn)
    cmd=sprintf('log -n 1 --pretty=format:%%ct -- %s',srcfn);
    r=run_git(cmd);

    t=str2double(regexp(r,'(\d*)','match','once'));

    if isempty(t)
        t=NaN;
    end
    assert(numel(t)==1);

function tf=is_in_staging(fn)
    in_staging_str=run_git('diff HEAD  --name-only | xargs basename');
    in_staging=cosmo_strsplit(in_staging_str,'\n');

    basefn=cosmo_strsplit(fn,filesep,-1);
    tf=cosmo_match({basefn},in_staging);

function mkdir_recursively(trgdir)
    parent=fileparts(trgdir);
    if ~isdir(parent)
        mkdir_recursively(parent);
    end
    mkdir(trgdir);