text:
"""
Regular Expression Preprocessor (REPP)
"""

from typing import (
    TYPE_CHECKING,
    Optional,
    Union,
    NamedTuple,
    List,
    Tuple,
    Dict,
    Set,
    Pattern,
    Match,
    Iterable,
    Iterator,
)
from sre_parse import parse_template
from pathlib import Path
from array import array
from itertools import takewhile
import warnings
import logging

# use regex library if available; otherwise warn
try:
    import regex as re
    re.DEFAULT_VERSION = re.V1
    _regex_available = True
except ImportError:
    import re  # type: ignore
    _regex_available = False

from delphin.util import PathLike
from delphin.tokens import YYToken, YYTokenLattice
from delphin.lnk import Lnk
from delphin.exceptions import PyDelphinException, PyDelphinWarning
# Default modules need to import the PyDelphin version
from delphin.__about__ import __version__  # noqa: F401

logger = logging.getLogger(__name__)

#: The tokenization pattern used if none is given in a REPP module.
DEFAULT_TOKENIZER = r'[ \t]+'

if TYPE_CHECKING:
    _CMap = array[int]  # characterization map
else:
    _CMap = array

# Mask values
_MASK_B = 1  # start of mask
_MASK_I = 2  # inside mask
_MASK_O = 0  # not masked

class REPPError(PyDelphinException):
    """Raised when there is an error in tokenizing with REPP."""

class REPPWarning(PyDelphinWarning):
    """Issued when REPP may not behave as expected."""

if not _regex_available:
    warnings.warn(
        "The 'regex' library is not installed, so some regular expression "
        "features may not work as expected. Install PyDelphin with the "
        "[repp] extra to include the 'regex' library.",
        REPPWarning)

class REPPResult(NamedTuple):
    """
    The final result of REPP application.

    Attributes:
        string (str): resulting string after all rules have applied
        startmap (:py:class:`array`): integer array of start offsets
        endmap (:py:class:`array`): integer array of end offsets
    """
    string: str
    startmap: _CMap
    endmap: _CMap

class REPPStep(NamedTuple):
    """
    A single rule application in REPP.

    Attributes:
        input (str): input string (prior to application)
        output (str): output string (after application)
        operation: operation performed
        applied (bool): `True` if the rule was applied
        startmap (:py:class:`array`): integer array of start offsets
        endmap (:py:class:`array`): integer array of end offsets
        mask (:py:class:`array`): integer array of mask indicators
    """
    input: str
    output: str
    operation: '_REPPOperation'
    applied: bool
    startmap: _CMap
    endmap: _CMap
    mask: _CMap  # BIO scheme, B=1, I=2, O=0

_Trace = Iterator[Union[REPPStep, REPPResult]]

class _REPPOperation:
    """
    The supertype of REPP groups and rules.

    This class defines the apply(), trace(), and tokenize() methods
    which are available in [_REPPRule], [_REPPGroup],
    [_REPPInternalGroup], and [REPP] instances.
    """

    def _apply(
        self,
        s: str,
        active: Set[str],
        mask: _CMap
    ) -> Iterator[REPPStep]:
        raise NotImplementedError()

class _REPPRule(_REPPOperation):
    """
    A REPP rewrite rule.

    The apply() method of this class works like re.sub() in Python's
    standard library, but it analyzes the replacement pattern in order
    to ensure that character positions in the resulting string can be
    traced back (as much as possible) to the original string.

    Args:
        pattern: the regular expression pattern to match
        replacement: the replacement template
    """
    def __init__(self, pattern: str, replacement: str):
        self.pattern = pattern
        self.replacement = replacement
        self._re = _compile(pattern)
        self._tracked, self._untracked = _get_segments(replacement, self._re)

    def __str__(self):
        return f'!{self.pattern}\t\t{self.replacement}'

    def _apply(
        self,
        s: str,
        active: Set[str],
        mask: _CMap
    ) -> Iterator[REPPStep]:
        logger.debug(' %s', self)

        applied = False
        ms = list(self._re.finditer(s))

        if ms:
            pos = 0  # current position in the original string
            shift = 0  # current original/target length difference
            parts: List[str] = []
            smap = array('i', [0])
            emap = array('i', [0])
            new_mask = array('i', [_MASK_O])

            for m in ms:
                sub, _smap, _emap, _mask, delta, blocked = _process_match(
                    m, mask, shift, self._tracked, self._untracked
                )
                if blocked:
                    continue
                applied = True

                start = m.start()

                # copy up to point of match
                if pos < start:
                    _copy_part(s[pos:start], shift, parts, smap, emap)
                    new_mask.extend(mask[pos+1:start+1])

                parts.append(sub)
                smap.extend(_smap)
                emap.extend(_emap)
                new_mask.extend(_mask)
                shift += delta
                pos = m.end()

            if pos < len(s):
                _copy_part(s[pos:], shift, parts, smap, emap)
                new_mask.extend(mask[pos+1:len(s)+1])

            smap.append(shift)
            emap.append(shift - 1)
            new_mask.append(_MASK_O)
            mask = new_mask
            o = ''.join(parts)

        else:
            o = s
            smap = _zeromap(o)
            emap = _zeromap(o)

        yield REPPStep(s, o, self, applied, smap, emap, mask)

class _REPPMask(_REPPOperation):
    """
    A REPP masking rule.

    When a mask is applied and matches a substring, the substring is
    blocked from further modification.

    Args:
        pattern: the regular expression pattern to match
    """
    def __init__(self, pattern: str):
        self.pattern = pattern
        self._re = _compile(pattern)

    def __str__(self):
        return f'={self.pattern}'

    def _apply(
        self,
        s: str,
        active: Set[str],
        mask: _CMap
    ) -> Iterator[REPPStep]:
        logger.debug(' %s', self)
        newmask = array('i', mask)  # make a copy
        for m in self._re.finditer(s):
            start = m.start() + 1
            newmask[start] = max(_MASK_B, mask[start])
            for i in range(start + 1, m.end() + 1):
                newmask[i] = _MASK_I
        yield REPPStep(s, s, self, True, _zeromap(s), _zeromap(s), newmask)

class _REPPGroup(_REPPOperation):
    def __init__(
        self, operations: List[_REPPOperation] = None, name: str = None
    ):
        if operations is None:
            operations = []
        self.operations: List[_REPPOperation] = operations
        self.name = name
        self._loaded = False

    def __repr__(self):
        name = '("{}") '.format(self.name) if self.name is not None else ''
        return '<{} object {}at {}>'.format(
            type(self).__name__, name, id(self)
        )

    def _apply(
        self,
        s: str,
        active: Set[str],
        mask: _CMap
    ) -> Iterator[REPPStep]:
        o = s
        applied = False
        for operation in self.operations:
            for step in operation._apply(o, active, mask):
                yield step
                o = step.output
                mask = step.mask
                applied |= step.applied

        yield REPPStep(s, o, self, applied, _zeromap(o), _zeromap(o), mask)

class _REPPInternalGroup(_REPPGroup):
    def __str__(self):
        return f'Internal group #{self.name}'

    def _apply(
        self,
        s: str,
        active: Set[str],
        mask: _CMap
    ) -> Iterator[REPPStep]:
        logger.debug('>%s', self.name)
        i = 0
        prev = s
        step = None  # in case _REPPGroup._apply() ever yields nothing
        for step in super()._apply(prev, active, mask):
            yield step
            mask = step.mask
        while step and prev != step.output:
            i += 1
            prev = step.output
            for step in super()._apply(prev, active, mask):
                yield step
                mask = step.mask

        logger.debug('>%s (done; iterated %d time(s))', self.name, i)

class REPP(_REPPGroup):
    """
    A Regular Expression Pre-Processor (REPP).

    The normal way to create a new REPP is to read a .rpp file via the
    :meth:`from_file` classmethod. For REPPs that are defined in code,
    there is the :meth:`from_string` classmethod, which parses the same
    definitions but does not require file I/O. Both methods, as does
    the class's `__init__()` method, allow for pre-loaded and named
    external *modules* to be provided, which allow for external group
    calls (also see :meth:`from_file` or implicit module loading). By
    default, all external submodules are deactivated, but they can be
    activated by adding the module names to *active* or, later, via the
    :meth:`activate` method.

    A third classmethod, :meth:`from_config`, reads a PET-style
    configuration file (e.g., `repp.set`) which may specify the
    available and active modules, and therefore does not take the
    *modules* and *active* parameters.

    Args:
        name (str, optional): the name assigned to this module
        modules (dict, optional): a mapping from identifiers to REPP
            modules
        active (iterable, optional): an iterable of default module
            activations
    """

    def __init__(
        self,
        operations: List[_REPPOperation] = None,
        name: str = None,
        modules: Dict[str, 'REPP'] = None,
        active: Iterable[str] = None
    ):
        super().__init__(operations=operations, name=name)
        self.info: Optional[str] = None
        self.tokenize_pattern: Optional[str] = None

        if modules is None:
            modules = {}
        self.modules = dict(modules)
        for modname, mod in self.modules.items():
            mod.name = modname
        self.active: Set[str] = set()
        if active is None:
            active = []
        for modname in active:
            self.activate(modname)

    @classmethod
    def from_config(cls, path: PathLike, directory=None):
        """
        Instantiate a REPP from a PET-style `.set` configuration file.

        The *path* parameter points to the configuration file.
        Submodules are loaded from *directory*. If *directory* is not
        given, it is the directory part of *path*.

        Args:
            path (str): the path to the REPP configuration file
            directory (str, optional): the directory in which to search
                for submodules
        """
        path = Path(path).expanduser()
        if not path.is_file():
            raise REPPError(f'REPP config file not found: {path!s}')
        confdir = path.parent

        # TODO: can TDL parsing be repurposed for this variant?
        conf = path.read_text(encoding='utf-8')
        conf = re.sub(r';.*', '', conf).replace('\n', ' ')
        m = re.search(
            r'repp-modules\s*:=\s*((?:[-\w]+\s+)*[-\w]+)\s*\.', conf)
        t = re.search(
            r'repp-tokenizer\s*:=\s*([-\w]+)\s*\.', conf)
        a = re.search(
            r'repp-calls\s*:=\s*((?:[-\w]+\s+)*[-\w]+)\s*\.', conf)
        # f = re.search(
        #     r'format\s*:=\s*(\w+)\s*\.', conf)
        d = re.search(
            r'repp-directory\s*:=\s*(.*)\.\s*$', conf)

        if m is None:
            raise REPPError('repp-modules option must be set')
        if t is None:
            raise REPPError('repp-tokenizer option must be set')

        # mods = m.group(1).split()
        tok = t.group(1).strip()
        active = a.group(1).split() if a is not None else None
        # fmt = f.group(1).strip() if f is not None else None

        if directory is None:
            if d is not None:
                directory = d.group(1).strip(' "')
            elif confdir.joinpath(tok + '.rpp').is_file():
                directory = confdir
            elif confdir.joinpath('rpp', tok + '.rpp').is_file():
                directory = confdir.joinpath('rpp')
            elif confdir.joinpath('../rpp', tok + '.rpp').is_file():
                directory = confdir.joinpath('../rpp')
            else:
                raise REPPError('Could not find a suitable REPP directory.')

        # ignore repp-modules and format?
        return REPP.from_file(
            directory.joinpath(tok + '.rpp'),
            directory=directory,
            active=active
        )

    @classmethod
    def from_file(cls, path, directory=None, modules=None, active=None):
        """
        Instantiate a REPP from a `.rpp` file.

        The *path* parameter points to the top-level module. Submodules
        are loaded from *directory*. If *directory* is not given, it is
        the directory part of *path*.

        A REPP module may utilize external submodules, which may be
        defined in two ways. The first method is to map a module name
        to an instantiated REPP instance in *modules*. The second
        method assumes that an external group call `>abc` corresponds
        to a file `abc.rpp` in *directory* and loads that file. The
        second method only happens if the name (e.g., `abc`) does not
        appear in *modules*. Only one module may define a tokenization
        pattern.

        Args:
            path (str): the path to the base REPP file to load
            directory (str, optional): the directory in which to search
                for submodules
            modules (dict, optional): a mapping from identifiers to
                REPP modules
            active (iterable, optional): an iterable of default module
                activations
        """
        path = Path(path).expanduser()
        name, directory, lines = _read_file(path, directory)
        r = cls(name=name, modules=modules, active=active)
        _parse_repp_module(lines, r, directory)
        return r

    @classmethod
    def from_string(cls, s, name=None, modules=None, active=None):
        """
        Instantiate a REPP from a string.

        Args:
            name (str, optional): the name of the REPP module
            modules (dict, optional): a mapping from identifiers to
                REPP modules
            active (iterable, optional): an iterable of default module
                activations
        """
        r = cls(name=name, modules=modules, active=active)
        _parse_repp_module(s.splitlines(), r, None)
        return r

    def activate(self, mod: str) -> None:
        """
        Set external module *mod* to active.
        """
        self.active.add(mod)

    def deactivate(self, mod: str) -> None:
        """
        Set external module *mod* to inactive.
        """
        if mod in self.active:
            self.active.remove(mod)

    def _apply(
        self,
        s: str,
        active: Set[str],
        mask: _CMap
    ) -> Iterator[REPPStep]:
        if self.name in active:
            logger.info('>%s', self.name)
            for step in super()._apply(s, active, mask):
                yield step
                mask = step.mask
            logger.debug('>%s (done)', self.name)
        else:
            logger.debug('>%s (inactive)', self.name)

    def apply(self, s: str, active: Iterable[str] = None) -> REPPResult:
        """
        Apply the REPP's rewrite rules to the input string *s*.

        Args:
            s (str): the input string to process
            active (optional): a collection of external module names
                that may be applied if called
        Returns:
            a :class:`REPPResult` object containing the processed
                string and characterization maps
        """
        logger.info('apply(%r)', s)
        active = self.active if active is None else set(active)
        result = last(self._trace(s, active, False))
        return result

    def trace(
        self, s: str, active: Iterable[str] = None, verbose: bool = False
    ) -> _Trace:
        """
        Rewrite string *s* like `apply()`, but yield each rewrite step.

        Args:
            s (str): the input string to process
            active (optional): a collection of external module names
                that may be applied if called
            verbose (bool, optional): if `False`, only output rules or
                groups that matched the input
        Yields:
            a :class:`REPPStep` object for each intermediate rewrite
                step, and finally a :class:`REPPResult` object after
                the last rewrite
        """
        logger.info('trace(%r)', s)
        active = self.active if active is None else set(active)
        yield from self._trace(s, active, verbose)

    def _trace(
        self, s: str, active: Set[str], verbose: bool
    ) -> _Trace:
        startmap = _zeromap(s)
        endmap = _zeromap(s)
        mask = _zeromap(s)
        # initial boundaries
        startmap[0] = 1
        endmap[-1] = -1
        step = None
        for step in super()._apply(s, active, mask):
            if step.applied or verbose:
                yield step
            if step.applied:
                startmap = _mergemap(startmap, step.startmap)
                endmap = _mergemap(endmap, step.endmap)
        if step is not None:
            s = step.output
        yield REPPResult(s, startmap, endmap)

    def tokenize(
        self, s: str, pattern: str = None, active: Iterable[str] = None
    ) -> YYTokenLattice:
        """
        Rewrite and tokenize the input string *s*.

        Args:
            s (str): the input string to process
            pattern (str, optional): the regular expression pattern on
                which to split tokens; defaults to `[ \t]+`
            active (optional): a collection of external module names
                that may be applied if called
        Returns:
            a :class:`~delphin.tokens.YYTokenLattice` containing the
            tokens and their characterization information
        """
        logger.info('tokenize(%r, %r)', s, pattern)
        if pattern is None:
            if self.tokenize_pattern is None:
                pattern = DEFAULT_TOKENIZER
            else:
                pattern = self.tokenize_pattern
        active = self.active if active is None else set(active)
        result = last(self._trace(s, active, False))
        return self.tokenize_result(result, pattern=pattern)

    def tokenize_result(
        self, result: REPPResult, pattern: str = DEFAULT_TOKENIZER
    ) -> YYTokenLattice:
        """
        Tokenize the result of rule application.

        Args:
            result: a :class:`REPPResult` object
            pattern (str, optional): the regular expression pattern on
                which to split tokens; defaults to `[ \t]+`
        Returns:
            a :class:`~delphin.tokens.YYTokenLattice` containing the
            tokens and their characterization information
        """
        logger.info('tokenize_result(%r, %r)', result, pattern)
        tokens = [
            YYToken(id=i, start=i, end=(i + 1),
                    lnk=Lnk.charspan(tok[0], tok[1]),
                    form=tok[2])
            for i, tok in enumerate(_tokenize(result, pattern))
        ]
        return YYTokenLattice(tokens)

def _compile(pattern: str) -> Pattern[str]:
    try:
        return re.compile(pattern)
    except re.error:
        if _regex_available and '[' in pattern or ']' in pattern:
            warnings.warn(
                'Invalid regex in REPP; see warning log for details.',
                REPPWarning)
            logger.warn("Possible unescaped brackets in %r; "
                        "attempting to parse in compatibility mode",
                        pattern)
            return re.compile(pattern, flags=re.V0)
        else:
            raise

def _get_segments(replacement: str, _re):
    # parse_template() is an undocumented function in the standard library
    groups, literals = parse_template(replacement, _re)
    # literals is a list of strings or None (group position);
    # groups is a list of (i, g) where i is the index in literals
    # and g is the capturing group number.

    # first determine the last trackable group, where trackable
    # segments are transparent for characterization. For PET behavior,
    # these must appear in strictly increasing order with no gaps
    last_trackable = 0
    for expected, (i, grp) in zip(range(1, len(groups)+1), groups):
        if grp == expected:
            last_trackable = i + 1  # +1 for slice end

    # we can also combine groups and literals into a single list of
    # (literal, None) or (None, group) pairs for convenience
    group_map = dict(groups)
    segments: List[Tuple[Optional[str], Optional[int]]] = [
        (literal, group_map.get(i))
        for i, literal
        in enumerate(literals)
    ]
    # Divide the segments into trackable/untrackable
    return segments[:last_trackable], segments[last_trackable:]

def last(steps: _Trace) -> REPPResult:
    for step in steps:
        pass
    assert isinstance(step, REPPResult)
    return step

def _zeromap(s: str) -> _CMap:
    return array('i', [0] * (len(s) + 2))

def _mergemap(map1: _CMap, map2: _CMap) -> _CMap:
    """
    Positions in map2 have an integer indicating the relative shift to
    the equivalent position in map1. E.g., the i'th position in map2
    corresponds to the i + map2[i] position in map1.
    """
    merged = array('i', [0] * len(map2))
    for i, shift in enumerate(map2):
        newshift = shift + map1[i + shift]
        merged[i] = newshift
    return merged

def _copy_part(
    s: str,
    shift: int,
    parts: List[str],
    smap: _CMap,
    emap: _CMap
) -> None:
    parts.append(s)
    map_part = [shift] * len(s)
    smap.extend(map_part)
    emap.extend(map_part)

def _insert_part(
    s: str,
    width: int,
    shift: int,
    parts: List[str],
    smap: _CMap,
    emap: _CMap
) -> None:
    parts.append(s)
    endshift = shift - len(s)
    _width = width - 1
    smap.extend(range(shift, endshift, -1))
    emap.extend(range(shift + _width, endshift + _width, -1))

def _process_match(
    m: Match[str],
    prev_mask: _CMap,
    shift: int,
    tracked,
    untracked
) -> Tuple[str, _CMap, _CMap, _CMap, int, bool]:
    parts: List[str] = []
    smap = array('i', [])
    emap = array('i', [])
    mask = array('i', [])
    delta = 0
    blocked = False

    if tracked or untracked:
        start = m.start()
        # for literals, the end is the start of the next backreference
        end = next((m.start(g) for _, g in tracked if g), m.end())

        for literal, group in tracked:
            if literal is None:
                literal = m.group(group) or ''
                _copy_part(literal, shift + delta, parts, smap, emap)
                mask.extend(prev_mask[(start+1):(start+len(literal)+1)])
                end = m.start(group+1) if group < m.lastindex else m.end()
            else:
                # block if overlap with mask
                if any(prev_mask[start+1:end+1]):
                    blocked = True
                    break
                width = end - start
                litlen = len(literal)
                _insert_part(literal, width, shift + delta, parts, smap, emap)
                mask.extend([_MASK_O] * litlen)
                delta += width - litlen

            start = end

        if untracked:
            # block if untracked overlaps with mask, including backreferences
            if (any(prev_mask[start+1:m.end()+1])
                or any(any(prev_mask[m.start(grp)+1:m.end(grp)+1])
                       for lit, grp in untracked if grp)):
                blocked = True
            else:
                # untracked segments can be collapsed into one substring
                literal = ''.join(
                    m.group(group) or '' if literal is None else literal
                    for literal, group in untracked
                )
                width = m.end() - start
                litlen = len(literal)
                _insert_part(literal, width, shift+delta, parts, smap, emap)
                mask.extend([_MASK_O] * litlen)
                delta += width - litlen
    else:
        # the replacement is empty (match is deleted)
        delta = m.end() - m.start()

    substring = ''.join(parts)
    if not blocked:
        blocked = _check_mask(substring, m, smap, emap, mask, prev_mask)

    return substring, smap, emap, mask, delta, blocked

def _check_mask(
    s: str,
    m: Match[str],
    smap: _CMap,
    emap: _CMap,
    mask: _CMap,
    prev_mask: _CMap
) -> bool:
    """Returns True if any masked material has changed.

    There are three contexts for mask checks:

      1. The first character in the match is a mask continuation
      2. The first character after the end of the match is a mask continuation
      3. The whole mask is contained within the match

    For (1) and (2), the mask must not change nor move around. For (1)
    it cannot change but it can move.

    """
    start, end = m.span()
    mstart, mend = start + 1, end + 1
    orig = m.group(0)
    # no masked material; no problem
    if not any(prev_mask[mstart:mend]):
        return False
    # whether the final mask is fixed depends on the next mask value;
    # check now as the _make_mask_info() function won't see it
    end_fixed = prev_mask[mend] == _MASK_I
    # prev/next left, middle, right
    pl, pm, pr = _make_mask_info(orig, prev_mask[mstart:mend], end_fixed)
    nl, nm, nr = _make_mask_info(s, mask, end_fixed)

    # check fixed start/end masks
    if pl != nl or pr != nr:
        return True
    # other masks just need to be present in equal number
    for substr in set(pm).union(nm):
        if pm.get(substr, 0) != nm.get(substr, 0):
            return True

    return False

def _make_mask_info(
    s: str,
    mask: _CMap,
    end_fixed: bool
) -> Tuple[str, Dict[str, int], str]:
    if not mask:
        return '', {}, ''
    left = s[:_get_mask_len(mask, 0)] if mask[0] == _MASK_I else ''
    right = s[-_get_mask_len(mask[::-1], 0) or len(s):] if end_fixed else ''
    middle: Dict[str, int] = {}
    i = len(left)
    j = len(s) - len(right)
    while i < j:
        if mask[i] == _MASK_B:
            mlen = _get_mask_len(mask, i+1) + 1
            substr = s[i:mlen]
            if substr not in middle:
                middle[substr] = 1
            else:
                middle[substr] += 1
            i += mlen
        else:
            i += 1
    return left, middle, right

def _get_mask_len(mask: _CMap, i: int):
    return sum(1 for _ in takewhile(lambda v: v == _MASK_I, mask[i:]))

def _tokenize(result: REPPResult, pattern: str) -> List[Tuple[int, int, str]]:
    s, sm, em = result  # unpack for efficiency in loop
    toks = []
    pos = 0
    for m in re.finditer(pattern, result.string):
        if pos < m.start():
            toks.append((pos + sm[pos + 1],
                         m.start() + em[m.start()],
                         s[pos:m.start()]))
        pos = m.end()
    if pos < len(s):
        toks.append((pos + sm[pos + 1],
                     len(s) + em[len(s)],
                     s[pos:]))
    return toks

def _read_file(
    path: Path,
    directory: Optional[Path]
) -> Tuple[str, Path, List[str]]:
    if directory is not None:
        directory = Path(directory).expanduser()
    else:
        directory = path.parent
    name = path.with_suffix('').name
    lines = _repp_lines(path)
    return name, directory, lines

def _repp_lines(path: Path) -> List[str]:
    if not path.is_file():
        raise REPPError(f'REPP file not found: {path!s}')
    return path.read_text(encoding='utf-8').splitlines()

def _parse_repp_module(
    lines: List[str],
    r: REPP,
    directory: Path
) -> None:
    r._loaded = True

    operations: List[_REPPOperation] = r.operations
    stack: List[List[_REPPOperation]] = [operations]
    internal_groups: Dict[str, _REPPInternalGroup] = {}

    while lines:
        line = lines.pop(0)
        if line.startswith(';') or line.strip() == '':
            continue  # skip comments and empty lines

        operator, operand = line[0], line[1:].rstrip()

        if operator == '!':
            # don't use operand because it was rstripped; use line[1:]
            operations.append(_parse_rewrite_rule(line[1:]))

        elif operator == '<':
            fn = directory.joinpath(operand)
            lines = _repp_lines(fn) + lines

        elif operator == '>':
            operations.append(
                _handle_group_call(operand, internal_groups, r, directory)
            )

        elif operator == '=':
            # don't use operand because it was rstripped; use line[1:]
            operations.append(_REPPMask(line[1:]))

        elif operator == '#':
            _handle_internal_group(operand, internal_groups, stack)
            operations = stack[-1]

        elif operator == ':':
            _handle_tokenization_pattern(operand, r, len(stack) > 1)

        elif operator == '@':
            _handle_metainfo_declaration(operand, r, len(stack) > 1)

        else:
            raise REPPError(f'Invalid declaration: {line}')

    _verify_internal_groups(internal_groups)

def _parse_rewrite_rule(operand: str) -> _REPPRule:
    match = re.match(r'([^\t]+)\t+(.*)', operand)
    if match is None:
        raise REPPError(f'Invalid rewrite rule: !{operand}')
    return _REPPRule(match.group(1), match.group(2))

def _handle_group_call(
    operand: str,
    internal_groups,
    r: REPP,
    directory: Optional[Path],
) -> _REPPGroup:
    if operand.isdigit():
        if operand not in internal_groups:
            internal_groups[operand] = _REPPInternalGroup(operations=[],
                                                          name=operand)
        return internal_groups[operand]
    elif not operand:
        raise REPPError('Missing group name')
    else:
        if operand not in r.modules:
            r.modules[operand] = REPP(name=operand, modules=r.modules)
        mod = r.modules[operand]
        if not mod._loaded:
            if directory is None:
                raise REPPError('Cannot implicitly load modules if '
                                'a directory is not given.')
            modpath = directory / (operand + '.rpp')
            _parse_repp_module(_repp_lines(modpath), mod, directory)
        return mod

def _handle_internal_group(operand, internal_groups, stack) -> None:
    if operand.isdigit():
        if operand not in internal_groups:
            internal_groups[operand] = _REPPInternalGroup(operations=[],
                                                          name=operand)
        ig = internal_groups[operand]
        if ig._loaded:
            raise REPPError(f'Internal group name already defined: {operand}')
        ig._loaded = True
        stack.append(ig.operations)
    elif operand == '':
        stack.pop()
    else:
        raise REPPError('Invalid internal group name: ' + operand)

def _handle_tokenization_pattern(
    operand: str,
    r: REPP,
    in_internal_group: bool,
) -> None:
    if in_internal_group:
        raise REPPError('tokenization pattern defined in internal group')
    if r.tokenize_pattern is not None:
        raise REPPError('Only one tokenization pattern (:) may be defined.')
    r.tokenize_pattern = operand

def _handle_metainfo_declaration(
    operand: str,
    r: REPP,
    in_internal_group: bool,
) -> None:
    if in_internal_group:
        raise REPPError('meta-info declaration defined in internal group')
    if r.info is not None:
        raise REPPError('Only one meta-info declaration (@) may be defined.')
    r.info = operand

def _verify_internal_groups(internal_groups):
    for grpname, grp in internal_groups.items():
        if not grp._loaded:
            raise REPPError(f'internal group not defined: #{grpname}')


text:
import React, { useState } from 'react';
import { makeStyles } from '@material-ui/core';
import { useHistory } from 'react-router';
import { Container } from './Container';
import { AppBar } from './AppBar';
import { useAuth } from './AuthContext';
import { Drawer } from './Drawer';

const drawerWidth = 240;

const useStyles = makeStyles((theme) => ({
  root: {
    display: 'flex',
  },
  menuButton: {
    marginRight: theme.spacing(2),
    [theme.breakpoints.up('sm')]: {
      display: 'none',
    },
  },
  title: {
    flexGrow: 1,
  },
  drawer: {
    [theme.breakpoints.up('sm')]: {
      width: drawerWidth,
      flexShrink: 0,
    },
  },
}));

interface AuthenticatedContainerProps {
  children: React.ReactNode;
}

export const AuthenticatedContainer = ({ children }: AuthenticatedContainerProps) => {
  const classes = useStyles();
  const history = useHistory();
  const { logout } = useAuth();
  const [mobileOpen, setMobileOpen] = useState(false);

  const onDrawerToggle = () => {
    setMobileOpen(!mobileOpen);
  };

  const onLogout = async () => {
    await logout();
    history.push('/login');
  };

  return (
    <div className={classes.root}>
      <nav className={classes.drawer}>
        <AppBar onDrawerToggle={onDrawerToggle} onLogout={onLogout} />
      </nav>

      <Drawer open={mobileOpen} onDrawerToggle={onDrawerToggle} onLogout={onLogout} />

      <Container>{children}</Container>
    </div>
  );
};

redpajama_set_name: RedPajamaGithub


text:
Family Charades
===============

Child-friendly Charades webapp in which the phrases to guess are represented by pictures


text:
// Copyright (c) 2010-2015, Shane Jarych
// 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.
//
// * Neither the name of lib65oh2 nor the names of its contributors may be used
//   to endorse or promote products derived from this software without specific
//   prior written permission.
//
// 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.

#ifndef INDEXEDINDIRECTFETCHPOLICY_H
#define INDEXEDINDIRECTFETCHPOLICY_H

#include "Core.h"
#include "MemoryController.h"
#include "OperandFetchPolicy.h"
#include "CPUTypes.h"
#include "BitUtils.h"

/**
 * @class  IndexedIndirectFetchPolicy
 * @brief  A FetchPolicy implementation for indexed indirect addressing
 *
 * @tparam FetchedDataType  The type of the data fetched
 *
 * @author Shane Jarych
 */
template <class FetchedDataType>
class IndexedIndirectFetchPolicy
    : public OperandFetchPolicy<data8_t>
{
public:
    IndexedIndirectFetchPolicy()
        : OperandFetchPolicy<data8_t>()
    {
    }

    /**
     * @brief  Using the operand as an offset to the operand, fetches the data
     *         using indexed indirect addressing.
     *
     * @param[in]   core         The Core reference for obtaining cpu values
     * @param[in]   memory       The MemoryController reference for obtaining data
     * @param[out]  fetchedData  The fetched data
     * @return                   The number of cycles to perform the operation
     */
    int fetch(Core& core, MemoryController& memory, FetchedDataType** fetchedData)
    {
        // fetch the 8bit operand
        const data8_t* operand = fetchOperand(core, memory);

        addr16_t calcAddr = 0;

        const addr8_t lowAddr = *operand + core.getX();
        const addr8_t lowAddrData = *memory.get(lowAddr);

        BitUtils::setAddr8(calcAddr, lowAddrData, BitUtils::LOW);
        BitUtils::setAddr8(calcAddr, *memory.get(lowAddr + 1), BitUtils::HIGH);

        *fetchedData = memory.get(calcAddr);

        return 0;
    }
};

#endif // INDEXEDINDIRECTFETCHPOLICY_H


text:
package com.oose.chessgame.darkchessman;

import android.graphics.Bitmap;

import com.oose.darkchess.DarkChessBoard;
import com.oose.darkchess.DarkChessMan;
import com.oose.factory.DarkChessPictureList;
import com.oose.prototype.ChessMan;

public class BlackCar extends DarkChessMan {

  private static final long serialVersionUID = 2267427798908938020L;

  public BlackCar(int x, int y, DarkChessBoard board) {
    super(x, y, ChessMan.BLACK, board, 4);
  }

  public BlackCar(BlackCar bc) {
    super(bc);
  }

  @Override
  public BlackCar clone() throws CloneNotSupportedException {
    return new BlackCar(this);
  }

  @Override
  public Bitmap getIcon() {

    if (this.visible)
      return DarkChessPictureList.getIcon(this.getClass().getName());

    else
      return DarkChessPictureList.getIcon("com.oose.chessgame.darkchessman.UnFlip");
  }

  @Override
  public Bitmap getSelectedIcon() {
    if (this.visible)
      return DarkChessPictureList.getIcon(this.getClass().getName() + "SELECTED");
    else
      return DarkChessPictureList.getIcon("com.oose.chessgame.darkchessman.UnFlip" + "SELECTED");
  }
}


text:
module JsTestDriver
  module CLI

    class StartServer

      attr_reader :jstd_jar_command, :runner

      def initialize(jstd_jar_command, runner)
        @jstd_jar_command = jstd_jar_command
        @runner = runner
      end

      def run(opts = {})
        runner.run(jstd_jar_command.start_server.to_s)
      end

    end

  end
end


text:
Writing Rsyslog Output Plugins
==============================

This page is the begin of some developer documentation for writing
output plugins. Doing so is quite easy (and that was a design goal), but
there currently is only sparse documentation on the process available. I
was tempted NOT to write this guide here because I know I will most
probably not be able to write a complete guide.

However, I finally concluded that it may be better to have some
information and pointers than to have nothing.

Getting Started and Samples
---------------------------

The best to get started with rsyslog plugin development is by looking at
existing plugins. All that start with "om" are **o**\ utput
**m**\ odules. That means they are primarily thought of being message
sinks. In theory, however, output plugins may aggergate other
functionality, too. Nobody has taken this route so far so if you would
like to do that, it is highly suggested to post your plan on the rsyslog
mailing list, first (so that we can offer advise).

The rsyslog distribution tarball contains the omstdout plugin which is
extremely well targeted for getting started. Just note that this plugin
itself is not meant for production use. But it is very simplistic and so
a really good starting point to grasp the core ideas.

In any case, you should also read the comments in
./runtime/module-template.h. Output plugins are build based on a large
set of code-generating macros. These macros handle most of the plumbing
needed by the interface. As long as no special callback to rsyslog is
needed (it typically is not), an output plugin does not really need to
be aware that it is executed by rsyslog. As a plug-in programmer, you
can (in most cases) "code as usual". However, all macros and entry
points need to be provided and thus reading the code comments in the
files mentioned is highly suggested.

For testing, you need rsyslog's debugging support. Some useful
information is given in "`troubleshooting rsyslog <troubleshoot.html>`_
from the doc set.

Special Topics
--------------

Threading
~~~~~~~~~

Rsyslog uses massive parallel processing and multithreading. However, a
plugin's entry points are guaranteed to be never called concurrently
**for the same action**. That means your plugin must be able to be
called concurrently by two or more threads, but you can be sure that for
the same instance no concurrent calls happen. This is guaranteed by the
interface specification and the rsyslog core guards against multiple
concurrent calls. An instance, in simple words, is one that shares a
single instanceData structure.

So as long as you do not mess around with global data, you do not need
to think about multithreading (and can apply a purely sequential
programming methodology).

Please note that during the configuration parsing stage of execution,
access to global variables for the configuration system is safe. In that
stage, the core will only call sequentially into the plugin.

Getting Message Data
~~~~~~~~~~~~~~~~~~~~

The doAction() entry point of your plugin is provided with messages to
be processed. It will only be activated after filtering and all other
conditions, so you do not need to apply any other conditional but can
simply process the message.

Note that you do NOT receive the full internal representation of the
message object. There are various (including historical) reasons for
this and, among others, this is a design decision based on security.

Your plugin will only receive what the end user has configured in a
$template statement. However, starting with 4.1.6, there are two ways of
receiving the template content. The default mode, and in most cases
sufficient and optimal, is to receive a single string with the expanded
template. As I said, this is usually optimal, think about writing things
to files, emailing content or forwarding it.

The important philosophy is that a plugin should **never** reformat any
of such strings - that would either remove the user's ability to fully
control message formats or it would lead to duplicating code that is
already present in the core. If you need some formatting that is not yet
present in the core, suggest it to the rsyslog project, best done by
sending a patch ;), and we will try hard to get it into the core (so
far, we could accept all such suggestions - no promise, though).

If a single string seems not suitable for your application, the plugin
can also request access to the template components. The typical use case
seems to be databases, where you would like to access properties via
specific fields. With that mode, you receive a char \*\* array, where
each array element points to one field from the template (from left to
right). Fields start at array index 0 and a NULL pointer means you have
reached the end of the array (the typical Unix "poor man's linked list
in an array" design). Note, however, that each of the individual
components is a string. It is not a date stamp, number or whatever, but
a string. This is because rsyslog processes strings (from a high-level
design look at it) and so this is the natural data type. Feel free to
convert to whatever you need, but keep in mind that malformed packets
may have lead to field contents you'd never expected...

If you like to use the array-based parameter passing method, think that
it is only available in rsyslog 4.1.6 and above. If you can accept that
your plugin will not be working with previous versions, you do not need
to handle pre 4.1.6 cases. However, it would be "nice" if you shut down
yourself in these cases - otherwise the older rsyslog core engine will
pass you a string where you expect the array of pointers, what most
probably results in a segfault. To check whether or not the core
supports the functionality, you can use this code sequence:

::

    BEGINmodInit()
        rsRetVal localRet;
        rsRetVal (*pomsrGetSupportedTplOpts)(unsigned long *pOpts);
        unsigned long opts;
        int bArrayPassingSupported;     /* does core support template passing as an array? */
    CODESTARTmodInit
        *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
    CODEmodInit_QueryRegCFSLineHdlr
        /* check if the rsyslog core supports parameter passing code */
        bArrayPassingSupported = 0;
        localRet = pHostQueryEtryPt((uchar*)"OMSRgetSupportedTplOpts", &pomsrGetSupportedTplOpts);
        if(localRet == RS_RET_OK) {
            /* found entry point, so let's see if core supports array passing */
            CHKiRet((*pomsrGetSupportedTplOpts)(&opts));
            if(opts & OMSR_TPL_AS_ARRAY)
                bArrayPassingSupported = 1;
        } else if(localRet != RS_RET_ENTRY_POINT_NOT_FOUND) {
            ABORT_FINALIZE(localRet); /* Something else went wrong, what is not acceptable */
        }
        DBGPRINTF("omstdout: array-passing is %ssupported by rsyslog core.\n", bArrayPassingSupported ? "" : "not ");

        if(!bArrayPassingSupported) {
            DBGPRINTF("rsyslog core too old, shutting down this plug-in\n");
            ABORT_FINALIZE(RS_RET_ERR);
        }

The code first checks if the core supports the OMSRgetSupportedTplOpts()
API (which is also not present in all versions!) and, if so, queries the
core if the OMSR\_TPL\_AS\_ARRAY mode is supported. If either does not
exits, the core is too old for this functionality. The sample snippet
above then shuts down, but a plugin may instead just do things
different. In omstdout, you can see how a plugin may deal with the
situation.

**In any case, it is recommended that at least a graceful shutdown is
made and the array-passing capability not blindly be used.** In such
cases, we can not guard the plugin from segfaulting and if the plugin
(as currently always) is run within rsyslog's process space, that
results in a segfault for rsyslog. So do not do this.

Another possible mode is OMSR\_TPL\_AS\_JSON, where instead of the
template a json-c memory object tree is passed to the module. The module
can extract data via json-c API calls. It MUST NOT modify the provided
structure. This mode is primarily aimed at plugins that need to process
tree-like data, as found for example in MongoDB or ElasticSearch.

Batching of Messages
~~~~~~~~~~~~~~~~~~~~

Starting with rsyslog 4.3.x, batching of output messages is supported.
Previously, only a single-message interface was supported.

With the **single message** plugin interface, each message is passed via
a separate call to the plugin. Most importantly, the rsyslog engine
assumes that each call to the plugin is a complete transaction and as
such assumes that messages be properly committed after the plugin returns
to the engine.

With the **batching** interface, rsyslog employs something along the
line of "transactions". Obviously, the rsyslog core can not make
non-transactional outputs to be fully transactional. But what it can is
support that the output tells the core which messages have been committed
by the output and which not yet. The core can than take care of those
uncommitted messages when problems occur. For example, if a plugin has
received 50 messages but not yet told the core that it committed them,
and then returns an error state, the core assumes that all these 50
messages were **not** written to the output. The core then requeues all
50 messages and does the usual retry processing. Once the output plugin
tells the core that it is ready again to accept messages, the rsyslog
core will provide it with these 50 not yet committed messages again
(actually, at this point, the rsyslog core no longer knows that it is
re-submitting the messages). If, in contrary, the plugin had told rsyslog
that 40 of these 50 messages were committed (before it failed), then only
10 would have been requeued and resubmitted.

In order to provide an efficient implementation, there are some (mild)
constraints in that transactional model: first of all, rsyslog itself
specifies the ultimate transaction boundaries. That is, it tells the
plugin when a transaction begins and when it must finish. The plugin is
free to commit messages in between, but it **must** commit all work done
when the core tells it that the transaction ends. All messages passed in
between a begin and end transaction notification are called a batch of
messages. They are passed in one by one, just as without transaction
support. Note that batch sizes are variable within the range of 1 to a
user configured maximum limit. Most importantly, that means that plugins
may receive batches of single messages, so they are required to commit
each message individually. If the plugin tries to be "smarter" than the
rsyslog engine and does not commit messages in those cases (for
example), the plugin puts message stream integrity at risk: once rsyslog
has notified the plugin of transaction end, it discards all messages as
it considers them committed and save. If now something goes wrong, the
rsyslog core does not try to recover lost messages (and keep in mind
that "goes wrong" includes such uncontrollable things like connection
loss to a database server). So it is highly recommended to fully abide
to the plugin interface details, even though you may think you can do it
better. The second reason for that is that the core engine will have
configuration settings that enable the user to tune commit rate to their
use-case specific needs. And, as a relief: why would rsyslog ever decide
to use batches of one? There is a trivial case and that is when we have
very low activity so that no queue of messages builds up, in which case
it makes sense to commit work as it arrives. (As a side-note, there are
some valid cases where a timeout-based commit feature makes sense. This
is also under evaluation and, once decided, the core will offer an
interface plus a way to preserve message stream integrity for
properly-crafted plugins).

The second restriction is that if a plugin makes commits in between
(what is perfectly legal) those commits must be in-order. So if a commit
is made for message ten out of 50, this means that messages one to nine
are also committed. It would be possible to remove this restriction, but
we have decided to deliberately introduce it to simplify things.

Output Plugin Transaction Interface
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In order to keep compatible with existing output plugins (and because it
introduces no complexity), the transactional plugin interface is build
on the traditional non-transactional one. Well... actually the
traditional interface was transactional since its introduction, in the
sense that each message was processed in its own transaction.

So the current
``doAction() entry point can be considered to have this structure (from the transactional interface point of view):``

::

    doAction()
        {
        beginTransaction()
        ProcessMessage()
        endTransaction()
        }

For the **transactional interface**, we now move these implicit
``beginTransaction()`` and ``endTransaction(()`` call out of the message
processing body, resulting is such a structure:

::

    beginTransaction()
        {
        /* prepare for transaction */
        }

    doAction()
        {
        ProcessMessage()
        /* maybe do partial commits */
        }

    endTransaction()
        {
        /* commit (rest of) batch */
        }

And this calling structure actually is the transactional interface! It
is as simple as this. For the new interface, the core calls a
``beginTransaction()`` entry point inside the plugin at the start of the
batch. Similarly, the core call ``endTransaction()`` at the end of the
batch. The plugin must implement these entry points according to its
needs.

But how does the core know when to use the old or the new calling
interface? This is rather easy: when loading a plugin, the core queries
the plugin for the ``beginTransaction()`` and ``endTransaction()`` entry
points. If the plugin supports these, the new interface is used. If the
plugin does not support them, the old interface is used and rsyslog
implies that a commit is done after each message. Note that there is no
special "downlevel" handling necessary to support this. In the case of
the non-transactional interface, rsyslog considers each completed call
to ``doAction`` as partial commit up to the current message. So
implementation inside the core is very straightforward.

Actually, **we recommend that the transactional entry points only be
defined by those plugins that actually need them**. All others should
not define them in which case the default commit behaviour inside
rsyslog will apply (thus removing complexity from the plugin).

In order to support partial commits, special return codes must be
defined for ``doAction``. All those return codes mean that processing
completed successfully. But they convey additional information about the
commit status as follows:

+----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| *RS\_RET\_OK*                    | The record and all previous inside the batch has been commited. *Note:* this definition is what makes integrating plugins without the transaction being/end calls so easy - this is the traditional "success" return state and if every call returns it, there is no need for actually calling ``endTransaction()``, because there is no transaction open).       |
+----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| *RS\_RET\_DEFER\_COMMIT*         | The record has been processed, but is not yet commited. This is the expected state for transactional-aware plugins.                                                                                |
+----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| *RS\_RET\_PREVIOUS\_COMMITTED*   | The **previous** record inside the batch has been committed, but the current one not yet. This state is introduced to support sources that fill up buffers and commit once a buffer is completely filled. That may occur halfway in the next record, so it may be important to be able to tell the engine the everything up to the previouos record is commited   |
+----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

Note that the typical **calling cycle** is ``beginTransaction()``,
followed by *n* times ``doAction()`` followed by ``endTransaction()``.
However, if either ``beginTransaction()`` or ``doAction()`` return back
an error state (including RS\_RET\_SUSPENDED), then the transaction is
considered aborted. In result, the remaining calls in this cycle (e.g.
``endTransaction()``) are never made and a new cycle (starting with
``beginTransaction()`` is begun when processing resumes. So an output
plugin must expect and handle those partial cycles gracefully.

**The question remains how can a plugin know if the core supports
batching?** First of all, even if the engine would not know it, the
plugin would return with RS\_RET\_DEFER\_COMMIT, what then would be
treated as an error by the engine. This would effectively disable the
output, but cause no further harm (but may be harm enough in itself).

The real solution is to enable the plugin to query the rsyslog core if
this feature is supported or not. At the time of the introduction of
batching, no such query-interface exists. So we introduce it with that
release. What the means is if a rsyslog core can not provide this query
interface, it is a core that was build before batching support was
available. So the absence of a query interface indicates that the
transactional interface is not available. One might now be tempted to
think there is no need to do the actual check, but is is recommended to
ask the rsyslog engine explicitly if the transactional interface is
present and will be honored. This enables us to create versions in the
future which have, for whatever reason we do not yet know, no support
for this interface.

The logic to do these checks is contained in the ``INITChkCoreFeature``
macro, which can be used as follows:

::

    INITChkCoreFeature(bCoreSupportsBatching, CORE_FEATURE_BATCHING);

Here, bCoreSupportsBatching is a plugin-defined integer which after
execution is 1 if batches (and thus the transactional interface) is
supported and 0 otherwise. CORE\_FEATURE\_BATCHING is the feature we are
interested in. Future versions of rsyslog may contain additional
feature-test-macros (you can see all of them in ./runtime/rsyslog.h).

Note that the ompsql output plugin supports transactional mode in a
hybrid way and thus can be considered good example code.

Open Issues
-----------

-  Processing errors handling
-  reliable re-queue during error handling and queue termination

Licensing
~~~~~~~~~

From the rsyslog point of view, plugins constitute separate projects. As
such, we think plugins are not required to be compatible with GPLv3.
However, this is no legal advise. If you intend to release something
under a non-GPLV3 compatible license it is probably best to consult with
your lawyer.

Most importantly, and this is definite, the rsyslog team does not expect
or require you to contribute your plugin to the rsyslog project (but of
course we are happy if you do).

Copyright
---------

Copyright (c) 2009 `Rainer Gerhards <http://www.gerhards.net/rainer>`_
and `Adiscon <http://www.adiscon.com/en/>`_.

Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.2 or
any later version published by the Free Software Foundation; with no
Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A
copy of the license can be viewed at
`http://www.gnu.org/copyleft/fdl.html <http://www.gnu.org/copyleft/fdl.html>`_.


text:
<?php
namespace Crunch\Inotify;

/**
 * Symbolic interface
 *
 * It's not required for a receiver to implement this interface, any callable is accepted. This interfacce
 * describes the expected signature.
 */
interface Receiver {
    /**
     * Gets invoke on every event
     *
     * @param Event   $event
     * @param Handler $handler
     * @return mixed
     */
    public function __invoke (Event $event, Handler $handler);
}