<commit_before># Copyright (c) 2013, Preferred Infrastructure, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#     * Redistributions of source code must retain the above copyright notice,
#       this list of conditions and the following disclaimer.
#
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

import functools
import itertools
import json
import numpy.random
import types

import maflib.core

def rule(callback_body):
    """Decorator to define a rule function that takes parameters and arguments
    interchangeably.

    When one defines a rule that takes some parameters or arguments, he/she is
    recommended to use :py:func:`rule` decorator to define it. The main reason
    is that it should be decided by users of the rule whether an argument is
    contained into task parameter or not, since it is decided by the design of
    his/her experiment, not by the design of the rule.

    usage:

    .. code-block:: python

       @maflib.util.rule
       def my_rule(task):
           do something with parameters 'a' and 'b'

       def build(exp):
           # indicate by argument
           exp(target='t', rule=my_rule(a=1, b=2))

           # indicate by parameter
           exp(target='s', parameters=[{'a': 1, 'b': 2}], rule=my_rule())

           # mixed usage
           exp(target='u', parameters=[{'a': 1}], rule=my_rule(b=2))

           # if no arguments are used, parens can be omitted
           exp(target='r', parameters=[{'a': 1, 'b': 2}], rule=my_rule)

    :param ``function`` callback_body: A function that receives a task instance
        and does its own work. It is almost same as a usual task function; the
        only different thing is that parameter of the given task is expanded by
        the arguments as the above example.
    :return: A task generator function.
    :rtype: ``function``

    """
    @functools.wraps(callback_body)
    def rule_generator(**args):
        # When parens are omitted (i.e. like exp(..., rule=callback_body)),
        # rule_generator function itself is used as a rule function.
        if len(args) == 1 and isinstance(args[0], maflib.core.ExperimentTask):
            callback_body(args[0])
            return

        def impl(task):
            # Note that parameter is overwritten by args when some entries are
            # existing in both.
            task.parameter.update(args)
            callback_body(task)

        return maflib.core.Rule(impl, [callback_body, args])

    return rule_generator


def aggregator(callback_body):
    """Creates an aggregator using function ``callback_body`` independent from
    waf.

    This function creates a wrapper of given callback function that behaves as
    a rule of an aggregation task. It supposes that input files are represented
    by JSON files each of which is a flat JSON object (i.e. an object that does
    not contain any objects) or a JSON array of flat objects. The created rule
    first combines these JSON objects into an array of Python dictionaries, and
    then passes it to the user-defined callback body.

    There are two ways to write the result to the output node. First is to let
    ``callback_body`` return the content string to be written to the output
    node; then the rule automatically writes it to the output node. Second is
    to let ``callback_body`` write it using its second argument (called
    ``abspath``), which is the absolute path to the output node. In this case,
    ``callback_body`` **MUST** return None to suppress the automatic writing.

    This function is often used as a decorator.
    See :py:mod:`maflib.rules` or :py:mod:`maflib.plot` to get
    examples of ``callback_body``.

    :param callback_body: A function or a callable object that takes three
        arguments: ``values``, ``abspath``, and ``parameter``. ``values`` is an
        array of dictionaries that represents the content of input files.<commit_msg>Fix bug of rule decorator on no-paren usage<commit_after># Copyright (c) 2013, Preferred Infrastructure, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#     * Redistributions of source code must retain the above copyright notice,
#       this list of conditions and the following disclaimer.
#
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

import functools
import itertools
import json
import numpy.random
import types

import maflib.core

def rule(callback_body):
    """Decorator to define a rule function that takes parameters and arguments
    interchangeably.

    When one defines a rule that takes some parameters or arguments, he/she is
    recommended to use :py:func:`rule` decorator to define it. The main reason
    is that it should be decided by users of the rule whether an argument is
    contained into task parameter or not, since it is decided by the design of
    his/her experiment, not by the design of the rule.

    usage:

    .. code-block:: python

       @maflib.util.rule
       def my_rule(task):
           do something with parameters 'a' and 'b'

       def build(exp):
           # indicate by argument
           exp(target='t', rule=my_rule(a=1, b=2))

           # indicate by parameter
           exp(target='s', parameters=[{'a': 1, 'b': 2}], rule=my_rule())

           # mixed usage
           exp(target='u', parameters=[{'a': 1}], rule=my_rule(b=2))

           # if no arguments are used, parens can be omitted
           exp(target='r', parameters=[{'a': 1, 'b': 2}], rule=my_rule)

    :param ``function`` callback_body: A function that receives a task instance
        and does its own work. It is almost same as a usual task function; the
        only different thing is that parameter of the given task is expanded by
        the arguments as the above example.
    :return: A task generator function.
    :rtype: ``function``

    """
    @functools.wraps(callback_body)
    def rule_generator(*args, **kargs):
        # When parens are omitted (i.e. like exp(..., rule=callback_body)),
        # rule_generator function itself is used as a rule function.
        if args:
            callback_body(*args)
            return

        def impl(task):
            # Note that parameter is overwritten by args when some entries are
            # existing in both.
            task.parameter.update(kargs)
            callback_body(task)

        return maflib.core.Rule(impl, [callback_body, kargs])

    return rule_generator


def aggregator(callback_body):
    """Creates an aggregator using function ``callback_body`` independent from
    waf.

    This function creates a wrapper of given callback function that behaves as
    a rule of an aggregation task. It supposes that input files are represented
    by JSON files each of which is a flat JSON object (i.e. an object that does
    not contain any objects) or a JSON array of flat objects. The created rule
    first combines these JSON objects into an array of Python dictionaries, and
    then passes it to the user-defined callback body.

    There are two ways to write the result to the output node. First is to let
    ``callback_body`` return the content string to be written to the output
    node; then the rule automatically writes it to the output node. Second is
    to let ``callback_body`` write it using its second argument (called
    ``abspath``), which is the absolute path to the output node. In this case,
    ``callback_body`` **MUST** return None to suppress the automatic writing.

    This function is often used as a decorator.
    See :py:mod:`maflib.rules` or :py:mod:`maflib.plot` to get
    examples of ``callback_body``.

    :param callback_body: A function or a callable object that takes three
        arguments: ``values``, ``abspath``, and ``parameter``. ``values`` is an
        array of dictionaries that represents the content of input files.
{'fraction_non_alphanumeric': 0.07078494193031638, 'fraction_numerical': 0.0027032438926712053, 'mean_word_length': 3.216547066272689, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2523754', 'n_tokens_mistral': 2657, 'n_tokens_neox': 2495, 'n_words': 1281}