Parse and fix configuration:
        - processed file should end up being same as input
        - pipelines should contain CLI commands to run
        - add missing sections

    :param config: raw configuration object
    :type config: dict
    :return: configuration ready for `diet()`
    :rtype: dict

def parse_configuration(config):
    '''
    Parse and fix configuration:
        - processed file should end up being same as input
        - pipelines should contain CLI commands to run
        - add missing sections

    :param config: raw configuration object
    :type config: dict
    :return: configuration ready for `diet()`
    :rtype: dict
    '''
    new_config = copy.deepcopy(config)

    required_parts = ('commands', 'parameters', 'pipelines')
    for part in required_parts:
        new_config.setdefault(part, {})

    # Parse only if it hasn't been parsed yet so it is safe to run it more than
    # once.
    if not new_config.get('parsed'):
        try:
            # Always end up with input file. If app outputs to a different one,
            # then replace old one with it
            for prog in new_config['parameters']:
                if '{output_file}' in new_config['parameters'][prog]:
                    new_config['parameters'][prog] += " && mv '{output_file}' '{file}'"

            # Build pipelines
            for label, raw_pipeline in new_config['pipelines'].items():
                commands = []
                for app in raw_pipeline:
                    full_command = " ".join([new_config['commands'][app],
                                             new_config['parameters'][app]])
                    commands.append(full_command)
                new_config['pipelines'][label] = " && ".join(commands)
        except KeyError as e:
            error_msg = "Missing key(s) in configuration: {0}.".format(
                ",".join(e.args))
            raise ConfigurationErrorDietException(error_msg)

        new_config['parsed'] = True

    return new_config