Generate all of the components of a CWL workflow from input steps.

    file_vs and std_vs are the list of world variables, split into those that
    reference files (and need declaration at each step) and those that don't
    and can be safely passed to each step. This function keeps track of these
    as they're added and updated by steps, passing this information on to
    creation of the CWL files.

def generate(variables, steps, final_outputs):
    """Generate all of the components of a CWL workflow from input steps.

    file_vs and std_vs are the list of world variables, split into those that
    reference files (and need declaration at each step) and those that don't
    and can be safely passed to each step. This function keeps track of these
    as they're added and updated by steps, passing this information on to
    creation of the CWL files.
    """
    file_vs, std_vs = _split_variables([_flatten_nested_input(v) for v in variables])
    parallel_ids = []
    for step in steps:
        if hasattr(step, "workflow"):
            wf_inputs = []
            wf_outputs = []
            wf_steps = []
            for i, wf_step in enumerate(step.workflow):
                inputs, parallel_ids, nested_inputs = _get_step_inputs(wf_step, file_vs, std_vs, parallel_ids, step)
                # flatten inputs that are scattered on entering a sub-workflow
                if step.parallel.endswith("-parallel"):
                    if i == 0:
                        inputs = [_flatten_nested_input(v) for v in inputs]
                        wf_scatter = [get_base_id(v["id"]) for v in inputs]
                    else:
                        inputs = [_flatten_nested_input(v) if get_base_id(v["id"]) in wf_scatter else v
                                  for v in inputs]
                wf_inputs, inputs = _merge_wf_inputs(inputs, wf_inputs, wf_outputs,
                                                     step.internal, wf_step.parallel, nested_inputs)
                outputs, file_vs, std_vs = _get_step_outputs(wf_step, wf_step.outputs, file_vs, std_vs)
                wf_steps.append(("step", wf_step.name, wf_step.parallel, inputs,
                                 outputs, wf_step.image, wf_step.programs, wf_step.disk, wf_step.cores,
                                 wf_step.no_files))
                parallel_ids = _find_split_vs(outputs, wf_step.parallel)
                wf_outputs = _merge_wf_outputs(outputs, wf_outputs, wf_step.parallel)
            yield "wf_start", wf_inputs
            for wf_step in wf_steps:
                yield wf_step
            wf_outputs = [v for v in wf_outputs
                          if v["id"] not in set(["%s" % _get_string_vid(x) for x in step.internal])]
            yield "upload", wf_outputs
            wf_outputs, file_vs, std_vs = _get_step_outputs(step, wf_outputs, file_vs, std_vs)
            yield "wf_finish", step.name, step.parallel, wf_inputs, wf_outputs, wf_scatter
            file_vs = _extract_from_subworkflow(file_vs, step)
            std_vs = _extract_from_subworkflow(std_vs, step)
        elif hasattr(step, "expression"):
            inputs, parallel_ids, nested_inputs = _get_step_inputs(step, file_vs, std_vs, parallel_ids)
            outputs, file_vs, std_vs = _get_step_outputs(step, step.outputs, file_vs, std_vs)
            parallel_ids = _find_split_vs(outputs, step.parallel)
            yield ("expressiontool", step.name, inputs, outputs, step.expression, step.parallel)
        else:
            inputs, parallel_ids, nested_inputs = _get_step_inputs(step, file_vs, std_vs, parallel_ids)
            outputs, file_vs, std_vs = _get_step_outputs(step, step.outputs, file_vs, std_vs)
            parallel_ids = _find_split_vs(outputs, step.parallel)
            yield ("step", step.name, step.parallel, inputs, outputs, step.image, step.programs,
                   step.disk, step.cores, step.no_files)
    yield "upload", [_get_upload_output(x, file_vs) for x in final_outputs]