cosmo structjoin

function s = cosmo_structjoin(varargin)
    % joins values in structs or key-value pairs
    %
    % s=cosmo_structjoin(arg1, arg2, ...)
    %
    % Inputs:
    %   arg{X}      Any of the following:
    %               - if a cell, then it should contain structs or key-value
    %                 pairs
    %               - if a struct, then value=arg{X}.(key) for each key in
    %                 fieldnames(arg{X}) is stored as s.(key)=value.
    %                 In this case, it is required that the struct is of size
    %                 1x1.
    %               - if a string, then it is stored as s.(arg{X})=arg{X+1}
    %
    % Returns:
    %   s           Struct with fieldnames and their associated values
    %               as key-value pairs. Values in the input are stored from
    %               left-to-right.
    %
    % Example:
    %     x=cosmo_structjoin('a',{1;2},'b',{3,4});
    %     cosmo_disp(x);
    %     %|| .a
    %     %||   { [ 1 ]
    %     %||     [ 2 ] }
    %     %|| .b
    %     %||   { [ 3 ]  [ 4 ] }
    %     y=cosmo_structjoin(x,'a',66,'x',x,{'c','hello'});
    %     cosmo_disp(y);
    %     %|| .a
    %     %||   [ 66 ]
    %     %|| .b
    %     %||   { [ 3 ]  [ 4 ] }
    %     %|| .x
    %     %||   .a
    %     %||     { [ 1 ]
    %     %||       [ 2 ] }
    %     %||   .b
    %     %||     { [ 3 ]  [ 4 ] }
    %     %|| .c
    %     %||   'hello'
    %
    %     % simulate a function definition function out=f(varargin)
    %     % to illustrate overriding default values
    %     varargin={'radius',2,'nsamples',12};
    %     defaults=struct();
    %     defaults.radius=10;
    %     defaults.nfeatures=2;
    %     params=cosmo_structjoin(defaults,varargin);
    %     cosmo_disp(params);
    %     %|| .radius
    %     %||   [ 2 ]
    %     %|| .nfeatures
    %     %||   [ 2 ]
    %     %|| .nsamples
    %     %||   [ 12 ]
    %
    %     % illustrate overriding values in 'sub-structs' (structs as values in
    %     % other structs)
    %     v=struct();
    %     v.a.foo={1,2};
    %     v.b=3;
    %     w=struct();
    %     w.a.bar=[2,3];
    %     w.c=4;
    %     j=cosmo_structjoin(v,w);
    %     cosmo_disp(j)
    %     %|| .a
    %     %||   .foo
    %     %||     { [ 1 ]  [ 2 ] }
    %     %||   .bar
    %     %||     [ 2         3 ]
    %     %|| .b
    %     %||   [ 3 ]
    %     %|| .c
    %     %||   [ 4 ]
    %
    %
    % Notes:
    %  - this function can be used to parse input arguments (including
    %    varargin in a "'key1',value1,'key2',value2,..." fashion)
    %
    % #   For CoSMoMVPA's copyright information and license terms,   #
    % #   see the COPYING file distributed with CoSMoMVPA.           #

    % use a wrapper so that recursive calls can be forwarded to the wrapper
    % without using mfilename
    s = structjoin_wrapper(varargin{:});

function s = structjoin_wrapper(varargin)

    s = struct(); % output
    n = numel(varargin);

    k = 0;
    while k < n
        % go over all input arguments
        k = k + 1;
        v = varargin{k}; % k-th argument

        if iscell(v)
            if isempty(v)
                continue
            end

            % use recursion
            v = structjoin_wrapper(v{:});
        end

        if isstruct(v)
            if numel(v) ~= 1
                error(['only singleton structs (of size 1x1) '...
                       'are supported']);
            end

            % overwrite any values in s
            fns = fieldnames(v);

            for j = 1:numel(fns)
                fn = fns{j};
                v_fn = v.(fn);

                if isstruct(v_fn) && isfield(s, fn) && isstruct(s.(fn))
                    s.(fn) = structjoin_wrapper(s.(fn), v_fn);
                else
                    s.(fn) = v_fn;
                end
            end
        elseif ischar(v)
            % <key>, <value> pair
            if k + 1 > n
                % cannot be last argument
                error('Missing argument after key ''%s''', v);
            end

            % move forward to next argument and get value
            k = k + 1;
            vv = varargin{k};

            if isstruct(vv) && isfield(s, v) && isstruct(s.(v))
                s.(v) = structjoin_wrapper(s.(v), vv);
            else
                s.(v) = vv;
            end
        else
            error(['Illegal input at position %d: expected cell, struct, ', ...
                   'or string'], k);
        end
    end