cosmo config skl

function [config, fn] = cosmo_config(fn, config)
    % return a struct with configuration settings, or store such settings
    %
    % Usages:
    % - get the configuration (either default, or what is in '.cosmomvpa.cfg'):
    %   >> config=cosmo_config();
    %
    % - read configuration from a specified file
    %   >> config=cosmo_config(fn)
    %
    % - store configuration in a file
    %   >> cosmo_config(fn, to_store)
    %
    % - related function (in CoSMoMVPA's 'examples/' directory):
    %   >> cosmo_wizard_set_config
    %
    % Inputs:
    %   fn          optional filename of a configuration file.
    %               This can either be a path of a file, or (on Unix platforms)
    %               a filename in the user's home directory.
    %               If fn is omitted, then it defaults to '.cosmomvpa.cfg'
    %               and will read the configuration from that file if it
    %               exists in one of the aforementioned locations; see the
    %               notes below for details.
    %   to_store    optional struct with configuration to store.
    %               If omitted then the configuration is not stored.
    %
    % Returns:
    %   config      Struct with configurations
    %
    % Notes:
    %  - the rationale for this function is to keep the example code fixed
    %    (that is, without any paths hard-coded) and still allow each
    %    user to store the example data in a directory of their choice.
    %
    %  - the format for a configuration file is of the form <key>=<value>,
    %    where <key> cannot contain the '=' character and <value> cannot start
    %    or end with a white-space character.

    %  - if the .cosmomvpa.cfg file is in the same directory as this file
    %   (presently, 'mvpa/'), it will be found there as well.
    %
    %  - an example configuration file (of three lines):
    %
    % # path for runnable examples
    % tutorial_data_path=/Users/nick/organized/_datasets/CoSMoMVPA/tutorial_data
    % output_data_path=/Users/nick/organized/_datasets/CoSMoMVPA/tutorial_data
    %
    %  - If a configuration file with the name '.cosmomvpa.cfg' is stored
    %    in a directory that is in the matlab path, the user path, or (on Unix
    %    platforms) the user's home directory, then calling this function with
    %    no arguments will read that file and return the configuration stored
    %    in it.
    %
    %  - The configuration can also be changed as follows:
    %    >> % get default configuration
    %    >> % If the following command gives an error, do: config=struct()
    %    >> config=cosmo_config();
    %    >>
    %    >> % update settings
    %    >> config.tutorial_data_path='/path/to/some/data';
    %    >> config.output_data_path='/where/i/want/output';
    %    >>
    %    >> % get first directory in user path
    %    >> matlab_path=cosmo_strsplit (userpath,':',1);
    %    >>
    %    >> % set configuration filename
    %    >> config_fn=fullfile(matlab_path,'.cosmomvpa.cfg');
    %    >> cosmo_config(config_fn, config);
    %    >> fprintf('Configuration stored in %s\n', config_fn);
    %    >>
    %    >> % now check they are the same
    %    >> loaded_config=cosmo_config();
    %    >> assert(isequal(loaded_config,config));
    %
    %  - The configuration can be set using cosmo_wizard_set_config in
    %    CoSMoMVPA's 'examples/' directory
    %
    % #   For CoSMoMVPA's copyright information and license terms,   #
    % #   see the COPYING file distributed with CoSMoMVPA.           #

    default_config_fn = '.cosmomvpa.cfg';

    if nargin == 1 && ischar(fn)
        fn = find_config_file(fn);
        config = read_config(fn);
    elseif nargin == 0
        % see if the configuration file can be found
        fn = find_config_file(default_config_fn);
        if isempty(fn)
            config = struct();
        else
            config = read_config(fn);
        end
    elseif nargin == 2 && ischar(fn) && isstruct(config)
        write_config(fn, config);
    else
        error('Illegal input');
    end

    expected_fields = {'tutorial_data_path', 'output_data_path'};
    missing_fields = setdiff(expected_fields, fieldnames(config));

    if numel(missing_fields) > 0
        missing_fields_str = cosmo_strjoin(missing_fields, ''', ''');
        cosmo_warning(['Using %s, fields ''%s'' are missing. This makes '...
                       'it more complicated to run CoSMoMVPA''s '...
                       'exercises and examples.\n'...
                       'To set the configuration, consider running\n\n'...
                       '    cosmo_wizard_set_config\n\n'...
                       'in the CoSMoMVPA ''examples/'' directory'], ...
                      mfilename(), missing_fields_str);
    end

    validate_config(config);

function validate_config(config)
    % simple validation of config

    % poor-man version of OO
    path_exists = struct();
    path_exists.match = @(x)isempty(cosmo_strsplit(x, '_path', -1));
    path_exists.test = @(p) exist(p, 'file');
    path_exists.msg = @(key, p) sprintf('%s: path "%s" not found. ', key, p);

    checks = {path_exists};
    add_msg = sprintf('To set the configuration, run: help %s', mfilename());

    % perform checks on fieldnames present in 'checks'.

    fns = fieldnames(config);
    for k = 1:numel(fns)
        fn = fns{k};
        for j = 1:numel(checks)
            check = checks{j};
            if check.match(fn)
                test_func = check.test;
                value = config.(fn);

                if ~test_func(value)
                    msg_func = check.msg;
                    cosmo_warning('%s\n%s', msg_func(fn, value), add_msg);
                end
            end
        end
    end

function path_fn = find_config_file(fn, raise_)
    % tries to find a configuration file by looking:
    % - for the path of the file
    % - in the matlab path
    % - in the user's home directory (on Unix)
    if nargin < 2
        raise_ = false;
    end

    exist_ = @(fn_) exist(fn_, 'file');

    path_fn = [];

    % simulate 'go-to' statement using a while loop with break at the end
    while true
        % does the file exist 'as is'?
        if exist_(fn)
            path_fn = fn;
            break
        end

        % is it in the mvpa directory?
        parent_dir = fileparts(mfilename('fullpath'));
        m_fn = fullfile(parent_dir, fn);
        if exist_(m_fn)
            path_fn = m_fn;
            break
        end

        % is it in the user path?
        % (not supported on octave)
        if cosmo_wtf('is_matlab')
            upaths = cosmo_strsplit(userpath(), pathsep());

            for k = 1:numel(upaths)
                u_fn = fullfile(upaths{k}, fn);
                if exist_(u_fn)
                    path_fn = u_fn;
                    break
                end
            end
            if ~isempty(path_fn)
                break
            end
        end

        if isunix()
            % is it in the home directory?
            u_fn = fullfile(getenv('HOME'), fn);
            if exist_(u_fn)
                path_fn = u_fn;
                break
            end
        end

        if raise_
            error('Cannot find config file "%s"', fn);
        end

        break
    end

function config = read_config(fn)
    % reads configuration from a file fn

    config = struct(); % space for output
    if isempty(fn)
        error('Input file not specified - cannot open');
    end

    fid = fopen(fn);
    if fid == -1
        error('Unable to open file %s', fn);
    end

    line_number = 0;
    while true
        % read each line
        line = fgetl(fid);
        if ~ischar(line)
            % end of file
            break
        end

        line_number = line_number + 1;

        % ignore empty lines or lines starting with '#'
        if isempty(line) || line(1) == '#'
            continue
        end

        % look for lines of form '<key>=<value>'.
        % white spaces around key or value are ignored.
        m = regexp(line, '(?<key>[^=]+)\s*=\s*(?<value>.*)\s*', 'names');

        if isempty(m)
            cosmo_warning('Skipping non-recognized line "%s"', line);
            continue
        end

        % get key and value
        key = m.key;
        value = m.value;

        verify_no_illegal_char(fn, line_number, [key value]);

        % see if it can be converted to numeric
        value_num = str2double(value);
        if ~isnan(value_num)
            value = value_num;
        end

        config.(key) = value;
    end

    fclose(fid);

function verify_no_illegal_char(fn, line_number, s)
    illegal_chars = sprintf('"''');
    matching = bsxfun(@eq, s, illegal_chars(:));
    if any(matching(:))
        error(['File %s, line %d: found illegal character. '...
               'note that quote characters are not allowed '...
               'for keys or values'], fn, line_number);
    end

function write_config(fn, config)
    % writes the config to a file fn
    % no support for comments or empty lines

    fid = fopen(fn, 'w');

    fns = fieldnames(config);
    for k = 1:numel(fns)
        fn = fns{k};

        v = config.(fn);
        if isnumeric(v)
            % convert numeric to string
            v = sprintf('%d ', v);
        elseif ischar(v)
            % no conversion
        else
            cosmo_warning('Skipping unsupported data type for key "%s"', ...
                          fn);
        end
        fprintf(fid, '%s=%s\n', fn, v);
    end

    fclose(fid);