def Func(arg_0):
        """Checks whether the channels to each process have been closed.
        """

        with open(arg_0.log_file) as fh:

            for arg_1 in fh:

                # Exit barrier update after session abort signal
                if "Session aborted" in arg_1:
                    return

                if "<<< barrier arrive" in arg_1:
                    # Retrieve process name from string
                    arg_2 = re.match(".*process: (.*)\)", arg_1)
                    if arg_2:
                        arg_3 = arg_2.group(1)
                        # Updates process channel to complete
                        if arg_3 in arg_0.processes:
                            arg_0.processes[arg_3]["barrier"] = "C"


def Func(arg_0):
        """Method used to retrieve the contents of a log file into a list.

        Parameters
        ----------
        path

        Returns
        -------
        list or None
            Contents of the provided file, each line as a list entry
        """

        if not os.path.exists(arg_0):
            return None

        with open(arg_0) as fh:
            return fh.readlines()


def Func(arg_0, arg_1, arg_2):
        """Assess whether the cpu load or memory usage is above the allocation

        Parameters
        ----------
        process : str
            Process name
        vals : vals
            List of trace information for each tag of that process

        Returns
        -------
        cpu_warnings : dict
            Keys are tags and values are the excessive cpu load
        mem_warnings : dict
            Keys are tags and values are the excessive rss
        """

        arg_3 = {}
        arg_4 = {}

        for arg_5 in arg_2:
            try:
                arg_6 = float(arg_5["cpus"]) * 100
                arg_7 = float(arg_5["%cpu"].replace(",", ".").replace("%", ""))

                if arg_6 * 0.9 > arg_7 > arg_6 * 1.10:
                    arg_3[arg_5["tag"]] = {
                        "expected":  arg_6,
                        "value": arg_7
                    }
            except (ValueError, KeyError):
                pass

            try:
                arg_8 = arg_0._size_coverter(arg_5["rss"])
                arg_9 = arg_0._size_coverter(arg_5["memory"])

                if arg_8 > arg_9 * 1.10:
                    arg_4[arg_5["tag"]] = {
                        "expected": arg_9,
                        "value": arg_8
                    }
            except (ValueError, KeyError):
                pass

        return arg_3, arg_4


def Func(arg_0):
        """Updates the process stats with the information from the processes

        This method is called at the end of each static parsing of the nextflow
        trace file. It re-populates the :attr:`process_stats` dictionary
        with the new stat metrics.
        """

        arg_1 = ["COMPLETED", "CACHED"]

        for arg_2, arg_3 in arg_0.trace_info.items():

            # Update submission status of tags for each process
            arg_3 = arg_0._update_tag_status(arg_2, arg_3)

            # Update process resources
            arg_0._update_process_resources(arg_2, arg_3)

            arg_0.process_stats[arg_2] = {}

            arg_5 = arg_0.process_stats[arg_2]

            # Get number of completed samples
            arg_5["completed"] = "{}".format(
                len([x for x in arg_3 if x["status"] in arg_1]))

            # Get average time
            try:
                arg_6 = [arg_0._hms(x["realtime"]) for x in arg_3]
                arg_7 = round(sum(arg_6) / len(arg_6), 1)
                arg_8 = strftime('%H:%M:%S', gmtime(arg_7))
                arg_5["realtime"] = arg_8
            # When the realtime column is not present
            except KeyError:
                arg_5["realtime"] = "-"

            # Get cumulative cpu/hours
            try:
                arg_9 = [arg_0._cpu_load_parser(
                    x["cpus"], x["%cpu"], x["realtime"]) for x in arg_3]
                arg_5["cpuhour"] = round(sum(arg_9), 2)
            # When the realtime, cpus or %cpus column are not present
            except KeyError:
                arg_5["cpuhour"] = "-"

            # Assess resource warnings
            arg_5["cpu_warnings"], arg_5["mem_warnings"] = \
                arg_0._assess_resource_warnings(arg_2, arg_3)

            # Get maximum memory
            try:
                arg_10 = [arg_0._size_coverter(x["rss"]) for x in arg_3
                              if x["rss"] != "-"]
                if arg_10:
                    arg_11 = round(max(arg_10))
                    arg_12 = arg_0._size_compress(arg_11)
                else:
                    arg_12 = "-"
                arg_5["maxmem"] = arg_12
            except KeyError:
                arg_5["maxmem"] = "-"

            # Get read size
            try:
                arg_13 = [arg_0._size_coverter(x["rchar"]) for x in arg_3
                                if x["rchar"] != "-"]
                if arg_13:
                    arg_14 = round(sum(arg_13) / len(arg_13))
                    arg_15 = arg_0._size_compress(arg_14)
                else:
                    arg_15 = "-"
            except KeyError:
                arg_15 = "-"
            arg_5["avgread"] = arg_15

            # Get write size
            try:
                arg_16 = [arg_0._size_coverter(x["wchar"]) for x in arg_3
                                if x["wchar"] != "-"]
                if arg_16:
                    arg_17 = round(sum(arg_16) / len(arg_16))
                    arg_18 = arg_0._size_compress(arg_17)
                else:
                    arg_18 = "-"
            except KeyError:
                arg_18 = "-"
            arg_5["avgwrite"] = arg_18


def Func(arg_0):
        """Method that parses the nextflow log file once and updates the
        submitted number of samples for each process
        """

        # Check the timestamp of the log file. Only proceed with the parsing
        # if it changed from the previous time.
        arg_1 = os.path.getsize(arg_0.log_file)
        arg_0.log_retry = 0
        if arg_1 and arg_1 == arg_0.log_sizestamp:
            return
        else:
            logger.debug("Updating log size stamp to: {}".format(arg_1))
            arg_0.log_sizestamp = arg_1

        # Regular expression to catch four groups:
        # 1. Start timestamp
        # 2. Work directory hash
        # 3. Process name
        # 4. Tag name
        arg_4 = ".* (.*) \[.*\].*\[(.*)\].*process > (.*) \((.*)\).*"

        with open(arg_0.log_file) as fh:

            for arg_5 in fh:
                if "Submitted process >" in arg_5 or \
                        "Re-submitted process >" in arg_5 or \
                        "Cached process >" in arg_5:
                    arg_6 = re.match(arg_4, arg_5)
                    if not arg_6:
                        continue

                    arg_7 = arg_6.group(1)
                    arg_8 = arg_6.group(2)
                    arg_9 = arg_6.group(3)
                    arg_10 = arg_6.group(4)

                    # Skip if this line has already been parsed
                    if arg_7 + arg_10 not in arg_0.stored_log_ids:
                        arg_0.stored_log_ids.append(arg_7 + arg_10)
                    else:
                        continue

                    # For first time processes
                    if arg_9 not in arg_0.processes:
                        continue
                    arg_11 = arg_0.processes[arg_9]

                    # Skip is process/tag combination has finished or is retrying
                    if arg_10 in list(arg_11["finished"]) + list(arg_11["retry"]):
                        continue

                    # Update failed process/tags when they have been re-submitted
                    if arg_10 in list(arg_11["failed"]) and \
                            "Re-submitted process >" in arg_5:
                        arg_11["retry"].add(arg_10)
                        arg_0.send = True
                        continue

                    # Set process barrier to running. Check for barrier status
                    # are performed at the end of the trace parsing in the
                    # _update_barrier_status method.
                    arg_11["barrier"] = "R"
                    if arg_10 not in arg_11["submitted"]:
                        arg_11["submitted"].add(arg_10)
                        # Update the process_tags attribute with the new tag.
                        # Update only when the tag does not exist. This may rarely
                        # occur when the tag is parsed first in the trace file
                        if arg_10 not in arg_0.process_tags[arg_9]:
                            arg_0.process_tags[arg_9][arg_10] = {
                                "workdir": arg_0._expand_path(arg_8),
                                "start": arg_7
                            }
                            arg_0.send = True
                        # When the tag is filled in the trace file parsing,
                        # the timestamp may not be present in the trace. In
                        # those cases, fill that information here.
                        elif not arg_0.process_tags[arg_9][arg_10]["start"]:
                            arg_0.process_tags[arg_9][arg_10]["start"] = arg_7
                            arg_0.send = True

        arg_0._update_pipeline_status()


def Func(arg_0, arg_1):
        """Provides curses scroll functionality.
        """

        if arg_1 == "up" and arg_0.top_line != 0:
            arg_0.top_line -= 1
        elif arg_1 == "down" and \
                arg_0.screen.getmaxyx()[0] + arg_0.top_line\
                <= arg_0.content_lines + 3:
            arg_0.top_line += 1


def Func(arg_0, arg_1):
        """Provides curses horizontal padding"""

        if arg_1 == "left" and arg_0.padding != 0:
            arg_0.padding -= 1

        if arg_1 == "right" and \
                arg_0.screen.getmaxyx()[1] + arg_0.padding < arg_0.max_width:
            arg_0.padding += 1


def Func(arg_0):
        """Prepares the first batch of information, containing static
        information such as the pipeline file, and configuration files

        Returns
        -------
        dict
            Dict with the static information for the first POST request
        """

        arg_1 = {}

        with open(join(arg_0.workdir, arg_0.pipeline_name)) as fh:
            arg_1["pipelineFile"] = fh.readlines()

        arg_2 = join(arg_0.workdir, "nextflow.config")
        if os.path.exists(arg_2):
            with open(arg_2) as fh:
                arg_1["configFile"] = fh.readlines()

        # Check for specific flowcraft configurations files
        arg_3 = {
            "params.config": "paramsFile",
            "resources.config": "resourcesFile",
            "containers.config": "containersFile",
            "user.config": "userFile",
        }
        for arg_4, arg_5 in arg_3.items():
            arg_6 = join(arg_0.workdir, arg_4)
            if os.path.exists(arg_6):
                with open(arg_6) as fh:
                    arg_1[arg_5] = fh.readlines()

        return arg_1


def Func(arg_0):
        """Function that opens the dotfile named .treeDag.json in the current
        working directory

        Returns
        -------
        Returns a dictionary with the dag object to be used in the post
        instance available through the method _establish_connection

        """
        try:
            arg_1 = open(os.path.join(arg_0.workdir, ".treeDag.json"))
            arg_2 = json.load(arg_1)
        except (FileNotFoundError, json.decoder.JSONDecodeError):
            logger.warning(colored_print(
                "WARNING: dotfile named .treeDag.json not found or corrupted",
                "red_bold"))
            arg_2 = {}

        return arg_2


def Func(arg_0):
    """Gets the nextflow file path from the nextflow log file. It searches for
    the nextflow run command throughout the file.

    Parameters
    ----------
    log_file : str
        Path for the .nextflow.log file

    Returns
    -------
    str
        Path for the nextflow file
    """

    with open(arg_0) as fh:
        # Searches for the first occurence of the nextflow pipeline
        # file name in the .nextflow.log file
        while 1:
            arg_1 = fh.readline()
            if not arg_1:
                # file is empty
                raise eh.LogError("Nextflow command path could not be found - Is "
                                 ".nextflow.log empty?")
            try:
                # Regex supports absolute paths and relative paths
                arg_2 = re.match(".*\s(.*.nf).*", arg_1) \
                    .group(1)
                return arg_2
            except AttributeError:
                continue


def Func(arg_0, arg_1, arg_2):
    """
    Parses a nextflow trace file, searches for processes with a specific tag
    and sends a JSON report with the relevant information

    The expected fields for the trace file are::

        0. task_id
        1. process
        2. tag
        3. status
        4. exit code
        5. start timestamp
        6. container
        7. cpus
        8. duration
        9. realtime
        10. queue
        11. cpu percentage
        12. memory percentage
        13. real memory size of the process
        14. virtual memory size of the process

    Parameters
    ----------
    trace_file : str
        Path to the nextflow trace file
    """

    # Determine the path of the stored JSON for the sample_id
    arg_3 = ".stats.json"
    arg_4 = join(arg_2, arg_0 + arg_3)
    arg_5 = join(arg_2, arg_1)

    logger.info("Starting pipeline status routine")

    logger.debug("Checking for previous pipeline status data")
    arg_6 = get_previous_stats(arg_4)
    logger.info("Stats JSON object set to : {}".format(arg_6))

    # Search for this substring in the tags field. Only lines with this
    # tag will be processed for the reports
    arg_7 = " getStats"
    logger.debug("Tag variable set to: {}".format(arg_7))

    logger.info("Starting parsing of trace file: {}".format(arg_5))
    with open(arg_5) as fh:

        arg_8 = next(fh).strip().split()
        logger.debug("Header set to: {}".format(arg_8))

        for arg_9 in fh:
            arg_10 = arg_9.strip().split("\t")
            # Check if tag substring is in the tag field of the nextflow trace
            if arg_7 in arg_10[2] and arg_10[3] == "COMPLETED":
                logger.debug(
                    "Parsing trace line with COMPLETED status: {}".format(
                        arg_9))
                arg_11 = get_json_info(arg_10, arg_8)

                arg_6[arg_10[0]] = arg_11
            else:
                logger.debug(
                    "Ignoring trace line without COMPLETED status"
                    " or stats specific tag: {}".format(
                        arg_9))

    with open(join(arg_4), "w") as fh, open(".report.json", "w") as rfh:
        fh.write(json.dumps(arg_6, separators=(",", ":")))
        rfh.write(json.dumps(arg_6, separators=(",", ":")))


def Func(arg_0):
    """Brews a given list of processes according to the recipe

    Parameters
    ----------
    args : argparse.Namespace
        The arguments passed through argparser that will be used to check the
        the recipe, tasks and brew the process

    Returns
    -------
    str
        The final pipeline string, ready for the engine.
    list
        List of process strings.
    """

    # Create recipe class instance
    arg_1 = Innuendo()

    if not arg_0.tasks:
        arg_2 = " ".join(
            arg_1.process_descriptions.keys())
    else:
        arg_2 = arg_0.tasks

    # Validate the provided pipeline processes
    arg_3 = arg_1.validate_pipeline(arg_2)
    if not arg_3:
        sys.exit(1)
    # Get the final pipeline string
    arg_4 = arg_1.run_auto_pipeline(arg_2)

    return arg_4


def Func(arg_0=False):
    """Method that iterates over all available recipes and prints their
    information to the standard output

    Parameters
    ----------
    full : bool
        If true, it will provide the pipeline string along with the recipe name
    """

    logger.info(colored_print(
        "\n===== L I S T   O F   R E C I P E S =====\n",
        "green_bold"))

    # This will iterate over all modules included in the recipes subpackage
    # It will return the import class and the module name, algon with the
    # correct prefix
    arg_1 = "{}.".format(recipes.__name__)
    for arg_2, arg_3, arg_4 in pkgutil.iter_modules(recipes.__path__, arg_1):

        # Import the current module
        arg_5 = arg_2.find_module(arg_3).load_module(arg_3)

        # Fetch all available classes in module
        arg_6 = [arg_7 for arg_7 in arg_5.__dict__.values() if
                           isinstance(arg_7, type)]

        # Iterate over each Recipe class, and check for a match with the
        # provided recipe name.
        for arg_7 in arg_6:

            arg_8 = arg_7()

            if hasattr(arg_8, "name"):
                logger.info(colored_print("=> {}".format(arg_8.name), "blue_bold"))
                if arg_0:
                    logger.info(colored_print("\t {}".format(arg_8.__doc__), "purple_bold"))
                    logger.info(colored_print("Pipeline string: {}\n".format(arg_8.pipeline_str), "yellow_bold"))

    sys.exit(0)


def Func(arg_0):
        """Validate pipeline string

        Validates the pipeline string by searching for forbidden characters

        Parameters
        ----------
        pipeline_string : str
            STring with the processes provided

        Returns
        -------

        """
        if "(" in arg_0 or ")" in arg_0 or "|" in \
                arg_0:
            logger.error(
                colored_print("Please provide a valid task list!", "red_bold")
            )
            return False

        return True


def Func(arg_0, arg_1, arg_2, arg_3,
                       arg_4,
                       arg_5, arg_6, arg_7):
        """Builds the upstream pipeline of the current process

        Checks for the upstream processes to the current process and
        adds them to the current pipeline fragment if they were provided in
        the process list.

        Parameters
        ----------
        process_descriptions : dict
            Information of processes input, output and if is forkable
        task : str
            Current process
        all_tasks : list
            A list of all provided processes
        task_pipeline : list
            Current pipeline fragment
        count_forks : int
            Current number of forks
        total_tasks : str
            All space separated processes
        forks : list
            Current forks
        Returns
        -------
        list : resulting pipeline fragment
        """
        if arg_2 in arg_1:
            if arg_1[arg_2][1] is not None:
                if len(arg_1[arg_2][1].split("|")) > 1:
                    arg_8 = arg_1[arg_2][1].split("|")

                    # Produces a new pipeline fragment for each forkable
                    #  process
                    for arg_9 in arg_8:
                        if arg_9 in arg_6:
                            arg_5 += 1
                            arg_4.insert(
                                0,
                                arg_1[arg_2][1]
                            )
                            arg_0.define_pipeline_string(
                                arg_1,
                                arg_9,
                                False,
                                True,
                                arg_5,
                                arg_6,
                                arg_7
                            )

                    return arg_4
                else:
                    # Adds the process to the pipeline fragment in case it is
                    # provided in the task list
                    if arg_1[arg_2][1] in arg_6:
                        arg_4.insert(
                            0,
                            arg_1[arg_2][1].split("|")[0]
                        )

                        # Proceeds building upstream until the input for a
                        # process is None
                        arg_0.Func(
                            arg_1,
                            arg_1[arg_2][1].split("|")[0],
                            arg_3,
                            arg_4,
                            arg_5,
                            arg_6,
                            arg_7
                        )
                    else:
                        logger.error(
                            colored_print("{} not in provided protocols as "
                                          "input for {}".format(
                                arg_1[arg_2][1], arg_2), "red_bold"
                            )
                        )

                        sys.exit()

                    return arg_4
            else:
                return arg_4


def Func(arg_0, arg_1, arg_2,
                               arg_3,
                               arg_4, arg_5, arg_6,
                               arg_7):
        """Builds the possible forks and connections between the provided
        processes

        This method loops through all the provided tasks and builds the
        upstream and downstream pipeline if required. It then returns all
        possible forks than need to be merged à posteriori`

        Parameters
        ----------
        process_descriptions : dict
            Information of processes input, output and if is forkable
        tasks : str
            Space separated processes
        check_upstream : bool
            If is to build the upstream pipeline of the current task
        check_downstream : bool
            If is to build the downstream pipeline of the current task
        count_forks : int
            Number of current forks
        total_tasks : str
            All space separated processes
        forks : list
            Current forks

        Returns
        -------
        list : List with all the possible pipeline forks
        """

        arg_8 = arg_2.split()

        for arg_9 in arg_8:
            arg_10 = arg_9.split("=")[0]

            if arg_10 not in arg_1.keys():
                logger.error(
                    colored_print(
                        "{} not in the possible processes".format(arg_10),
                        "red_bold"
                    )
                )

                sys.exit()
            else:
                arg_11 = arg_9.split("=")

                if len(arg_11) > 1:
                    arg_0.process_to_id[arg_11[0]] = arg_11[1]

            # Only uses the process if it is not already in the possible forks
            if not bool([arg_13 for arg_13 in arg_7 if arg_10 in arg_13]) and not bool([arg_14 for arg_14 in arg_7 if arg_1[arg_10][2] in arg_14]):
                arg_15 = []

                if arg_10 in arg_1:

                    if arg_3:
                        arg_15 = arg_0.build_upstream(
                            arg_1,
                            arg_10,
                            arg_8,
                            arg_15,
                            arg_5,
                            arg_6,
                            arg_7
                        )

                    arg_15.append(arg_10)

                    if arg_4:
                        arg_15 = arg_0.build_downstream(
                            arg_1,
                            arg_10,
                            arg_8,
                            arg_15,
                            arg_5,
                            arg_6,
                            arg_7
                        )

                # Adds the pipeline fragment to the list of possible forks
                arg_7.append(list(OrderedDict.fromkeys(arg_15)))

            # Checks for task in fork. Case order of input processes is reversed
            elif bool([arg_14 for arg_14 in arg_7 if arg_1[arg_10][2] in arg_14]):
                for arg_16 in arg_7:
                    if arg_10 not in arg_16:
                        try:
                            arg_17 = arg_16.index(arg_1[arg_10][2])
                            arg_16.insert(arg_17, arg_10)
                        except ValueError:
                            continue

        for arg_18 in range(0, len(arg_7)):
            for arg_19 in range(0, len(arg_7[arg_18])):
                try:
                    if len(arg_7[arg_18][arg_19].split("|")) > 1:
                        arg_7[arg_18][arg_19] = arg_7[arg_18][arg_19].split("|")
                        arg_20 = []
                        for arg_21 in arg_7[arg_18][arg_19]:
                            if arg_21 in arg_6:
                                arg_20.append(arg_21)

                        arg_7[arg_18][arg_19] = arg_20

                except AttributeError as e:
                    continue

        return arg_7


def Func(arg_0, arg_1=None, arg_2=None):
        """ Generates a component string based on the provided parameters and
        directives

        Parameters
        ----------
        component : str
            Component name
        params : dict
            Dictionary with parameter information
        directives : dict
            Dictionary with directives information

        Returns
        -------
        str
            Component string with the parameters and directives, ready for
            parsing by flowcraft engine
        """

        arg_3 = {}

        if arg_2:
            arg_3 = arg_2

        if arg_1:
            arg_3["params"] = arg_1

        if arg_3:
            return "{}={}".format(
                arg_0, json.dumps(arg_3, separators=(",", ":")))
        else:
            return arg_0


def Func(arg_0, arg_1):
        """Public method for parsing abricate output files.

        This method is called at at class instantiation for the provided
        output files. Additional abricate output files can be added using
        this method after the class instantiation.

        Parameters
        ----------
        fls : list
            List of paths to Abricate files

        """

        for arg_2 in arg_1:
            # Make sure paths exists
            if os.path.exists(arg_2):
                arg_0._parser(arg_2)
            else:
                logger.warning("File {} does not exist".format(arg_2))


def Func(arg_0, arg_1):
        """Parser for a single abricate output file.

        This parser will scan a single Abricate output file and populate
        the :py:attr:`Abricate.storage` attribute.

        Parameters
        ----------
        fl : str
            Path to abricate output file

        Notes
        -----
        This method will populate the :py:attr:`Abricate.storage` attribute
        with all compliant lines in the abricate output file. Entries are
        inserted using an arbitrary key that is set by the
        :py:attr:`Abricate._key` attribute.

        """

        with open(arg_1) as fh:

            for arg_2 in fh:
                # Skip header and comment lines
                if arg_2.startswith("#") or arg_2.strip() == "":
                    continue

                arg_3 = arg_2.strip().split("\t")

                try:
                    arg_4 = float(arg_3[8])
                except ValueError:
                    arg_4 = None
                try:
                    arg_5 = float(arg_3[9])
                except ValueError:
                    arg_5 = None

                try:
                    arg_6 = arg_3[11]
                except IndexError:
                    arg_6 = None

                arg_0.storage[arg_0._key] = {
                    "log_file": os.path.basename(arg_1),
                    "infile": arg_3[0],
                    "reference": arg_3[1],
                    "seq_range": (int(arg_3[2]), int(arg_3[3])),
                    "gene": arg_3[4],
                    "accession": arg_6,
                    "database": arg_3[10],
                    "coverage": arg_4,
                    "identity": arg_5
                }

                arg_0._key += 1


def Func(arg_0, arg_1, arg_2=None, arg_3=None,
                    arg_4="and"):
        """General purpose filter iterator.

        This general filter iterator allows the filtering of entries based
        on one or more custom filters. These filters must contain
        an entry of the `storage` attribute, a comparison operator, and the
        test value. For example, to filter out entries with coverage below 80::

            my_filter = ["coverage", ">=", 80]

        Filters should always be provide as a list of lists::

            Func([["coverage", ">=", 80]])
            # or
            my_filters = [["coverage", ">=", 80],
                          ["identity", ">=", 50]]

            Func(my_filters)

        As a convenience, a list of the desired databases can be directly
        specified using the `database` argument, which will only report
        entries for the specified databases::

            Func(my_filters, databases=["plasmidfinder"])

        By default, this method will yield the complete entry record. However,
        the returned filters can be specified using the `fields` option::

            Func(my_filters, fields=["reference", "coverage"])

        Parameters
        ----------
        filters : list
            List of lists with the custom filter. Each list should have three
            elements. (1) the key from the entry to be compared; (2) the
            comparison operator; (3) the test value. Example:
                ``[["identity", ">", 80]]``.
        databases : list
            List of databases that should be reported.
        fields : list
            List of fields from each individual entry that are yielded.
        filter_behavior : str
            options: ``'and'`` ``'or'``
            Sets the behaviour of the filters, if multiple filters have been
            provided. By default it is set to ``'and'``, which means that an
            entry has to pass all filters. It can be set to ``'or'``, in which
            case one one of the filters has to pass.

        yields
        ------
        dic : dict
            Dictionary object containing a :py:attr:`Abricate.storage` entry
            that passed the filters.

        """

        if arg_4 not in ["and", "or"]:
            raise ValueError("Filter behavior must be either 'and' or 'or'")

        for arg_5 in arg_0.storage.values():

            # This attribute will determine whether an entry will be yielded
            # or not
            arg_6 = False

            # Stores the flags with the test results for each filter
            # The results will be either True or False
            arg_7 = []

            # Filter for databases
            if arg_2:
                # Skip entry if not in specified database
                if arg_5["database"] not in arg_2:
                    continue

            # Apply filters
            for arg_8 in arg_1:
                # Get value of current filter
                arg_9 = arg_5[arg_8[0]]
                if not arg_0._test_truth(arg_9, arg_8[1], arg_8[2]):
                    arg_7.append(False)
                else:
                    arg_7.append(True)

            # Test whether the entry will pass based on the test results
            # and the filter behaviour
            if arg_4 == "and":
                if all(arg_7):
                    arg_6 = True
            elif arg_4 == "or":
                if any(arg_7):
                    arg_6 = True

            if arg_6:
                if arg_3:
                    yield dict((arg_10, arg_11) for arg_10, arg_11 in arg_5.items() if arg_10 in arg_3)
                else:
                    yield arg_5


def Func(arg_0):
        """Writes the JSON report to a json file
        """

        arg_1 = arg_0.get_plot_data()
        arg_2 = arg_0.get_table_data()

        arg_3 = {**arg_1, **arg_2}

        with open(".report.json", "w") as json_report:
            json_report.write(json.dumps(arg_3, separators=(",", ":")))


def Func(arg_0, arg_1, arg_2=None):
    """Main executor of the assembly_report template.

    Parameters
    ----------
    sample_id : str
        Sample Identification string.
    assembly_file : str
        Path to assembly file in Fasta format.

    """

    logger.info("Starting assembly report")
    arg_3 = Assembly(arg_1, arg_0)

    logger.info("Retrieving summary statistics for assembly")
    arg_3.get_summary_stats("{}_assembly_report.csv".format(arg_0))

    arg_4 = [len(x) for x in arg_3.contigs.values()]
    arg_5 = {
        "tableRow": [{
            "sample": arg_0,
            "data": [
                {"header": "Contigs",
                 "value": arg_3.summary_info["ncontigs"],
                 "table": "assembly",
                 "columnBar": True},
                {"header": "Assembled BP",
                 "value": arg_3.summary_info["total_len"],
                 "table": "assembly",
                 "columnBar": True},
            ]
        }],
        "plotData": [{
            "sample": arg_0,
            "data": {
                "size_dist": arg_4
            }
        }]
    }

    if arg_2:
        try:
            arg_6 = 2000
            arg_7 = arg_3.get_gc_sliding(arg_6=arg_6)
            arg_8 = \
                arg_3.get_coverage_sliding(arg_2,
                                                  arg_6=arg_6)

            # Get total basepairs based on the individual coverage of each
            # contig bpx
            arg_9 = sum(
                [sum(x) for x in arg_3.contig_coverage.values()]
            )

            # Add data to json report
            arg_5["plotData"][0]["data"]["genomeSliding"] = {
                "gcData": arg_7,
                "covData": arg_8,
                "window": arg_6,
                "xbars": arg_3._get_window_labels(arg_6),
                "assemblyFile": os.path.basename(arg_1)
            }
            arg_5["plotData"][0]["data"]["sparkline"] = arg_9

        except:
            logger.error("Unexpected error creating sliding window data:\\n"
                         "{}".format(traceback.format_exc()))

    # Write json report
    with open(".report.json", "w") as json_report:

        json_report.write(json.dumps(arg_5, separators=(",", ":")))

    with open(".status", "w") as status_fh:
        status_fh.write("pass")


def Func(arg_0, arg_1=None):
        """Generates a CSV report with summary statistics about the assembly

        The calculated statistics are:

            - Number of contigs
            - Average contig size
            - N50
            - Total assembly length
            - Average GC content
            - Amount of missing data

        Parameters
        ----------
        output_csv: str
            Name of the output CSV file.
        """

        arg_2 = []

        arg_0.summary_info["ncontigs"] = len(arg_0.contigs)

        for arg_4, arg_5 in arg_0.contigs.items():

            logger.debug("Processing contig: {}".format(arg_4))

            # Get contig sequence size
            arg_6 = len(arg_5)

            # Add size for average contig size
            arg_2.append(arg_6)

            # Add to total assembly length
            arg_0.summary_info["total_len"] += arg_6

            # Add to average gc
            arg_0.summary_info["avg_gc"].append(
                sum(map(arg_5.count, ["G", "C"])) / arg_6
            )

            # Add to missing data
            arg_0.summary_info["missing_data"] += arg_5.count("N")

        # Get average contig size
        logger.debug("Getting average contig size")
        arg_0.summary_info["avg_contig_size"] = \
            sum(arg_2) / len(arg_2)

        # Get average gc content
        logger.debug("Getting average GC content")
        arg_0.summary_info["avg_gc"] = \
            sum(arg_0.summary_info["avg_gc"]) / len(arg_0.summary_info["avg_gc"])

        # Get N50
        logger.debug("Getting N50")
        arg_7 = 0
        for arg_8 in sorted(arg_2, reverse=True):
            arg_7 += arg_8
            if arg_7 >= arg_0.summary_info["total_len"] / 2:
                arg_0.summary_info["n50"] = arg_8
                break

        if arg_1:
            logger.debug("Writing report to csv")
            # Write summary info to CSV
            with open(arg_1, "w") as fh:
                arg_9 = "{}, {}\\n".format(
                    arg_0.sample, ",".join(
                        [str(x) for x in arg_0.summary_info.values()]))
                fh.write(arg_9)


def Func(arg_0, arg_1):
        """Get proportion of GC from a string

        Parameters
        ----------
        s : str
            Arbitrary string

        Returns
        -------
        x : float
            GC proportion.
        """

        arg_2 = sum(map(arg_0.count, ["c", "g"]))

        return arg_2 / arg_1


def Func(arg_0, arg_1=2000):
        """Calculates a sliding window of the GC content for the assembly

        Returns
        -------
        gc_res : list
            List of GC proportion floats for each data point in the sliding
            window
        """

        arg_2 = []

        # Get complete sequence to calculate sliding window values
        arg_3 = "".join(arg_0.contigs.values()).lower()

        for arg_4 in range(0, len(arg_3), arg_1):

            arg_5 = arg_3[arg_4:arg_4 + arg_1]

            # Get GC proportion
            arg_2.append(round(arg_0._gc_prop(arg_5, len(arg_5)), 2))

        return arg_2


def Func(arg_0, arg_1, arg_2):
    """Main executor of the skesa template.

    Parameters
    ----------
    sample_id : str
        Sample Identification string.
    fastq_pair : list
        Two element list containing the paired FastQ files.
    clear : str
        Can be either 'true' or 'false'. If 'true', the input fastq files will
        be removed at the end of the run, IF they are in the working directory
    """

    logger.info("Starting skesa")

    # Determine output file
    if "_trim." in arg_1[0]:
        arg_0 += "_trim"
    arg_3 = __get_version_skesa()["version"]
    arg_4 = "{}_skesa{}.fasta".format(arg_0, arg_3.replace(".", ""))

    arg_5 = [
        "skesa",
        "--fastq",
        "{},{}".format(arg_1[0], arg_1[1]),
        "--gz",
        "--use_paired_ends",
        "--cores",
        "${task.cpus}"
    ]

    logger.debug("Running Skesa subprocess with command: {}".format(arg_5))

    with open(arg_4, "w") as fh:
        arg_6 = subprocess.Popen(arg_5, arg_7=fh, arg_8=PIPE)
    arg_7, arg_8 = arg_6.communicate()

    # Attempt to decode STDERR output from bytes. If unsuccessful, coerce to
    # string
    try:
        arg_8 = arg_8.decode("utf8")
        arg_7 = arg_7.decode("utf8")
    except (UnicodeDecodeError, AttributeError):
        arg_8 = str(arg_8)
        arg_7 = str(arg_7)

    logger.info("Finished Skesa subprocess with STDOUT:\\n"
                "======================================\\n{}".format(arg_7))
    logger.info("Fished Skesa subprocess with STDERR:\\n"
                "======================================\\n{}".format(arg_8))
    logger.info("Finished Skesa with return code: {}".format(
        arg_6.returncode))

    # Remove input fastq files when clear option is specified.
    # Only remove temporary input when the expected output exists.
    if arg_2 == "true" and os.path.exists(arg_4):
        clean_up(arg_1)

    with open(".status", "w") as fh:
        if arg_6.returncode != 0:
            fh.write("error")
            raise SystemExit(arg_6.returncode)
        else:
            fh.write("pass")


def Func(arg_0, arg_1, arg_2):
    """Writes the report

    Parameters
    ----------
    data1
    data2

    Returns
    -------

    """

    arg_3 = {
        "base_sequence_quality": ">>Per base sequence quality",
        "sequence_quality": ">>Per sequence quality scores",
        "base_gc_content": ">>Per sequence GC content",
        "base_n_content": ">>Per base N content",
        "sequence_length_dist": ">>Sequence Length Distribution",
        "per_base_sequence_content": ">>Per base sequence content"
    }

    arg_4 = {
        "plotData": [{
            "sample": arg_0,
            "data": {
                "base_sequence_quality": {"status": None, "data": []},
                "sequence_quality": {"status": None, "data": []},
                "base_gc_content": {"status": None, "data": []},
                "base_n_content": {"status": None, "data": []},
                "sequence_length_dist": {"status": None, "data": []},
                "per_base_sequence_content": {"status": None, "data": []}
            }
        }]
    }

    for arg_5, arg_6 in arg_3.items():

        if arg_5 == "per_base_sequence_content":
            arg_7 = 1
            arg_8 = 5
        else:
            arg_7 = 1
            arg_8 = 2

        arg_9, arg_10 = _get_quality_stats(arg_1, arg_6,
                                              field_start=arg_7, field_end=arg_8)
        arg_11, arg_12 = _get_quality_stats(arg_2, arg_6,
                                              field_start=arg_7, field_end=arg_8)

        arg_13 = None
        for arg_14 in ["fail", "warn", "pass"]:
            if arg_14 in [arg_10, arg_12]:
                arg_13 = arg_14

        arg_4["plotData"][0]["data"][arg_5]["data"] = [arg_9, arg_11]
        arg_4["plotData"][0]["data"][arg_5]["status"] = arg_13

    return arg_4


def Func(arg_0):
    """Returns the trim index from a ``bool`` list

    Provided with a list of ``bool`` elements (``[False, False, True, True]``),
    this function will assess the index of the list that minimizes the number
    of True elements (biased positions) at the extremities. To do so,
    it will iterate over the boolean list and find an index position where
    there are two consecutive ``False`` elements after a ``True`` element. This
    will be considered as an optimal trim position. For example, in the
    following list::

        [True, True, False, True, True, False, False, False, False, ...]

    The optimal trim index will be the 4th position, since it is the first
    occurrence of a ``True`` element with two False elements after it.

    If the provided ``bool`` list has no ``True`` elements, then the 0 index is
    returned.

    Parameters
    ----------
    biased_list: list
        List of ``bool`` elements, where ``True`` means a biased site.

    Returns
    -------
        x : index position of the biased list for the optimal trim.

    """

    # Return index 0 if there are no biased positions
    if set(arg_0) == {False}:
        return 0

    if set(arg_0[:5]) == {False}:
        return 0

    # Iterate over the biased_list array. Keep the iteration going until
    # we find a biased position with the two following positions unbiased
    # (e.g.: True, False, False).
    # When this condition is verified, return the last biased position
    # index for subsequent trimming.
    for arg_1, arg_2 in enumerate(arg_0):
        if arg_2 and set(arg_0[arg_1+1:arg_1+3]) == {False}:
            return arg_1 + 1

    # If the previous iteration could not find and index to trim, it means
    # that the whole list is basically biased. Return the length of the
    # biased_list
    return len(arg_0)


def Func(arg_0):
    """Assess the optimal trim range for a given FastQC data file.

    This function will parse a single FastQC data file, namely the
    *'Per base sequence content'* category. It will retrieve the A/T and G/C
    content for each nucleotide position in the reads, and check whether the
    G/C and A/T proportions are between 80% and 120%. If they are, that
    nucleotide position is marked as biased for future removal.

    Parameters
    ----------
    data_file: str
        Path to FastQC data file.

    Returns
    -------
    trim_nt: list
        List containing the range with the best trimming positions for the
        corresponding FastQ file. The first element is the 5' end trim index
        and the second element is the 3' end trim index.
    """

    logger.debug("Starting trim range assessment")

    # Target string for nucleotide bias assessment
    arg_1 = ">>Per base sequence content"
    logger.debug("Target string to start nucleotide bias assessment set to "
                 "{}".format(arg_1))
    # This flag will become True when gathering base proportion data
    # from file.
    arg_2 = False

    # This variable will store a boolean array on the biased/unbiased
    # positions. Biased position will be True, while unbiased positions
    # will be False
    arg_3 = []

    with open(arg_0) as fh:

        for arg_4 in fh:
            # Start assessment of nucleotide bias
            if arg_4.startswith(arg_1):
                # Skip comment line
                logger.debug("Found target string at line: {}".format(arg_4))
                next(fh)
                arg_2 = True
            # Stop assessment when reaching end of target module
            elif arg_4.startswith(">>END_MODULE") and arg_2:
                logger.debug("Stopping parsing at line: {}".format(arg_4))
                break
            elif arg_2:
                # Get proportions of each nucleotide
                arg_5, arg_6, arg_7, arg_8 = [float(x) for x in arg_4.strip().split()[1:]]
                # Get 'GC' and 'AT content
                arg_9 = (arg_5 + 0.1) / (arg_8 + 0.1)
                arg_10 = (arg_6 + 0.1) / (arg_7 + 0.1)
                # Assess bias
                if 0.8 <= arg_9 <= 1.2 and 0.8 <= arg_10 <= 1.2:
                    arg_3.append(False)
                else:
                    arg_3.append(True)

    logger.debug("Finished bias assessment with result: {}".format(arg_3))

    # Split biased list in half to get the 5' and 3' ends
    arg_11, arg_12 = arg_3[:int(len(arg_3)/2)],\
        arg_3[int(len(arg_3)/2):][::-1]

    logger.debug("Getting optimal trim range from biased list")
    arg_13 = [0, 0]
    # Assess number of nucleotides to clip at 5' end
    arg_13[0] = get_trim_index(arg_11)
    logger.debug("Optimal trim range at 5' end set to: {}".format(arg_13[0]))
    # Assess number of nucleotides to clip at 3' end
    arg_13[1] = len(arg_3) - get_trim_index(arg_12)
    logger.debug("Optimal trim range at 3' end set to: {}".format(arg_13[1]))

    return arg_13


def Func(arg_0, arg_1):
    """Get the optimal read trim range from data files of paired FastQ reads.

    Given the FastQC data report files for paired-end FastQ reads, this
    function will assess the optimal trim range for the 3' and 5' ends of
    the paired-end reads. This assessment will be based on the *'Per sequence
    GC content'*.

    Parameters
    ----------
    p1_data: str
        Path to FastQC data report file from pair 1
    p2_data: str
        Path to FastQC data report file from pair 2

    Returns
    -------
    optimal_5trim: int
        Optimal trim index for the 5' end of the reads
    optima_3trim: int
        Optimal trim index for the 3' end of the reads

    See Also
    --------
    trim_range

    """

    arg_2 = [trim_range(x) for x in [arg_0, arg_1]]

    # Get the optimal trim position for 5' end
    arg_3 = max([x[0] for x in arg_2])
    # Get optimal trim position for 3' end
    arg_4 = min([x[1] for x in arg_2])

    return arg_3, arg_4


def Func(arg_0, arg_1):
        """Parse a bowtie log file.

        This is a bowtie log parsing method that populates the
        :py:attr:`self.n_reads, self.align_0x, self.align_1x, self.align_mt1x and self.overall_rate` attributes with
        data from the log file.

        Disclamer: THIS METHOD IS HORRIBLE BECAUSE THE BOWTIE LOG IS HORRIBLE.

        The insertion of data on the attribytes is done by the
        :py:meth:`set_attribute method.

        Parameters
        ----------
        bowtie_log : str
            Path to the boetie log file.

       """

        print("is here!")

        # Regexes - thanks to https://github.com/ewels/MultiQC/blob/master/multiqc/modules/bowtie2/bowtie2.py
        arg_2 = {
            'unpaired': {
                'unpaired_aligned_none': r"(\\d+) \\([\\d\\.]+%\\) aligned 0 times",
                'unpaired_aligned_one': r"(\\d+) \\([\\d\\.]+%\\) aligned exactly 1 time",
                'unpaired_aligned_multi': r"(\\d+) \\([\\d\\.]+%\\) aligned >1 times"
            },
            'paired': {
                'paired_aligned_none': r"(\\d+) \\([\\d\\.]+%\\) aligned concordantly 0 times",
                'paired_aligned_one': r"(\\d+) \\([\\d\\.]+%\\) aligned concordantly exactly 1 time",
                'paired_aligned_multi': r"(\\d+) \\([\\d\\.]+%\\) aligned concordantly >1 times",
                'paired_aligned_discord_one': r"(\\d+) \\([\\d\\.]+%\\) aligned discordantly 1 time",
                'paired_aligned_discord_multi': r"(\\d+) \\([\\d\\.]+%\\) aligned discordantly >1 times",
                'paired_aligned_mate_one': r"(\\d+) \\([\\d\\.]+%\\) aligned exactly 1 time",
                'paired_aligned_mate_multi': r"(\\d+) \\([\\d\\.]+%\\) aligned >1 times",
                'paired_aligned_mate_none': r"(\\d+) \\([\\d\\.]+%\\) aligned 0 times"
            }
        }

        #Missing parser for unpaired (not implemented in flowcraft yet)

        with open(arg_1, "r") as f:
            #Go through log file line by line
            for arg_3 in f:

                print(arg_3)

                #total reads
                arg_4 = re.search(r"(\\d+) reads; of these:", arg_3)
                print(arg_4)
                if arg_4:
                    print(arg_4)
                    arg_0.set_n_reads(arg_4.group(1))

                # Paired end reads aka the pain
                arg_5 = re.search(r"(\\d+) \\([\\d\\.]+%\\) were paired; of these:", arg_3)
                if arg_5:
                    arg_6 = int(arg_5.group(1))

                    arg_7 = {}

                    # Do nested loop whilst we have this level of indentation
                    arg_3 = f.readline()
                    while arg_3.startswith('    '):
                        for arg_8, arg_9 in arg_2['paired'].items():
                            arg_10 = re.search(arg_9, arg_3)
                            if arg_10:
                                arg_7[arg_8] = int(arg_10.group(1))
                        arg_3 = f.readline()

                    arg_11 = arg_7['paired_aligned_none'] + arg_7['paired_aligned_mate_none']
                    if arg_11:
                        arg_0.set_align_0x(arg_11)

                    arg_12 = arg_7['paired_aligned_one'] + arg_7['paired_aligned_mate_one']
                    if arg_12:
                        arg_0.set_align_1x(arg_12)

                    arg_13 = arg_7['paired_aligned_multi'] + arg_7['paired_aligned_mate_multi']
                    if arg_13:
                        arg_0.set_align_mt1x(arg_13)

                # Overall alignment rate
                arg_14 = re.search(r"([\\d\\.]+)% overall alignment rate", arg_3)
                if arg_14:
                    arg_0.overall_rate = float(arg_14.group(1))


def Func(arg_0):
        """Parses the process string and returns the process name and its
        directives

        Process strings my contain directive information with the following
        syntax::

            proc_name={'directive':'val'}

        This method parses this string and returns the process name as a
        string and the directives information as a dictionary.

        Parameters
        ----------
        name_str : str
            Raw string with process name and, potentially, directive
            information

        Returns
        -------
        str
            Process name
        dict or None
            Process directives
        """

        arg_1 = None

        arg_2 = arg_0.split("=")
        arg_3 = arg_2[0]

        if len(arg_2) == 2:
            arg_4 = arg_2[1].replace("'", '"')
            try:
                arg_1 = json.loads(arg_4)
            except json.decoder.JSONDecodeError:
                raise eh.ProcessError(
                    "Could not parse directives for process '{}'. The raw"
                    " string is: {}\n"
                    "Possible causes include:\n"
                    "\t1. Spaces inside directives\n"
                    "\t2. Missing '=' symbol before directives\n"
                    "\t3. Missing quotes (' or \") around directives\n"
                    "A valid example: process_name={{'cpus':'2'}}".format(
                        arg_3, arg_0))

        return arg_3, arg_1


def Func(arg_0, arg_1, arg_2):
        """Searches the process tree backwards in search of a provided process

        The search takes into consideration the provided parent lanes and
        searches only those

        Parameters
        ----------
        template : str
            Name of the process template attribute being searched
        parent_lanes : list
            List of integers with the parent lanes to be searched

        Returns
        -------
        bool
            Returns True when the template is found. Otherwise returns False.
        """

        for arg_3 in arg_0.processes[::-1]:

            # Ignore process in different lanes
            if arg_3.lane not in arg_2:
                continue

            # template found
            if arg_3.template == arg_1:
                return True

        return False


def Func(arg_0):
        """Adds the footer template to the master template string"""

        logger.debug("===============")
        logger.debug("Building header")
        logger.debug("===============")
        arg_0.template += fs.footer


def Func(arg_0):
        """Sets the secondary channels for the pipeline

        This will iterate over the
        :py:attr:`NextflowGenerator.secondary_channels` dictionary that is
        populated when executing
        :func:`~NextflowGenerator._update_secondary_channels` method.
        """

        logger.debug("==========================")
        logger.debug("Setting secondary channels")
        logger.debug("==========================")

        logger.debug("Setting secondary channels: {}".format(
            arg_0.secondary_channels))

        for arg_1, arg_2 in arg_0.secondary_channels.items():

            for arg_3 in arg_2.values():

                if not arg_3["end"]:
                    logger.debug("[{}] No secondary links to setup".format(
                        arg_3["p"].template))
                    continue

                logger.debug("[{}] Setting secondary links for "
                             "source {}: {}".format(arg_3["p"].template,
                                                    arg_1,
                                                    arg_3["end"]))

                arg_3["p"].set_secondary_channel(arg_1, arg_3["end"])


def Func(arg_0):
        """Compiles all status channels for the status compiler process
        """

        arg_1 = pc.StatusCompiler(template="status_compiler")
        arg_2 = pc.ReportCompiler(template="report_compiler")

        # Compile status channels from pipeline process
        arg_3 = []
        for arg_4 in [arg_4 for arg_4 in arg_0.processes]:
            if not any([isinstance(arg_4, arg_5) for arg_5 in arg_0.skip_class]):

                arg_3.extend(arg_4.status_strs)

        if not arg_3:
            logger.debug("No status channels found. Skipping status compiler"
                         "process")
            return

        logger.debug("Setting status channels: {}".format(arg_3))

        # Check for duplicate channels. Raise exception if found.
        if len(arg_3) != len(set(arg_3)):
            raise eh.ProcessError(
                "Duplicate status channels detected. Please ensure that "
                "the 'status_channels' attributes of each process are "
                "unique. Here are the status channels:\n\n{}".format(
                    ", ".join(arg_3)
                ))

        arg_1.set_compiler_channels(arg_3)

        arg_6 = ["REPORT_{}".format(arg_5.lstrip("STATUS_")) for arg_5 in
                           arg_3]

        arg_2.set_compiler_channels(arg_6)

        arg_0.processes.extend([arg_1, arg_2])


def Func(arg_0, arg_1):
        """ Returns the nextflow resources string from a dictionary object

        If the dictionary has at least on of the resource directives, these
        will be compiled for each process in the dictionary and returned
        as a string read for injection in the nextflow config file template.

        This dictionary should be::

            dict = {"processA": {"cpus": 1, "memory": "4GB"},
                    "processB": {"cpus": 2}}

        Parameters
        ----------
        res_dict : dict
            Dictionary with the resources for processes.
        pid : int
            Unique identified of the process

        Returns
        -------
        str
            nextflow config string
        """

        arg_2 = ""
        arg_3 = ["container", "version"]

        for arg_4, arg_5 in arg_0.items():

            for arg_6, arg_7 in arg_5.items():

                if arg_6 in arg_3:
                    continue

                arg_2 += '\n\t${}_{}.{} = {}'.format(arg_4, arg_1, arg_6, arg_7)

        return arg_2


def Func(arg_0, arg_1):
        """ Returns the nextflow containers string from a dictionary object

        If the dictionary has at least on of the container directives, these
        will be compiled for each process in the dictionary and returned
        as a string read for injection in the nextflow config file template.

        This dictionary should be::

            dict = {"processA": {"container": "asd", "version": "1.0.0"},
                    "processB": {"container": "dsd"}}

        Parameters
        ----------
        cont_dict : dict
            Dictionary with the containers for processes.
        pid : int
            Unique identified of the process

        Returns
        -------
        str
            nextflow config string
        """

        arg_2 = ""

        for arg_3, arg_4 in arg_0.items():

            arg_5 = ""

            if "container" in arg_4:
                arg_5 += arg_4["container"]

                if "version" in arg_4:
                    arg_5 += ":{}".format(arg_4["version"])
                else:
                    arg_5 += ":latest"

            if arg_5:
                arg_2 += '\n\t${}_{}.container = "{}"'.format(arg_3, arg_1, arg_5)

        return arg_2


def Func(arg_0):
        """Returns the nextflow params string from a dictionary object.

        The params dict should be a set of key:value pairs with the
        parameter name, and the default parameter value::

            self.params = {
                "genomeSize": 2.1,
                "minCoverage": 15
            }

        The values are then added to the string as they are. For instance,
        a ``2.1`` float will appear as ``param = 2.1`` and a
        ``"'teste'" string will appear as ``param = 'teste'`` (Note the
        string).

        Returns
        -------
        str
            Nextflow params configuration string
        """

        arg_1 = ""

        for arg_2 in arg_0.processes:

            logger.debug("[{}] Adding parameters: {}\n".format(
                arg_2.template, arg_2.params)
            )

            # Add an header with the template name to structure the params
            # configuration
            if arg_2.params and arg_2.template != "init":

                arg_2.set_param_id("_{}".format(arg_2.pid))
                arg_1 += "\n\t/*"
                arg_1 += "\n\tComponent '{}_{}'\n".format(arg_2.template,
                                                               arg_2.pid)
                arg_1 += "\t{}\n".format("-" * (len(arg_2.template) + len(arg_2.pid) + 12))
                arg_1 += "\t*/\n"

            for arg_3, arg_4 in arg_2.params.items():

                if arg_2.template == "init":
                    arg_5 = arg_3
                else:
                    arg_5 = "{}_{}".format(arg_3, arg_2.pid)

                arg_1 += "\t{} = {}\n".format(arg_5, arg_4["default"])

        return arg_1


def Func(arg_0):
        """Returns the nextflow manifest config string to include in the
        config file from the information on the pipeline.

        Returns
        -------
        str
            Nextflow manifest configuration string
        """

        arg_1 = ""

        arg_1 += '\n\tname = "{}"'.format(arg_0.pipeline_name)
        arg_1 += '\n\tmainScript = "{}"'.format(arg_0.nf_file)

        return arg_1


def Func(arg_0, arg_1, arg_2=".treeDag.json"):
        """Writes dag to output file

        Parameters
        ----------
        dict_viz: dict
            Tree like dictionary that is used to export tree data of processes
            to html file and here for the dotfile .treeDag.json

        """

        arg_3 = open(os.path.join(dirname(arg_0.nf_file), arg_2)
                           , "w")
        arg_3.write(json.dumps(arg_1))
        arg_3.close()


def Func(arg_0, arg_1):
        """Wrapper method that writes all configuration files to the pipeline
        directory
        """

        # Write resources config
        with open(join(arg_1, "resources.config"), "w") as fh:
            fh.write(arg_0.resources)

        # Write containers config
        with open(join(arg_1, "containers.config"), "w") as fh:
            fh.write(arg_0.containers)

        # Write containers config
        with open(join(arg_1, "params.config"), "w") as fh:
            fh.write(arg_0.params)

        # Write manifest config
        with open(join(arg_1, "manifest.config"), "w") as fh:
            fh.write(arg_0.manifest)

        # Write user config if not present in the project directory
        if not exists(join(arg_1, "user.config")):
            with open(join(arg_1, "user.config"), "w") as fh:
                fh.write(arg_0.user_config)

        arg_2 = join(arg_1, "lib")
        if not exists(arg_2):
            os.makedirs(arg_2)
        with open(join(arg_2, "Helper.groovy"), "w") as fh:
            fh.write(arg_0.help)

        # Generate the pipeline DAG
        arg_3 = arg_0.render_pipeline()
        with open(splitext(arg_0.nf_file)[0] + ".html", "w") as fh:
            fh.write(arg_3)


def Func(arg_0):
        """Export pipeline params as a JSON to stdout

        This run mode iterates over the pipeline processes and exports the
        params dictionary of each component as a JSON to stdout.
        """

        arg_1 = {}

        # Skip first init process
        for arg_2 in arg_0.processes[1:]:
            arg_1[arg_2.template] = arg_2.params

        # Flush params json to stdout
        sys.stdout.write(json.dumps(arg_1))


def Func(arg_0):
        """Export pipeline directives as a JSON to stdout
        """

        arg_1 = {}

        # Skip first init process
        for arg_2 in arg_0.processes[1:]:
            arg_1[arg_2.template] = arg_2.directives

        # Flush params json to stdout
        sys.stdout.write(json.dumps(arg_1))


def Func(arg_0):
        """
        Export all dockerhub tags associated with each component given by
        the -t flag.
        """

        # dict to store the already parsed components (useful when forks are
        # given to the pipeline string via -t flag
        arg_1 = {}

        # fetches terminal width and subtracts 3 because we always add a
        # new line character and we want a space at the beggining and at the end
        # of each line
        arg_2 = shutil.get_terminal_size().columns - 3

        # first header
        arg_3 = " Selected container tags "

        # starts a list with the headers
        arg_4 = [
            [
                "=" * int(arg_2 / 4),
                "{0}{1}{0}".format(
                    "=" * int(((arg_2/2 - len(arg_3)) / 2)),
                    arg_3)
                ,
                "{}\n".format("=" * int(arg_2 / 4))
            ],
            ["component", "container", "tags"],
            [
                "=" * int(arg_2 / 4),
                "=" * int(arg_2 / 2),
                "=" * int(arg_2 / 4)
            ]
        ]

        # Skip first init process and iterate through the others
        for arg_5 in arg_0.processes[1:]:
            arg_6 = arg_5.template
            # if component has already been printed then skip and don't print
            # again
            if arg_6 in arg_1:
                continue

            # starts a list of  containers for the current process in
            # dict_of_parsed, in which each containers will be added to this
            # list once it gets parsed
            arg_1[arg_6] = {
                "container": []
            }

            # fetch repo name from directives of each component.
            for arg_7 in arg_5.directives.values():
                try:
                    arg_8 = arg_7["container"]
                    arg_9 = arg_7["version"]
                except KeyError:
                    # adds the default container if container key isn't present
                    # this happens for instance in integrity_coverage
                    arg_8 = "flowcraft/flowcraft_base"
                    arg_9 = "1.0.0-1"
                # checks if repo_version already exists in list of the
                # containers for the current component being queried
                arg_10 = arg_8 + arg_9
                if arg_10 not in arg_1[arg_6]["container"]:
                    # make the request to docker hub
                    arg_11 = requests.get(
                        "https://hub.docker.com/v2/repositories/{}/tags/"
                        .format(arg_8)
                    )
                    # checks the status code of the request, if it is 200 then
                    # parses docker hub entry, otherwise retrieve no tags but
                    # alerts the user
                    if arg_11.status_code != 404:
                        # parse response content to dict and fetch results key
                        arg_12 = json.loads(arg_11.content)["results"]
                        for arg_13 in arg_12:
                            arg_14 = (arg_13["name"] + "*") \
                                if arg_13["name"] == arg_9 \
                                else arg_13["name"]
                            arg_4.append([arg_6, arg_8, arg_14])
                    else:
                        arg_4.append([arg_6, arg_8, "No DockerHub tags"])

                arg_1[arg_6]["container"].append(arg_10)

        # iterate through each entry in tags_list and print the list of tags
        # for each component. Each entry (excluding the headers) contains
        # 3 elements (component name, container and tag version)
        for arg_15, arg_16 in enumerate(arg_4):
            # adds different color to the header in the first list and
            # if row is pair add one color and if is even add another (different
            # background)
            arg_17 = "blue_bold" if arg_15 < 3 else \
                ("white" if arg_15 % 2 != 0 else "0;37;40m")
            # generates a small list with the terminal width for each column,
            # this will be given to string formatting as the 3, 4 and 5 element
            arg_18 = [
                int(arg_2/4),
                int(arg_2/2),
                int(arg_2/4)
            ]
            # writes the string to the stdout
            sys.stdout.write(
                colored_print("\n {0: <{3}} {1: ^{4}} {2: >{5}}".format(
                    *arg_16, *arg_18), arg_17)
            )
        # assures that the entire line gets the same color
        sys.stdout.write("\n{0: >{1}}\n".format("(* = default)",
                                                arg_2 + 3))


def Func(arg_0):
        """Main pipeline Funcer

        This method is responsible for Funcing the
        :py:attr:`NextflowGenerator.template` attribute that will contain
        the nextflow code of the pipeline.

        First it Funcs the header, then sets the main channels, the
        secondary inputs, secondary channels and finally the
        status channels. When the pipeline is built, is writes the code
        to a nextflow file.
        """

        logger.info(colored_print(
            "\tSuccessfully connected {} process(es) with {} "
            "fork(s) across {} lane(s) \u2713".format(
                len(arg_0.processes[1:]), len(arg_0._fork_tree), arg_0.lanes)))

        # Generate regular nextflow header that sets up the shebang, imports
        # and all possible initial channels
        arg_0._Func_header()

        arg_0._set_channels()

        arg_0._set_init_process()

        arg_0._set_secondary_channels()

        logger.info(colored_print(
            "\tSuccessfully set {} secondary channel(s) \u2713".format(
                len(arg_0.secondary_channels))))

        arg_0._set_compiler_channels()

        arg_0._set_configurations()

        logger.info(colored_print(
            "\tFinished configurations \u2713"))

        for arg_1 in arg_0.processes:
            arg_0.template += "\n{}".format(arg_1.template_str)

        arg_0._Func_footer()

        arg_2 = dirname(arg_0.nf_file)

        # Write configs
        arg_0.write_configs(arg_2)

        # Write pipeline file
        with open(arg_0.nf_file, "w") as fh:
            fh.write(arg_0.template)

        logger.info(colored_print(
            "\tPipeline written into {} \u2713".format(arg_0.nf_file)))


def Func(arg_0, arg_1):
    """Returns a kmer list based on the provided kmer option and max read len.

    Parameters
    ----------
    kmer_opt : str
        The k-mer option. Can be either ``'auto'``, ``'default'`` or a
        sequence of space separated integers, ``'23, 45, 67'``.
    max_read_len : int
        The maximum read length of the current sample.

    Returns
    -------
    kmers : list
        List of k-mer values that will be provided to Spades.

    """

    logger.debug("Kmer option set to: {}".format(arg_0))

    # Check if kmer option is set to auto
    if arg_0 == "auto":

        if arg_1 >= 175:
            arg_2 = [55, 77, 99, 113, 127]
        else:
            arg_2 = [21, 33, 55, 67, 77]

        logger.debug("Kmer range automatically selected based on max read"
                     "length of {}: {}".format(arg_1, arg_2))

    # Check if manual kmers were specified
    elif len(arg_0.split()) > 1:

        arg_2 = arg_0.split()
        logger.debug("Kmer range manually set to: {}".format(arg_2))

    else:

        arg_2 = []
        logger.debug("Kmer range set to empty (will be automatically "
                     "determined by SPAdes")

    return arg_2


def Func(arg_0):
        """Returns a hash of the reports JSON file
        """

        if arg_0.watch:

            # Searches for the first occurence of the nextflow pipeline
            # file name in the .nextflow.log file
            arg_1 = get_nextflow_filepath(arg_0.log_file)

            # Get hash from the entire pipeline file
            arg_2 = hashlib.md5()
            with open(arg_1, "rb") as fh:
                for arg_3 in iter(lambda: fh.read(4096), b""):
                    arg_2.update(arg_3)
            # Get hash from the current working dir and hostname
            arg_4 = os.getcwd().encode("utf8")
            arg_5 = socket.gethostname().encode("utf8")
            arg_6 = str(uuid.getnode()).encode("utf8")
            arg_7 = hashlib.md5(arg_4 + arg_5 + arg_6)

            return arg_2.hexdigest() + arg_7.hexdigest()

        else:
            with open(arg_0.report_file) as fh:
                arg_8 = json.loads(fh.read())

            arg_9 = arg_8["data"]["results"][0]["nfMetadata"]

            try:
                arg_10 = arg_9["scriptId"] + arg_9["sessionId"]
            except KeyError:
                raise eh.ReportError("Incomplete or corrupt report JSON file "
                                     "missing the 'scriptId' and/or 'sessionId' "
                                     "metadata information")

            return arg_10


def Func(arg_0):
        """Parses the nextflow trace file and retrieves the path of report JSON
        files that have not been sent to the service yet.
        """

        # Check the size stamp of the tracefile. Only proceed with the parsing
        # if it changed from the previous size.
        arg_1 = os.path.getsize(arg_0.trace_file)
        arg_0.trace_retry = 0
        if arg_1 and arg_1 == arg_0.trace_sizestamp:
            return
        else:
            logger.debug("Updating trace size stamp to: {}".format(arg_1))
            arg_0.trace_sizestamp = arg_1

        with open(arg_0.trace_file) as fh:

            # Skip potential empty lines at the start of file
            arg_4 = next(fh).strip()
            while not arg_4:
                arg_4 = next(fh).strip()

            # Get header mappings before parsing the file
            arg_5 = arg_0._header_mapping(arg_4)

            for arg_6 in fh:
                # Skip empty lines
                if arg_6.strip() == "":
                    continue

                arg_7 = arg_6.strip().split("\t")

                # Skip if task ID was already processes
                if arg_7[arg_5["task_id"]] in arg_0.stored_ids:
                    continue

                if arg_7[arg_5["process"]] == "report":
                    arg_0.report_queue.append(
                        arg_0._expand_path(arg_7[arg_5["hash"]])
                    )
                    arg_0.send = True

                # Add the processed trace line to the stored ids. It will be
                # skipped in future parsers
                arg_0.stored_ids.append(arg_7[arg_5["task_id"]])


def Func(arg_0):
        """Parses nextflow log file and updates the run status
        """

        # Check the size stamp of the tracefile. Only proceed with the parsing
        # if it changed from the previous size.
        arg_1 = os.path.getsize(arg_0.log_file)
        arg_0.trace_retry = 0
        if arg_1 and arg_1 == arg_0.log_sizestamp:
            return
        else:
            logger.debug("Updating log size stamp to: {}".format(arg_1))
            arg_0.log_sizestamp = arg_1

        arg_0._update_pipeline_status()


def Func(arg_0, arg_1):
        """Sends a PUT request with the report JSON files currently in the
        report_queue attribute.

        Parameters
        ----------
        report_id : str
            Hash of the report JSON as retrieved from :func:`~_get_report_hash`
        """

        # Determines the maximum number of reports sent at the same time in
        # the same payload
        arg_2 = 100
        logger.debug("Report buffer size set to: {}".format(arg_2))

        for arg_3 in range(0, len(arg_0.report_queue), arg_2):

            # Reset the report compilation batch
            arg_4 = []

            # Iterate over report JSON batches determined by buffer_size
            for arg_5 in arg_0.report_queue[arg_3: arg_3 + arg_2]:
                try:
                    arg_6 = [x for x in os.listdir(arg_5)
                                   if x.endswith(".json")][0]
                except IndexError:
                    continue
                with open(join(arg_5, arg_6)) as fh:
                    arg_4.append(json.loads(fh.read()))

            logger.debug("Payload sent with size: {}".format(
                asizeof(json.dumps(arg_4))
            ))
            logger.debug("status: {}".format(arg_0.status_info))

            try:
                requests.put(
                    arg_0.broadcast_address,
                    json={"run_id": arg_1,
                          "report_json": arg_4,
                          "status": arg_0.status_info}
                )
            except requests.exceptions.ConnectionError:
                logger.error(colored_print(
                    "ERROR: Could not establish connection with server. The server"
                    " may be down or there is a problem with your internet "
                    "connection.", "red_bold"))
                sys.exit(1)

        # When there is no change in the report queue, but there is a change
        # in the run status of the pipeline
        if not arg_0.report_queue:

            logger.debug("status: {}".format(arg_0.status_info))

            try:
                requests.put(
                    arg_0.broadcast_address,
                    json={"run_id": arg_1,
                          "report_json": [],
                          "status": arg_0.status_info}
                )
            except requests.exceptions.ConnectionError:
                logger.error(colored_print(
                    "ERROR: Could not establish connection with server. The"
                    " server may be down or there is a problem with your "
                    "internet connection.", "red_bold"))
                sys.exit(1)

        # Reset the report queue after sending the request
        arg_0.report_queue = []


def Func(arg_0, arg_1):
        """Sends a delete request for the report JSON hash

        Parameters
        ----------
        report_id : str
            Hash of the report JSON as retrieved from :func:`~_get_report_hash`
        """

        logger.debug(
            "Closing connection and sending DELETE request to {}".format(
                arg_0.broadcast_address))

        try:
            arg_2 = requests.delete(arg_0.broadcast_address,
                                json={"run_id": arg_1})
            if arg_2.status_code != 202:
                logger.error(colored_print(
                    "ERROR: There was a problem sending data to the server"
                    "with reason: {}".format(arg_2.reason)))
        except requests.exceptions.ConnectionError:
            logger.error(colored_print(
                "ERROR: Could not establish connection with server. The server"
                " may be down or there is a problem with your internet "
                "connection.", "red_bold"))
            sys.exit(1)


def Func(arg_0):
    """Generates an adapter file for FastQC from a fasta file.

    The provided adapters file is assumed to be a simple fasta file with the
    adapter's name as header and the corresponding sequence::

        >TruSeq_Universal_Adapter
        AATGATACGGCGACCACCGAGATCTACACTCTTTCCCTACACGACGCTCTTCCGATCT
        >TruSeq_Adapter_Index 1
        GATCGGAAGAGCACACGTCTGAACTCCAGTCACATCACGATCTCGTATGCCGTCTTCTGCTTG

    Parameters
    ----------
    adapter_fasta : str
        Path to Fasta file with adapter sequences.

    Returns
    -------
    adapter_out : str or None
        The path to the reformatted adapter file. Returns ``None`` if the
        adapters file does not exist or the path is incorrect.
    """

    arg_1 = "fastqc_adapters.tab"
    logger.debug("Setting output adapters file to: {}".format(arg_1))

    try:

        with open(arg_0) as fh, \
                open(arg_1, "w") as adap_fh:

            for arg_2 in fh:
                if arg_2.startswith(">"):

                    arg_3 = arg_2[1:].strip()
                    # Get the next line with the sequence string
                    arg_4 = next(fh).strip()

                    adap_fh.write("{}\\t{}\\n".format(arg_3, arg_4))

        logger.info("Converted adapters file")

        return arg_1

    # If an invalid adapters file is provided, return None.
    except FileNotFoundError:
        logger.warning("Could not find the provided adapters file: {}".format(
            arg_0))
        return


def Func(arg_0, arg_1, arg_2, arg_3):
    """Send dictionary to output json file
    This function sends master_dict dictionary to a json file if master_dict is
    populated with entries, otherwise it won't create the file

    Parameters
    ----------
    master_dict: dict
        dictionary that stores all entries for a specific query sequence
        in multi-fasta given to mash dist as input against patlas database
    last_seq: str
        string that stores the last sequence that was parsed before writing to
        file and therefore after the change of query sequence between different
        rows on the input file
    mash_output: str
        the name/path of input file to main function, i.e., the name/path of
        the mash dist output txt file.
    sample_id: str
        The name of the sample being parse to .report.json file

    Returns
    -------

    """

    arg_4 = {}

    # create a new file only if master_dict is populated
    if arg_0:
        arg_5 = open("{}.json".format(
            "".join(arg_1.split(".")[0])), "w")
        arg_5.write(json.dumps(arg_0))
        arg_5.close()

        # iterate through master_dict in order to make contigs the keys
        for arg_6,arg_7 in arg_0.items():
            if not arg_7[2] in arg_4:
                arg_4[arg_7[2]] = [arg_6]
            else:
                arg_4[arg_7[2]].append(arg_6)

        arg_8 = len(arg_0)
    else:
        arg_8 = 0

    arg_9 = {
        "tableRow": [{
            "sample": arg_2,
            "data": [{
                "header": "Mash Dist",
                "table": "plasmids",
                "patlas_mashdist": arg_0,
                "value": arg_8
            }]
        }],
        "plotData": [{
            "sample": arg_2,
            "data": {
                "patlasMashDistXrange": arg_4
            },
            "assemblyFile": arg_3
        }]
    }

    with open(".report.json", "w") as json_report:
        json_report.write(json.dumps(arg_9, separators=(",", ":")))


def Func(arg_0, arg_1, arg_2, arg_3):
    """
    Main function that allows to dump a mash dist txt file to a json file

    Parameters
    ----------
    mash_output: str
        A string with the input file.
    hash_cutoff: str
        the percentage cutoff for the percentage of shared hashes between query
        and plasmid in database that is allowed for the plasmid to be reported
        to the results outputs
    sample_id: str
        The name of the sample.
    """

    arg_4 = open(arg_0, "r")

    arg_5 = {}

    for arg_6 in arg_4:

        arg_7 = arg_6.split("\t")
        arg_8 = arg_7[1].strip()
        arg_9 = "_".join(arg_7[0].strip().split("_")[0:3])
        arg_10 = arg_7[2].strip()
        arg_11 = arg_7[-1].strip().split("/")

        # creates a percentage of the shared hashes between the sample and the
        # reference
        arg_12 = float(arg_11[0]) / float(arg_11[1])

        # if ref_accession already in dict, i.e., if the same accession number
        # matches more than one contig.
        if arg_9 in arg_5.keys():
            arg_8 += ", {}".format(arg_5[arg_9][-1])

        # assures that only the hashes with a given shared percentage are
        # reported to json file
        if arg_12 > float(arg_1):

            arg_5[arg_9] = [
                round(1 - float(arg_10), 2),
                round(arg_12, 2),
                arg_8
            ]

    # assures that file is closed in last iteration of the loop
    send_to_output(arg_5, arg_0, arg_2, arg_3)


def Func(arg_0):
        """Writes versions JSON for a template file

        This method creates the JSON file ``.versions`` based on the metadata
        and specific functions that are present in a given template script.

        It starts by fetching the template metadata, which can be specified
        via the ``__version__``, ``__template__`` and ``__build__``
        attributes. If all of these attributes exist, it starts to populate
        a JSON/dict array (Note that the absence of any one of them will
        prevent the version from being written).

        Then, it will search the
        template scope for functions that start with the substring
        ``__set_version`` (For example ``def __set_version_fastqc()`).
        These functions should gather the version of
        an arbitrary program and return a JSON/dict object with the following
        information::

            {
                "program": <program_name>,
                "version": <version>
                "build": <build>
            }

        This JSON/dict object is then written in the ``.versions`` file.
        """

        arg_1 = []

        arg_2 = arg_0.context.get("__version__", None)
        arg_3 = arg_0.context.get("__template__", None)
        arg_4 = arg_0.context.get("__build__", None)

        if arg_2 and arg_3 and arg_4:
            if arg_0.logger:
                arg_0.logger.debug("Adding template version: {}; {}; "
                                  "{}".format(arg_3,
                                              arg_2,
                                              arg_4))
            arg_1.append({
                "program": arg_3,
                "version": arg_2,
                "build": arg_4
            })

        for arg_5, arg_6 in arg_0.context.items():
            if arg_5.startswith("__get_version"):
                arg_7 = arg_6()
                arg_1.append(arg_7)
                if arg_0.logger:
                    arg_0.logger.debug("Found additional software version"
                                      "{}".format(arg_7))

        with open(".versions", "w") as fh:
            fh.write(json.dumps(arg_1, separators=(",", ":")))


def Func(arg_0, arg_1="white_bold"):
    """
    This function enables users to add a color to the print. It also enables
    to pass end_char to print allowing to print several strings in the same line
    in different prints.

    Parameters
    ----------
    color_string: str
        The color code to pass to the function, which enables color change as
        well as background color change.
    msg: str
        The actual text to be printed
    end_char: str
        The character in which each print should finish. By default it will be
        "\n".

    """

    if sys.stdout.encoding != "UTF-8":
        arg_0 = "".join([i if ord(i) < 128 else "" for i in arg_0])

    # try except first looks for the color in COLORS dictionary, otherwise use
    # color_label as the color.
    try:
        arg_2 = COLORS[arg_1]
    except KeyError:
        arg_2 = arg_1

    return "\x1b[{}{}\x1b[0m".format(arg_2, arg_0)


def Func(arg_0):
    """
    This function handles the dictionary of attributes of each Process class
    to print to stdout lists of all the components or the components which the
    user specifies in the -t flag.

    Parameters
    ----------
    procs_dict: dict
        A dictionary with the class attributes for all the components (or
        components that are used by the -t flag), that allow to create
        both the short_list and detailed_list. Dictionary example:
        {"abyss": {'input_type': 'fastq', 'output_type': 'fasta',
        'dependencies': [], 'directives': {'abyss': {'cpus': 4,
        'memory': '{ 5.GB * task.attempt }', 'container': 'flowcraft/abyss',
        'version': '2.1.1', 'scratch': 'true'}}}
    """

    logger.info(colored_print(
        "\n===== L I S T   O F   P R O C E S S E S =====\n", "green_bold"))

    #Sort to print alphabetically ordered list of processes to ease reading
    arg_1 = {k: arg_0[k] for k in sorted(arg_0)}

    for arg_2, arg_3 in arg_1.items():
        arg_4 = "=> {}".format(arg_2)
        logger.info(colored_print(arg_4, "blue_bold"))

        for arg_5 in arg_3:
            arg_6 = "{}:".format(arg_5)

            if isinstance(arg_3[arg_5], list):
                if not arg_3[arg_5]:
                    arg_7 = "None"
                else:
                    arg_7 = ", ".join(arg_3[arg_5])
            elif arg_5 == "directives":
                # this is used for the "directives", which is a dict
                if not arg_3[arg_5]:
                    # if dict is empty then add None to the message
                    arg_7 = "None"
                else:
                    # otherwise fetch all template names within a component
                    # and all the directives for each template to a list
                    arg_8 = ["\n      {}: {}".format(
                        templt,
                        " , ".join(["{}: {}".format(dr, val)
                                    for dr, val in drs.items()]))
                                for templt, drs in arg_3[arg_5].items()
                    ]
                    # write list to a str
                    arg_7 = "".join(arg_8)
            else:
                arg_7 = arg_3[arg_5]

            logger.info("   {} {}".format(
                colored_print(arg_6, "white_underline"), arg_7
            ))


def Func(arg_0, arg_1, arg_2):
    """
    Function that collects all processes available and stores a dictionary of
    the required arguments of each process class to be passed to
    procs_dict_parser

    Parameters
    ----------
    process_map: dict
        The dictionary with the Processes currently available in flowcraft
        and their corresponding classes as values
    args: argparse.Namespace
        The arguments passed through argparser that will be access to check the
        type of list to be printed
    pipeline_string: str
        the pipeline string

    """

    arg_3 = []

    # prints a detailed list of the process class arguments
    if arg_1.detailed_list:
        # list of attributes to be passed to Func
        arg_3 += [
            "input_type",
            "output_type",
            "description",
            "dependencies",
            "conflicts",
            "directives"
        ]

    # prints a short list with each process and the corresponding description
    if arg_1.short_list:
        arg_3 += [
            "description"
        ]

    if arg_3:
        # dict to store only the required entries
        arg_4 = {}
        # loops between all process_map Processes
        for arg_5, arg_6 in arg_0.items():

            # instantiates each Process class
            arg_7 = arg_6(template=arg_5)

            # checks if recipe is provided
            if arg_2:
                if arg_5 not in arg_2:
                    continue

            arg_8 = {arg_key: vars(arg_7)[arg_key] for arg_key in
                 vars(arg_7) if arg_key in arg_3}
            arg_4[arg_5] = arg_8

        procs_dict_parser(arg_4)

        sys.exit(0)


def Func(arg_0, arg_1=None):
    """Guesses the compression of an input file.

    This function guesses the compression of a given file by checking for
    a binary signature at the beginning of the file. These signatures are
    stored in the :py:data:`MAGIC_DICT` dictionary. The supported compression
    formats are gzip, bzip2 and zip. If none of the signatures in this
    dictionary are found at the beginning of the file, it returns ``None``.

    Parameters
    ----------
    file_path : str
        Path to input file.
    magic_dict : dict, optional
        Dictionary containing the signatures of the compression types. The
        key should be the binary signature and the value should be the
        compression format. If left ``None``, it falls back to
        :py:data:`MAGIC_DICT`.

    Returns
    -------
    file_type : str or None
        If a compression type is detected, returns a string with the format.
        If not, returns ``None``.
    """

    if not arg_1:
        arg_1 = MAGIC_DICT

    arg_2 = max(len(x) for x in arg_1)

    with open(arg_0, "rb") as f:
        arg_3 = f.read(arg_2)

    logger.debug("Binary signature start: {}".format(arg_3))

    for arg_4, arg_5 in arg_1.items():
        if arg_3.startswith(arg_4):
            return arg_5

    return None


def Func(arg_0):
    """ Get range of the Unicode encode range for a given string of characters.

    The encoding is determined from the result of the :py:func:`ord` built-in.

    Parameters
    ----------
    qual_str : str
        Arbitrary string.

    Returns
    -------
    x : tuple
        (Minimum Unicode code, Maximum Unicode code).
    """

    arg_1 = [ord(c) for c in arg_0]

    return min(arg_1), max(arg_1)


def Func(arg_0, arg_1):
    """ Returns the valid encodings for a given encoding range.

    The encoding ranges are stored in the :py:data:`RANGES` dictionary, with
    the encoding name as a string and a list as a value containing the
    phred score and a tuple with the encoding range. For a given encoding
    range provided via the two first arguments, this function will return
    all possible encodings and phred scores.

    Parameters
    ----------
    rmin : int
        Minimum Unicode code in range.
    rmax : int
        Maximum Unicode code in range.

    Returns
    -------
    valid_encodings : list
        List of all possible encodings for the provided range.
    valid_phred : list
        List of all possible phred scores.

    """

    arg_2 = []
    arg_3 = []

    for arg_4, (arg_5, (arg_6, arg_7)) in RANGES.items():
        if arg_0 >= arg_6 and arg_1 <= arg_7:
            arg_2.append(arg_4)
            arg_3.append(arg_5)

    return arg_2, arg_3


def Func(arg_0):
    """Parses a file with coverage information into objects.

    This function parses a TSV file containing coverage results for
    all contigs in a given assembly and will build an ``OrderedDict``
    with the information about their coverage and length.  The length
    information is actually gathered from the contig header using a
    regular expression that assumes the usual header produced by Spades::

        contig_len = int(re.search("length_(.+?)_", line).group(1))

    Parameters
    ----------
    coverage_file : str
        Path to TSV file containing the coverage results.

    Returns
    -------
    coverage_dict : OrderedDict
        Contains the coverage and length information for each contig.
    total_size : int
        Total size of the assembly in base pairs.
    total_cov : int
        Sum of coverage values across all contigs.
    """

    # Stores the correspondence between a contig and the corresponding coverage
    # e.g.: {"contig_1": {"cov": 424} }
    arg_1 = OrderedDict()
    # Stores the total coverage
    arg_2 = 0

    with open(arg_0) as fh:
        for arg_3 in fh:
            # Get contig and coverage
            arg_4, arg_5 = arg_3.strip().split()
            arg_1[arg_4] = {"cov": int(arg_5)}
            # Add total coverage
            arg_2 += int(arg_5)
            logger.debug("Processing contig '{}' with coverage '{}'"
                         "".format(arg_4, arg_5))

    return arg_1, arg_2


def Func(arg_0, arg_1, arg_2,
                    arg_3):
    """Generates a filtered assembly file.

    This function generates a filtered assembly file based on an original
    assembly and a minimum coverage threshold.

    Parameters
    ----------
    assembly_file : str
        Path to original assembly file.
    minimum_coverage : int or float
        Minimum coverage required for a contig to pass the filter.
    coverage_info : OrderedDict or dict
        Dictionary containing the coverage information for each contig.
    output_file : str
        Path where the filtered assembly file will be generated.

    """

    # This flag will determine whether sequence data should be written or
    # ignored because the current contig did not pass the minimum
    # coverage threshold
    arg_4 = False

    with open(arg_0) as fh, open(arg_3, "w") as out_fh:

        for arg_5 in fh:
            if arg_5.startswith(">"):
                # Reset write_flag
                arg_4 = False
                # Get header of contig
                arg_6 = arg_5.strip()[1:]
                # Check coverage for current contig
                arg_7 = arg_2[arg_6]["cov"]
                # If the contig coverage is above the threshold, write to
                # output filtered assembly
                if arg_7 >= arg_1:
                    arg_4 = True
                    out_fh.write(arg_5)

            elif arg_4:
                out_fh.write(arg_5)


def Func(arg_0, arg_1, arg_2, arg_3, arg_4,
         arg_5, arg_6):
    """Main executor of the process_assembly_mapping template.

    Parameters
    ----------
    sample_id : str
        Sample Identification string.
    assembly_file : str
        Path to assembly file in Fasta format.
    coverage_file : str
        Path to TSV file with coverage information for each contig.
    coverage_bp_file : str
        Path to TSV file with coverage information for each base.
    bam_file : str
        Path to BAM file.
    opts : list
        List of options for processing assembly mapping.
    gsize : int
        Expected genome size

    """

    arg_7, arg_8 = arg_5

    logger.info("Starting assembly mapping processing")

    # Get coverage info, total size and total coverage from the assembly
    logger.info("Parsing coverage table")
    arg_9, arg_10 = parse_coverage_table(arg_2)
    arg_11, arg_12 = get_assembly_size(arg_1)
    logger.info("Assembly processed with a total size of '{}' and coverage"
                " of '{}'".format(arg_11, arg_10))
    # Get number of assembled bp after filters
    logger.info("Parsing coverage per bp table")
    arg_13 = get_coverage_from_file(arg_3)

    # Assess the minimum assembly coverage
    arg_14 = evaluate_min_coverage(arg_7, arg_10, arg_11)

    # Check if filtering the assembly using the provided min_coverage will
    # reduce the final bp number to less than 80% of the estimated genome
    # size.
    # If the check below passes with True, then the filtered assembly
    # is above the 80% genome size threshold.
    arg_15 = "{}_filt.fasta".format(
        os.path.splitext(arg_1)[0])
    arg_16 = "filtered.bam"
    logger.info("Checking filtered assembly")
    if check_filtered_assembly(arg_9, arg_13, arg_14,
                               arg_6, arg_12, int(arg_8),
                               arg_0):
        # Filter assembly contigs based on the minimum coverage.
        logger.info("Filtered assembly passed minimum size threshold")
        logger.info("Writting filtered assembly")
        filter_assembly(arg_1, arg_14, arg_9,
                        arg_15)
        logger.info("Filtering BAM file according to saved contigs")
        filter_bam(arg_9, arg_4, arg_14, arg_16)
    # Could not filter the assembly as it would drop below acceptable
    # length levels. Copy the original assembly to the output assembly file
    # for compliance with the output channel
    else:
        shutil.copy(arg_1, arg_15)
        shutil.copy(arg_4, arg_16)
        shutil.copy(arg_4 + ".bai", arg_16 + ".bai")

    with open(".status", "w") as status_fh:
        status_fh.write("pass")


def Func(arg_0):
    """Convers a CamelCase string into a snake_case one

    Parameters
    ----------
    name : str
        An arbitrary string that may be CamelCase

    Returns
    -------
    str
        The input string converted into snake_case

    """
    arg_1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', arg_0)
    return re.sub('([a-z0-9])([A-Z])', r'\1_\2', arg_1).lower()