diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000000000000000000000000000000000000..dae9093320d1b520f831d4ac0c6bf96645014edf
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "submodules/pyzenkit"]
+	path = submodules/pyzenkit
+	url = https://github.com/honzamach/pyzenkit.git
+[submodule "submodules/pynspect"]
+	path = submodules/pynspect
+	url = https://github.com/honzamach/pynspect.git
diff --git a/deploy/pynspect/LICENSE.txt b/deploy/pynspect/LICENSE.txt
deleted file mode 100644
index 18202f1e946329bf84dd5dbfc4a259af90cfe234..0000000000000000000000000000000000000000
--- a/deploy/pynspect/LICENSE.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License (MIT)
-
-Copyright (C) since 2011 CESNET, z.s.p.o
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/deploy/pynspect/Makefile b/deploy/pynspect/Makefile
deleted file mode 100644
index f25681a9eb6c309337ab80011c42c9e210fd28c5..0000000000000000000000000000000000000000
--- a/deploy/pynspect/Makefile
+++ /dev/null
@@ -1,71 +0,0 @@
-#-------------------------------------------------------------------------------
-# Copyright (c) since 2016, CESNET, z. s. p. o.
-# Authors: Pavel Kácha <pavel.kacha@cesnet.cz>
-#          Jan Mach <jan.mach@cesnet.cz>
-# Use of this source is governed by an ISC license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-all: archive sync bdist deploy
-
-build: archive sync bdist
-
-buildbot: sync bdist
-
-help:
-	$(info List of possible make targets:)
-	$(info   )
-	$(info   * all:     archive previous packages, build new distribution and deploy to PyPI [default])
-	$(info   * build:   archive previous packages and build new distribution)
-	$(info   * test:    run unit tests)
-	$(info   * archive: archive previous packages)
-	$(info   * sync:    synchronize source code)
-	$(info   * bdist:   build new distribution)
-	$(info   * install: install distribution on local machine)
-	$(info   * deploy:  deploy to PyPI)
-	$(info   )
-
-# Perform unit tests
-test: FORCE
-	$(info Testing source code)
-	nosetests
-
-# Move old distribution files to archive directory
-archive: FORCE
-	$(info Checking if dist archivation is needed)
-	@if ! [ `ls dist/pynspect* | wc -l` = "0" ]; then\
-		echo "Moving old distribution files to local archive";\
-		mv -f dist/pynspect* archive;\
-	fi
-
-# Synchronize directory contents
-sync: FORCE
-	$(info Syncing source code)
-	rsync -r --progress --archive --update --delete --force ../../lib/pynspect ./
-	find ./ -type f -name *.pyc -exec rm -f {} \;
-
-# Build various Python package distributions
-bdist:
-	$(info Building distributions)
-
-	# Build Python3 only wheel and egg.
-	python3 setup.py sdist bdist_wheel
-
-	# Or build universal wheel and egg.
-	#python3 setup.py sdist bdist_wheel --universal
-
-# Perform installation from local files for both Python 2 and 3
-install: FORCE
-	$(info Local installation)
-	#pip install dist/pynspect*.whl
-	pip3 install dist/pynspect*.whl
-
-# Deploy latest packages to PyPI
-deploy: FORCE
-	$(info PyPI deployment)
-
-	# Secure upload with Twine
-	twine upload dist/pynspect*
-
-# Empty rule as dependency wil force make to always perform target
-# Source: https://www.gnu.org/software/make/manual/html_node/Force-Targets.html
-FORCE:
diff --git a/deploy/pynspect/README.rst b/deploy/pynspect/README.rst
deleted file mode 100644
index 230af994ee87ee4e133add7cf21b3921daa90641..0000000000000000000000000000000000000000
--- a/deploy/pynspect/README.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-pynspect
-================================================================================
-
-Python 3 library for filtering, querying or inspecting almost arbitrary data
-structures.
-
-This README file is work in progress, for more information please consult source
-code and unit tests.
diff --git a/deploy/pynspect/archive/.gitplaceholder b/deploy/pynspect/archive/.gitplaceholder
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/deploy/pynspect/dist/.gitplaceholder b/deploy/pynspect/dist/.gitplaceholder
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/deploy/pynspect/setup.py b/deploy/pynspect/setup.py
deleted file mode 100644
index 15fcc0023f2fcb45f35aca485125cb51a6c77775..0000000000000000000000000000000000000000
--- a/deploy/pynspect/setup.py
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/usr/bin/python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# Copyright (c) since 2016, CESNET, z. s. p. o.
-# Authors: Jan Mach <jan.mach@cesnet.cz>
-#          Pavel Kácha <pavel.kacha@cesnet.cz>
-# Use of this source is governed by an ISC license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-# Resources:
-#   https://packaging.python.org/distributing/
-#   http://python-packaging-user-guide.readthedocs.io/distributing/
-
-# Always prefer setuptools over distutils
-from setuptools import setup, find_packages
-# To use a consistent encoding
-from codecs import open
-from os import path
-
-here = path.abspath(path.dirname(__file__))
-
-# Get the long description from the README file
-with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
-    long_description = f.read()
-
-setup(
-    name = 'pynspect',
-    version = '0.4',
-    description = 'Python 3 library for filtering, querying or inspecting almost arbitrary data structures.',
-    long_description = long_description,
-    classifiers = [
-        'Development Status :: 3 - Alpha',
-        'License :: OSI Approved :: MIT License',
-        'Programming Language :: Python :: 3 :: Only'
-    ],
-    keywords = 'library',
-    url = 'https://homeproj.cesnet.cz/git/mentat-ng.git',
-    author = 'Jan Mach',
-    author_email = 'jan.mach@cesnet.cz',
-    license = 'MIT',
-    packages = ['pynspect'],
-    test_suite = 'nose.collector',
-    tests_require = [
-        'nose'
-    ],
-    install_requires=[
-        'ipranges'
-    ],
-    zip_safe = True
-)
diff --git a/doc/sphinx/_doclib/lib.pynspect.filters.rst b/doc/sphinx/_doclib/lib.pynspect.filters.rst
deleted file mode 100644
index 9dc77a30676e5dca050e29fe483e04b0c0dcb8ae..0000000000000000000000000000000000000000
--- a/doc/sphinx/_doclib/lib.pynspect.filters.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-pynspect.filters module
-================================================================================
-
-.. automodule:: pynspect.filters
-    :show-inheritance:
-    :members:
-    :undoc-members:
diff --git a/doc/sphinx/_doclib/lib.pynspect.gparser.rst b/doc/sphinx/_doclib/lib.pynspect.gparser.rst
deleted file mode 100644
index f63c4135de9ead5f33d7bb680fd2dfbc2af63ab6..0000000000000000000000000000000000000000
--- a/doc/sphinx/_doclib/lib.pynspect.gparser.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-pynspect.gparser module
-================================================================================
-
-.. automodule:: pynspect.gparser
-    :show-inheritance:
diff --git a/doc/sphinx/_doclib/lib.pynspect.jpath.rst b/doc/sphinx/_doclib/lib.pynspect.jpath.rst
deleted file mode 100644
index 0484b30f29e9333ee0f48725c16ed79fdcf5f575..0000000000000000000000000000000000000000
--- a/doc/sphinx/_doclib/lib.pynspect.jpath.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-pynspect.jpath module
-================================================================================
-
-.. automodule:: pynspect.jpath
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/doc/sphinx/_doclib/lib.pynspect.lexer.rst b/doc/sphinx/_doclib/lib.pynspect.lexer.rst
deleted file mode 100644
index 9088a14cbde683ec9981bc688f710779701932d3..0000000000000000000000000000000000000000
--- a/doc/sphinx/_doclib/lib.pynspect.lexer.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-pynspect.lexer module
-================================================================================
-
-.. automodule:: pynspect.lexer
-    :show-inheritance:
diff --git a/doc/sphinx/_doclib/lib.pynspect.rules.rst b/doc/sphinx/_doclib/lib.pynspect.rules.rst
deleted file mode 100644
index 81dc0f0f06871b05e4f6fa1e005f729cd8ba3692..0000000000000000000000000000000000000000
--- a/doc/sphinx/_doclib/lib.pynspect.rules.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-pynspect.rules module
-================================================================================
-
-.. automodule:: pynspect.rules
-    :show-inheritance:
-    :members:
-    :undoc-members:
diff --git a/lib/pynspect/__init__.py b/lib/pynspect/__init__.py
deleted file mode 100644
index f04b228f73878c52d39d43cbb1e92857c28ca5c1..0000000000000000000000000000000000000000
--- a/lib/pynspect/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# This file is part of Mentat system (https://mentat.cesnet.cz/).
-#
-# Copyright (C) since 2011 CESNET, z.s.p.o (http://www.ces.net/)
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-__version__ = "0.3"
diff --git a/lib/pynspect/benchmark/bench_jpath.py b/lib/pynspect/benchmark/bench_jpath.py
deleted file mode 100644
index 6673bf579d24c2f91770cdad06d5c2e22ff9138e..0000000000000000000000000000000000000000
--- a/lib/pynspect/benchmark/bench_jpath.py
+++ /dev/null
@@ -1,143 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# This file is part of Mentat system (https://mentat.cesnet.cz/).
-#
-# Copyright (C) since 2011 CESNET, z.s.p.o (http://www.ces.net/)
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-import os
-import sys
-import shutil
-import random
-import string
-import timeit
-from pprint import pformat, pprint
-
-# Generate the path to custom 'lib' directory
-lib = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../lib'))
-sys.path.insert(0, lib)
-
-from pynspect.jpath import *
-
-#-------------------------------------------------------------------------------
-# HELPER FUNCTIONS
-#-------------------------------------------------------------------------------
-
-def random_jpath(depth = 3):
-    """
-    Generate random JPath with given node depth.
-    """
-    chunks = []
-    while depth > 0:
-        length = random.randint(5, 15)
-        ident  = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase) for _ in range(length))
-        if random.choice((True, False)):
-            index  = random.randint(0, 10)
-            ident = "{:s}[{:d}]".format(ident, index)
-        chunks.append(ident)
-        depth -= 1
-    return ".".join(chunks)
-
-RANDOM_JPATHS = [random_jpath(random.randint(1,5)) for i in range(50)]
-"""Pregenerated list of random JPaths."""
-
-#-------------------------------------------------------------------------------
-# BENCHMARK TESTS
-#-------------------------------------------------------------------------------
-
-b001 = jpath_parse
-b002 = jpath_parse_c
-
-def b003():
-    jpath = random.choice(RANDOM_JPATHS)
-    return jpath_parse(jpath)
-
-def b004():
-    jpath = random.choice(RANDOM_JPATHS)
-    return jpath_parse_c(jpath)
-
-#-------------------------------------------------------------------------------
-
-if __name__ == "__main__":
-    """
-    Performance benchmarking of :py:mod:`pynspect.jpath` module.
-    """
-
-    print("\n BENCHMARKING MENTAT.FILTERING.JPATH MODULE\n")
-
-    print("=" * 84)
-    print(" {:22s} | {:16s} | {:20s} | {:20s}".format(
-            "Name",
-            "Iterations (#)",
-            "Duration (s)",
-            "Speed (#/s)"))
-    print("=" * 84)
-    format_ptrn = " {:22s} | {:16,d} | {:20.10f} | {:15,.3f}"
-
-    #---------------------------------------------------------------------------
-
-    iterations = 1000000
-
-    """
-    Parsing of single reasonably complex JPath without caching.
-    """
-    result = timeit.timeit('b001("Long[*].Test.Path[*]")', number = iterations, setup = "from __main__ import b001")
-    speed = iterations / result
-    print(
-        format_ptrn.format(
-            "jpath_parse",
-            iterations,
-            result,
-            speed
-        )
-    )
-    """
-    Parsing of single reasonably complex JPath with caching.
-    """
-    result = timeit.timeit('b002("Long[*].Test.Path[*]")', number = iterations, setup = "from __main__ import b002")
-    speed = iterations / result
-    print(
-        format_ptrn.format(
-            "jpath_parse_c",
-            iterations,
-            result,
-            speed
-        )
-    )
-
-    #---------------------------------------------------------------------------
-
-    iterations = 1000000
-
-    """
-    Parsing of random reasonably complex JPath without caching.
-    """
-    result = timeit.timeit('b003()', number = iterations, setup = "from __main__ import b003")
-    speed = iterations / result
-    print(
-        format_ptrn.format(
-            "jpath_parse (random)",
-            iterations,
-            result,
-            speed
-        )
-    )
-    """
-    Parsing of random reasonably complex JPath with caching.
-    """
-    result = timeit.timeit('b004()', number = iterations, setup = "from __main__ import b004")
-    speed = iterations / result
-    print(
-        format_ptrn.format(
-            "jpath_parse_c (random)",
-            iterations,
-            result,
-            speed
-        )
-    )
-
-    #---------------------------------------------------------------------------
-
-    print("=" * 84)
diff --git a/lib/pynspect/filters.py b/lib/pynspect/filters.py
deleted file mode 100644
index 05d4de97fc039717c188041ceb848618d292ec48..0000000000000000000000000000000000000000
--- a/lib/pynspect/filters.py
+++ /dev/null
@@ -1,267 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# This file is part of Mentat system (https://mentat.cesnet.cz/).
-#
-# Copyright (C) since 2011 CESNET, z.s.p.o (http://www.ces.net/)
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-"""
-This module provides tools for data filtering based on filtering and query
-grammar.
-
-The filtering grammar is thoroughly described in following module:
-
-* :py:mod:`pynspect.lexer`
-
-  Lexical analyzer, descriptions of valid grammar tokens.
-
-* :py:mod:`pynspect.gparser`
-
-  Grammar parser, language grammar description
-
-* :py:mod:`pynspect.rules`
-
-  Object representation of grammar rules, interface definition
-
-* :py:mod:`pynspect.jpath`
-
-  The addressing language JPath.
-
-Please refer to appropriate module for more in-depth information.
-
-There are two main tools in this package:
-
-* :py:class:`DataObjectFilter`
-
-  Tool capable of filtering data structures according to given filtering rules.
-
-* :py:class:`IDEAFilterCompiler`
-
-  Filter compiler, that ensures appropriate data types for correct variable
-  comparison evaluation.
-
-.. todo::
-
-    There is quite a lot of code that needs to be written before actual filtering
-    can take place. In the future, there should be some kind of object, that will
-    be tailored for immediate processing and will take care of initializing
-    uderlying parser, compiler and filter. This object will be designed later.
-
-"""
-
-__version__ = "0.3"
-__author__ = "Jan Mach <jan.mach@cesnet.cz>"
-__credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>"
-
-import re
-import ipranges
-
-from pynspect.rules import *
-from pynspect.jpath import *
-
-class DataObjectFilter(RuleTreeTraverser):
-    """
-    Rule tree traverser implementing  default object filtering logic.
-
-    Following example demonstrates DataObjectFilter usage in conjuction with
-    MentatFilterParser::
-
-    >>> flt = DataObjectFilter()
-    >>> psr = MentatFilterParser()
-    >>> psr.build()
-    >>> rule = psr.parse('ID like "e214d2d9"')
-    >>> result = flt.filter(rule, test_msg)
-
-    Alternativelly rule tree can be created by hand/programatically:
-
-    >>> rule = ComparisonBinOpRule('OP_GT', VariableRule("ConnCount"), IntegerRule(1))
-    >>> result = flt.filter(rule, test_msg1)
-    """
-    def filter(self, rule, data):
-        """
-        Apply given filtering rule to given data structure.
-
-        :param Rule rule: filtering rule to be checked
-        :param any data: data structure to check against rule, ussually dict
-        :return: True or False or expression result
-        :rtype: bool or any
-        """
-        return rule.traverse(self, obj = data)
-
-    #---------------------------------------------------------------------------
-
-    def ipv4(self, rule, **kwargs):
-        """Implementation of :py:class:`pynspect.rules.RuleTreeTraverser` interface"""
-        return rule.value
-    def ipv6(self, rule, **kwargs):
-        """Implementation of :py:class:`pynspect.rules.RuleTreeTraverser` interface"""
-        return rule.value
-    def integer(self, rule, **kwargs):
-        """Implementation of :py:class:`pynspect.rules.RuleTreeTraverser` interface"""
-        return rule.value
-    def constant(self, rule, **kwargs):
-        """Implementation of :py:class:`pynspect.rules.RuleTreeTraverser` interface"""
-        return rule.value
-    def variable(self, rule, **kwargs):
-        """Implementation of :py:class:`pynspect.rules.RuleTreeTraverser` interface"""
-        return jpath_values(kwargs['obj'], rule.value)
-    def list(self, rule, **kwargs):
-        """Implementation of :py:class:`pynspect.rules.RuleTreeTraverser` interface"""
-        return [i.value for i in rule.value]
-    def binary_operation_logical(self, rule, left, right, **kwargs):
-        """Implementation of :py:class:`pynspect.rules.RuleTreeTraverser` interface"""
-        return self.evaluate_binop_logical(rule.operation, left, right, **kwargs)
-    def binary_operation_comparison(self, rule, left, right, **kwargs):
-        """Implementation of :py:class:`pynspect.rules.RuleTreeTraverser` interface"""
-        return self.evaluate_binop_comparison(rule.operation, left, right, **kwargs)
-    def binary_operation_math(self, rule, left, right, **kwargs):
-        """Implementation of :py:class:`pynspect.rules.RuleTreeTraverser` interface"""
-        return self.evaluate_binop_math(rule.operation, left, right, **kwargs)
-    def unary_operation(self, rule, right, **kwargs):
-        """Implementation of :py:class:`pynspect.rules.RuleTreeTraverser` interface"""
-        return self.evaluate_unop(rule.operation, right, **kwargs)
-
-def compile_ip_v4(rule):
-    """
-    Compiler helper method: attempt to compile constant into object representing
-    IPv4 address to enable relations and thus simple comparisons using Python
-    operators.
-    """
-    if isinstance(rule.value, ipranges.Range):
-        return rule
-    return IPV4Rule(ipranges.from_str_v4(rule.value))
-
-def compile_ip_v6(rule):
-    """
-    Compiler helper method: attempt to compile constant into object representing
-    IPv6 address to enable relations and thus simple comparisons using Python
-    operators.
-    """
-    if isinstance(rule.value, ipranges.Range):
-        print("IPv6 {} already compiled".format(rule.value))
-        return rule
-    print("Compiling IPv6 {} to Range object".format(rule.value))
-    return IPV6Rule(ipranges.from_str_v6(rule.value))
-
-CVRE = re.compile('\[\d+\]')
-def clean_variable(var):
-    """
-    Remove any array indices from variable name to enable indexing into :py:data:`COMPILATIONS`
-    callback dictionary.
-
-    This dictionary contains postprocessing callback appropriate for opposing
-    operand of comparison operation for variable on given JPath.
-    """
-    return CVRE.sub('', var)
-
-COMPILATIONS = {
-    'Source.IP4': compile_ip_v4,
-    'Target.IP4': compile_ip_v4,
-    'Source.IP6': compile_ip_v6,
-    'Target.IP6': compile_ip_v6,
-}
-
-class IDEAFilterCompiler(RuleTreeTraverser):
-    """
-    Rule tree traverser implementing IDEA filter compilation algorithm.
-
-    Following example demonstrates DataObjectFilter usage in conjuction with
-    MentatFilterParser::
-
-    >>> msg_idea = lite.Idea(test_msg)
-    >>> flt = DataObjectFilter()
-    >>> cpl = IDEAFilterCompiler()
-    >>> psr = MentatFilterParser()
-    >>> psr.build()
-    >>> rule = psr.parse('ID like "e214d2d9"')
-    >>> rule = cpl.compile(rule)
-    >>> result = flt.filter(rule, test_msg)
-    """
-    def compile(self, rule):
-        """
-        Compile given filtering rule into format appropriate for processing IDEA
-        messages.
-
-        :param Rule rule: filtering rule to be compiled
-        :return: compiled filtering rule
-        :rtype: Rule
-        """
-        return rule.traverse(self)
-
-    #---------------------------------------------------------------------------
-
-    def ipv4(self, rule, **kwargs):
-        """Implementation of :py:class:`pynspect.rules.RuleTreeTraverser` interface"""
-        rule = compile_ip_v4(rule)
-        return rule
-    def ipv6(self, rule, **kwargs):
-        """Implementation of :py:class:`pynspect.rules.RuleTreeTraverser` interface"""
-        rule = compile_ip_v4(rule)
-        return rule
-    def integer(self, rule, **kwargs):
-        """Implementation of :py:class:`pynspect.rules.RuleTreeTraverser` interface"""
-        rule.value = int(rule.value)
-        return rule
-    def constant(self, rule, **kwargs):
-        """Implementation of :py:class:`pynspect.rules.RuleTreeTraverser` interface"""
-        return rule
-    def variable(self, rule, **kwargs):
-        """Implementation of :py:class:`pynspect.rules.RuleTreeTraverser` interface"""
-        return rule
-    def list(self, rule, **kwargs):
-        """Implementation of :py:class:`pynspect.rules.RuleTreeTraverser` interface"""
-        return rule
-    def binary_operation_logical(self, rule, left, right, **kwargs):
-        """Implementation of :py:class:`pynspect.rules.RuleTreeTraverser` interface"""
-        return LogicalBinOpRule(rule.operation, left, right)
-    def binary_operation_comparison(self, rule, left, right, **kwargs):
-        """Implementation of :py:class:`pynspect.rules.RuleTreeTraverser` interface"""
-        var = val = None
-        if isinstance(left, VariableRule) and not isinstance(right, VariableRule):
-            var = left
-            val = right
-        elif isinstance(right, VariableRule) and not isinstance(left, VariableRule):
-            var = right
-            val = left
-        if var and val:
-            p = clean_variable(var.value)
-            if p in COMPILATIONS.keys():
-                if isinstance(val, ListRule):
-                    result = []
-                    for v in val.value:
-                        result.append(COMPILATIONS[p](v))
-                    right = ListRule(result)
-                else:
-                    right = COMPILATIONS[p](val)
-        return ComparisonBinOpRule(rule.operation, left, right)
-    def binary_operation_math(self, rule, left, right, **kwargs):
-        """Implementation of :py:class:`pynspect.rules.RuleTreeTraverser` interface"""
-        if isinstance(left, IntegerRule) and isinstance(right, IntegerRule):
-            result = self.evaluate_binop_math(rule.operation, left.value, right.value)
-            if isinstance(result, list):
-                return ListRule([IntegerRule(r) for r in result])
-            else:
-                return IntegerRule(result)
-        elif isinstance(left, NumberRule) and isinstance(right, NumberRule):
-            result = self.evaluate_binop_math(rule.operation, left.value, right.value)
-            if isinstance(result, list):
-                return ListRule([FloatRule(r) for r in result])
-            else:
-                return FloatRule(result)
-        return MathBinOpRule(rule.operation, left, right)
-    def unary_operation(self, rule, right, **kwargs):
-        """Implementation of :py:class:`pynspect.rules.RuleTreeTraverser` interface"""
-        return UnaryOperationRule(rule.operation, right)
-
-if __name__ == "__main__":
-    """
-    Perform the demonstration.
-    """
-    import pprint
-
-    data = {"Test": 15, "Attr": "ABC"}
-    rule = ComparisonBinOpRule('OP_GT', VariableRule("Test"), IntegerRule(10))
-    flt = DataObjectFilter()
-    pprint.pprint(flt.filter(rule, data))
diff --git a/lib/pynspect/gparser.py b/lib/pynspect/gparser.py
deleted file mode 100644
index a253b9fca13afa8691828439260e1f5696c356e4..0000000000000000000000000000000000000000
--- a/lib/pynspect/gparser.py
+++ /dev/null
@@ -1,332 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# This file is part of Mentat system (https://mentat.cesnet.cz/).
-#
-# Copyright (C) since 2011 CESNET, z.s.p.o (http://www.ces.net/)
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-"""
-This module contains object encapsulation of `PLY <http://www.dabeaz.com/ply/>`__
-parser for filtering and query language grammar used in Mentat project.
-
-Grammar features
-^^^^^^^^^^^^^^^^
-
-* Logical operations: ``and or xor not exists``
-
-  All logical operations support upper case and lower case name variants.
-  Additionally, there are also symbolic variants ``|| ^^ && ! ?`` with higher
-  priority and which can be used in some cases instead of parentheses.
-
-* Comparison operations: ``like in is eq ne gt ge lt le``
-
-  All comparison operations support upper case and lower case name variants.
-  Additionally, there are also symbolic variants.
-
-* Mathematical operations: ``+ - * / %``
-* JPath variables: ``Source[0].IP4[1]``
-* Directly recognized constants:
-
-    * IPv4: ``127.0.0.1 127.0.0.1/32 127.0.0.1-127.0.0.5 127.0.0.1..127.0.0.5``
-    * IPv6: ``::1 ::1/64 ::1-::5 ::1..::5``
-    * integer: ``0 1 42``
-    * float: ``3.14159``
-* Quoted literal constants: ``"double quotes"`` or ``'single quotes'``
-
-For more details on supported grammar token syntax please see the documentation
-of :py:mod:`pynspect.lexer` module.
-
-Currently implemented grammar
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: bnf
-
-    expression : xor_expression OP_OR expression
-               | xor_expression
-
-    xor_expression : and_expression OP_XOR xor_expression
-                   | and_expression
-
-    and_expression : or_p_expression OP_AND and_expression
-                   | or_p_expression
-
-    or_p_expression : xor_p_expression OP_OR_P or_p_expression
-                    | xor_p_expression
-
-    xor_p_expression : and_p_expression OP_XOR_P xor_p_expression
-                     | and_p_expression
-
-    and_p_expression : not_expression OP_AND_P and_p_expression
-                     | not_expression
-
-    not_expression : OP_NOT ex_expression
-                   | ex_expression
-
-    ex_expression : OP_EXISTS cmp_expression
-                  | cmp_expression
-
-    cmp_expression : term OP_LIKE cmp_expression
-                   | term OP_IN cmp_expression
-                   | term OP_IS cmp_expression
-                   | term OP_EQ cmp_expression
-                   | term OP_NE cmp_expression
-                   | term OP_GT cmp_expression
-                   | term OP_GE cmp_expression
-                   | term OP_LT cmp_expression
-                   | term OP_LE cmp_expression
-                   | term
-
-    term : factor OP_PLUS term
-         | factor OP_MINUS term
-         | factor OP_TIMES term
-         | factor OP_DIVIDE term
-         | factor OP_MODULO term
-         | factor
-
-    factor : IPV4
-           | IPV6
-           | INTEGER
-           | FLOAT
-           | VARIABLE
-           | CONSTANT
-           | LBRACK list RBRACK
-           | LPAREN expression RPAREN
-
-    list : IPV4
-         | IPV6
-         | INTEGER
-         | FLOAT
-         | VARIABLE
-         | CONSTANT
-         | IPV4 COMMA list
-         | IPV6 COMMA list
-         | INTEGER COMMA list
-         | FLOAT COMMA list
-         | VARIABLE COMMA list
-         | CONSTANT COMMA list
-
-.. note::
-
-    Implementation of this module is very *PLY* specific, please read the
-    appropriate `documentation <http://www.dabeaz.com/ply/ply.html#ply_nn3>`__
-    to understand it.
-
-"""
-
-__version__ = "0.3"
-__author__ = "Jan Mach <jan.mach@cesnet.cz>"
-__credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>"
-
-import re, logging
-import ply.yacc
-
-from pynspect.lexer import MentatFilterLexer
-from pynspect.rules import *
-
-class MentatFilterParser():
-    """
-    Object encapsulation of *PLY* parser implementation for filtering and
-    query language grammar used in Mentat project.
-    """
-
-    def build(self, **kwargs):
-        """
-        Build/rebuild the parser object
-        """
-        self.logger = logging.getLogger('ply_parser')
-
-        self.lexer = MentatFilterLexer()
-        self.lexer.build()
-
-        self.tokens = self.lexer.tokens
-
-        self.parser = ply.yacc.yacc(
-            module=self
-            #start='statements',
-            #debug=yacc_debug,
-            #optimize=yacc_optimize,
-            #tabmodule=yacctab
-        )
-
-    def parse(self, data, filename='', debuglevel=0):
-        """
-        Parse given data.
-
-            data:
-                A string containing the filter definition
-            filename:
-                Name of the file being parsed (for meaningful
-                error messages)
-            debuglevel:
-                Debug level to yacc
-        """
-        self.lexer.filename = filename
-        self.lexer.reset_lineno()
-        if not data or data.isspace():
-            return []
-        else:
-            return self.parser.parse(data, lexer=self.lexer, debug=debuglevel)
-
-    #---------------------------------------------------------------------------
-
-    def _create_factor_rule(self, t):
-        """
-        Simple helper method for creating factor node objects based on node name.
-        """
-        if (t[0] == 'IPV4'):
-            return IPV4Rule(t[1])
-        elif (t[0] == 'IPV6'):
-            return IPV6Rule(t[1])
-        elif (t[0] == 'INTEGER'):
-            return IntegerRule(t[1])
-        elif (t[0] == 'FLOAT'):
-            return FloatRule(t[1])
-        elif (t[0] == 'VARIABLE'):
-            return VariableRule(t[1])
-        else:
-            return ConstantRule(t[1])
-
-    def p_expression(self, t):
-        """expression : xor_expression OP_OR expression
-                      | xor_expression"""
-        if (len(t) == 4):
-            t[0] = LogicalBinOpRule(t[2], t[1], t[3])
-        else:
-            t[0] = t[1]
-
-    def p_xor_expression(self, t):
-        """xor_expression : and_expression OP_XOR xor_expression
-                          | and_expression"""
-        if (len(t) == 4):
-            t[0] = LogicalBinOpRule(t[2], t[1], t[3])
-        else:
-            t[0] = t[1]
-
-    def p_and_expression(self, t):
-        """and_expression : or_p_expression OP_AND and_expression
-                          | or_p_expression"""
-        if (len(t) == 4):
-            t[0] = LogicalBinOpRule(t[2], t[1], t[3])
-        else:
-            t[0] = t[1]
-
-    def p_or_p_expression(self, t):
-        """or_p_expression : xor_p_expression OP_OR_P or_p_expression
-                      | xor_p_expression"""
-        if (len(t) == 4):
-            t[0] = LogicalBinOpRule(t[2], t[1], t[3])
-        else:
-            t[0] = t[1]
-
-    def p_xor_p_expression(self, t):
-        """xor_p_expression : and_p_expression OP_XOR_P xor_p_expression
-                          | and_p_expression"""
-        if (len(t) == 4):
-            t[0] = LogicalBinOpRule(t[2], t[1], t[3])
-        else:
-            t[0] = t[1]
-
-    def p_and_p_expression(self, t):
-        """and_p_expression : not_expression OP_AND_P and_p_expression
-                          | not_expression"""
-        if (len(t) == 4):
-            t[0] = LogicalBinOpRule(t[2], t[1], t[3])
-        else:
-            t[0] = t[1]
-
-    def p_not_expression(self, t):
-        """not_expression : OP_NOT ex_expression
-                          | ex_expression"""
-        if (len(t) == 3):
-            t[0] = UnaryOperationRule(t[1], t[2])
-        else:
-            t[0] = t[1]
-
-    def p_ex_expression(self, t):
-        """ex_expression : OP_EXISTS cmp_expression
-                         | cmp_expression"""
-        if (len(t) == 3):
-            t[0] = UnaryOperationRule(t[1], t[2])
-        else:
-            t[0] = t[1]
-
-    def p_cmp_expression(self, t):
-        """cmp_expression : term OP_LIKE cmp_expression
-                          | term OP_IN cmp_expression
-                          | term OP_IS cmp_expression
-                          | term OP_EQ cmp_expression
-                          | term OP_NE cmp_expression
-                          | term OP_GT cmp_expression
-                          | term OP_GE cmp_expression
-                          | term OP_LT cmp_expression
-                          | term OP_LE cmp_expression
-                          | term"""
-        if (len(t) == 4):
-            t[0] = ComparisonBinOpRule(t[2], t[1], t[3])
-        else:
-            t[0] = t[1]
-
-    def p_term(self, t):
-        """term : factor OP_PLUS term
-                | factor OP_MINUS term
-                | factor OP_TIMES term
-                | factor OP_DIVIDE term
-                | factor OP_MODULO term
-                | factor"""
-        if (len(t) == 4):
-            t[0] = MathBinOpRule(t[2], t[1], t[3])
-        else:
-            t[0] = t[1]
-
-    def p_factor(self, t):
-        """factor : IPV4
-                  | IPV6
-                  | INTEGER
-                  | FLOAT
-                  | VARIABLE
-                  | CONSTANT
-                  | LBRACK list RBRACK
-                  | LPAREN expression RPAREN"""
-        if (len(t) == 2):
-            t[0] = self._create_factor_rule(t[1])
-        else:
-            t[0] = t[2]
-
-    def p_list(self, t):
-        """list : IPV4
-                | IPV6
-                | INTEGER
-                | FLOAT
-                | VARIABLE
-                | CONSTANT
-                | IPV4 COMMA list
-                | IPV6 COMMA list
-                | INTEGER COMMA list
-                | FLOAT COMMA list
-                | VARIABLE COMMA list
-                | CONSTANT COMMA list"""
-        n = self._create_factor_rule(t[1])
-        if (len(t) == 2):
-            t[0] = ListRule(n)
-        else:
-            t[0] = ListRule(n, t[3])
-
-    def p_error(self, t):
-        print("Syntax error at '%s'" % t.value)
-
-if __name__ == "__main__":
-    """
-    Perform the demonstration.
-    """
-    import pprint
-
-    data = "1 and 1 or 1 xor 1"
-
-    # Build the parser and try it out
-    m = MentatFilterParser()
-    m.build()
-
-    print("Parsing: {}".format(data))
-    pprint.pprint(m.parse(data))
diff --git a/lib/pynspect/jpath.py b/lib/pynspect/jpath.py
deleted file mode 100644
index afa6533d05f1b2bbee2887e1afad423970f3f800..0000000000000000000000000000000000000000
--- a/lib/pynspect/jpath.py
+++ /dev/null
@@ -1,474 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# This file is part of Mentat system (https://mentat.cesnet.cz/).
-#
-# Copyright (C) since 2011 CESNET, z.s.p.o (http://www.ces.net/)
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-"""
-This module provides tools for parsing **JPaths** and setting or retrieving values
-on given **JPath** within data structures.
-
-*JPath* is simplified version of `JSONPath <http://goessner.net/articles/JsonPath/>`__
-and can be used to addressing nodes within arbitrary data structure composed
-of dict-like and list-like objects. Basically it can be used for any data
-structure of objects implementing Python 3 list and/or dict interface.
-
-The motivation for implementing this module were following two use cases:
-
-* Enable writing of simple rules in various filtering expressions, for example::
-
-    Source.IP4 in [192.168.0.0/24, 192.168.0.0/24]
-
-* Enable simple message modifications based on key => value rules, for example::
-
-    "Source[1].Type[*]" = "source type tag"
-
-The obvious first choice for a solution was the `jsonpath-rw <https://pypi.python.org/pypi/jsonpath-rw>`__
-library. The full *JSONPath* however seems to be too big of a gun for our needs and
-in some cases it could even enable users to cut the branch they are sitting on. For
-this reason we have designed this simplified version with only required set of basic features.
-
-*JPath* syntax uses only dot characters ``.`` as node delimiters. Each node name may
-contain only one or more of the following characters::
-
-    [a-zA-Z0-9_]+
-
-Node delimiters implicitly work with nested dictionaries and using delimiter
-results in appending new dictionary as a value of given key in parent disctionary.
-Working with lists is enabled by using indices. List indices must be enclosed in
-brackets '[' and ']' and may contain one of the following values:
-
-* ``[int]`` - precise index (negative values not permitted, numbering starts with 1)
-* ``[#]`` - last (because you might not know number of nodes)
-* ``[*]`` - all nodes
-* (index omitted)
-
-When retrieving value(s) at given *JPath*, use of indices will have following effects:
-
-* ``[int]`` - Return node at particular list position (starting with 1)
-* ``[#]`` - Return last node
-* ``[*]`` - Return all nodes (same as omitting)
-* (index omitted) - Return all nodes (same as '*')
-
-When setting value(s) to given *JPath*, use of indices will have following effects:
-
-* ``[int]`` - Set value to particular list node (starting with 1)
-* ``[#]`` - Set value to already existing last node, or append new one to an empty list
-* ``[*]`` - Append new value to a list
-* (index omitted) - This will result in a dictionary key instead of a list
-
-Consider following examples::
-
-    >>> msg = {
-       'Format': 'IDEA0',
-       'ID': 'MESSAGE_ID',
-       'DetectTime': 'DETECT TIME',
-       'Category': ['CATEGORY'],
-       'ConnCount': 633,
-       'Description': 'Ping scan',
-       'Source': [
-          {
-             'IP4': ['192.168.1.1', '192.168.1.2'],
-             'Proto': ['icmp']
-          },
-          {
-             'IP4': ['192.168.2.1', '192.168.2.2'],
-             'Proto': ['icmp']
-          }
-       ],
-       'Target': [
-          {
-             'Proto': ['icmp'],
-             'IP4': ['192.168.3.1', '192.168.3.2'],
-             'Anonymised': True
-          }
-       ],
-       'Node': [
-          {
-             'SW' : ['KIPPO'],
-             'Name' : 'NODE_NAME'
-          }
-       ]
-    }
-
-    >>> jpath_value(msg, 'Format')
-    'IDEA0'
-    >>> jpath_value(msg, 'Category')
-    'CATEGORY'
-    >>> jpath_value(msg, 'Node.Name')
-    'NODE_NAME'
-    >>> jpath_value(msg, 'Source.IP4')
-    '192.168.1.1'
-
-    >>> jpath_values(msg, 'Format')
-    ['IDEA0']
-    >>> jpath_values(msg, 'Category')
-    ['CATEGORY']
-    >>> jpath_values(msg, 'Node.Name')
-    ['NODE_NAME']
-    >>> jpath_values(msg, 'Source.IP4')
-    ['192.168.1.1', '192.168.1.2', '192.168.2.1', '192.168.2.2']
-
-The current implementation has following known drawbacks:
-
-* Toplevel element must be a dict-like object
-* Nested list-like objects are not possible: ``[[1,2],[3,4]]``
-* It is not possible to set value to multiple elements at once
-* It is not possible to customize type of created structure, only lists and
-  dicts are always created
-
-"""
-
-__version__ = "0.3"
-__author__ = "Jan Mach <jan.mach@cesnet.cz>"
-__credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>"
-
-
-import re
-import collections
-
-
-#
-# Define global constants.
-#
-
-#: Status code for ``success``, returned by function :py:func:`jpath_set`
-RC_VALUE_SET = 0
-
-#: Status code for ``already-exists``, returned by function :py:func:`jpath_set`
-RC_VALUE_EXISTS = 1
-
-#: Status code for ``not-unique``, returned by function :py:func:`jpath_set`
-RC_VALUE_DUPLICATE = 2
-
-#: Regular expression for single JPath chunk.
-RE_JPATH_CHUNK = re.compile(r"^([a-zA-Z0-9_]+)(\[(#|\*|\d+)\])?$")
-
-#: Internal cache for parsed JPaths.
-_JPATH_CACHE = {}
-
-
-class JPathException(Exception):
-    """
-    Custom JPath specific exception.
-
-    This exception will be thrown on module specific errors.
-    """
-    def __init__(self, description):
-        self._description = description
-    def __str__(self):
-        return repr(self._description)
-
-def cache_size():
-    """
-    Return the size of internal JPath cache.
-
-    :return: Cache size
-    :rtype: int
-    """
-    return len(_JPATH_CACHE)
-
-def cache_clear():
-    """
-    Clear internal JPath cache.
-    """
-    global _JPATH_CACHE
-    _JPATH_CACHE = {}
-
-def jpath_parse(jpath):
-    """
-    Parse given JPath into chunks.
-
-    Returns list of dictionaries describing all of the JPath chunks.
-
-    :param str jpath: JPath to be parsed into chunks
-    :return: JPath chunks as list of dicts
-    :rtype: :py:class:`list`
-    :raises JPathException: in case of invalid JPath syntax
-    """
-    result = []
-    breadcrumbs = []
-
-    # Split JPath into chunks based on '.' character
-    chunks = jpath.split('.')
-    for ch in chunks:
-        match = RE_JPATH_CHUNK.match(ch)
-        if match:
-            r = {}
-
-            # Record whole match
-            r['m'] = ch
-
-            # Record breadcrumb path
-            breadcrumbs.append(ch)
-            r['p'] = '.'.join(breadcrumbs)
-
-            # Handle node name
-            r['n'] = match.group(1)
-
-            # Handle node index (optional, may be omitted)
-            if match.group(2):
-                r['i'] = match.group(3)
-                if str(r['i']) == '#':
-                    r['i'] = -1
-                elif str(r['i']) == '*':
-                    pass
-                else:
-                    r['i'] = int(r['i']) - 1
-
-            result.append(r)
-        else:
-            raise JPathException("Invalid JPath chunk '{}'".format(ch))
-    return result
-
-def jpath_parse_c(jpath):
-    """
-    Caching variant of :py:func:`jpath_parse` function. Same arguments and return
-    value.
-
-    For performance reasons thee is no copying and all returned values are
-    references to internal cache. Treat the returned values as read only, or
-    suffer the consequences.
-    """
-    if not jpath in _JPATH_CACHE:
-        _JPATH_CACHE[jpath] = jpath_parse(jpath)
-    return _JPATH_CACHE[jpath]
-
-def jpath_values(structure, jpath):
-    """
-    Return all values at given JPath within given data structure.
-
-    For performance reasons this method is intentionally not written as
-    recursive.
-
-    :param str structure: data structure to be searched
-    :param str jpath: JPath to be evaluated
-    :return: found values as a list
-    :rtype: :py:class:`list`
-    """
-    # Current working node set
-    nodes_a = [structure]
-
-    # Next iteration working node set
-    nodes_b = []
-
-    # Process sequentially all JPath chunks
-    chunks = jpath_parse_c(jpath)
-    for ch in chunks:
-        # Process all currently active nodes
-        for node in nodes_a:
-            key = ch['n']
-            if not isinstance(node, dict) and not isinstance(node, collections.Mapping):
-                continue
-
-            # Process indexed nodes
-            if 'i' in ch:
-                idx = ch['i']
-                # Skip the node, if the key does not exist, the value is not
-                # a list-like object or the list is empty
-                if not key in node or not (isinstance(node[key], list) or isinstance(node[key], collections.MutableSequence)) or not len(node[key]):
-                    continue
-                try:
-                    # Handle '*' special index - append all nodes
-                    if str(idx) == '*':
-                        for i in node[key]:
-                            nodes_b.append(i)
-                    # Append only node at particular index
-                    else:
-                        nodes_b.append(node[key][idx])
-                except:
-                    pass
-
-            # Process unindexed nodes
-            else:
-                # Skip the node, if the key does not exist
-                if not key in node:
-                    continue
-
-                # Handle list values - expand them
-                if isinstance(node[key], list) or isinstance(node[key], collections.MutableSequence):
-                    for i in node[key]:
-                        nodes_b.append(i)
-                # Handle scalar values
-                else:
-                    nodes_b.append(node[key])
-
-        nodes_a = nodes_b
-        nodes_b = []
-
-    return nodes_a
-
-def jpath_value(structure, jpath):
-    """
-    Return single value or first value from list at given JPath within
-    given data structure.
-
-    This method returns None for non-existent JPaths.
-
-    :param str structure: data structure to be searched
-    :param str jpath: JPath to be evaluated
-    :return: None or found value
-    """
-    values = jpath_values(structure, jpath)
-    if values:
-        return values[0]
-    return None
-
-def jpath_exists(structure, jpath):
-    """
-    Check if node at given JPath within given data structure does exist.
-
-    :param str structure: data structure to be searched
-    :param str jpath: JPath to be evaluated
-    :return: True or False
-    :rtype: bool
-    """
-    result = jpath_value(structure, jpath)
-    if not result is None:
-        return True
-    return False
-
-def jpath_set(structure, jpath, value, overwrite = True, unique = False):
-    """
-    Set given JPath to given value within given structure.
-
-    For performance reasons this method is intentionally not written as
-    recursive.
-
-    :param str structure: data structure to be searched
-    :param str jpath: JPath to be evaluated
-    :param any value: value of any type to be set at given path
-    :param bool overwrite: enable/disable overwriting of already existing value
-    :param bool unique: ensure uniqueness of value, works only for lists
-    :return: numerical return code, one of the (:py:data:`RC_VALUE_SET`,
-             :py:data:`RC_VALUE_EXISTS`, :py:data:`RC_VALUE_DUPLICATE`)
-    :rtype: int
-    """
-    chunks = jpath_parse_c(jpath)
-    size = len(chunks) - 1
-    current = structure
-
-    # Process chunks in order, enumeration is used for detection of the last JPath chunk.
-    for i, ch in enumerate(chunks):
-        key = ch['n']
-
-        if not isinstance(current, dict) and not isinstance(current, collections.Mapping):
-            raise JPathException("Expected dict-like structure to attach node '{}'".format(ch['p']))
-
-        # Process indexed nodes
-        if 'i' in ch:
-            idx = ch['i']
-
-            # Automatically create nodes for non-existent keys
-            if not key in current:
-                current[key] = []
-            if not isinstance(current[key], list) and not isinstance(current[key], collections.MutableSequence):
-                raise JPathException("Expected list-like object under structure key '{}'".format(key))
-
-            # Detection of the last JPath chunk - node somewhere in the middle
-            if i != size:
-                # Attempt to access node at given index
-                try:
-                    current = current[key][idx]
-                # IndexError: list index out of range
-                # Node at given index does not exist, append new one. Using insert()
-                # does not work, item is appended to the end of the list anyway.
-                # TypeError: list indices must be integers or slices, not str
-                # In the case list index was '*', we are appending to the end of
-                # list.
-                except (IndexError, TypeError):
-                    current[key].append({})
-                    current = current[key][-1]
-
-            # Detection of the last JPath chunk - node at the end
-            else:
-                # Attempt to insert value at given index
-                try:
-                    if overwrite or not current[key][idx]:
-                        current[key][idx] = value
-                    else:
-                        return RC_VALUE_EXISTS
-                # IndexError: list index out of range
-                # Node at given index does not exist, append new one. Using insert()
-                # does not work, item is appended to the end of the list anyway.
-                # TypeError: list indices must be integers or slices, not str
-                # In the case list index was '*', we are appending to the end of
-                # list.
-                except (IndexError, TypeError):
-                    # At this point only deal with unique, overwrite does not make
-                    # sense, because we would not be here otherwise.
-                    if not unique or not value in current[key]:
-                        current[key].append(value)
-                    else:
-                        return RC_VALUE_DUPLICATE
-
-        # Process unindexed nodes
-        else:
-            # Detection of the last JPath chunk - node somewhere in the middle
-            if i != size:
-                # Automatically create nodes for non-existent keys
-                if not key in current:
-                    current[key] = {}
-                if not isinstance(current[key], dict) and not isinstance(current[key], collections.Mapping):
-                    raise JPathException("Expected dict-like object under structure key '{}'".format(key))
-
-                current = current[key]
-
-            # Detection of the last JPath chunk - node at the end
-            else:
-                if overwrite or not key in current:
-                    current[key] = value
-                else:
-                    return RC_VALUE_EXISTS
-    return RC_VALUE_SET
-
-if __name__ == "__main__":
-    """
-    Perform the demonstration.
-    """
-    import pprint
-
-    print("Path parsing:")
-    pprint.pprint(jpath_parse("Test"))
-    pprint.pprint(jpath_parse("Test.Path"))
-    pprint.pprint(jpath_parse("Long.Test.Path"))
-    pprint.pprint(jpath_parse("Long[1].Test.Path"))
-    pprint.pprint(jpath_parse("Long.Test[2].Path"))
-    pprint.pprint(jpath_parse("Long.Test.Path[3]"))
-    pprint.pprint(jpath_parse("Long[*].Test.Path"))
-    pprint.pprint(jpath_parse("Long.Test[*].Path"))
-    pprint.pprint(jpath_parse("Long.Test.Path[*]"))
-    pprint.pprint(jpath_parse("Long[#].Test.Path"))
-    pprint.pprint(jpath_parse("Long.Test[#].Path"))
-    pprint.pprint(jpath_parse("Long.Test.Path[#]"))
-
-    print("Path fetching:")
-    msg = {
-        'TestA': { 'ValueA1': 'A1', 'ValueA2': 'A2' },
-        'TestB': { 'ValueB1': 'B1', 'ValueB2': 'B2' },
-        'TestC': { 'ValueC1': 'C1', 'ValueC2': 'C2' },
-        'TestD': { 'ValueD1': ['D11','D12'], 'ValueC2': 'C2' }
-    }
-    pprint.pprint(jpath_values(msg, 'TestD.ValueD1'))
-    pprint.pprint(jpath_values(msg, 'TestD.ValueD1[1]'))
-    pprint.pprint(jpath_values(msg, 'TestD.ValueD1[2]'))
-    pprint.pprint(jpath_values(msg, 'TestD.ValueD1[#]'))
-
-    print("Path seting:")
-    msg = {
-        'TestA': { 'ValueA1': 'A1', 'ValueA2': 'A2' },
-        'TestB': { 'ValueB1': 'B1', 'ValueB2': 'B2' },
-        'TestC': { 'ValueC1': 'C1', 'ValueC2': 'C2' },
-        'TestD': { 'ValueD1': ['D11','D12'], 'ValueC2': 'C2' }
-    }
-    pprint.pprint(jpath_set(msg, 'TestE.ValueE1', "Added value"))
-    pprint.pprint(jpath_set(msg, 'TestE.ValueE2[1]', "Added value 2"))
-    pprint.pprint(jpath_set(msg, 'TestE.ValueE2[2]', "Added value 3"))
-    pprint.pprint(jpath_set(msg, 'TestE.ValueE2[#]', "Added value 4"))
-    pprint.pprint(jpath_set(msg, 'TestE.ValueE3[1].Subval1', "Added subvalue 11"))
-    pprint.pprint(jpath_set(msg, 'TestE.ValueE3[1].Subval2[1]', "Added subval 21"))
-    pprint.pprint(jpath_set(msg, 'TestE.ValueE3[#].Subval2[2]', "Added subval 22"))
-    pprint.pprint(msg)
diff --git a/lib/pynspect/lexer.py b/lib/pynspect/lexer.py
deleted file mode 100644
index 52321d05570552410359ceb1d7e49dd8b5582f4f..0000000000000000000000000000000000000000
--- a/lib/pynspect/lexer.py
+++ /dev/null
@@ -1,327 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# This file is part of Mentat system (https://mentat.cesnet.cz/).
-#
-# Copyright (C) since 2011 CESNET, z.s.p.o (http://www.ces.net/)
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-"""
-This module contains object encapsulation of `PLY <http://www.dabeaz.com/ply/>`__
-lexical analyzer for filtering and query language grammar used in Mentat
-project.
-
-Currently recognized tokens
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
-    # Mathematical operation tokens
-    OP_PLUS   = '\+'
-    OP_MINUS  = '-'
-    OP_TIMES  = '\*'
-    OP_DIVIDE = '/'
-    OP_MODULO = '%'
-
-    # Logical operation tokens
-    OP_OR     = '(or|OR)'
-    OP_XOR    = '(xor|XOR)'
-    OP_AND    = '(and|AND)'
-    OP_NOT    = '(not|NOT)'
-    OP_EXISTS = '(exists|EXISTS|\?)'
-
-    # Priority logical operation tokens
-    OP_OR_P  = '\|\|'
-    OP_XOR_P = '\^\^'
-    OP_AND_P = '&&'
-
-    # Comparison operation tokens
-    OP_LIKE = '(like|LIKE|=~)'
-    OP_IN   = '(in|IN|~~)'
-    OP_IS   = '(is|IS)'
-    OP_EQ   = '(eq|EQ|==)'
-    OP_NE   = '(ne|NE|!=|<>)'
-    OP_GT   = '(gt|GT|>)'
-    OP_GE   = '(ge|GE|>=)'
-    OP_LT   = '(lt|LT|<)'
-    OP_LE   = '(le|LE|<=)'
-
-    # Special tokens
-    COMMA  = '\s*,\s*|\s*;\s*'
-    LPAREN = '\('
-    RPAREN = '\)'
-    LBRACK = '\['
-    RBRACK = '\]'
-
-    # Contant and variable tokens
-    IPV4     = '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?:\/\d{1,2}|(?:-|..)\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?'
-    IPV6     = '[:a-fA-F0-9]+:[:a-fA-F0-9]*(?:\/\d{1,3}|(?:-|..)[:a-fA-F0-9]+:[:a-fA-F0-9]*)?'
-    INTEGER  = '\d+'
-    FLOAT    = '\d+\.\d+'
-    CONSTANT = '"([^"]+)"|\'([^\']+)\''
-    VARIABLE = '[_a-zA-Z][-_a-zA-Z0-9]*(?:\[(?:\d+|-\d+|\#)\])?(?:\.?[a-zA-Z][-_a-zA-Z0-9]*(?:\[(?:\d+|-\d+|\#)\])?)*'
-
-.. note::
-
-    Implementation of this module is very *PLY* specific, please read the
-    appropriate `documentation <http://www.dabeaz.com/ply/ply.html#ply_nn3>`__
-    to understand it.
-
-.. todo::
-
-    Consider following options:
-
-    * Support negative integers and floats
-    * Recognize RFC timestamps as constant without quotes for better
-      time value input
-    * Support functions (count, max, min, first, last, time, etc.)
-
-"""
-
-__version__ = "0.3"
-__author__ = "Jan Mach <jan.mach@cesnet.cz>"
-__credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>"
-
-import re
-import ply.lex as lex
-
-class MentatFilterLexer():
-    """
-    Object encapsulation of *PLY* lexical analyzer implementation for
-    filtering and query language grammar.
-    """
-
-    # List of all reserved words.
-    reserved = {
-       'or':     'OP_OR',
-       'xor':    'OP_XOR',
-       'and':    'OP_AND',
-       'not':    'OP_NOT',
-       'exists': 'OP_EXISTS',
-
-       'like': 'OP_LIKE',
-       'in':   'OP_IN',
-       'is':   'OP_IS',
-       'eq':   'OP_EQ',
-       'ne':   'OP_NE',
-       'gt':   'OP_GT',
-       'ge':   'OP_GE',
-       'lt':   'OP_LT',
-       'le':   'OP_LE',
-
-       'OR':     'OP_OR',
-       'XOR':    'OP_XOR',
-       'AND':    'OP_AND',
-       'NOT':    'OP_NOT',
-       'EXISTS': 'OP_EXISTS',
-
-       'LIKE': 'OP_LIKE',
-       'IN':   'OP_IN',
-       'IS':   'OP_IS',
-       'EQ':   'OP_EQ',
-       'NE':   'OP_NE',
-       'GT':   'OP_GT',
-       'GE':   'OP_GE',
-       'LT':   'OP_LT',
-       'LE':   'OP_LE',
-
-       '||': 'OP_OR_P',
-       '^^': 'OP_XOR_P',
-       '&&': 'OP_AND_P',
-       '!':  'OP_NOT',
-       '?':  'OP_EXISTS',
-
-       '=~': 'OP_LIKE',
-       '~~': 'OP_IN',
-       '==': 'OP_EQ',
-       '!=': 'OP_NE',
-       '<>': 'OP_NE',
-       '>':  'OP_GT',
-       '>=': 'OP_GE',
-       '<':  'OP_LT',
-       '<=': 'OP_LE',
-
-       '+': 'OP_PLUS',
-       '-': 'OP_MINUS',
-       '*': 'OP_TIMES',
-       '/': 'OP_DIVIDE',
-       '%': 'OP_MODULO'
-    }
-
-    # List of grammar token names.
-    tokens = [
-       'EXP_ALL',
-
-       'OP_PLUS',
-       'OP_MINUS',
-       'OP_TIMES',
-       'OP_DIVIDE',
-       'OP_MODULO',
-
-       'OP_OR',
-       'OP_XOR',
-       'OP_AND',
-       'OP_NOT',
-       'OP_EXISTS',
-
-       'OP_OR_P',
-       'OP_XOR_P',
-       'OP_AND_P',
-
-       'OP_LIKE',
-       'OP_IN',
-       'OP_IS',
-       'OP_EQ',
-       'OP_NE',
-       'OP_GT',
-       'OP_GE',
-       'OP_LT',
-       'OP_LE',
-
-       'COMMA',
-       'LPAREN',
-       'RPAREN',
-       'LBRACK',
-       'RBRACK',
-
-       'IPV4',
-       'IPV6',
-       'INTEGER',
-       'FLOAT',
-       'CONSTANT',
-       'VARIABLE'
-    ]
-
-    # Regular expressions for simple tokens
-    t_COMMA  = r'\s*,\s*|\s*;\s*'
-    t_LPAREN = r'\('
-    t_RPAREN = r'\)'
-    t_LBRACK = r'\['
-    t_RBRACK = r'\]'
-
-    # Regular expression for ignored tokens
-    t_ignore = ' \t'
-
-    def build(self, **kwargs):
-        """
-        Build/rebuild the lexer object.
-
-        (Re)Initialize internal PLY lexer object.
-        """
-        self.lexer = lex.lex(module=self, **kwargs)
-
-    def test(self, data, separator = ''):
-        """
-        Test the lexer on given input string.
-
-
-        """
-        self.lexer.input(data)
-        result = ''
-        while True:
-            tok = self.lexer.token()
-            if not tok:
-                break
-            result = '{}{}{}'.format(result, tok, separator)
-        return result
-
-    #---------------------------------------------------------------------------
-
-    # According to the documentation, section 4.3 Specification of tokens
-    # (http://www.dabeaz.com/ply/ply.html#ply_nn6), best practice is to
-    # reduce the number of required regular expressions. So following
-    # is the ugly as hell uber regular expression for unary and binary
-    # operators.
-    def t_EXP_ALL(self, t):
-        r'(-|\+|\*|/|%|like|LIKE|=~|in|IN|~~|is|IS|eq|EQ|==|ne|NE|!=|<>|ge|GE|>=|gt|GT|>|le|LE|<=|lt|LT|<|or|OR|\|\||xor|XOR|\^\^|and|AND|&&|not|NOT|!|exists|EXISTS|\?)'
-        t.type = self.reserved.get(t.value)
-        t.value = t.type
-        return t
-
-    def t_IPV4(self, t):
-        r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?:\/\d{1,2}|(?:-|..)\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?'
-        t.value = (t.type, t.value)
-        return t
-
-    def t_IPV6(self, t):
-        r'[:a-fA-F0-9]+:[:a-fA-F0-9]*(?:\/\d{1,3}|(?:-|..)[:a-fA-F0-9]+:[:a-fA-F0-9]*)?'
-        t.value = (t.type, t.value)
-        return t
-
-    def t_FLOAT(self, t):
-        r'\d+\.\d+'
-        t.value = (t.type, float(t.value))
-        return t
-
-    def t_INTEGER(self, t):
-        r'\d+'
-        t.value = (t.type, int(t.value))
-        return t
-
-    def t_VARIABLE(self, t):
-        r'[_a-zA-Z][-_a-zA-Z0-9]*(?:\[(?:\d+|-\d+|\#)\])?(?:\.?[a-zA-Z][-_a-zA-Z0-9]*(?:\[(?:\d+|-\d+|\#)\])?)*'
-        t.value = (t.type, t.value)
-        return t
-
-    def t_CONSTANT(self, t):
-        r'"([^"]+)"|\'([^\']+)\''
-        t.value = (t.type, re.sub('["\']', '', t.value))
-        return t
-
-    def t_newline(self, t):
-        r'\n+'
-        t.lexer.lineno += len(t.value)
-
-    def t_error(self, t):
-        print("Illegal character '%s'" % t.value[0])
-        t.lexer.skip(1)
-
-    def reset_lineno(self):
-        """
-        Reset internal line counter.
-        """
-        self.lexer.lineno = 1
-
-    def input(self, text):
-        """
-        Proxy method for underlying Lexer object interface.
-        """
-        self.lexer.input(text)
-
-    def token(self):
-        """
-        Proxy method for underlying Lexer object interface.
-        """
-        g = self.lexer.token()
-        return g
-
-if __name__ == "__main__":
-    """
-    Perform the demonstration by parsing text containing all possible
-    tokens.
-    """
-    data = """
-        1 + 1 - 1 * 1 % 1
-        OR 2 or 2 || 2
-        XOR 3 xor 3 ^^ 3
-        AND 4 and 4 && 4
-        NOT 5 not 5 ! 5
-        EXISTS 6 exists 4 ? 6
-        LIKE 7 like 7 =~ 7
-        IN 8 in 8 ~~ 8
-        IS 9 is 9
-        EQ 10 eq 10 == 10
-        NE 11 ne 11 <> 11 != 11
-        GT 12 gt 12 > 12
-        GE 13 ge 13 >= 13
-        LT 14 lt 14 < 14
-        LE 15 le 15 <= 15
-        (127.0.0.1 eq ::1 eq 2001:afdc::58 eq Source.Node eq "Value 525.89:X><" eq 'Value 525.89:X><')
-        [1, 2, 3 , 4]
-    """
-
-    # Build the lexer and try it out
-    m = MentatFilterLexer()
-    m.build()
-    print(m.test(data, "\n"))
diff --git a/lib/pynspect/rules.py b/lib/pynspect/rules.py
deleted file mode 100644
index 4a9c665202763ab0262b56081c896d6a0111e9be..0000000000000000000000000000000000000000
--- a/lib/pynspect/rules.py
+++ /dev/null
@@ -1,578 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# This file is part of Mentat system (https://mentat.cesnet.cz/).
-#
-# Copyright (C) since 2011 CESNET, z.s.p.o (http://www.ces.net/)
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-"""
-This module contains implementation of object representations of filtering
-and query language grammar.
-
-There is a separate class defined for each grammar rule. There are following
-classes representing all possible constant and variable values (tree leaves,
-without child nodes):
-
-* :py:class:`VariableRule`
-* :py:class:`ConstantRule`
-* :py:class:`IPv4Rule`
-* :py:class:`IPv6Rule`
-* :py:class:`IntegerRule`
-* :py:class:`FloatRule`
-* :py:class:`ListRule`
-
-There are following classes representing various binary and unary operations:
-
-* :py:class:`LogicalBinOpRule`
-* :py:class:`ComparisonBinOpRule`
-* :py:class:`MathBinOpRule`
-* :py:class:`UnaryOperationRule`
-
-Desired hierarchical rule tree can be created either programatically, or by
-parsing string rules using :py:mod:`pynspect.gparser`.
-
-Working with rule tree is then done via objects implementing rule tree
-traverser interface:
-
-* :py:class:`RuleTreeTraverser`
-
-The provided :py:class:`RuleTreeTraverser` class contains also implementation of
-all necessary evaluation methods.
-
-There is a simple example implementation of rule tree traverser capable of
-printing rule tree into a formated string:
-
-* :py:class:`PrintingTreeTraverser`
-
-Rule evaluation
-^^^^^^^^^^^^^^^
-
-* Logical operations ``and or xor not exists``
-
-  There is no special handling for operands of logical operations. Operand(s) are
-  evaluated in logical expression exactly as they are received, there is no
-  mangling involved.
-
-* Comparison operations
-
-    All comparison operations are designed to work with lists as both operands.
-    This is because :py:func:`pynspect.jpath.jpath_values` function is
-    used to retrieve variable values and this function always returns list.
-
-    * Operation: ``is``
-
-      Like in the case of logical operations, there is no mangling involved when
-      evaluating this operation. Both operands are compared using Python`s native
-      ``is`` operation and result is returned.
-
-    * Operation: ``in``
-
-      In this case left operand is iterated and each value is compared using Python`s
-      native ``in`` operation with right operand. First ``True`` result wins and
-      operation immediatelly returns ``True``, ``False`` is returned otherwise.
-
-    * Any other operation: ``like eq ne gt ge lt le``
-
-      In case of this operation both of the operands are iterated and each one is
-      compared with each other. First ``True`` result wins and operation immediatelly
-      returns ``True``, ``False`` is returned otherwise.
-
-    * Math operations: ``+ - * / %``
-
-      Current math operation implementation supports following options:
-
-        * Both operands are lists of the same length. In this case corresponding
-          elements at certain position within the list are evaluated with given
-          operation. Result is a list.
-
-        * One of the operands is a list, second is scalar value or list of the
-          size 1. In this case given operation is evaluated with each element of
-          the longer list. Result is a list.
-
-        * Operands are lists of the different size. This option is **forbidden**
-          and the result is ``None``.
-
-"""
-
-__version__ = "0.3"
-__author__ = "Jan Mach <jan.mach@cesnet.cz>"
-__credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>"
-
-import collections
-import re
-import datetime
-
-class FilteringRuleException(Exception):
-    """
-    Custom filtering rule specific exception.
-
-    This exception will be thrown on module specific errors.
-    """
-    def __init__(self, description):
-        self._description = description
-    def __str__(self):
-        return repr(self._description)
-
-class Rule():
-    """
-    Base class for all filter tree rules.
-    """
-    pass
-
-class ValueRule(Rule):
-    """
-    Base class for all filter tree value rules.
-    """
-    pass
-
-class VariableRule(ValueRule):
-    """
-    Class for expression variables.
-    """
-    def __init__(self, value):
-        """
-        Initialize the variable with given path value.
-        """
-        self.value = value
-
-    def __str__(self):
-        return "{}".format(self.value)
-
-    def __repr__(self):
-        return "VARIABLE({})".format(repr(self.value))
-
-    def traverse(self, traverser, **kwargs):
-        return traverser.variable(self, **kwargs)
-
-class ConstantRule(ValueRule):
-    """
-    Class for all expression constant values.
-    """
-    def __init__(self, value):
-        """
-        Initialize the constant with given value.
-        """
-        self.value = value
-
-    def __str__(self):
-        return '"{}"'.format(self.value)
-
-    def __repr__(self):
-        return "CONSTANT({})".format(repr(self.value))
-
-    def traverse(self, traverser, **kwargs):
-        return traverser.constant(self, **kwargs)
-
-class IPV4Rule(ConstantRule):
-    """
-    Class for IPv4 address constants.
-    """
-    def __str__(self):
-        return '{}'.format(self.value)
-
-    def __repr__(self):
-        return "IPV4({})".format(repr(self.value))
-
-    def traverse(self, traverser, **kwargs):
-        return traverser.ipv4(self, **kwargs)
-
-class IPV6Rule(ConstantRule):
-    """
-    Class for IPv6 address constants.
-    """
-    def __str__(self):
-        return '{}'.format(self.value)
-
-    def __repr__(self):
-        return "IPV6({})".format(repr(self.value))
-
-    def traverse(self, traverser, **kwargs):
-        return traverser.ipv6(self, **kwargs)
-
-class NumberRule(ConstantRule):
-    """
-    Base class for all numerical constants.
-    """
-    pass
-
-class IntegerRule(NumberRule):
-    """
-    Class for integer constants.
-    """
-    def __str__(self):
-        return '{}'.format(self.value)
-
-    def __repr__(self):
-        return "INTEGER({})".format(repr(self.value))
-
-    def traverse(self, traverser, **kwargs):
-        return traverser.integer(self, **kwargs)
-
-class FloatRule(NumberRule):
-    """
-    Class for float constants.
-    """
-    def __str__(self):
-        return '{}'.format(self.value)
-
-    def __repr__(self):
-        return "FLOAT({})".format(repr(self.value))
-
-    def traverse(self, traverser, **kwargs):
-        return traverser.float(self, **kwargs)
-
-class ListRule(ValueRule):
-    """
-    Base class for all filter tree rules.
-    """
-    def __init__(self, rule, next_rule = None):
-        """
-        Initialize the constant with given value.
-        """
-        if not isinstance(rule, list):
-            rule = [rule]
-        self.value = rule
-        if next_rule:
-            self.value += next_rule.value
-
-    def __str__(self):
-        return '[{}]'.format(', '.join([str(v) for v in self.value]))
-
-    def __repr__(self):
-        return "LIST({})".format(', '.join([repr(v) for v in self.value]))
-
-    def traverse(self, traverser, **kwargs):
-        return traverser.list(self, **kwargs)
-
-class OperationRule(Rule):
-    """
-    Base class for all expression operations (both unary and binary).
-    """
-    pass
-
-class BinaryOperationRule(OperationRule):
-    """
-    Base class for all binary operations.
-    """
-    def __init__(self, operation, left, right):
-        """
-        Initialize the object with operation type and both operands.
-        """
-        self.operation = operation
-        self.left = left
-        self.right = right
-
-    def __str__(self):
-        return "({} {} {})".format(str(self.left), str(self.operation), str(self.right))
-
-class LogicalBinOpRule(BinaryOperationRule):
-    """
-    Base class for all logical binary operations.
-    """
-    def __repr__(self):
-        return "LOGBINOP({} {} {})".format(repr(self.left), str(self.operation), repr(self.right))
-
-    def traverse(self, traverser, **kwargs):
-        lr = self.left.traverse(traverser, **kwargs)
-        rr = self.right.traverse(traverser, **kwargs)
-        return traverser.binary_operation_logical(self, lr, rr, **kwargs)
-
-class ComparisonBinOpRule(BinaryOperationRule):
-    """
-    Base class for all comparison binary operations.
-    """
-    def __repr__(self):
-        return "COMPBINOP({} {} {})".format(repr(self.left), str(self.operation), repr(self.right))
-
-    def traverse(self, traverser, **kwargs):
-        lr = self.left.traverse(traverser, **kwargs)
-        rr = self.right.traverse(traverser, **kwargs)
-        return traverser.binary_operation_comparison(self, lr, rr, **kwargs)
-
-class MathBinOpRule(BinaryOperationRule):
-    """
-    Base class for all mathematical binary operations.
-    """
-    def __repr__(self):
-        return "MATHBINOP({} {} {})".format(repr(self.left), str(self.operation), repr(self.right))
-
-    def traverse(self, traverser, **kwargs):
-        lr = self.left.traverse(traverser, **kwargs)
-        rr = self.right.traverse(traverser, **kwargs)
-        return traverser.binary_operation_math(self, lr, rr, **kwargs)
-
-class UnaryOperationRule(OperationRule):
-    """
-    Base class for all unary operations.
-    """
-    def __init__(self, operation, right):
-        """
-        Initialize the object with operation type operand.
-        """
-        self.operation = operation
-        self.right = right
-
-    def __str__(self):
-        return "({} {})".format(str(self.operation), str(self.right))
-
-    def __repr__(self):
-        return "UNOP({} {})".format(str(self.operation), repr(self.right))
-
-    def traverse(self, traverser, **kwargs):
-        rr = self.right.traverse(traverser, **kwargs)
-        return traverser.unary_operation(self, rr, **kwargs)
-
-def _to_numeric(val):
-    """
-    Helper function for conversion of various data types into numeric representation.
-    """
-    if isinstance(val, int) or isinstance(val, float):
-        return val
-    if isinstance(val, datetime.datetime):
-        return val.timestamp()
-    return float(val)
-
-class RuleTreeTraverser():
-    """
-    Base class for all rule tree traversers.
-    """
-
-    """
-    Definitions of all logical binary operations.
-    """
-    binops_logical = {
-        'OP_OR':    lambda x, y : x or y,
-        'OP_XOR':   lambda x, y : (x and not y) or (not x and y),
-        'OP_AND':   lambda x, y : x and y,
-        'OP_OR_P':  lambda x, y : x or y,
-        'OP_XOR_P': lambda x, y : (x and not y) or (not x and y),
-        'OP_AND_P': lambda x, y : x and y,
-    }
-
-    """
-    Definitions of all comparison binary operations.
-    """
-    binops_comparison = {
-        'OP_LIKE': lambda x, y : re.search(y, x),
-        'OP_IN':   lambda x, y : x in y,
-        'OP_IS':   lambda x, y : x == y,
-        'OP_EQ':   lambda x, y : x == y,
-        'OP_NE':   lambda x, y : x != y,
-        'OP_GT':   lambda x, y : x > y,
-        'OP_GE':   lambda x, y : x >= y,
-        'OP_LT':   lambda x, y : x < y,
-        'OP_LE':   lambda x, y : x <= y,
-    }
-
-    """
-    Definitions of all mathematical binary operations.
-    """
-    binops_math = {
-        'OP_PLUS':   lambda x, y : x + y,
-        'OP_MINUS':  lambda x, y : x - y,
-        'OP_TIMES':  lambda x, y : x * y,
-        'OP_DIVIDE': lambda x, y : x / y,
-        'OP_MODULO': lambda x, y : x % y,
-    }
-
-    """
-    Definitions of all unary operations.
-    """
-    unops = {
-        'OP_NOT':    lambda x : not x,
-        'OP_EXISTS': lambda x : x,
-    }
-
-    def evaluate_binop_logical(self, operation, left, right, **kwargs):
-        """
-        Evaluate given logical binary operation with given operands.
-        """
-        if not operation in self.binops_logical:
-            raise Exception("Invalid logical binary operation '{}'".format(operation))
-        result = self.binops_logical[operation](left, right)
-        if result:
-            return True
-        else:
-            return False
-
-    def evaluate_binop_comparison(self, operation, left, right, **kwargs):
-        """
-        Evaluate given comparison binary operation with given operands.
-        """
-        if not operation in self.binops_comparison:
-            raise Exception("Invalid comparison binary operation '{}'".format(operation))
-        if left is None or right is None:
-            return None
-        if not isinstance(left, list):
-            left = [left]
-        if not isinstance(right, list):
-            right = [right]
-        if not len(left) or not len(right):
-            return None
-        if operation in ['OP_IS']:
-            res = self.binops_comparison[operation](left, right)
-            if res:
-                return True
-        elif operation in ['OP_IN']:
-            for l in left:
-                res = self.binops_comparison[operation](l, right)
-                if res:
-                    return True
-        else:
-            for l in left:
-                if l is None:
-                    continue
-                for r in right:
-                    if r is None:
-                        continue
-                    res = self.binops_comparison[operation](l, r)
-                    if res:
-                        return True
-        return False
-
-    def _calculate_vector(self, operation, left, right):
-        """
-
-        """
-        result = []
-        if len(right) == 1:
-            right = _to_numeric(right[0])
-            for l in left:
-                l  = _to_numeric(l)
-                result.append(self.binops_math[operation](l, right))
-        elif len(left) == 1:
-            left = _to_numeric(left[0])
-            for r in right:
-                r  = _to_numeric(r)
-                result.append(self.binops_math[operation](left, r))
-        elif len(left) == len(right):
-            for l, r in zip(left, right):
-                l  = _to_numeric(l)
-                r  = _to_numeric(r)
-                result.append(self.binops_math[operation](l, r))
-        else:
-            raise FilteringRuleException("Uneven lenth of math operation '{}' operands".format(operation))
-        return result
-
-    def evaluate_binop_math(self, operation, left, right, **kwargs):
-        """
-        Evaluate given mathematical binary operation with given operands.
-        """
-        if not operation in self.binops_math:
-            raise Exception("Invalid math binary operation '{}'".format(operation))
-        if left is None or right is None:
-            return None
-        if not isinstance(left, list):
-            left = [left]
-        if not isinstance(right, list):
-            right = [right]
-        if not len(left) or not len(right):
-            return None
-        try:
-            v = self._calculate_vector(operation, left, right)
-            if len(v) > 1:
-                return v
-            else:
-                return v[0]
-        except:
-            return None
-
-    def evaluate_unop(self, operation, right, **kwargs):
-        """
-        Evaluate given unary operation with given operand.
-        """
-        if not operation in self.unops:
-            raise Exception("Invalid unary operation '{}'".format(operation))
-        if right is None:
-            return None
-        return self.unops[operation](right)
-
-    def evaluate(self, operation, *args):
-        """
-        Master method for evaluating any operation (both unary and binary).
-        """
-        if operation in self.binops_comparison:
-            return self.evaluate_binop_comparison(rule, *args)
-        if operation in self.binops_logical:
-            return self.evaluate_binop_logical(rule, *args)
-        if operation in self.binops_math:
-            return self.evaluate_binop_math(rule, *args)
-        if operation in self.unops:
-            return self.evaluate_unop(rule, *args)
-        raise Exception("Invalid operation '{}'".format(operation))
-
-class PrintingTreeTraverser(RuleTreeTraverser):
-    """
-    Demonstation of simple rule tree traverser - printing traverser.
-    """
-    def ipv4(self, rule, **kwargs):
-        return "IPV4({})".format(rule.value)
-    def ipv6(self, rule, **kwargs):
-        return "IPV6({})".format(rule.value)
-    def integer(self, rule, **kwargs):
-        return "INTEGER({})".format(rule.value)
-    def float(self, rule, **kwargs):
-        return "FLOAT({})".format(rule.value)
-    def constant(self, rule, **kwargs):
-        return "CONSTANT({})".format(rule.value)
-    def variable(self, rule, **kwargs):
-        return "VARIABLE({})".format(rule.value)
-    def list(self, rule, **kwargs):
-        return "LIST({})".format(', '.join([str(v) for v in rule.value]))
-    def binary_operation_logical(self, rule, left, right, **kwargs):
-        return "LOGBINOP({};{};{})".format(rule.operation, left, right)
-    def binary_operation_comparison(self, rule, left, right, **kwargs):
-        return "COMPBINOP({};{};{})".format(rule.operation, left, right)
-    def binary_operation_math(self, rule, left, right, **kwargs):
-        return "MATHBINOP({};{};{})".format(rule.operation, left, right)
-    def unary_operation(self, rule, right, **kwargs):
-        return "UNOP({};{})".format(rule.operation, right)
-
-if __name__ == "__main__":
-    """
-    Perform the demonstration.
-    """
-    print("* Rule usage:")
-    rule_var = VariableRule("Test")
-    print("STR:  {}".format(str(rule_var)))
-    print("REPR: {}".format(repr(rule_var)))
-    rule_const = ConstantRule("constant")
-    print("STR:  {}".format(str(rule_const)))
-    print("REPR: {}".format(repr(rule_const)))
-    rule_ipv4 = IPV4Rule("127.0.0.1")
-    print("STR:  {}".format(str(rule_ipv4)))
-    print("REPR: {}".format(repr(rule_ipv4)))
-    rule_ipv6 = IPV6Rule("::1")
-    print("STR:  {}".format(str(rule_ipv6)))
-    print("REPR: {}".format(repr(rule_ipv6)))
-    rule_integer = IntegerRule(15)
-    print("STR:  {}".format(str(rule_integer)))
-    print("REPR: {}".format(repr(rule_integer)))
-    rule_float = FloatRule(15.5)
-    print("STR:  {}".format(str(rule_float)))
-    print("REPR: {}".format(repr(rule_float)))
-    rule_binop_l = LogicalBinOpRule('OP_OR', rule_var, rule_integer)
-    print("STR:  {}".format(str(rule_binop_l)))
-    print("REPR: {}".format(repr(rule_binop_l)))
-    rule_binop_c = ComparisonBinOpRule('OP_GT', rule_var, rule_integer)
-    print("STR:  {}".format(str(rule_binop_c)))
-    print("REPR: {}".format(repr(rule_binop_c)))
-    rule_binop_m = MathBinOpRule('OP_PLUS', rule_var, rule_integer)
-    print("STR:  {}".format(str(rule_binop_m)))
-    print("REPR: {}".format(repr(rule_binop_m)))
-    rule_binop = LogicalBinOpRule('OP_OR', ComparisonBinOpRule('OP_GT', MathBinOpRule('OP_PLUS', VariableRule("Test"), IntegerRule(10)), IntegerRule(20)), ComparisonBinOpRule('OP_LT', VariableRule("Test"), IntegerRule(5)))
-    print("STR:  {}".format(str(rule_binop)))
-    print("REPR: {}".format(repr(rule_binop)))
-    rule_unop = UnaryOperationRule('OP_NOT', rule_var)
-    print("STR:  {}".format(str(rule_unop)))
-    print("REPR: {}".format(repr(rule_unop)))
-
-    print("\n* Traverser usage:")
-    traverser = PrintingTreeTraverser()
-    print("{}".format(rule_binop_l.traverse(traverser)))
-    print("{}".format(rule_binop_c.traverse(traverser)))
-    print("{}".format(rule_binop_m.traverse(traverser)))
-    print("{}".format(rule_binop.traverse(traverser)))
-    print("{}".format(rule_unop.traverse(traverser)))
diff --git a/lib/pynspect/test_filters.py b/lib/pynspect/test_filters.py
deleted file mode 100644
index 2f6c873e0fb00380608008615b4fec36653cd4ae..0000000000000000000000000000000000000000
--- a/lib/pynspect/test_filters.py
+++ /dev/null
@@ -1,310 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# This file is part of Mentat system (https://mentat.cesnet.cz/).
-#
-# Copyright (C) since 2011 CESNET, z.s.p.o (http://www.ces.net/)
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-import os
-import sys
-import shutil
-import unittest
-from pprint import pformat, pprint
-
-# Generate the path to custom 'lib' directory
-lib = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../lib'))
-sys.path.insert(0, lib)
-
-from pynspect.jpath import *
-from pynspect.rules import *
-from pynspect.gparser import MentatFilterParser
-from pynspect.filters import DataObjectFilter
-
-#-------------------------------------------------------------------------------
-# NOTE: Sorry for the long lines in this file. They are deliberate, because the
-# assertion permutations are (IMHO) more readable this way.
-#-------------------------------------------------------------------------------
-
-class TestMentatDataObjectFilter(unittest.TestCase):
-    test_msg1 = {
-        "ID" : "e214d2d9-359b-443d-993d-3cc5637107a0",
-        "WinEndTime" : "2016-06-21 11:25:01Z",
-        "ConnCount" : 2,
-        "Source" : [
-            {
-                "IP4" : [
-                    "188.14.166.39"
-                ]
-            }
-        ],
-        "Format" : "IDEA0",
-        "WinStartTime" : "2016-06-21 11:20:01Z",
-        "_CESNET" : {
-            "StorageTime" : 1466508305
-        },
-        "Target" : [
-            {
-                "IP4" : [
-                    "195.113.165.128/25"
-                ],
-                "Port" : [
-                    "22"
-                ],
-                "Proto" : [
-                    "tcp",
-                    "ssh"
-                ],
-                "Anonymised" : True
-            }
-        ],
-        "Note" : "SSH login attempt",
-        "DetectTime" : "2016-06-21 13:08:27Z",
-        "Node" : [
-            {
-                "Name" : "cz.cesnet.mentat.warden_filer",
-                "Type" : [
-                    "Relay"
-                ]
-            },
-            {
-                "AggrWin" : "00:05:00",
-                "Type" : [
-                    "Connection",
-                    "Honeypot",
-                    "Recon"
-                ],
-                "SW" : [
-                    "Kippo"
-                ],
-                "Name" : "cz.uhk.apate.cowrie"
-            }
-        ],
-        "Category" : [
-            "Attempt.Login"
-        ]
-    }
-
-    def test_01_basic_logical(self):
-        """
-        Perform basic filtering tests.
-        """
-        self.maxDiff = None
-
-        flt = DataObjectFilter()
-
-        rule = LogicalBinOpRule('OP_AND', ConstantRule(True), ConstantRule(True))
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = LogicalBinOpRule('OP_AND', ConstantRule(True), ConstantRule(False))
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = LogicalBinOpRule('OP_AND', ConstantRule(False), ConstantRule(True))
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = LogicalBinOpRule('OP_AND', ConstantRule(False), ConstantRule(False))
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-
-        rule = LogicalBinOpRule('OP_OR', ConstantRule(True), ConstantRule(True))
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = LogicalBinOpRule('OP_OR', ConstantRule(True), ConstantRule(False))
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = LogicalBinOpRule('OP_OR', ConstantRule(False), ConstantRule(True))
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = LogicalBinOpRule('OP_OR', ConstantRule(False), ConstantRule(False))
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-
-        rule = LogicalBinOpRule('OP_XOR', ConstantRule(True), ConstantRule(True))
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = LogicalBinOpRule('OP_XOR', ConstantRule(True), ConstantRule(False))
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = LogicalBinOpRule('OP_XOR', ConstantRule(False), ConstantRule(True))
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = LogicalBinOpRule('OP_XOR', ConstantRule(False), ConstantRule(False))
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-
-        rule = UnaryOperationRule('OP_NOT', ConstantRule(True))
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = UnaryOperationRule('OP_NOT', ConstantRule(False))
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = UnaryOperationRule('OP_NOT', VariableRule("Target.Anonymised"))
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-
-    def test_02_basic_comparison(self):
-        """
-        Perform basic filtering tests.
-        """
-        self.maxDiff = None
-
-        flt = DataObjectFilter()
-        psr = MentatFilterParser()
-        psr.build()
-
-        rule = ComparisonBinOpRule('OP_EQ', VariableRule("ID"), ConstantRule("e214d2d9-359b-443d-993d-3cc5637107a0"))
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = ComparisonBinOpRule('OP_EQ', VariableRule("ID"), ConstantRule("e214d2d9-359b-443d-993d-3cc5637107"))
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = ComparisonBinOpRule('OP_NE', VariableRule("ID"), ConstantRule("e214d2d9-359b-443d-993d-3cc5637107a0"))
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = ComparisonBinOpRule('OP_NE', VariableRule("ID"), ConstantRule("e214d2d9-359b-443d-993d-3cc5637107"))
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-
-        rule = ComparisonBinOpRule('OP_LIKE', VariableRule("ID"), ConstantRule("e214d2d9"))
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = ComparisonBinOpRule('OP_LIKE', VariableRule("ID"), ConstantRule("xxxxxxxx"))
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = ComparisonBinOpRule('OP_IN', VariableRule("Category"), ListRule(ConstantRule("Phishing"), ListRule(ConstantRule("Attempt.Login"))))
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = ComparisonBinOpRule('OP_IN', VariableRule("Category"), ListRule(ConstantRule("Phishing"), ListRule(ConstantRule("Spam"))))
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = ComparisonBinOpRule('OP_IS', VariableRule("Category"), ListRule(ConstantRule("Attempt.Login")))
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = ComparisonBinOpRule('OP_IS', VariableRule("Category"), ListRule(ConstantRule("Phishing"), ListRule(ConstantRule("Attempt.Login"))))
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = ComparisonBinOpRule('OP_EQ', VariableRule("ConnCount"), IntegerRule(2))
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = ComparisonBinOpRule('OP_EQ', VariableRule("ConnCount"), IntegerRule(4))
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = ComparisonBinOpRule('OP_NE', VariableRule("ConnCount"), IntegerRule(2))
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = ComparisonBinOpRule('OP_NE', VariableRule("ConnCount"), IntegerRule(4))
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = ComparisonBinOpRule('OP_GT', VariableRule("ConnCount"), IntegerRule(2))
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = ComparisonBinOpRule('OP_GT', VariableRule("ConnCount"), IntegerRule(1))
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = ComparisonBinOpRule('OP_GE', VariableRule("ConnCount"), IntegerRule(2))
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = ComparisonBinOpRule('OP_GE', VariableRule("ConnCount"), IntegerRule(1))
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = ComparisonBinOpRule('OP_GE', VariableRule("ConnCount"), IntegerRule(3))
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = ComparisonBinOpRule('OP_LT', VariableRule("ConnCount"), IntegerRule(2))
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = ComparisonBinOpRule('OP_LT', VariableRule("ConnCount"), IntegerRule(3))
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = ComparisonBinOpRule('OP_LE', VariableRule("ConnCount"), IntegerRule(2))
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = ComparisonBinOpRule('OP_LE', VariableRule("ConnCount"), IntegerRule(3))
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = ComparisonBinOpRule('OP_LE', VariableRule("ConnCount"), IntegerRule(1))
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-
-        rule = psr.parse('ID == "e214d2d9-359b-443d-993d-3cc5637107a0"')
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = psr.parse('ID eq "e214d2d9-359b-443d-993d-3cc5637107"')
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = psr.parse('ID != "e214d2d9-359b-443d-993d-3cc5637107a0"')
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = psr.parse('ID ne "e214d2d9-359b-443d-993d-3cc5637107"')
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-
-        rule = psr.parse('ID like "e214d2d9"')
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = psr.parse('ID LIKE "xxxxxxxx"')
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = psr.parse('Category in ["Phishing" , "Attempt.Login"]')
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = psr.parse('Category IN ["Phishing" , "Spam"]')
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = psr.parse('Category is ["Attempt.Login"]')
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = psr.parse('Category IS ["Phishing" , "Attempt.Login"]')
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = psr.parse('ConnCount == 2')
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = psr.parse('ConnCount eq 4')
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = psr.parse('ConnCount != 2')
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = psr.parse('ConnCount ne 4')
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = psr.parse('ConnCount > 2')
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = psr.parse('ConnCount gt 1')
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = psr.parse('ConnCount >= 2')
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = psr.parse('ConnCount ge 1')
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = psr.parse('ConnCount GE 3')
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = psr.parse('ConnCount < 2')
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = psr.parse('ConnCount lt 3')
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = psr.parse('ConnCount <= 2')
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = psr.parse('ConnCount le 3')
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = psr.parse('ConnCount LE 1')
-        self.assertEqual(flt.filter(rule, self.test_msg1), False)
-        rule = psr.parse('ConnCounts LE 1')
-        self.assertEqual(flt.filter(rule, self.test_msg1), None)
-
-    def test_03_basic_math(self):
-        """
-        Perform basic math tests.
-        """
-        self.maxDiff = None
-
-        flt = DataObjectFilter()
-        psr = MentatFilterParser()
-        psr.build()
-
-        rule = MathBinOpRule('OP_PLUS', VariableRule("ConnCount"), IntegerRule(1))
-        self.assertEqual(flt.filter(rule, self.test_msg1), 3)
-        rule = MathBinOpRule('OP_MINUS', VariableRule("ConnCount"), IntegerRule(1))
-        self.assertEqual(flt.filter(rule, self.test_msg1), 1)
-        rule = MathBinOpRule('OP_TIMES', VariableRule("ConnCount"), IntegerRule(5))
-        self.assertEqual(flt.filter(rule, self.test_msg1), 10)
-        rule = MathBinOpRule('OP_DIVIDE', VariableRule("ConnCount"), IntegerRule(2))
-        self.assertEqual(flt.filter(rule, self.test_msg1), 1)
-        rule = MathBinOpRule('OP_MODULO', VariableRule("ConnCount"), IntegerRule(2))
-        self.assertEqual(flt.filter(rule, self.test_msg1), 0)
-
-        rule = psr.parse('ConnCount + 1')
-        self.assertEqual(flt.filter(rule, self.test_msg1), 3)
-        rule = psr.parse('ConnCount - 1')
-        self.assertEqual(flt.filter(rule, self.test_msg1), 1)
-        rule = psr.parse('ConnCount * 5')
-        self.assertEqual(flt.filter(rule, self.test_msg1), 10)
-        rule = psr.parse('ConnCount / 2')
-        self.assertEqual(flt.filter(rule, self.test_msg1), 1)
-        rule = psr.parse('ConnCount % 2')
-        self.assertEqual(flt.filter(rule, self.test_msg1), 0)
-
-    def test_04_advanced_filters(self):
-        """
-        Perform advanced filtering tests.
-        """
-        self.maxDiff = None
-
-        flt = DataObjectFilter()
-        psr = MentatFilterParser()
-        psr.build()
-
-        rule = psr.parse('(ConnCount + 10) > 11')
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = psr.parse('((ConnCount + 3) < 5) or ((ConnCount + 10) > 11)')
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-        rule = psr.parse('1')
-        self.assertEqual(flt.filter(rule, self.test_msg1), True)
-
-    def test_05_non_existent_nodes(self):
-        """
-        Perform advanced filtering tests.
-        """
-        self.maxDiff = None
-
-        flt = DataObjectFilter()
-        psr = MentatFilterParser()
-        psr.build()
-
-        rule = psr.parse('(ConnCounts + 10) > 11')
-        self.assertEqual(flt.filter(rule, self.test_msg1), None)
-        rule = psr.parse('ConnCount > ConnCounts')
-        self.assertEqual(flt.filter(rule, self.test_msg1), None)
-        rule = psr.parse('DetectTime < InspectionTime')
-        self.assertEqual(flt.filter(rule, self.test_msg1), None)
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/pynspect/test_filters_idea.py b/lib/pynspect/test_filters_idea.py
deleted file mode 100644
index 08ee7d06df974161aeade2e4b59fd46f95f569b6..0000000000000000000000000000000000000000
--- a/lib/pynspect/test_filters_idea.py
+++ /dev/null
@@ -1,386 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# This file is part of Mentat system (https://mentat.cesnet.cz/).
-#
-# Copyright (C) since 2011 CESNET, z.s.p.o (http://www.ces.net/)
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-import os
-import sys
-import shutil
-import unittest
-from pprint import pformat, pprint
-
-# Generate the path to custom 'lib' directory
-lib = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../lib'))
-sys.path.insert(0, lib)
-
-from idea import lite
-from pynspect.jpath import *
-from pynspect.rules import *
-from pynspect.gparser import MentatFilterParser
-from pynspect.filters import DataObjectFilter, IDEAFilterCompiler, clean_variable
-
-#-------------------------------------------------------------------------------
-# NOTE: Sorry for the long lines in this file. They are deliberate, because the
-# assertion permutations are (IMHO) more readable this way.
-#-------------------------------------------------------------------------------
-
-class TestMentatDataObjectFilterIDEA(unittest.TestCase):
-    test_msg1 = {
-        "ID" : "e214d2d9-359b-443d-993d-3cc5637107a0",
-        "WinEndTime" : "2016-06-21 11:25:01Z",
-        "ConnCount" : 2,
-        "Source" : [
-            {
-                "IP4" : [
-                    "188.14.166.39"
-                ]
-            }
-        ],
-        "Format" : "IDEA0",
-        "WinStartTime" : "2016-06-21 11:20:01Z",
-        "_CESNET" : {
-            "StorageTime" : 1466508305
-        },
-        "Target" : [
-            {
-                "IP4" : [
-                    "195.113.165.128/25"
-                ],
-                "Port" : [
-                    "22"
-                ],
-                "Proto" : [
-                    "tcp",
-                    "ssh"
-                ],
-                "Anonymised" : True
-            }
-        ],
-        "Note" : "SSH login attempt",
-        "DetectTime" : "2016-06-21 13:08:27Z",
-        "Node" : [
-            {
-                "Name" : "cz.cesnet.mentat.warden_filer",
-                "Type" : [
-                    "Relay"
-                ]
-            },
-            {
-                "AggrWin" : "00:05:00",
-                "Type" : [
-                    "Connection",
-                    "Honeypot",
-                    "Recon"
-                ],
-                "SW" : [
-                    "Kippo"
-                ],
-                "Name" : "cz.uhk.apate.cowrie"
-            }
-        ],
-        "Category" : [
-            "Attempt.Login"
-        ]
-    }
-
-    def test_01_basic_logical(self):
-        """
-        Perform basic filtering tests.
-        """
-        self.maxDiff = None
-
-        msg_idea = lite.Idea(self.test_msg1)
-        flt = DataObjectFilter()
-
-        rule = LogicalBinOpRule('OP_AND', ConstantRule(True), ConstantRule(True))
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = LogicalBinOpRule('OP_AND', ConstantRule(True), ConstantRule(False))
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = LogicalBinOpRule('OP_AND', ConstantRule(False), ConstantRule(True))
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = LogicalBinOpRule('OP_AND', ConstantRule(False), ConstantRule(False))
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-
-        rule = LogicalBinOpRule('OP_OR', ConstantRule(True), ConstantRule(True))
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = LogicalBinOpRule('OP_OR', ConstantRule(True), ConstantRule(False))
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = LogicalBinOpRule('OP_OR', ConstantRule(False), ConstantRule(True))
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = LogicalBinOpRule('OP_OR', ConstantRule(False), ConstantRule(False))
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-
-        rule = LogicalBinOpRule('OP_XOR', ConstantRule(True), ConstantRule(True))
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = LogicalBinOpRule('OP_XOR', ConstantRule(True), ConstantRule(False))
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = LogicalBinOpRule('OP_XOR', ConstantRule(False), ConstantRule(True))
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = LogicalBinOpRule('OP_XOR', ConstantRule(False), ConstantRule(False))
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-
-        rule = UnaryOperationRule('OP_NOT', ConstantRule(True))
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = UnaryOperationRule('OP_NOT', ConstantRule(False))
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = UnaryOperationRule('OP_NOT', VariableRule("Target.Anonymised"))
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-
-    def test_02_basic_comparison(self):
-        """
-        Perform basic filtering tests.
-        """
-        self.maxDiff = None
-
-        msg_idea = lite.Idea(self.test_msg1)
-        flt = DataObjectFilter()
-        psr = MentatFilterParser()
-        psr.build()
-
-        rule = ComparisonBinOpRule('OP_EQ', VariableRule("ID"), ConstantRule("e214d2d9-359b-443d-993d-3cc5637107a0"))
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = ComparisonBinOpRule('OP_EQ', VariableRule("ID"), ConstantRule("e214d2d9-359b-443d-993d-3cc5637107"))
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = ComparisonBinOpRule('OP_NE', VariableRule("ID"), ConstantRule("e214d2d9-359b-443d-993d-3cc5637107a0"))
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = ComparisonBinOpRule('OP_NE', VariableRule("ID"), ConstantRule("e214d2d9-359b-443d-993d-3cc5637107"))
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-
-        rule = ComparisonBinOpRule('OP_LIKE', VariableRule("ID"), ConstantRule("e214d2d9"))
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = ComparisonBinOpRule('OP_LIKE', VariableRule("ID"), ConstantRule("xxxxxxxx"))
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = ComparisonBinOpRule('OP_IN', VariableRule("Category"), ListRule(ConstantRule("Phishing"), ListRule(ConstantRule("Attempt.Login"))))
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = ComparisonBinOpRule('OP_IN', VariableRule("Category"), ListRule(ConstantRule("Phishing"), ListRule(ConstantRule("Spam"))))
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = ComparisonBinOpRule('OP_IS', VariableRule("Category"), ListRule(ConstantRule("Attempt.Login")))
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = ComparisonBinOpRule('OP_IS', VariableRule("Category"), ListRule(ConstantRule("Phishing"), ListRule(ConstantRule("Attempt.Login"))))
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = ComparisonBinOpRule('OP_EQ', VariableRule("ConnCount"), IntegerRule(2))
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = ComparisonBinOpRule('OP_EQ', VariableRule("ConnCount"), IntegerRule(4))
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = ComparisonBinOpRule('OP_NE', VariableRule("ConnCount"), IntegerRule(2))
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = ComparisonBinOpRule('OP_NE', VariableRule("ConnCount"), IntegerRule(4))
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = ComparisonBinOpRule('OP_GT', VariableRule("ConnCount"), IntegerRule(2))
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = ComparisonBinOpRule('OP_GT', VariableRule("ConnCount"), IntegerRule(1))
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = ComparisonBinOpRule('OP_GE', VariableRule("ConnCount"), IntegerRule(2))
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = ComparisonBinOpRule('OP_GE', VariableRule("ConnCount"), IntegerRule(1))
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = ComparisonBinOpRule('OP_GE', VariableRule("ConnCount"), IntegerRule(3))
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = ComparisonBinOpRule('OP_LT', VariableRule("ConnCount"), IntegerRule(2))
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = ComparisonBinOpRule('OP_LT', VariableRule("ConnCount"), IntegerRule(3))
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = ComparisonBinOpRule('OP_LE', VariableRule("ConnCount"), IntegerRule(2))
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = ComparisonBinOpRule('OP_LE', VariableRule("ConnCount"), IntegerRule(3))
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = ComparisonBinOpRule('OP_LE', VariableRule("ConnCount"), IntegerRule(1))
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-
-        rule = psr.parse('ID == "e214d2d9-359b-443d-993d-3cc5637107a0"')
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = psr.parse('ID eq "e214d2d9-359b-443d-993d-3cc5637107"')
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = psr.parse('ID != "e214d2d9-359b-443d-993d-3cc5637107a0"')
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = psr.parse('ID ne "e214d2d9-359b-443d-993d-3cc5637107"')
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-
-        rule = psr.parse('ID like "e214d2d9"')
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = psr.parse('ID LIKE "xxxxxxxx"')
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = psr.parse('Category in ["Phishing" , "Attempt.Login"]')
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = psr.parse('Category IN ["Phishing" , "Spam"]')
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = psr.parse('Category is ["Attempt.Login"]')
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = psr.parse('Category IS ["Phishing" , "Attempt.Login"]')
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = psr.parse('ConnCount == 2')
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = psr.parse('ConnCount eq 4')
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = psr.parse('ConnCount != 2')
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = psr.parse('ConnCount ne 4')
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = psr.parse('ConnCount > 2')
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = psr.parse('ConnCount gt 1')
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = psr.parse('ConnCount >= 2')
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = psr.parse('ConnCount ge 1')
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = psr.parse('ConnCount GE 3')
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = psr.parse('ConnCount < 2')
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-        rule = psr.parse('ConnCount lt 3')
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = psr.parse('ConnCount <= 2')
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = psr.parse('ConnCount le 3')
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-        rule = psr.parse('ConnCount LE 1')
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-
-    def test_03_basic_math(self):
-        """
-        Perform basic math tests.
-        """
-        self.maxDiff = None
-
-        msg_idea = lite.Idea(self.test_msg1)
-        flt = DataObjectFilter()
-        psr = MentatFilterParser()
-        psr.build()
-
-        rule = MathBinOpRule('OP_PLUS', VariableRule("ConnCount"), IntegerRule(1))
-        self.assertEqual(flt.filter(rule, msg_idea), 3)
-        rule = MathBinOpRule('OP_MINUS', VariableRule("ConnCount"), IntegerRule(1))
-        self.assertEqual(flt.filter(rule, msg_idea), 1)
-        rule = MathBinOpRule('OP_TIMES', VariableRule("ConnCount"), IntegerRule(5))
-        self.assertEqual(flt.filter(rule, msg_idea), 10)
-        rule = MathBinOpRule('OP_DIVIDE', VariableRule("ConnCount"), IntegerRule(2))
-        self.assertEqual(flt.filter(rule, msg_idea), 1)
-        rule = MathBinOpRule('OP_MODULO', VariableRule("ConnCount"), IntegerRule(2))
-        self.assertEqual(flt.filter(rule, msg_idea), 0)
-
-        rule = psr.parse('ConnCount + 1')
-        self.assertEqual(flt.filter(rule, msg_idea), 3)
-        rule = psr.parse('ConnCount - 1')
-        self.assertEqual(flt.filter(rule, msg_idea), 1)
-        rule = psr.parse('ConnCount * 5')
-        self.assertEqual(flt.filter(rule, msg_idea), 10)
-        rule = psr.parse('ConnCount / 2')
-        self.assertEqual(flt.filter(rule, msg_idea), 1)
-        rule = psr.parse('ConnCount % 2')
-        self.assertEqual(flt.filter(rule, msg_idea), 0)
-
-    def test_04_basic_compilations(self):
-        """
-        Perform advanced filtering tests.
-        """
-        self.maxDiff = None
-
-        self.assertEqual(clean_variable('Target.IP4'), 'Target.IP4')
-        self.assertEqual(clean_variable('Target[1].IP4'), 'Target.IP4')
-        self.assertEqual(clean_variable('Target[1].IP4[22]'), 'Target.IP4')
-
-        msg_idea = lite.Idea(self.test_msg1)
-        cpl = IDEAFilterCompiler()
-        psr = MentatFilterParser()
-        psr.build()
-
-        rule = psr.parse('(Source.IP4 == "188.14.166.39")')
-        self.assertEqual(repr(rule), "COMPBINOP(VARIABLE('Source.IP4') OP_EQ CONSTANT('188.14.166.39'))")
-        res = cpl.compile(rule)
-        self.assertEqual(repr(res), "COMPBINOP(VARIABLE('Source.IP4') OP_EQ IPV4(IP4('188.14.166.39')))")
-
-        rule = psr.parse('(Source.IP4 == 188.14.166.39)')
-        self.assertEqual(repr(rule), "COMPBINOP(VARIABLE('Source.IP4') OP_EQ IPV4('188.14.166.39'))")
-        res = cpl.compile(rule)
-        self.assertEqual(repr(res), "COMPBINOP(VARIABLE('Source.IP4') OP_EQ IPV4(IP4('188.14.166.39')))")
-
-        rule = psr.parse('5 + 6 - 9')
-        self.assertEqual(repr(rule), "MATHBINOP(INTEGER(5) OP_PLUS MATHBINOP(INTEGER(6) OP_MINUS INTEGER(9)))")
-        res = cpl.compile(rule)
-        self.assertEqual(repr(res), "INTEGER(2)")
-
-        rule = psr.parse('Test + 10 - 9')
-        self.assertEqual(repr(rule), "MATHBINOP(VARIABLE('Test') OP_PLUS MATHBINOP(INTEGER(10) OP_MINUS INTEGER(9)))")
-        res = cpl.compile(rule)
-        self.assertEqual(repr(res), "MATHBINOP(VARIABLE('Test') OP_PLUS INTEGER(1))")
-
-        rule = psr.parse('Test + (10 - 9)')
-        self.assertEqual(repr(rule), "MATHBINOP(VARIABLE('Test') OP_PLUS MATHBINOP(INTEGER(10) OP_MINUS INTEGER(9)))")
-        res = cpl.compile(rule)
-        self.assertEqual(repr(res), "MATHBINOP(VARIABLE('Test') OP_PLUS INTEGER(1))")
-
-        rule = psr.parse('(Test + 10) - 9')
-        self.assertEqual(repr(rule), "MATHBINOP(MATHBINOP(VARIABLE('Test') OP_PLUS INTEGER(10)) OP_MINUS INTEGER(9))")
-        res = cpl.compile(rule)
-        self.assertEqual(repr(res), "MATHBINOP(MATHBINOP(VARIABLE('Test') OP_PLUS INTEGER(10)) OP_MINUS INTEGER(9))")
-
-        rule = psr.parse('9 - 6 + Test')
-        self.assertEqual(repr(rule), "MATHBINOP(INTEGER(9) OP_MINUS MATHBINOP(INTEGER(6) OP_PLUS VARIABLE('Test')))")
-        res = cpl.compile(rule)
-        self.assertEqual(repr(res), "MATHBINOP(INTEGER(9) OP_MINUS MATHBINOP(INTEGER(6) OP_PLUS VARIABLE('Test')))")
-
-        rule = psr.parse('9 - (6 + Test)')
-        self.assertEqual(repr(rule), "MATHBINOP(INTEGER(9) OP_MINUS MATHBINOP(INTEGER(6) OP_PLUS VARIABLE('Test')))")
-        res = cpl.compile(rule)
-        self.assertEqual(repr(res), "MATHBINOP(INTEGER(9) OP_MINUS MATHBINOP(INTEGER(6) OP_PLUS VARIABLE('Test')))")
-
-        rule = psr.parse('(9 - 6) + Test')
-        self.assertEqual(repr(rule), "MATHBINOP(MATHBINOP(INTEGER(9) OP_MINUS INTEGER(6)) OP_PLUS VARIABLE('Test'))")
-        res = cpl.compile(rule)
-        self.assertEqual(repr(res), "MATHBINOP(INTEGER(3) OP_PLUS VARIABLE('Test'))")
-
-    def test_05_advanced_filters(self):
-        """
-        Perform advanced filtering tests.
-        """
-        self.maxDiff = None
-
-        msg_idea = lite.Idea(self.test_msg1)
-        flt = DataObjectFilter()
-        cpl = IDEAFilterCompiler()
-        psr = MentatFilterParser()
-        psr.build()
-
-        rule = psr.parse('DetectTime + 3600')
-        self.assertEqual(repr(rule), "MATHBINOP(VARIABLE('DetectTime') OP_PLUS INTEGER(3600))")
-        res = cpl.compile(rule)
-        self.assertEqual(repr(res), "MATHBINOP(VARIABLE('DetectTime') OP_PLUS INTEGER(3600))")
-        self.assertEqual(flt.filter(rule, msg_idea), 1466510907.0)
-
-        rule = psr.parse('(ConnCount + 10) > 11')
-        self.assertEqual(repr(rule), "COMPBINOP(MATHBINOP(VARIABLE('ConnCount') OP_PLUS INTEGER(10)) OP_GT INTEGER(11))")
-        rule = cpl.compile(rule)
-        self.assertEqual(repr(rule), "COMPBINOP(MATHBINOP(VARIABLE('ConnCount') OP_PLUS INTEGER(10)) OP_GT INTEGER(11))")
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-
-        rule = psr.parse('(ConnCount + 3) < 5')
-        self.assertEqual(repr(rule), "COMPBINOP(MATHBINOP(VARIABLE('ConnCount') OP_PLUS INTEGER(3)) OP_LT INTEGER(5))")
-        rule = cpl.compile(rule)
-        self.assertEqual(repr(rule), "COMPBINOP(MATHBINOP(VARIABLE('ConnCount') OP_PLUS INTEGER(3)) OP_LT INTEGER(5))")
-        self.assertEqual(flt.filter(rule, msg_idea), False)
-
-        rule = psr.parse('((ConnCount + 3) < 5) or ((ConnCount + 10) > 11)')
-        self.assertEqual(repr(rule), "LOGBINOP(COMPBINOP(MATHBINOP(VARIABLE('ConnCount') OP_PLUS INTEGER(3)) OP_LT INTEGER(5)) OP_OR COMPBINOP(MATHBINOP(VARIABLE('ConnCount') OP_PLUS INTEGER(10)) OP_GT INTEGER(11)))")
-        rule = cpl.compile(rule)
-        self.assertEqual(repr(rule), "LOGBINOP(COMPBINOP(MATHBINOP(VARIABLE('ConnCount') OP_PLUS INTEGER(3)) OP_LT INTEGER(5)) OP_OR COMPBINOP(MATHBINOP(VARIABLE('ConnCount') OP_PLUS INTEGER(10)) OP_GT INTEGER(11)))")
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-
-        rule = psr.parse('(Source.IP4 == 188.14.166.39)')
-        self.assertEqual(repr(rule), "COMPBINOP(VARIABLE('Source.IP4') OP_EQ IPV4('188.14.166.39'))")
-        rule = cpl.compile(rule)
-        self.assertEqual(repr(rule), "COMPBINOP(VARIABLE('Source.IP4') OP_EQ IPV4(IP4('188.14.166.39')))")
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-
-        rule = psr.parse('(Source.IP4 in ["188.14.166.39","188.14.166.40","188.14.166.41"])')
-        self.assertEqual(repr(rule), "COMPBINOP(VARIABLE('Source.IP4') OP_IN LIST(CONSTANT('188.14.166.39'), CONSTANT('188.14.166.40'), CONSTANT('188.14.166.41')))")
-        rule = cpl.compile(rule)
-        self.assertEqual(repr(rule), "COMPBINOP(VARIABLE('Source.IP4') OP_IN LIST(IPV4(IP4('188.14.166.39')), IPV4(IP4('188.14.166.40')), IPV4(IP4('188.14.166.41'))))")
-        self.assertEqual(flt.filter(rule, msg_idea), True)
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/pynspect/test_filters_inspector.py b/lib/pynspect/test_filters_inspector.py
deleted file mode 100644
index 39b72a50d63601e89ce8d72f5d26414e8994bcf1..0000000000000000000000000000000000000000
--- a/lib/pynspect/test_filters_inspector.py
+++ /dev/null
@@ -1,188 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# This file is part of Mentat system (https://mentat.cesnet.cz/).
-#
-# Copyright (C) since 2011 CESNET, z.s.p.o (http://www.ces.net/)
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-import os
-import sys
-import shutil
-import unittest
-from pprint import pformat, pprint
-
-# Generate the path to custom 'lib' directory
-lib = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../lib'))
-sys.path.insert(0, lib)
-
-from idea import lite
-from pynspect.jpath import *
-from pynspect.rules import *
-from pynspect.gparser import MentatFilterParser
-from pynspect.filters import DataObjectFilter, IDEAFilterCompiler, clean_variable
-
-#-------------------------------------------------------------------------------
-# NOTE: Sorry for the long lines in this file. They are deliberate, because the
-# assertion permutations are (IMHO) more readable this way.
-#-------------------------------------------------------------------------------
-
-class TestMentatDataObjectFilterInspector(unittest.TestCase):
-
-    def test_01_current_inspector_filters(self):
-        """
-        Perform tests of filters currently used in mentat-inspector.py.
-        """
-        self.maxDiff = None
-
-        flt = DataObjectFilter()
-        cpl = IDEAFilterCompiler()
-        psr = MentatFilterParser()
-        psr.build()
-
-        inspection_rules = [
-            {
-                "filter": "Category in ['Attempt.Login'] and Target.Port in [3389]",
-                "str":    '((Category OP_IN ["Attempt.Login"]) OP_AND (Target.Port OP_IN [3389]))',
-                "tests": [
-                    [
-                        {"Format" : "IDEA0","ID" : "e214d2d9-359b-443d-993d-3cc5637107a0","Source":[{"IP4":["188.14.166.39"]}],"Target":[{"IP4":["195.113.165.128/25"],"Port":["3389"],"Proto":["tcp","ssh"]}],"Note":"SSH login attempt","DetectTime" : "2016-06-21 13:08:27Z","Node":[{"Type":["Connection","Honeypot"],"SW":["Kippo"],"Name":"cz.uhk.apate.cowrie"}],"Category":["Attempt.Login"]},
-                        True
-                    ],
-                    [
-                        {"Format" : "IDEA0","ID" : "e214d2d9-359b-443d-993d-3cc5637107a0","Source":[{"IP4":["188.14.166.39"]}],"Target":[{"IP4":["195.113.165.128/25"],"Port":["338"],"Proto":["tcp","ssh"]}],"Note":"SSH login attempt","DetectTime" : "2016-06-21 13:08:27Z","Node":[{"Type":["Connection","Honeypot"],"SW":["Kippo"],"Name":"cz.uhk.apate.cowrie"}],"Category":["Attempt.Login"]},
-                        False
-                    ]
-                ],
-            },
-            {
-                "filter": "Category in ['Attempt.Login'] and (Target.Proto in ['telnet'] or Source.Proto in ['telnet'] or Target.Port in [23])",
-                "str":    '((Category OP_IN ["Attempt.Login"]) OP_AND ((Target.Proto OP_IN ["telnet"]) OP_OR ((Source.Proto OP_IN ["telnet"]) OP_OR (Target.Port OP_IN [23]))))'
-            },
-            {
-                "filter": "Category in ['Attempt.Login'] and (Target.Proto in ['ssh'] or Source.Proto in ['ssh'] or Target.Port in [22])",
-                "str":    '((Category OP_IN ["Attempt.Login"]) OP_AND ((Target.Proto OP_IN ["ssh"]) OP_OR ((Source.Proto OP_IN ["ssh"]) OP_OR (Target.Port OP_IN [22]))))'
-            },
-            {
-                "filter": "Category in ['Attempt.Login'] and (Target.Proto in ['sip', 'sip-tls'] or Source.Proto in ['sip', 'sip-tls'] or Target.Port in [5060])",
-                "str":    '((Category OP_IN ["Attempt.Login"]) OP_AND ((Target.Proto OP_IN ["sip", "sip-tls"]) OP_OR ((Source.Proto OP_IN ["sip", "sip-tls"]) OP_OR (Target.Port OP_IN [5060]))))'
-            },
-            {
-                "filter": "Category in ['Attempt.Exploit'] and (Target.Proto in ['sip', 'sip-tls'] or Source.Proto in ['sip', 'sip-tls'] or Target.Port in [5060])",
-                "str":    '((Category OP_IN ["Attempt.Exploit"]) OP_AND ((Target.Proto OP_IN ["sip", "sip-tls"]) OP_OR ((Source.Proto OP_IN ["sip", "sip-tls"]) OP_OR (Target.Port OP_IN [5060]))))'
-            },
-            {
-                "filter": "Category in ['Attempt.Exploit'] and Target.Port in [23]",
-                "str":    '((Category OP_IN ["Attempt.Exploit"]) OP_AND (Target.Port OP_IN [23]))'
-            },
-            {
-                "filter": "Category in ['Attempt.Exploit'] and (Target.Port in [80, 443] or Source.Proto in ['http', 'https', 'http-alt'] or Target.Proto in ['http', 'https', 'http-alt'])",
-                "str":    '((Category OP_IN ["Attempt.Exploit"]) OP_AND ((Target.Port OP_IN [80, 443]) OP_OR ((Source.Proto OP_IN ["http", "https", "http-alt"]) OP_OR (Target.Proto OP_IN ["http", "https", "http-alt"]))))'
-            },
-            {
-                "filter": "Category in ['Attempt.Exploit'] and (Target.Port in [3306] or Source.Proto in ['mysql'] or Target.Proto in ['mysql'])",
-                "str":    '((Category OP_IN ["Attempt.Exploit"]) OP_AND ((Target.Port OP_IN [3306]) OP_OR ((Source.Proto OP_IN ["mysql"]) OP_OR (Target.Proto OP_IN ["mysql"]))))'
-            },
-            {
-                "filter": "Category in ['Attempt.Exploit'] and (Target.Port in [445] or Source.Proto in ['microsoft-ds', 'smb'] or Target.Proto in ['microsoft-ds', 'smb'])",
-                "str":    '((Category OP_IN ["Attempt.Exploit"]) OP_AND ((Target.Port OP_IN [445]) OP_OR ((Source.Proto OP_IN ["microsoft-ds", "smb"]) OP_OR (Target.Proto OP_IN ["microsoft-ds", "smb"]))))'
-            },
-            {
-                "filter": "Category in ['Attempt.Exploit'] and (Target.Port in [135] or Source.Proto in ['loc-srv', 'epmap'] or Target.Proto in ['loc-srv', 'epmap'])",
-                "str":    '((Category OP_IN ["Attempt.Exploit"]) OP_AND ((Target.Port OP_IN [135]) OP_OR ((Source.Proto OP_IN ["loc-srv", "epmap"]) OP_OR (Target.Proto OP_IN ["loc-srv", "epmap"]))))'
-            },
-            {
-                "filter": "Category in ['Attempt.Exploit'] and (Target.Port in [1900] or Source.Proto in ['upnp', 'ssdp'] or Target.Proto in ['upnp', 'ssdp'])",
-                "str":    '((Category OP_IN ["Attempt.Exploit"]) OP_AND ((Target.Port OP_IN [1900]) OP_OR ((Source.Proto OP_IN ["upnp", "ssdp"]) OP_OR (Target.Proto OP_IN ["upnp", "ssdp"]))))'
-            },
-            {
-                "filter": "Category in ['Attempt.Exploit'] and (Target.Port in [20, 21, 989, 990] or Source.Proto in ['ftp', 'ftp-data', 'ftps', 'ftps-data'] or Target.Proto in ['ftp', 'ftp-data', 'ftps', 'ftps-data'])",
-                "str":    '((Category OP_IN ["Attempt.Exploit"]) OP_AND ((Target.Port OP_IN [20, 21, 989, 990]) OP_OR ((Source.Proto OP_IN ["ftp", "ftp-data", "ftps", "ftps-data"]) OP_OR (Target.Proto OP_IN ["ftp", "ftp-data", "ftps", "ftps-data"]))))'
-            },
-            {
-                "filter": "Category in ['Attempt.Exploit'] and (Target.Port in [1433, 1434] or Source.Proto in ['ms-sql-s', 'ms-sql-m'] or Target.Proto in ['ms-sql-s', 'ms-sql-m'])",
-                "str":    '((Category OP_IN ["Attempt.Exploit"]) OP_AND ((Target.Port OP_IN [1433, 1434]) OP_OR ((Source.Proto OP_IN ["ms-sql-s", "ms-sql-m"]) OP_OR (Target.Proto OP_IN ["ms-sql-s", "ms-sql-m"]))))'
-            },
-            {
-                "filter": "Category in ['Attempt.Exploit'] and (Target.Port in [42] or Source.Proto in ['nameserver'] or Target.Proto in ['nameserver'])",
-                "str":    '((Category OP_IN ["Attempt.Exploit"]) OP_AND ((Target.Port OP_IN [42]) OP_OR ((Source.Proto OP_IN ["nameserver"]) OP_OR (Target.Proto OP_IN ["nameserver"]))))'
-            },
-            {
-                "filter": "Category in ['Attempt.Exploit'] and Node.SW in ['Dionaea']",
-                "str":    '((Category OP_IN ["Attempt.Exploit"]) OP_AND (Node.SW OP_IN ["Dionaea"]))'
-            },
-            {
-                "filter": "Category in ['Availability.DoS', 'Availability.DDoS'] and (Target.Proto in ['dns', 'domain'] or Source.Proto in ['dns', 'domain'] or Target.Port in [53] or Source.Port in [53])",
-                "str":    '((Category OP_IN ["Availability.DoS", "Availability.DDoS"]) OP_AND ((Target.Proto OP_IN ["dns", "domain"]) OP_OR ((Source.Proto OP_IN ["dns", "domain"]) OP_OR ((Target.Port OP_IN [53]) OP_OR (Source.Port OP_IN [53])))))'
-            },
-            {
-                "filter": "Category in ['Availability.DDoS'] and Node.Type in ['Flow'] and Node.Type in ['Statistical']",
-                "str":    '((Category OP_IN ["Availability.DDoS"]) OP_AND ((Node.Type OP_IN ["Flow"]) OP_AND (Node.Type OP_IN ["Statistical"])))'
-            },
-            {
-                "filter": "Category in ['Abusive.Spam'] and Node.SW in ['UCEPROT']",
-                "str":    '((Category OP_IN ["Abusive.Spam"]) OP_AND (Node.SW OP_IN ["UCEPROT"]))'
-            },
-            {
-                "filter": "Category in ['Abusive.Spam'] and Node.SW in ['Fail2Ban', 'IntelMQ']",
-                "str":    '((Category OP_IN ["Abusive.Spam"]) OP_AND (Node.SW OP_IN ["Fail2Ban", "IntelMQ"]))'
-            },
-            {
-                "filter": "Category in ['Vulnerable.Config'] and (Source.Proto in ['qotd'] or Source.Port in [17])",
-                "str":    '((Category OP_IN ["Vulnerable.Config"]) OP_AND ((Source.Proto OP_IN ["qotd"]) OP_OR (Source.Port OP_IN [17])))'
-            },
-            {
-                "filter": "Category in ['Vulnerable.Config'] and Source.Proto in ['ssdp']",
-                "str":    '((Category OP_IN ["Vulnerable.Config"]) OP_AND (Source.Proto OP_IN ["ssdp"]))'
-            },
-            {
-                "filter": "Category in ['Vulnerable.Config'] and (Source.Proto in ['ntp'] or Source.Port in [123])",
-                "str":    '((Category OP_IN ["Vulnerable.Config"]) OP_AND ((Source.Proto OP_IN ["ntp"]) OP_OR (Source.Port OP_IN [123])))'
-            },
-            {
-                "filter": "Category in ['Vulnerable.Config'] and (Source.Proto in ['domain'] or Source.Port in [53])",
-                "str":    '((Category OP_IN ["Vulnerable.Config"]) OP_AND ((Source.Proto OP_IN ["domain"]) OP_OR (Source.Port OP_IN [53])))'
-            },
-            {
-                "filter": "Category in ['Vulnerable.Config'] and (Source.Proto in ['netbios-ns'] or Source.Port in [137])",
-                "str":    '((Category OP_IN ["Vulnerable.Config"]) OP_AND ((Source.Proto OP_IN ["netbios-ns"]) OP_OR (Source.Port OP_IN [137])))'
-            },
-            {
-                "filter": "Category in ['Vulnerable.Config'] and (Source.Proto in ['ipmi'] or Source.Port in [623])",
-                "str":    '((Category OP_IN ["Vulnerable.Config"]) OP_AND ((Source.Proto OP_IN ["ipmi"]) OP_OR (Source.Port OP_IN [623])))'
-            },
-            {
-                "filter": "Category in ['Vulnerable.Config'] and (Source.Proto in ['chargen'] or Source.Port in [19])",
-                "str":    '((Category OP_IN ["Vulnerable.Config"]) OP_AND ((Source.Proto OP_IN ["chargen"]) OP_OR (Source.Port OP_IN [19])))'
-            },
-            {
-                "filter": "Category in ['Anomaly.Traffic']",
-                "str":    '(Category OP_IN ["Anomaly.Traffic"])'
-            },
-            {
-                "filter": "Category in ['Anomaly.Connection'] and Source.Type in ['Booter']",
-                "str":    '((Category OP_IN ["Anomaly.Connection"]) OP_AND (Source.Type OP_IN ["Booter"]))'
-            },
-            {
-                "filter": "Category in ['Intrusion.Botnet'] and Source.Type in ['Botnet']",
-                "str":    '((Category OP_IN ["Intrusion.Botnet"]) OP_AND (Source.Type OP_IN ["Botnet"]))'
-            },
-            {
-                "filter": "Category in ['Recon.Scanning']",
-                "str":    '(Category OP_IN ["Recon.Scanning"])'
-            }
-        ]
-
-        for ir in inspection_rules:
-            rule = psr.parse(ir['filter'])
-            rule = cpl.compile(rule)
-            self.assertEqual(str(rule), ir['str'])
-            if 'tests' in ir:
-                for t in ir['tests']:
-                    msg_idea = lite.Idea(t[0])
-                    self.assertEqual([ir['filter'], flt.filter(rule, msg_idea)], [ir['filter'], t[1]])
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/pynspect/test_gparser.py b/lib/pynspect/test_gparser.py
deleted file mode 100644
index 395a841a075e8e62ce203fcd75067921ba86159f..0000000000000000000000000000000000000000
--- a/lib/pynspect/test_gparser.py
+++ /dev/null
@@ -1,266 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# This file is part of Mentat system (https://mentat.cesnet.cz/).
-#
-# Copyright (C) since 2011 CESNET, z.s.p.o (http://www.ces.net/)
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-import os
-import sys
-import shutil
-import unittest
-from pprint import pformat, pprint
-
-# Generate the path to custom 'lib' directory
-lib = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../lib'))
-sys.path.insert(0, lib)
-
-from pynspect.gparser import MentatFilterParser
-
-#-------------------------------------------------------------------------------
-# NOTE: Sorry for the long lines in this file. They are deliberate, because the
-# assertion permutations are (IMHO) more readable this way.
-#-------------------------------------------------------------------------------
-
-class TestMentatFilterParser(unittest.TestCase):
-
-    def test_01_basic_logical(self):
-        """
-        Test the parsing of basic logical operations.
-        """
-        self.maxDiff = None
-
-        p = MentatFilterParser()
-        p.build()
-
-        self.assertEqual(repr(p.parse('1 and 1')),  'LOGBINOP(INTEGER(1) OP_AND INTEGER(1))')
-        self.assertEqual(repr(p.parse('1 AND 1')),  'LOGBINOP(INTEGER(1) OP_AND INTEGER(1))')
-        self.assertEqual(repr(p.parse('1 && 1')),   'LOGBINOP(INTEGER(1) OP_AND_P INTEGER(1))')
-        self.assertEqual(repr(p.parse('1 or 1')),   'LOGBINOP(INTEGER(1) OP_OR INTEGER(1))')
-        self.assertEqual(repr(p.parse('1 OR 1')),   'LOGBINOP(INTEGER(1) OP_OR INTEGER(1))')
-        self.assertEqual(repr(p.parse('1 || 1')),   'LOGBINOP(INTEGER(1) OP_OR_P INTEGER(1))')
-        self.assertEqual(repr(p.parse('1 xor 1')),  'LOGBINOP(INTEGER(1) OP_XOR INTEGER(1))')
-        self.assertEqual(repr(p.parse('1 XOR 1')),  'LOGBINOP(INTEGER(1) OP_XOR INTEGER(1))')
-        self.assertEqual(repr(p.parse('1 ^^ 1')),   'LOGBINOP(INTEGER(1) OP_XOR_P INTEGER(1))')
-        self.assertEqual(repr(p.parse('not 1')),    'UNOP(OP_NOT INTEGER(1))')
-        self.assertEqual(repr(p.parse('NOT 1')),    'UNOP(OP_NOT INTEGER(1))')
-        self.assertEqual(repr(p.parse('! 1')),      'UNOP(OP_NOT INTEGER(1))')
-        self.assertEqual(repr(p.parse('exists 1')), 'UNOP(OP_EXISTS INTEGER(1))')
-        self.assertEqual(repr(p.parse('EXISTS 1')), 'UNOP(OP_EXISTS INTEGER(1))')
-        self.assertEqual(repr(p.parse('? 1')),      'UNOP(OP_EXISTS INTEGER(1))')
-
-        self.assertEqual(repr(p.parse('(1 and 1)')),  'LOGBINOP(INTEGER(1) OP_AND INTEGER(1))')
-        self.assertEqual(repr(p.parse('(1 AND 1)')),  'LOGBINOP(INTEGER(1) OP_AND INTEGER(1))')
-        self.assertEqual(repr(p.parse('(1 && 1)')),   'LOGBINOP(INTEGER(1) OP_AND_P INTEGER(1))')
-        self.assertEqual(repr(p.parse('(1 or 1)')),   'LOGBINOP(INTEGER(1) OP_OR INTEGER(1))')
-        self.assertEqual(repr(p.parse('(1 OR 1)')),   'LOGBINOP(INTEGER(1) OP_OR INTEGER(1))')
-        self.assertEqual(repr(p.parse('(1 || 1)')),   'LOGBINOP(INTEGER(1) OP_OR_P INTEGER(1))')
-        self.assertEqual(repr(p.parse('(1 xor 1)')),  'LOGBINOP(INTEGER(1) OP_XOR INTEGER(1))')
-        self.assertEqual(repr(p.parse('(1 XOR 1)')),  'LOGBINOP(INTEGER(1) OP_XOR INTEGER(1))')
-        self.assertEqual(repr(p.parse('(1 ^^ 1)')),   'LOGBINOP(INTEGER(1) OP_XOR_P INTEGER(1))')
-        self.assertEqual(repr(p.parse('(not 1)')),    'UNOP(OP_NOT INTEGER(1))')
-        self.assertEqual(repr(p.parse('(NOT 1)')),    'UNOP(OP_NOT INTEGER(1))')
-        self.assertEqual(repr(p.parse('(! 1)')),      'UNOP(OP_NOT INTEGER(1))')
-        self.assertEqual(repr(p.parse('(exists 1)')), 'UNOP(OP_EXISTS INTEGER(1))')
-        self.assertEqual(repr(p.parse('(EXISTS 1)')), 'UNOP(OP_EXISTS INTEGER(1))')
-        self.assertEqual(repr(p.parse('(? 1)')),      'UNOP(OP_EXISTS INTEGER(1))')
-
-        self.assertEqual(repr(p.parse('((1 and 1))')),  'LOGBINOP(INTEGER(1) OP_AND INTEGER(1))')
-        self.assertEqual(repr(p.parse('((1 AND 1))')),  'LOGBINOP(INTEGER(1) OP_AND INTEGER(1))')
-        self.assertEqual(repr(p.parse('((1 && 1))')),   'LOGBINOP(INTEGER(1) OP_AND_P INTEGER(1))')
-        self.assertEqual(repr(p.parse('((1 or 1))')),   'LOGBINOP(INTEGER(1) OP_OR INTEGER(1))')
-        self.assertEqual(repr(p.parse('((1 OR 1))')),   'LOGBINOP(INTEGER(1) OP_OR INTEGER(1))')
-        self.assertEqual(repr(p.parse('((1 || 1))')),   'LOGBINOP(INTEGER(1) OP_OR_P INTEGER(1))')
-        self.assertEqual(repr(p.parse('((1 xor 1))')),  'LOGBINOP(INTEGER(1) OP_XOR INTEGER(1))')
-        self.assertEqual(repr(p.parse('((1 XOR 1))')),  'LOGBINOP(INTEGER(1) OP_XOR INTEGER(1))')
-        self.assertEqual(repr(p.parse('((1 ^^ 1))')),   'LOGBINOP(INTEGER(1) OP_XOR_P INTEGER(1))')
-        self.assertEqual(repr(p.parse('((not 1))')),    'UNOP(OP_NOT INTEGER(1))')
-        self.assertEqual(repr(p.parse('((NOT 1))')),    'UNOP(OP_NOT INTEGER(1))')
-        self.assertEqual(repr(p.parse('((! 1))')),      'UNOP(OP_NOT INTEGER(1))')
-        self.assertEqual(repr(p.parse('((exists 1))')), 'UNOP(OP_EXISTS INTEGER(1))')
-        self.assertEqual(repr(p.parse('((EXISTS 1))')), 'UNOP(OP_EXISTS INTEGER(1))')
-        self.assertEqual(repr(p.parse('((? 1))')),      'UNOP(OP_EXISTS INTEGER(1))')
-
-    def test_02_basic_comparison(self):
-        """
-        Test the parsing of basic comparison operations.
-        """
-        self.maxDiff = None
-
-        p = MentatFilterParser()
-        p.build()
-
-        self.assertEqual(repr(p.parse('2 like 2')), 'COMPBINOP(INTEGER(2) OP_LIKE INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 LIKE 2')), 'COMPBINOP(INTEGER(2) OP_LIKE INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 =~ 2')),   'COMPBINOP(INTEGER(2) OP_LIKE INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 in 2')),   'COMPBINOP(INTEGER(2) OP_IN INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 IN 2')),   'COMPBINOP(INTEGER(2) OP_IN INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 ~~ 2')),   'COMPBINOP(INTEGER(2) OP_IN INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 is 2')),   'COMPBINOP(INTEGER(2) OP_IS INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 IS 2')),   'COMPBINOP(INTEGER(2) OP_IS INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 eq 2')),   'COMPBINOP(INTEGER(2) OP_EQ INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 EQ 2')),   'COMPBINOP(INTEGER(2) OP_EQ INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 == 2')),   'COMPBINOP(INTEGER(2) OP_EQ INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 ne 2')),   'COMPBINOP(INTEGER(2) OP_NE INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 NE 2')),   'COMPBINOP(INTEGER(2) OP_NE INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 != 2')),   'COMPBINOP(INTEGER(2) OP_NE INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 <> 2')),   'COMPBINOP(INTEGER(2) OP_NE INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 ge 2')),   'COMPBINOP(INTEGER(2) OP_GE INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 GE 2')),   'COMPBINOP(INTEGER(2) OP_GE INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 >= 2')),   'COMPBINOP(INTEGER(2) OP_GE INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 gt 2')),   'COMPBINOP(INTEGER(2) OP_GT INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 GT 2')),   'COMPBINOP(INTEGER(2) OP_GT INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 > 2')),    'COMPBINOP(INTEGER(2) OP_GT INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 le 2')),   'COMPBINOP(INTEGER(2) OP_LE INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 LE 2')),   'COMPBINOP(INTEGER(2) OP_LE INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 <= 2')),   'COMPBINOP(INTEGER(2) OP_LE INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 lt 2')),   'COMPBINOP(INTEGER(2) OP_LT INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 LT 2')),   'COMPBINOP(INTEGER(2) OP_LT INTEGER(2))')
-        self.assertEqual(repr(p.parse('2 < 2')),    'COMPBINOP(INTEGER(2) OP_LT INTEGER(2))')
-
-        self.assertEqual(repr(p.parse('(2 like 2)')), 'COMPBINOP(INTEGER(2) OP_LIKE INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 LIKE 2)')), 'COMPBINOP(INTEGER(2) OP_LIKE INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 =~ 2)')),   'COMPBINOP(INTEGER(2) OP_LIKE INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 in 2)')),   'COMPBINOP(INTEGER(2) OP_IN INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 IN 2)')),   'COMPBINOP(INTEGER(2) OP_IN INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 ~~ 2)')),   'COMPBINOP(INTEGER(2) OP_IN INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 is 2)')),   'COMPBINOP(INTEGER(2) OP_IS INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 IS 2)')),   'COMPBINOP(INTEGER(2) OP_IS INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 eq 2)')),   'COMPBINOP(INTEGER(2) OP_EQ INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 EQ 2)')),   'COMPBINOP(INTEGER(2) OP_EQ INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 == 2)')),   'COMPBINOP(INTEGER(2) OP_EQ INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 ne 2)')),   'COMPBINOP(INTEGER(2) OP_NE INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 NE 2)')),   'COMPBINOP(INTEGER(2) OP_NE INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 != 2)')),   'COMPBINOP(INTEGER(2) OP_NE INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 <> 2)')),   'COMPBINOP(INTEGER(2) OP_NE INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 ge 2)')),   'COMPBINOP(INTEGER(2) OP_GE INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 GE 2)')),   'COMPBINOP(INTEGER(2) OP_GE INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 >= 2)')),   'COMPBINOP(INTEGER(2) OP_GE INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 gt 2)')),   'COMPBINOP(INTEGER(2) OP_GT INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 GT 2)')),   'COMPBINOP(INTEGER(2) OP_GT INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 > 2)')),    'COMPBINOP(INTEGER(2) OP_GT INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 le 2)')),   'COMPBINOP(INTEGER(2) OP_LE INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 LE 2)')),   'COMPBINOP(INTEGER(2) OP_LE INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 <= 2)')),   'COMPBINOP(INTEGER(2) OP_LE INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 lt 2)')),   'COMPBINOP(INTEGER(2) OP_LT INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 LT 2)')),   'COMPBINOP(INTEGER(2) OP_LT INTEGER(2))')
-        self.assertEqual(repr(p.parse('(2 < 2)')),    'COMPBINOP(INTEGER(2) OP_LT INTEGER(2))')
-
-        self.assertEqual(repr(p.parse('((2 like 2))')), 'COMPBINOP(INTEGER(2) OP_LIKE INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 LIKE 2))')), 'COMPBINOP(INTEGER(2) OP_LIKE INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 =~ 2))')),   'COMPBINOP(INTEGER(2) OP_LIKE INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 in 2))')),   'COMPBINOP(INTEGER(2) OP_IN INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 IN 2))')),   'COMPBINOP(INTEGER(2) OP_IN INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 ~~ 2))')),   'COMPBINOP(INTEGER(2) OP_IN INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 is 2))')),   'COMPBINOP(INTEGER(2) OP_IS INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 IS 2))')),   'COMPBINOP(INTEGER(2) OP_IS INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 eq 2))')),   'COMPBINOP(INTEGER(2) OP_EQ INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 EQ 2))')),   'COMPBINOP(INTEGER(2) OP_EQ INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 == 2))')),   'COMPBINOP(INTEGER(2) OP_EQ INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 ne 2))')),   'COMPBINOP(INTEGER(2) OP_NE INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 NE 2))')),   'COMPBINOP(INTEGER(2) OP_NE INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 != 2))')),   'COMPBINOP(INTEGER(2) OP_NE INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 <> 2))')),   'COMPBINOP(INTEGER(2) OP_NE INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 ge 2))')),   'COMPBINOP(INTEGER(2) OP_GE INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 GE 2))')),   'COMPBINOP(INTEGER(2) OP_GE INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 >= 2))')),   'COMPBINOP(INTEGER(2) OP_GE INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 gt 2))')),   'COMPBINOP(INTEGER(2) OP_GT INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 GT 2))')),   'COMPBINOP(INTEGER(2) OP_GT INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 > 2))')),    'COMPBINOP(INTEGER(2) OP_GT INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 le 2))')),   'COMPBINOP(INTEGER(2) OP_LE INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 LE 2))')),   'COMPBINOP(INTEGER(2) OP_LE INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 <= 2))')),   'COMPBINOP(INTEGER(2) OP_LE INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 lt 2))')),   'COMPBINOP(INTEGER(2) OP_LT INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 LT 2))')),   'COMPBINOP(INTEGER(2) OP_LT INTEGER(2))')
-        self.assertEqual(repr(p.parse('((2 < 2))')),    'COMPBINOP(INTEGER(2) OP_LT INTEGER(2))')
-
-    def test_03_basic_math(self):
-        """
-        Test the parsing of basic mathematical operations.
-        """
-        self.maxDiff = None
-
-        p = MentatFilterParser()
-        p.build()
-
-        self.assertEqual(repr(p.parse('3 + 3')), 'MATHBINOP(INTEGER(3) OP_PLUS INTEGER(3))')
-        self.assertEqual(repr(p.parse('3 - 3')), 'MATHBINOP(INTEGER(3) OP_MINUS INTEGER(3))')
-        self.assertEqual(repr(p.parse('3 * 3')), 'MATHBINOP(INTEGER(3) OP_TIMES INTEGER(3))')
-        self.assertEqual(repr(p.parse('3 / 3')), 'MATHBINOP(INTEGER(3) OP_DIVIDE INTEGER(3))')
-        self.assertEqual(repr(p.parse('3 % 3')), 'MATHBINOP(INTEGER(3) OP_MODULO INTEGER(3))')
-
-        self.assertEqual(repr(p.parse('(3 + 3)')), 'MATHBINOP(INTEGER(3) OP_PLUS INTEGER(3))')
-        self.assertEqual(repr(p.parse('(3 - 3)')), 'MATHBINOP(INTEGER(3) OP_MINUS INTEGER(3))')
-        self.assertEqual(repr(p.parse('(3 * 3)')), 'MATHBINOP(INTEGER(3) OP_TIMES INTEGER(3))')
-        self.assertEqual(repr(p.parse('(3 / 3)')), 'MATHBINOP(INTEGER(3) OP_DIVIDE INTEGER(3))')
-        self.assertEqual(repr(p.parse('(3 % 3)')), 'MATHBINOP(INTEGER(3) OP_MODULO INTEGER(3))')
-
-        self.assertEqual(repr(p.parse('((3 + 3))')), 'MATHBINOP(INTEGER(3) OP_PLUS INTEGER(3))')
-        self.assertEqual(repr(p.parse('((3 - 3))')), 'MATHBINOP(INTEGER(3) OP_MINUS INTEGER(3))')
-        self.assertEqual(repr(p.parse('((3 * 3))')), 'MATHBINOP(INTEGER(3) OP_TIMES INTEGER(3))')
-        self.assertEqual(repr(p.parse('((3 / 3))')), 'MATHBINOP(INTEGER(3) OP_DIVIDE INTEGER(3))')
-        self.assertEqual(repr(p.parse('((3 % 3))')), 'MATHBINOP(INTEGER(3) OP_MODULO INTEGER(3))')
-
-    def test_04_basic_factors(self):
-        """
-        Test parsing of all available factors.
-        """
-        self.maxDiff = None
-
-        p = MentatFilterParser()
-        p.build()
-
-        self.assertEqual(repr(p.parse("127.0.0.1")),   "IPV4('127.0.0.1')")
-        self.assertEqual(repr(p.parse("::1")),         "IPV6('::1')")
-        self.assertEqual(repr(p.parse("1")),           "INTEGER(1)")
-        self.assertEqual(repr(p.parse("1.1")),         "FLOAT(1.1)")
-        self.assertEqual(repr(p.parse("Test")),        "VARIABLE('Test')")
-        self.assertEqual(repr(p.parse('"constant1"')), "CONSTANT('constant1')")
-
-        self.assertEqual(repr(p.parse("(127.0.0.1)")),   "IPV4('127.0.0.1')")
-        self.assertEqual(repr(p.parse("(::1)")),         "IPV6('::1')")
-        self.assertEqual(repr(p.parse("(1)")),           "INTEGER(1)")
-        self.assertEqual(repr(p.parse("(1.1)")),         "FLOAT(1.1)")
-        self.assertEqual(repr(p.parse("(Test)")),        "VARIABLE('Test')")
-        self.assertEqual(repr(p.parse('("constant1")')), "CONSTANT('constant1')")
-
-        self.assertEqual(repr(p.parse("((127.0.0.1))")),   "IPV4('127.0.0.1')")
-        self.assertEqual(repr(p.parse("((::1))")),         "IPV6('::1')")
-        self.assertEqual(repr(p.parse("((1))")),           "INTEGER(1)")
-        self.assertEqual(repr(p.parse("((1.1))")),         "FLOAT(1.1)")
-        self.assertEqual(repr(p.parse("((Test))")),        "VARIABLE('Test')")
-        self.assertEqual(repr(p.parse('(("constant1"))')), "CONSTANT('constant1')")
-
-        self.assertEqual(repr(p.parse("[127.0.0.1]")),   "LIST(IPV4('127.0.0.1'))")
-        self.assertEqual(repr(p.parse("[::1]")),         "LIST(IPV6('::1'))")
-        self.assertEqual(repr(p.parse("[1]")),           "LIST(INTEGER(1))")
-        self.assertEqual(repr(p.parse("[1.1]")),         "LIST(FLOAT(1.1))")
-        self.assertEqual(repr(p.parse("[Test]")),        "LIST(VARIABLE('Test'))")
-        self.assertEqual(repr(p.parse('["constant1"]')), "LIST(CONSTANT('constant1'))")
-
-        self.assertEqual(repr(p.parse("[127.0.0.1 , 127.0.0.2]")),        "LIST(IPV4('127.0.0.1'), IPV4('127.0.0.2'))")
-        self.assertEqual(repr(p.parse("[::1 , ::2]")),                    "LIST(IPV6('::1'), IPV6('::2'))")
-        self.assertEqual(repr(p.parse("[1,2, 3,4 , 5]")),                 "LIST(INTEGER(1), INTEGER(2), INTEGER(3), INTEGER(4), INTEGER(5))")
-        self.assertEqual(repr(p.parse("[1.1,2.2, 3.3,4.4 , 5.5]")),       "LIST(FLOAT(1.1), FLOAT(2.2), FLOAT(3.3), FLOAT(4.4), FLOAT(5.5))")
-        self.assertEqual(repr(p.parse("[Var1,Var2, Var3,Var4 , Var5 ]")), "LIST(VARIABLE('Var1'), VARIABLE('Var2'), VARIABLE('Var3'), VARIABLE('Var4'), VARIABLE('Var5'))")
-        self.assertEqual(repr(p.parse('["c1","c2", "c3","c4" , "c5" ]')), "LIST(CONSTANT('c1'), CONSTANT('c2'), CONSTANT('c3'), CONSTANT('c4'), CONSTANT('c5'))")
-
-    def test_05_advanced(self):
-        """
-        Test parsing of advanced filtering expressions.
-        """
-        self.maxDiff = None
-
-        p = MentatFilterParser()
-        p.build()
-
-        self.assertEqual(repr(p.parse('Category in ["Abusive.Spam" , "Attempt.Exploit"]')), "COMPBINOP(VARIABLE('Category') OP_IN LIST(CONSTANT('Abusive.Spam'), CONSTANT('Attempt.Exploit')))")
-        self.assertEqual(repr(p.parse('Category is ["Abusive.Spam" , "Attempt.Exploit"]')), "COMPBINOP(VARIABLE('Category') OP_IS LIST(CONSTANT('Abusive.Spam'), CONSTANT('Attempt.Exploit')))")
-        self.assertEqual(repr(p.parse('Node.Name in ["cz.cesnet.labrea"]')), "COMPBINOP(VARIABLE('Node.Name') OP_IN LIST(CONSTANT('cz.cesnet.labrea')))")
-        self.assertEqual(repr(p.parse('Source.IP4 in [127.0.0.1 , 127.0.0.2]')), "COMPBINOP(VARIABLE('Source.IP4') OP_IN LIST(IPV4('127.0.0.1'), IPV4('127.0.0.2')))")
-        self.assertEqual(repr(p.parse('(Source.IP4 eq 127.0.0.1) or (Node[#].Name is "cz.cesnet.labrea")')), "LOGBINOP(COMPBINOP(VARIABLE('Source.IP4') OP_EQ IPV4('127.0.0.1')) OP_OR COMPBINOP(VARIABLE('Node[#].Name') OP_IS CONSTANT('cz.cesnet.labrea')))")
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/pynspect/test_jpath.py b/lib/pynspect/test_jpath.py
deleted file mode 100644
index 46eabfd675ea65e923d54c6abbde401c1efe45b7..0000000000000000000000000000000000000000
--- a/lib/pynspect/test_jpath.py
+++ /dev/null
@@ -1,527 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# This file is part of Mentat system (https://mentat.cesnet.cz/).
-#
-# Copyright (C) since 2011 CESNET, z.s.p.o (http://www.ces.net/)
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-import os
-import sys
-import shutil
-import unittest
-from pprint import pformat, pprint
-
-# Generate the path to custom 'lib' directory
-lib = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../lib'))
-sys.path.insert(0, lib)
-
-import pynspect.jpath
-from idea import lite
-from pynspect.jpath import *
-
-#-------------------------------------------------------------------------------
-# NOTE: Sorry for the long lines in this file. They are deliberate, because the
-# assertion permutations are (IMHO) more readable this way.
-#-------------------------------------------------------------------------------
-
-class TestJPath(unittest.TestCase):
-
-    msg_dict = {
-        "Format": "IDEA0",
-        "ID": "MESSAGE_ID",
-        "DetectTime": "2016-06-21 13:08:27Z",
-        "Category": ["CATEGORY"],
-        "ConnCount": 633,
-        "Description": "Ping scan",
-        "Source": [
-                {
-                    "IP4": ["192.168.1.1", "192.168.1.2"],
-                    "Proto": ["icmp"]
-                },
-                {
-                    "IP4": ["192.168.2.1", "192.168.2.2"],
-                    "Proto": ["tcp"]
-                }
-            ],
-        "Target": [
-            {
-                "Proto": ["udp"],
-                "IP4": ["192.168.3.1", "192.168.3.2"],
-                "Anonymised": True
-            }
-        ],
-        "Node": [
-            {
-                "SW" : ["KIPPO","FAIL_TO_BAN"],
-                "Name" : "node.name"
-            }
-        ]
-    }
-
-    msg_idea = lite.Idea(msg_dict)
-
-    def test_01_jpath_parse(self):
-        """
-        Perform the basic JPath parsing tests.
-
-        Make sure all possible JPath forms parse correctly.
-        """
-        self.maxDiff = None
-
-        cache_clear()
-        self.assertEqual(cache_size(), 0)
-
-        self.assertEqual(jpath_parse("Test"),           [{'m': 'Test', 'n': 'Test', 'p': 'Test'}])
-        self.assertEqual(jpath_parse("Test.Path"),      [{'m': 'Test', 'n': 'Test', 'p': 'Test'}, {'m': 'Path', 'n': 'Path', 'p': 'Test.Path'}])
-        self.assertEqual(jpath_parse("Long.Test.Path"), [{'m': 'Long', 'n': 'Long', 'p': 'Long'}, {'m': 'Test', 'n': 'Test', 'p': 'Long.Test'}, {'m': 'Path', 'n': 'Path', 'p': 'Long.Test.Path'}])
-
-        self.assertEqual(jpath_parse("Long[1].Test.Path"),    [{'i': 0, 'm': 'Long[1]', 'n': 'Long', 'p': 'Long[1]'}, {        'm': 'Test',    'n': 'Test', 'p': 'Long[1].Test'},    {        'm': 'Path',    'n': 'Path', 'p': 'Long[1].Test.Path'}])
-        self.assertEqual(jpath_parse("Long.Test[2].Path"),    [{        'm': 'Long',    'n': 'Long', 'p': 'Long'},    {'i': 1, 'm': 'Test[2]', 'n': 'Test', 'p': 'Long.Test[2]'},    {        'm': 'Path',    'n': 'Path', 'p': 'Long.Test[2].Path'}])
-        self.assertEqual(jpath_parse("Long.Test.Path[3]"),    [{        'm': 'Long',    'n': 'Long', 'p': 'Long'},    {        'm': 'Test',    'n': 'Test', 'p': 'Long.Test'},       {'i': 2, 'm': 'Path[3]', 'n': 'Path', 'p': 'Long.Test.Path[3]'}])
-        self.assertEqual(jpath_parse("Long[1].Test[1].Path"), [{'i': 0, 'm': 'Long[1]', 'n': 'Long', 'p': 'Long[1]'}, {'i': 0, 'm': 'Test[1]', 'n': 'Test', 'p': 'Long[1].Test[1]'}, {        'm': 'Path',    'n': 'Path', 'p': 'Long[1].Test[1].Path'}])
-        self.assertEqual(jpath_parse("Long.Test[2].Path[2]"), [{        'm': 'Long',    'n': 'Long', 'p': 'Long'},    {'i': 1, 'm': 'Test[2]', 'n': 'Test', 'p': 'Long.Test[2]'},    {'i': 1, 'm': 'Path[2]', 'n': 'Path', 'p': 'Long.Test[2].Path[2]'}])
-        self.assertEqual(jpath_parse("Long[3].Test.Path[3]"), [{'i': 2, 'm': 'Long[3]', 'n': 'Long', 'p': 'Long[3]'}, {        'm': 'Test',    'n': 'Test', 'p': 'Long[3].Test'},    {'i': 2, 'm': 'Path[3]', 'n': 'Path', 'p': 'Long[3].Test.Path[3]'}])
-
-        self.assertEqual(jpath_parse("Long[#].Test.Path"),    [{'i': -1, 'm': 'Long[#]', 'n': 'Long', 'p': 'Long[#]'}, {         'm': 'Test',    'n': 'Test', 'p': 'Long[#].Test'},    {         'm': 'Path',    'n': 'Path', 'p': 'Long[#].Test.Path'}])
-        self.assertEqual(jpath_parse("Long.Test[#].Path"),    [{         'm': 'Long',    'n': 'Long', 'p': 'Long'},    {'i': -1, 'm': 'Test[#]', 'n': 'Test', 'p': 'Long.Test[#]'},    {         'm': 'Path',    'n': 'Path', 'p': 'Long.Test[#].Path'}])
-        self.assertEqual(jpath_parse("Long.Test.Path[#]"),    [{         'm': 'Long',    'n': 'Long', 'p': 'Long'},    {         'm': 'Test',    'n': 'Test', 'p': 'Long.Test'},       {'i': -1, 'm': 'Path[#]', 'n': 'Path', 'p': 'Long.Test.Path[#]'}])
-        self.assertEqual(jpath_parse("Long[#].Test[#].Path"), [{'i': -1, 'm': 'Long[#]', 'n': 'Long', 'p': 'Long[#]'}, {'i': -1, 'm': 'Test[#]', 'n': 'Test', 'p': 'Long[#].Test[#]'}, {         'm': 'Path',    'n': 'Path', 'p': 'Long[#].Test[#].Path'}])
-        self.assertEqual(jpath_parse("Long.Test[#].Path[#]"), [{         'm': 'Long',    'n': 'Long', 'p': 'Long'},    {'i': -1, 'm': 'Test[#]', 'n': 'Test', 'p': 'Long.Test[#]'},    {'i': -1, 'm': 'Path[#]', 'n': 'Path', 'p': 'Long.Test[#].Path[#]'}])
-        self.assertEqual(jpath_parse("Long[#].Test.Path[#]"), [{'i': -1, 'm': 'Long[#]', 'n': 'Long', 'p': 'Long[#]'}, {         'm': 'Test',    'n': 'Test', 'p': 'Long[#].Test'},    {'i': -1, 'm': 'Path[#]', 'n': 'Path', 'p': 'Long[#].Test.Path[#]'}])
-
-        self.assertEqual(jpath_parse("Long[*].Test.Path"),    [{'i': '*', 'm': 'Long[*]', 'n': 'Long', 'p': 'Long[*]'}, {          'm': 'Test',    'n': 'Test', 'p': 'Long[*].Test'},    {          'm': 'Path',    'n': 'Path', 'p': 'Long[*].Test.Path'}])
-        self.assertEqual(jpath_parse("Long.Test[*].Path"),    [{          'm': 'Long',    'n': 'Long', 'p': 'Long'},    {'i': '*', 'm': 'Test[*]', 'n': 'Test', 'p': 'Long.Test[*]'},    {          'm': 'Path',    'n': 'Path', 'p': 'Long.Test[*].Path'}])
-        self.assertEqual(jpath_parse("Long.Test.Path[*]"),    [{          'm': 'Long',    'n': 'Long', 'p': 'Long'},    {          'm': 'Test',    'n': 'Test', 'p': 'Long.Test'},       {'i': '*', 'm': 'Path[*]', 'n': 'Path', 'p': 'Long.Test.Path[*]'}])
-        self.assertEqual(jpath_parse("Long[*].Test[*].Path"), [{'i': '*', 'm': 'Long[*]', 'n': 'Long', 'p': 'Long[*]'}, {'i': '*', 'm': 'Test[*]', 'n': 'Test', 'p': 'Long[*].Test[*]'}, {          'm': 'Path',    'n': 'Path', 'p': 'Long[*].Test[*].Path'}])
-        self.assertEqual(jpath_parse("Long.Test[*].Path[*]"), [{          'm': 'Long',    'n': 'Long', 'p': 'Long'},    {'i': '*', 'm': 'Test[*]', 'n': 'Test', 'p': 'Long.Test[*]'},    {'i': '*', 'm': 'Path[*]', 'n': 'Path', 'p': 'Long.Test[*].Path[*]'}])
-        self.assertEqual(jpath_parse("Long[*].Test.Path[*]"), [{'i': '*', 'm': 'Long[*]', 'n': 'Long', 'p': 'Long[*]'}, {          'm': 'Test',    'n': 'Test', 'p': 'Long[*].Test'},    {'i': '*', 'm': 'Path[*]', 'n': 'Path', 'p': 'Long[*].Test.Path[*]'}])
-
-        self.assertEqual(jpath_parse("Test"),  [{'m': 'Test',  'n': 'Test',  'p': 'Test'}])
-        self.assertEqual(jpath_parse("test"),  [{'m': 'test',  'n': 'test',  'p': 'test'}])
-        self.assertEqual(jpath_parse("TEST"),  [{'m': 'TEST',  'n': 'TEST',  'p': 'TEST'}])
-        self.assertEqual(jpath_parse("_test"), [{'m': '_test', 'n': '_test', 'p': '_test'}])
-
-        self.assertRaisesRegex(JPathException, "Invalid JPath chunk", jpath_parse, 'Test/Value')
-        self.assertRaisesRegex(JPathException, "Invalid JPath chunk", jpath_parse, 'Test|Value')
-        self.assertRaisesRegex(JPathException, "Invalid JPath chunk", jpath_parse, 'Test-Value')
-        self.assertRaisesRegex(JPathException, "Invalid JPath chunk", jpath_parse, 'Test-.Value')
-        self.assertRaisesRegex(JPathException, "Invalid JPath chunk", jpath_parse, 'Test[]Value')
-        self.assertRaisesRegex(JPathException, "Invalid JPath chunk", jpath_parse, 'TestValue[]')
-        self.assertRaisesRegex(JPathException, "Invalid JPath chunk", jpath_parse, 'Test[1]Value')
-        self.assertRaisesRegex(JPathException, "Invalid JPath chunk", jpath_parse, 'Test[].Value')
-        self.assertRaisesRegex(JPathException, "Invalid JPath chunk", jpath_parse, 'Test.Value[]')
-        self.assertRaisesRegex(JPathException, "Invalid JPath chunk", jpath_parse, 'Test[-1].Value')
-        self.assertRaisesRegex(JPathException, "Invalid JPath chunk", jpath_parse, 'Test.[1].Value')
-        self.assertRaisesRegex(JPathException, "Invalid JPath chunk", jpath_parse, 'Test.Value.[1]')
-
-        self.assertEqual(jpath_parse_c("Test"),           [{'m': 'Test', 'n': 'Test', 'p': 'Test'}])
-        self.assertEqual(jpath_parse_c("Test.Path"),      [{'m': 'Test', 'n': 'Test', 'p': 'Test'}, {'m': 'Path', 'n': 'Path', 'p': 'Test.Path'}])
-        self.assertEqual(jpath_parse_c("Long.Test.Path"), [{'m': 'Long', 'n': 'Long', 'p': 'Long'}, {'m': 'Test', 'n': 'Test', 'p': 'Long.Test'}, {'m': 'Path', 'n': 'Path', 'p': 'Long.Test.Path'}])
-        self.assertEqual(jpath_parse_c("Test"),           [{'m': 'Test', 'n': 'Test', 'p': 'Test'}])
-        self.assertEqual(jpath_parse_c("Test.Path"),      [{'m': 'Test', 'n': 'Test', 'p': 'Test'}, {'m': 'Path', 'n': 'Path', 'p': 'Test.Path'}])
-        self.assertEqual(jpath_parse_c("Long.Test.Path"), [{'m': 'Long', 'n': 'Long', 'p': 'Long'}, {'m': 'Test', 'n': 'Test', 'p': 'Long.Test'}, {'m': 'Path', 'n': 'Path', 'p': 'Long.Test.Path'}])
-
-        self.assertEqual(cache_size(), 3)
-        cache_clear()
-        self.assertEqual(cache_size(), 0)
-
-    def test_02_jpath_values(self):
-        """
-        Perform the basic JPath values retrieval tests.
-
-        Make sure all possible JPath forms return expected results.
-        """
-        self.maxDiff = None
-
-        self.assertEqual(jpath_values(self.msg_dict, 'Format'),    ['IDEA0'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Format[1]'), [])
-        self.assertEqual(jpath_values(self.msg_dict, 'Format[#]'), [])
-        self.assertEqual(jpath_values(self.msg_dict, 'Format[*]'), [])
-
-        self.assertEqual(jpath_values(self.msg_dict, 'Category'),    ['CATEGORY'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Category[1]'), ['CATEGORY'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Category[2]'), [])
-        self.assertEqual(jpath_values(self.msg_dict, 'Category[#]'), ['CATEGORY'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Category[*]'), ['CATEGORY'])
-
-        self.assertEqual(jpath_values(self.msg_dict, 'Node.SW'),       ['KIPPO','FAIL_TO_BAN'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Node[1].SW'),    ['KIPPO','FAIL_TO_BAN'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Node[#].SW'),    ['KIPPO','FAIL_TO_BAN'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Node[*].SW'),    ['KIPPO','FAIL_TO_BAN'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Node[#].SW[1]'), ['KIPPO'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Node[#].SW[2]'), ['FAIL_TO_BAN'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Node[#].SW[#]'), ['FAIL_TO_BAN'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Node[#].SW[*]'), ['KIPPO','FAIL_TO_BAN'])
-
-        self.assertEqual(jpath_values(self.msg_dict, 'Node.Name'),       ['node.name'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Node[1].Name'),    ['node.name'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Node[#].Name'),    ['node.name'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Node[*].Name'),    ['node.name'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Node[1].Name[1]'), [])
-        self.assertEqual(jpath_values(self.msg_dict, 'Node[#].Name[#]'), [])
-        self.assertEqual(jpath_values(self.msg_dict, 'Node[*].Name[*]'), [])
-
-        self.assertEqual(jpath_values(self.msg_dict, 'Source.IP4'),       ['192.168.1.1','192.168.1.2','192.168.2.1','192.168.2.2'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source[1].IP4'),    ['192.168.1.1','192.168.1.2'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source[2].IP4'),    ['192.168.2.1','192.168.2.2'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source[#].IP4'),    ['192.168.2.1','192.168.2.2'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source[*].IP4'),    ['192.168.1.1','192.168.1.2','192.168.2.1','192.168.2.2'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source.IP4[1]'),    ['192.168.1.1','192.168.2.1'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source.IP4[2]'),    ['192.168.1.2','192.168.2.2'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source.IP4[#]'),    ['192.168.1.2','192.168.2.2'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source.IP4[*]'),    ['192.168.1.1','192.168.1.2','192.168.2.1','192.168.2.2'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source[1].IP4[1]'), ['192.168.1.1'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source[1].IP4[2]'), ['192.168.1.2'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source[1].IP4[#]'), ['192.168.1.2'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source[1].IP4[*]'), ['192.168.1.1','192.168.1.2'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source[2].IP4[1]'), ['192.168.2.1'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source[2].IP4[2]'), ['192.168.2.2'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source[2].IP4[#]'), ['192.168.2.2'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source[2].IP4[*]'), ['192.168.2.1','192.168.2.2'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source[#].IP4[1]'), ['192.168.2.1'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source[#].IP4[2]'), ['192.168.2.2'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source[#].IP4[#]'), ['192.168.2.2'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source[#].IP4[*]'), ['192.168.2.1','192.168.2.2'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source[*].IP4[1]'), ['192.168.1.1','192.168.2.1'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source[*].IP4[2]'), ['192.168.1.2','192.168.2.2'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source[*].IP4[#]'), ['192.168.1.2','192.168.2.2'])
-        self.assertEqual(jpath_values(self.msg_dict, 'Source[*].IP4[*]'), ['192.168.1.1','192.168.1.2','192.168.2.1','192.168.2.2'])
-
-        self.assertEqual(jpath_values(self.msg_idea, 'Format'),          ['IDEA0'])
-        self.assertEqual(jpath_values(self.msg_idea, 'Node.Name'),       ['node.name'])
-        self.assertEqual(jpath_values(self.msg_idea, 'Node[1].Name'),    ['node.name'])
-        self.assertEqual(jpath_values(self.msg_idea, 'Node[#].Name'),    ['node.name'])
-        self.assertEqual(jpath_values(self.msg_idea, 'Node[*].Name'),    ['node.name'])
-        self.assertEqual(jpath_values(self.msg_idea, 'Node[1].Name[1]'), [])
-        self.assertEqual(jpath_values(self.msg_idea, 'Node[#].Name[#]'), [])
-        self.assertEqual(jpath_values(self.msg_idea, 'Node[*].Name[*]'), [])
-        self.assertEqual(jpath_values(self.msg_idea, 'Node.SW'),         ['KIPPO','FAIL_TO_BAN'])
-        self.assertEqual(jpath_values(self.msg_idea, 'Node[1].SW'),      ['KIPPO','FAIL_TO_BAN'])
-        self.assertEqual(jpath_values(self.msg_idea, 'Node[#].SW'),      ['KIPPO','FAIL_TO_BAN'])
-        self.assertEqual(jpath_values(self.msg_idea, 'Node[*].SW'),      ['KIPPO','FAIL_TO_BAN'])
-        self.assertEqual(jpath_values(self.msg_idea, 'Node[#].SW[1]'),   ['KIPPO'])
-        self.assertEqual(jpath_values(self.msg_idea, 'Node[#].SW[2]'),   ['FAIL_TO_BAN'])
-        self.assertEqual(jpath_values(self.msg_idea, 'Node[#].SW[#]'),   ['FAIL_TO_BAN'])
-        self.assertEqual(jpath_values(self.msg_idea, 'Node[#].SW[*]'),   ['KIPPO','FAIL_TO_BAN'])
-
-    def test_03_jpath_value(self):
-        """
-        Perform the basic JPath value retrieval tests.
-
-        Make sure all possible JPath forms return expected results.
-        """
-        self.maxDiff = None
-
-        self.assertEqual(jpath_value(self.msg_dict, 'Format'),    'IDEA0')
-        self.assertEqual(jpath_value(self.msg_dict, 'Format[1]'), None)
-        self.assertEqual(jpath_value(self.msg_dict, 'Format[#]'), None)
-        self.assertEqual(jpath_value(self.msg_dict, 'Format[*]'), None)
-
-        self.assertEqual(jpath_value(self.msg_dict, 'Category'),    'CATEGORY')
-        self.assertEqual(jpath_value(self.msg_dict, 'Category[1]'), 'CATEGORY')
-        self.assertEqual(jpath_value(self.msg_dict, 'Category[2]'), None)
-        self.assertEqual(jpath_value(self.msg_dict, 'Category[#]'), 'CATEGORY')
-        self.assertEqual(jpath_value(self.msg_dict, 'Category[*]'), 'CATEGORY')
-
-        self.assertEqual(jpath_value(self.msg_dict, 'Node.SW'),       'KIPPO')
-        self.assertEqual(jpath_value(self.msg_dict, 'Node[1].SW'),    'KIPPO')
-        self.assertEqual(jpath_value(self.msg_dict, 'Node[#].SW'),    'KIPPO')
-        self.assertEqual(jpath_value(self.msg_dict, 'Node[*].SW'),    'KIPPO')
-        self.assertEqual(jpath_value(self.msg_dict, 'Node[#].SW[1]'), 'KIPPO')
-        self.assertEqual(jpath_value(self.msg_dict, 'Node[#].SW[2]'), 'FAIL_TO_BAN')
-        self.assertEqual(jpath_value(self.msg_dict, 'Node[#].SW[#]'), 'FAIL_TO_BAN')
-        self.assertEqual(jpath_value(self.msg_dict, 'Node[#].SW[*]'), 'KIPPO')
-
-        self.assertEqual(jpath_value(self.msg_dict, 'Node.Name'),       'node.name')
-        self.assertEqual(jpath_value(self.msg_dict, 'Node[1].Name'),    'node.name')
-        self.assertEqual(jpath_value(self.msg_dict, 'Node[#].Name'),    'node.name')
-        self.assertEqual(jpath_value(self.msg_dict, 'Node[*].Name'),    'node.name')
-        self.assertEqual(jpath_value(self.msg_dict, 'Node[1].Name[1]'), None)
-        self.assertEqual(jpath_value(self.msg_dict, 'Node[#].Name[#]'), None)
-        self.assertEqual(jpath_value(self.msg_dict, 'Node[*].Name[*]'), None)
-
-        self.assertEqual(jpath_value(self.msg_dict, 'Source.IP4'),       '192.168.1.1')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source[1].IP4'),    '192.168.1.1')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source[2].IP4'),    '192.168.2.1')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source[#].IP4'),    '192.168.2.1')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source[*].IP4'),    '192.168.1.1')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source.IP4[1]'),    '192.168.1.1')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source.IP4[2]'),    '192.168.1.2')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source.IP4[#]'),    '192.168.1.2')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source.IP4[*]'),    '192.168.1.1')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source[1].IP4[1]'), '192.168.1.1')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source[1].IP4[2]'), '192.168.1.2')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source[1].IP4[#]'), '192.168.1.2')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source[1].IP4[*]'), '192.168.1.1')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source[2].IP4[1]'), '192.168.2.1')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source[2].IP4[2]'), '192.168.2.2')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source[2].IP4[#]'), '192.168.2.2')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source[2].IP4[*]'), '192.168.2.1')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source[#].IP4[1]'), '192.168.2.1')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source[#].IP4[2]'), '192.168.2.2')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source[#].IP4[#]'), '192.168.2.2')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source[#].IP4[*]'), '192.168.2.1')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source[*].IP4[1]'), '192.168.1.1')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source[*].IP4[2]'), '192.168.1.2')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source[*].IP4[#]'), '192.168.1.2')
-        self.assertEqual(jpath_value(self.msg_dict, 'Source[*].IP4[*]'), '192.168.1.1')
-
-        self.assertEqual(jpath_value(self.msg_idea, 'Format'),          'IDEA0')
-        self.assertEqual(jpath_value(self.msg_idea, 'Node.Name'),       'node.name')
-        self.assertEqual(jpath_value(self.msg_idea, 'Node[1].Name'),    'node.name')
-        self.assertEqual(jpath_value(self.msg_idea, 'Node[#].Name'),    'node.name')
-        self.assertEqual(jpath_value(self.msg_idea, 'Node[*].Name'),    'node.name')
-        self.assertEqual(jpath_value(self.msg_idea, 'Node[1].Name[1]'), None)
-        self.assertEqual(jpath_value(self.msg_idea, 'Node[#].Name[#]'), None)
-        self.assertEqual(jpath_value(self.msg_idea, 'Node[*].Name[*]'), None)
-        self.assertEqual(jpath_value(self.msg_idea, 'Node.SW'),         'KIPPO')
-        self.assertEqual(jpath_value(self.msg_idea, 'Node[1].SW'),      'KIPPO')
-        self.assertEqual(jpath_value(self.msg_idea, 'Node[#].SW'),      'KIPPO')
-        self.assertEqual(jpath_value(self.msg_idea, 'Node[*].SW'),      'KIPPO')
-        self.assertEqual(jpath_value(self.msg_idea, 'Node[#].SW[1]'),   'KIPPO')
-        self.assertEqual(jpath_value(self.msg_idea, 'Node[#].SW[2]'),   'FAIL_TO_BAN')
-        self.assertEqual(jpath_value(self.msg_idea, 'Node[#].SW[#]'),   'FAIL_TO_BAN')
-        self.assertEqual(jpath_value(self.msg_idea, 'Node[#].SW[*]'),   'KIPPO')
-
-    def test_04_jpath_exists(self):
-        """
-        Perform the basic JPath elements existence tests.
-
-        Make sure all possible JPath forms return expected results.
-        """
-        self.maxDiff = None
-
-        self.assertEqual(jpath_exists(self.msg_dict, 'Format'),    True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Format[1]'), False)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Format[#]'), False)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Format[*]'), False)
-
-        self.assertEqual(jpath_exists(self.msg_dict, 'Category'),    True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Category[1]'), True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Category[2]'), False)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Category[#]'), True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Category[*]'), True)
-
-        self.assertEqual(jpath_exists(self.msg_dict, 'Node.SW'),       True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Node[1].SW'),    True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Node[#].SW'),    True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Node[*].SW'),    True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Node[#].SW[1]'), True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Node[#].SW[2]'), True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Node[#].SW[#]'), True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Node[#].SW[*]'), True)
-
-        self.assertEqual(jpath_exists(self.msg_dict, 'Node.Name'),       True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Node[1].Name'),    True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Node[#].Name'),    True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Node[*].Name'),    True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Node[1].Name[1]'), False)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Node[#].Name[#]'), False)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Node[*].Name[*]'), False)
-
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source.IP4'),       True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source[1].IP4'),    True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source[2].IP4'),    True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source[#].IP4'),    True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source[*].IP4'),    True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source.IP4[1]'),    True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source.IP4[2]'),    True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source.IP4[#]'),    True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source.IP4[*]'),    True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source[1].IP4[1]'), True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source[1].IP4[2]'), True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source[1].IP4[#]'), True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source[1].IP4[*]'), True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source[2].IP4[1]'), True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source[2].IP4[2]'), True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source[2].IP4[#]'), True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source[2].IP4[*]'), True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source[#].IP4[1]'), True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source[#].IP4[2]'), True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source[#].IP4[#]'), True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source[#].IP4[*]'), True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source[*].IP4[1]'), True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source[*].IP4[2]'), True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source[*].IP4[#]'), True)
-        self.assertEqual(jpath_exists(self.msg_dict, 'Source[*].IP4[*]'), True)
-
-        self.assertEqual(jpath_exists(self.msg_idea, 'Format'),          True)
-        self.assertEqual(jpath_exists(self.msg_idea, 'Node.Name'),       True)
-        self.assertEqual(jpath_exists(self.msg_idea, 'Node[1].Name'),    True)
-        self.assertEqual(jpath_exists(self.msg_idea, 'Node[#].Name'),    True)
-        self.assertEqual(jpath_exists(self.msg_idea, 'Node[*].Name'),    True)
-        self.assertEqual(jpath_exists(self.msg_idea, 'Node[1].Name[1]'), False)
-        self.assertEqual(jpath_exists(self.msg_idea, 'Node[#].Name[#]'), False)
-        self.assertEqual(jpath_exists(self.msg_idea, 'Node[*].Name[*]'), False)
-        self.assertEqual(jpath_exists(self.msg_idea, 'Node.SW'),         True)
-        self.assertEqual(jpath_exists(self.msg_idea, 'Node[1].SW'),      True)
-        self.assertEqual(jpath_exists(self.msg_idea, 'Node[#].SW'),      True)
-        self.assertEqual(jpath_exists(self.msg_idea, 'Node[*].SW'),      True)
-        self.assertEqual(jpath_exists(self.msg_idea, 'Node[#].SW[1]'),   True)
-        self.assertEqual(jpath_exists(self.msg_idea, 'Node[#].SW[2]'),   True)
-        self.assertEqual(jpath_exists(self.msg_idea, 'Node[#].SW[#]'),   True)
-        self.assertEqual(jpath_exists(self.msg_idea, 'Node[#].SW[*]'),   True)
-
-    def test_05_jpath_set(self):
-        """
-        Perform the basic JPath value setting tests.
-        """
-        self.maxDiff = None
-
-        msg = {}
-        self.assertEqual(jpath_set(msg, 'TestA.ValueA1', 'A1'), RC_VALUE_SET)
-        self.assertEqual(
-                msg,
-                {
-                    'TestA': { 'ValueA1': 'A1'}
-                }
-            )
-        self.assertEqual(jpath_set(msg, 'TestA.ValueA2', 'A2'), RC_VALUE_SET)
-        self.assertEqual(
-                msg,
-                {
-                    'TestA': { 'ValueA1': 'A1', 'ValueA2': 'A2' }
-                }
-            )
-        self.assertEqual(jpath_set(msg, 'TestB[1].ValueB1', 'B1'), RC_VALUE_SET)
-        self.assertEqual(
-                msg,
-                {
-                    'TestA': { 'ValueA1': 'A1', 'ValueA2': 'A2' },
-                    'TestB': [{ 'ValueB1': 'B1' }]
-                }
-            )
-        self.assertEqual(jpath_set(msg, 'TestB[#].ValueB2', 'B2'), RC_VALUE_SET)
-        self.assertEqual(
-                msg,
-                {
-                    'TestA': { 'ValueA1': 'A1', 'ValueA2': 'A2' },
-                    'TestB': [{ 'ValueB1': 'B1', 'ValueB2': 'B2' }]
-                }
-            )
-        self.assertEqual(jpath_set(msg, 'TestB[*].ValueB3', 'B3'), RC_VALUE_SET)
-        self.assertEqual(
-                msg,
-                {
-                    'TestA': { 'ValueA1': 'A1', 'ValueA2': 'A2' },
-                    'TestB': [{ 'ValueB1': 'B1', 'ValueB2': 'B2' }, { 'ValueB3': 'B3' }]
-                }
-            )
-        self.assertEqual(jpath_set(msg, 'TestB[#].ValueB4', 'B4'), RC_VALUE_SET)
-        self.assertEqual(
-                msg,
-                {
-                    'TestA': { 'ValueA1': 'A1', 'ValueA2': 'A2' },
-                    'TestB': [{ 'ValueB1': 'B1', 'ValueB2': 'B2' }, { 'ValueB3': 'B3', 'ValueB4': 'B4' }]
-                }
-            )
-        self.assertEqual(jpath_set(msg, 'TestB[#]', 'DROP'), RC_VALUE_SET)
-        self.assertEqual(
-                msg,
-                {
-                    'TestA': { 'ValueA1': 'A1', 'ValueA2': 'A2' },
-                    'TestB': [{ 'ValueB1': 'B1', 'ValueB2': 'B2' }, "DROP"]
-                }
-            )
-
-        # This will fail, because "TestA" node is not a list
-        self.assertRaisesRegex(JPathException, "Expected list-like object under structure key", jpath_set, msg, 'TestA[#].ValueC1', 'C1')
-
-        # This will fail, because "TestA.ValueA1" node is not a dict
-        self.assertRaisesRegex(JPathException, "Expected dict-like object under structure key", jpath_set, msg, 'TestA.ValueA1.ValueC1', 'C1')
-
-        # This will fail, because we try to attach a node to scalar "TestB[#]"
-        self.assertRaisesRegex(JPathException, "Expected dict-like structure to attach node", jpath_set, msg, 'TestB[#].ValueB5', 'RAISE EXCEPTION')
-
-    def test_06_jpath_set_unique(self):
-        """
-        Perform JPath value setting tests with unique flag.
-        """
-        self.maxDiff = None
-
-        msg = {}
-        self.assertEqual(jpath_set(msg, 'TestC[#].ListVals1[*]', 'LV1', unique = True), RC_VALUE_SET)
-        self.assertEqual(
-                msg,
-                {
-                    'TestC': [{ 'ListVals1': ['LV1']}]
-                }
-            )
-        self.assertEqual(jpath_set(msg, 'TestC[#].ListVals1[*]', 'LV2', unique = True), RC_VALUE_SET)
-        self.assertEqual(
-                msg,
-                {
-                    'TestC': [{ 'ListVals1': ['LV1','LV2']}]
-                }
-            )
-        self.assertEqual(jpath_set(msg, 'TestC[#].ListVals1[*]', 'LV1', unique = True), RC_VALUE_DUPLICATE)
-        self.assertEqual(
-                msg,
-                {
-                    'TestC': [{ 'ListVals1': ['LV1','LV2']}]
-                }
-            )
-
-    def test_07_jpath_set_overwrite(self):
-        """
-        Perform JPath value setting tests with overwrite flag.
-        """
-        self.maxDiff = None
-
-        msg = {}
-
-        #
-        # Overwriting in lists.
-        #
-        self.assertEqual(jpath_set(msg, 'TestD[#].ListVals1[*]', 'LV1', overwrite = False), RC_VALUE_SET)
-        self.assertEqual(
-                msg,
-                {
-                    'TestD': [{ 'ListVals1': ['LV1']}]
-                }
-            )
-        self.assertEqual(jpath_set(msg, 'TestD[#].ListVals1[*]', 'LV2', overwrite = False), RC_VALUE_SET)
-        self.assertEqual(
-                msg,
-                {
-                    'TestD': [{ 'ListVals1': ['LV1','LV2']}]
-                }
-            )
-        self.assertEqual(jpath_set(msg, 'TestD[#].ListVals1[2]', 'LV3', overwrite = False), RC_VALUE_EXISTS)
-        self.assertEqual(
-                msg,
-                {
-                    'TestD': [{ 'ListVals1': ['LV1','LV2']}]
-                }
-            )
-        self.assertEqual(jpath_set(msg, 'TestD[#].ListVals1[3]', 'LV3', overwrite = False), RC_VALUE_SET)
-        self.assertEqual(
-                msg,
-                {
-                    'TestD': [{ 'ListVals1': ['LV1','LV2','LV3']}]
-                }
-            )
-
-        #
-        # Overwriting in dicts.
-        #
-        self.assertEqual(jpath_set(msg, 'TestD[#].DictVal', 'DV1', overwrite = False), RC_VALUE_SET)
-        self.assertEqual(
-                msg,
-                {
-                    'TestD': [{ 'ListVals1': ['LV1','LV2','LV3'], 'DictVal': 'DV1' }]
-                }
-            )
-        self.assertEqual(jpath_set(msg, 'TestD[#].DictVal', 'DV2', overwrite = False), RC_VALUE_EXISTS)
-        self.assertEqual(
-                msg,
-                {
-                    'TestD': [{ 'ListVals1': ['LV1','LV2','LV3'], 'DictVal': 'DV1' }]
-                }
-            )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/lib/pynspect/test_lexer.py b/lib/pynspect/test_lexer.py
deleted file mode 100644
index df40e5d4d851664fcca1a81aa01e8012be5e5b41..0000000000000000000000000000000000000000
--- a/lib/pynspect/test_lexer.py
+++ /dev/null
@@ -1,106 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# This file is part of Mentat system (https://mentat.cesnet.cz/).
-#
-# Copyright (C) since 2011 CESNET, z.s.p.o (http://www.ces.net/)
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-import os
-import sys
-import shutil
-import unittest
-from pprint import pformat, pprint
-
-# Generate the path to custom 'lib' directory
-lib = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../lib'))
-sys.path.insert(0, lib)
-
-from pynspect.lexer import MentatFilterLexer
-
-#-------------------------------------------------------------------------------
-# NOTE: Sorry for the long lines in this file. They are deliberate, because the
-# assertion permutations are (IMHO) more readable this way.
-#-------------------------------------------------------------------------------
-
-class TestMentatFilterLexer(unittest.TestCase):
-
-    def test_01_basic(self):
-        """
-        Perfom basic lexer tests: check, that all lexical tokens are
-        correctly recognized.
-        """
-        self.maxDiff = None
-
-        l = MentatFilterLexer()
-        l.build()
-
-        self.assertEqual(l.test('+-*/%'), "LexToken(OP_PLUS,'OP_PLUS',1,0)LexToken(OP_MINUS,'OP_MINUS',1,1)LexToken(OP_TIMES,'OP_TIMES',1,2)LexToken(OP_DIVIDE,'OP_DIVIDE',1,3)LexToken(OP_MODULO,'OP_MODULO',1,4)")
-
-        self.assertEqual(l.test('OR or ||'),        "LexToken(OP_OR,'OP_OR',1,0)LexToken(OP_OR,'OP_OR',1,3)LexToken(OP_OR_P,'OP_OR_P',1,6)")
-        self.assertEqual(l.test('XOR xor ^^'),      "LexToken(OP_XOR,'OP_XOR',1,0)LexToken(OP_XOR,'OP_XOR',1,4)LexToken(OP_XOR_P,'OP_XOR_P',1,8)")
-        self.assertEqual(l.test('AND and &&'),      "LexToken(OP_AND,'OP_AND',1,0)LexToken(OP_AND,'OP_AND',1,4)LexToken(OP_AND_P,'OP_AND_P',1,8)")
-        self.assertEqual(l.test('NOT not !'),       "LexToken(OP_NOT,'OP_NOT',1,0)LexToken(OP_NOT,'OP_NOT',1,4)LexToken(OP_NOT,'OP_NOT',1,8)")
-        self.assertEqual(l.test('EXISTS exists ?'), "LexToken(OP_EXISTS,'OP_EXISTS',1,0)LexToken(OP_EXISTS,'OP_EXISTS',1,7)LexToken(OP_EXISTS,'OP_EXISTS',1,14)")
-
-        self.assertEqual(l.test('LIKE like =~'), "LexToken(OP_LIKE,'OP_LIKE',1,0)LexToken(OP_LIKE,'OP_LIKE',1,5)LexToken(OP_LIKE,'OP_LIKE',1,10)")
-        self.assertEqual(l.test('IN in ~~'),     "LexToken(OP_IN,'OP_IN',1,0)LexToken(OP_IN,'OP_IN',1,3)LexToken(OP_IN,'OP_IN',1,6)")
-        self.assertEqual(l.test('IS is'),        "LexToken(OP_IS,'OP_IS',1,0)LexToken(OP_IS,'OP_IS',1,3)")
-        self.assertEqual(l.test('EQ eq =='),     "LexToken(OP_EQ,'OP_EQ',1,0)LexToken(OP_EQ,'OP_EQ',1,3)LexToken(OP_EQ,'OP_EQ',1,6)")
-        self.assertEqual(l.test('NE ne <> !='),  "LexToken(OP_NE,'OP_NE',1,0)LexToken(OP_NE,'OP_NE',1,3)LexToken(OP_NE,'OP_NE',1,6)LexToken(OP_NE,'OP_NE',1,9)")
-        self.assertEqual(l.test('GT gt >'),      "LexToken(OP_GT,'OP_GT',1,0)LexToken(OP_GT,'OP_GT',1,3)LexToken(OP_GT,'OP_GT',1,6)")
-        self.assertEqual(l.test('GE ge >='),     "LexToken(OP_GE,'OP_GE',1,0)LexToken(OP_GE,'OP_GE',1,3)LexToken(OP_GE,'OP_GE',1,6)")
-        self.assertEqual(l.test('LT lt <'),      "LexToken(OP_LT,'OP_LT',1,0)LexToken(OP_LT,'OP_LT',1,3)LexToken(OP_LT,'OP_LT',1,6)")
-        self.assertEqual(l.test('LE le <='),     "LexToken(OP_LE,'OP_LE',1,0)LexToken(OP_LE,'OP_LE',1,3)LexToken(OP_LE,'OP_LE',1,6)")
-
-        self.assertEqual(l.test('127.0.0.1'),            "LexToken(IPV4,('IPV4', '127.0.0.1'),1,0)")
-        self.assertEqual(l.test('127.0.0.1/32'),         "LexToken(IPV4,('IPV4', '127.0.0.1/32'),1,0)")
-        self.assertEqual(l.test('127.0.0.1-127.0.0.5'),  "LexToken(IPV4,('IPV4', '127.0.0.1-127.0.0.5'),1,0)")
-        self.assertEqual(l.test('127.0.0.1..127.0.0.5'), "LexToken(IPV4,('IPV4', '127.0.0.1..127.0.0.5'),1,0)")
-
-        self.assertEqual(l.test('::1'),      "LexToken(IPV6,('IPV6', '::1'),1,0)")
-        self.assertEqual(l.test('::1/64'),   "LexToken(IPV6,('IPV6', '::1/64'),1,0)")
-        self.assertEqual(l.test('::1-::5'),  "LexToken(IPV6,('IPV6', '::1-::5'),1,0)")
-        self.assertEqual(l.test('::1..::5'), "LexToken(IPV6,('IPV6', '::1..::5'),1,0)")
-
-        self.assertEqual(l.test('15'),   "LexToken(INTEGER,('INTEGER', 15),1,0)")
-        self.assertEqual(l.test('15.5'), "LexToken(FLOAT,('FLOAT', 15.5),1,0)")
-
-        self.assertEqual(l.test('S'),                   "LexToken(VARIABLE,('VARIABLE', 'S'),1,0)")
-        self.assertEqual(l.test('S.N'),                 "LexToken(VARIABLE,('VARIABLE', 'S.N'),1,0)")
-        self.assertEqual(l.test('Source.Node'),         "LexToken(VARIABLE,('VARIABLE', 'Source.Node'),1,0)")
-        self.assertEqual(l.test('Source[1].Node[2]'),   "LexToken(VARIABLE,('VARIABLE', 'Source[1].Node[2]'),1,0)")
-        self.assertEqual(l.test('Source[-1].Node[-2]'), "LexToken(VARIABLE,('VARIABLE', 'Source[-1].Node[-2]'),1,0)")
-        self.assertEqual(l.test('Source[#].Node[#]'),   "LexToken(VARIABLE,('VARIABLE', 'Source[#].Node[#]'),1,0)")
-        self.assertEqual(l.test('"Value 525.89:X><"'),  "LexToken(CONSTANT,('CONSTANT', 'Value 525.89:X><'),1,0)")
-        self.assertEqual(l.test("'Value 525.89:X><'"),  "LexToken(CONSTANT,('CONSTANT', 'Value 525.89:X><'),1,0)")
-
-        self.assertEqual(l.test(','),     "LexToken(COMMA,',',1,0)")
-        self.assertEqual(l.test(', '),    "LexToken(COMMA,', ',1,0)")
-        self.assertEqual(l.test(' , '),   "LexToken(COMMA,', ',1,1)")
-        self.assertEqual(l.test('  ,  '), "LexToken(COMMA,',  ',1,2)")
-        self.assertEqual(l.test(';'),     "LexToken(COMMA,';',1,0)")
-        self.assertEqual(l.test('; '),    "LexToken(COMMA,'; ',1,0)")
-        self.assertEqual(l.test(' ; '),   "LexToken(COMMA,'; ',1,1)")
-        self.assertEqual(l.test('  ;  '), "LexToken(COMMA,';  ',1,2)")
-
-        self.assertEqual(l.test('()'), "LexToken(LPAREN,'(',1,0)LexToken(RPAREN,')',1,1)")
-        self.assertEqual(l.test('[]'), "LexToken(LBRACK,'[',1,0)LexToken(RBRACK,']',1,1)")
-
-        self.assertEqual(l.test('[127.0.0.1 , 127.0.0.2]'), "LexToken(LBRACK,'[',1,0)LexToken(IPV4,('IPV4', '127.0.0.1'),1,1)LexToken(COMMA,', ',1,11)LexToken(IPV4,('IPV4', '127.0.0.2'),1,13)LexToken(RBRACK,']',1,22)")
-        self.assertEqual(l.test('[::1 , ::2]'), "LexToken(LBRACK,'[',1,0)LexToken(IPV6,('IPV6', '::1'),1,1)LexToken(COMMA,', ',1,5)LexToken(IPV6,('IPV6', '::2'),1,7)LexToken(RBRACK,']',1,10)")
-        self.assertEqual(l.test('[1,2, 3,4 , 5 ]'), "LexToken(LBRACK,'[',1,0)LexToken(INTEGER,('INTEGER', 1),1,1)LexToken(COMMA,',',1,2)LexToken(INTEGER,('INTEGER', 2),1,3)LexToken(COMMA,', ',1,4)LexToken(INTEGER,('INTEGER', 3),1,6)LexToken(COMMA,',',1,7)LexToken(INTEGER,('INTEGER', 4),1,8)LexToken(COMMA,', ',1,10)LexToken(INTEGER,('INTEGER', 5),1,12)LexToken(RBRACK,']',1,14)")
-        self.assertEqual(l.test('[15.5,16.6, 17.7,18.8 , 19.9 ]'), "LexToken(LBRACK,'[',1,0)LexToken(FLOAT,('FLOAT', 15.5),1,1)LexToken(COMMA,',',1,5)LexToken(FLOAT,('FLOAT', 16.6),1,6)LexToken(COMMA,', ',1,10)LexToken(FLOAT,('FLOAT', 17.7),1,12)LexToken(COMMA,',',1,16)LexToken(FLOAT,('FLOAT', 18.8),1,17)LexToken(COMMA,', ',1,22)LexToken(FLOAT,('FLOAT', 19.9),1,24)LexToken(RBRACK,']',1,29)")
-        self.assertEqual(l.test('[Test.Node1,Test.Node2, Test.Node3,Test.Node4 , Test.Node5 ]'), "LexToken(LBRACK,'[',1,0)LexToken(VARIABLE,('VARIABLE', 'Test.Node1'),1,1)LexToken(COMMA,',',1,11)LexToken(VARIABLE,('VARIABLE', 'Test.Node2'),1,12)LexToken(COMMA,', ',1,22)LexToken(VARIABLE,('VARIABLE', 'Test.Node3'),1,24)LexToken(COMMA,',',1,34)LexToken(VARIABLE,('VARIABLE', 'Test.Node4'),1,35)LexToken(COMMA,', ',1,46)LexToken(VARIABLE,('VARIABLE', 'Test.Node5'),1,48)LexToken(RBRACK,']',1,59)")
-        self.assertEqual(l.test('["constant1","constant2", "constant3","constant4" , "constant5" ]'), "LexToken(LBRACK,'[',1,0)LexToken(CONSTANT,('CONSTANT', 'constant1'),1,1)LexToken(COMMA,',',1,12)LexToken(CONSTANT,('CONSTANT', 'constant2'),1,13)LexToken(COMMA,', ',1,24)LexToken(CONSTANT,('CONSTANT', 'constant3'),1,26)LexToken(COMMA,',',1,37)LexToken(CONSTANT,('CONSTANT', 'constant4'),1,38)LexToken(COMMA,', ',1,50)LexToken(CONSTANT,('CONSTANT', 'constant5'),1,52)LexToken(RBRACK,']',1,64)")
-        self.assertEqual(l.test('[127.0.0.1 ; 127.0.0.2]'), "LexToken(LBRACK,'[',1,0)LexToken(IPV4,('IPV4', '127.0.0.1'),1,1)LexToken(COMMA,'; ',1,11)LexToken(IPV4,('IPV4', '127.0.0.2'),1,13)LexToken(RBRACK,']',1,22)")
-        self.assertEqual(l.test('[::1 ; ::2]'), "LexToken(LBRACK,'[',1,0)LexToken(IPV6,('IPV6', '::1'),1,1)LexToken(COMMA,'; ',1,5)LexToken(IPV6,('IPV6', '::2'),1,7)LexToken(RBRACK,']',1,10)")
-        self.assertEqual(l.test('[1;2; 3;4 ; 5 ]'), "LexToken(LBRACK,'[',1,0)LexToken(INTEGER,('INTEGER', 1),1,1)LexToken(COMMA,';',1,2)LexToken(INTEGER,('INTEGER', 2),1,3)LexToken(COMMA,'; ',1,4)LexToken(INTEGER,('INTEGER', 3),1,6)LexToken(COMMA,';',1,7)LexToken(INTEGER,('INTEGER', 4),1,8)LexToken(COMMA,'; ',1,10)LexToken(INTEGER,('INTEGER', 5),1,12)LexToken(RBRACK,']',1,14)")
-        self.assertEqual(l.test('[15.5;16.6; 17.7;18.8 ; 19.9 ]'), "LexToken(LBRACK,'[',1,0)LexToken(FLOAT,('FLOAT', 15.5),1,1)LexToken(COMMA,';',1,5)LexToken(FLOAT,('FLOAT', 16.6),1,6)LexToken(COMMA,'; ',1,10)LexToken(FLOAT,('FLOAT', 17.7),1,12)LexToken(COMMA,';',1,16)LexToken(FLOAT,('FLOAT', 18.8),1,17)LexToken(COMMA,'; ',1,22)LexToken(FLOAT,('FLOAT', 19.9),1,24)LexToken(RBRACK,']',1,29)")
-        self.assertEqual(l.test('[Test.Node1;Test.Node2; Test.Node3;Test.Node4 ; Test.Node5 ]'), "LexToken(LBRACK,'[',1,0)LexToken(VARIABLE,('VARIABLE', 'Test.Node1'),1,1)LexToken(COMMA,';',1,11)LexToken(VARIABLE,('VARIABLE', 'Test.Node2'),1,12)LexToken(COMMA,'; ',1,22)LexToken(VARIABLE,('VARIABLE', 'Test.Node3'),1,24)LexToken(COMMA,';',1,34)LexToken(VARIABLE,('VARIABLE', 'Test.Node4'),1,35)LexToken(COMMA,'; ',1,46)LexToken(VARIABLE,('VARIABLE', 'Test.Node5'),1,48)LexToken(RBRACK,']',1,59)")
-        self.assertEqual(l.test('["constant1";"constant2"; "constant3";"constant4" ; "constant5" ]'), "LexToken(LBRACK,'[',1,0)LexToken(CONSTANT,('CONSTANT', 'constant1'),1,1)LexToken(COMMA,';',1,12)LexToken(CONSTANT,('CONSTANT', 'constant2'),1,13)LexToken(COMMA,'; ',1,24)LexToken(CONSTANT,('CONSTANT', 'constant3'),1,26)LexToken(COMMA,';',1,37)LexToken(CONSTANT,('CONSTANT', 'constant4'),1,38)LexToken(COMMA,'; ',1,50)LexToken(CONSTANT,('CONSTANT', 'constant5'),1,52)LexToken(RBRACK,']',1,64)")
-        self.assertEqual(l.test(''), "")
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/lib/pynspect/test_rules.py b/lib/pynspect/test_rules.py
deleted file mode 100644
index 6caf6f0e1c94f84a71f92e7df4559e2c96ab50d7..0000000000000000000000000000000000000000
--- a/lib/pynspect/test_rules.py
+++ /dev/null
@@ -1,354 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# This file is part of Mentat system (https://mentat.cesnet.cz/).
-#
-# Copyright (C) since 2011 CESNET, z.s.p.o (http://www.ces.net/)
-# Use of this source is governed by the MIT license, see LICENSE file.
-#-------------------------------------------------------------------------------
-
-import os
-import sys
-import shutil
-import unittest
-from pprint import pformat, pprint
-
-# Generate the path to custom 'lib' directory
-lib = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../lib'))
-sys.path.insert(0, lib)
-
-from pynspect.rules import *
-
-#-------------------------------------------------------------------------------
-# NOTE: Sorry for the long lines in this file. They are deliberate, because the
-# assertion permutations are (IMHO) more readable this way.
-#-------------------------------------------------------------------------------
-
-class TestMentatRules(unittest.TestCase):
-
-    def test_01_basic(self):
-        """
-        Perform basic rules tests: instantinate and check all rule objects.
-        """
-        self.maxDiff = None
-
-        rule_var = VariableRule("Test")
-        self.assertEqual(str(rule_var), "Test")
-        self.assertEqual(repr(rule_var), "VARIABLE('Test')")
-        rule_const = ConstantRule("constant")
-        self.assertEqual(str(rule_const), '"constant"')
-        self.assertEqual(repr(rule_const), "CONSTANT('constant')")
-        rule_ipv4 = IPV4Rule("127.0.0.1")
-        self.assertEqual(str(rule_ipv4), "127.0.0.1")
-        self.assertEqual(repr(rule_ipv4), "IPV4('127.0.0.1')")
-        rule_ipv6 = IPV6Rule("::1")
-        self.assertEqual(str(rule_ipv6), "::1")
-        self.assertEqual(repr(rule_ipv6), "IPV6('::1')")
-        rule_integer = IntegerRule(15)
-        self.assertEqual(str(rule_integer), "15")
-        self.assertEqual(repr(rule_integer), "INTEGER(15)")
-        rule_float = FloatRule(15.5)
-        self.assertEqual(str(rule_float), "15.5")
-        self.assertEqual(repr(rule_float), "FLOAT(15.5)")
-        rule_list = ListRule(VariableRule("Test"), ListRule(ConstantRule("constant"), ListRule(IPV4Rule("127.0.0.1"))))
-        self.assertEqual(str(rule_list), '[Test, "constant", 127.0.0.1]')
-        self.assertEqual(repr(rule_list), "LIST(VARIABLE('Test'), CONSTANT('constant'), IPV4('127.0.0.1'))")
-        self.assertEqual(str(rule_list.value), "[VARIABLE('Test'), CONSTANT('constant'), IPV4('127.0.0.1')]")
-        self.assertEqual(pformat(rule_list.value), "[VARIABLE('Test'), CONSTANT('constant'), IPV4('127.0.0.1')]")
-        rule_binop_l = LogicalBinOpRule("OP_OR", rule_var, rule_integer)
-        self.assertEqual(str(rule_binop_l), "(Test OP_OR 15)")
-        self.assertEqual(repr(rule_binop_l), "LOGBINOP(VARIABLE('Test') OP_OR INTEGER(15))")
-        rule_binop_c = ComparisonBinOpRule("OP_GT", rule_var, rule_integer)
-        self.assertEqual(str(rule_binop_c), "(Test OP_GT 15)")
-        self.assertEqual(repr(rule_binop_c), "COMPBINOP(VARIABLE('Test') OP_GT INTEGER(15))")
-        rule_binop_m = MathBinOpRule("OP_PLUS", rule_var, rule_integer)
-        self.assertEqual(str(rule_binop_m), "(Test OP_PLUS 15)")
-        self.assertEqual(repr(rule_binop_m), "MATHBINOP(VARIABLE('Test') OP_PLUS INTEGER(15))")
-        rule_binop = LogicalBinOpRule("OP_OR", ComparisonBinOpRule("OP_GT", MathBinOpRule("OP_PLUS", VariableRule("Test"), IntegerRule(10)), IntegerRule(20)), ComparisonBinOpRule("OP_LT", VariableRule("Test"), IntegerRule(5)))
-        self.assertEqual(str(rule_binop), "(((Test OP_PLUS 10) OP_GT 20) OP_OR (Test OP_LT 5))")
-        self.assertEqual(repr(rule_binop), "LOGBINOP(COMPBINOP(MATHBINOP(VARIABLE('Test') OP_PLUS INTEGER(10)) OP_GT INTEGER(20)) OP_OR COMPBINOP(VARIABLE('Test') OP_LT INTEGER(5)))")
-        rule_unop = UnaryOperationRule("OP_NOT", rule_var)
-        self.assertEqual(str(rule_unop), "(OP_NOT Test)")
-        self.assertEqual(repr(rule_unop), "UNOP(OP_NOT VARIABLE('Test'))")
-
-class TestMentatRuleTreeTraverser(unittest.TestCase):
-
-    def test_01_evaluate_binops_logical(self):
-        """
-        Test the logical binary operations evaluations.
-        """
-        self.maxDiff = None
-
-        traverser = RuleTreeTraverser()
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND', 1,    1),    True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND', 0,    1),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND', 1,    0),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND', 0,    0),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND', None, None), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND', 0,    None), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND', None, 0),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND', 0,    0),    False)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR', 1,    1),    True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR', 0,    1),    True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR', 1,    0),    True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR', 0,    0),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR', None, None), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR', 0,    None), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR', None, 0),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR', 0,    0),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR', 1,    None), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR', None, 1),    True)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR', 1,    1),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR', 0,    1),    True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR', 1,    0),    True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR', 0,    0),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR', None, None), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR', 0,    None), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR', None, 0),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR', 0,    0),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR', 1,    None), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR', None, 1),    True)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND', "True", "True"), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND', "",     "True"), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND', "True", ""),     False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND', "",     ""),     False)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR', "True", "True"), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR', "",     "True"), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR', "True", ""),     True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR', "",     ""),     False)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR', "True", "True"), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR', "",     "True"), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR', "True", ""),     True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR', "",     ""),     False)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND', [1,2], [1,2]), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND', [],    [1,2]), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND', [1,2], []),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND', [],    []),    False)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR', [1,2], [1,2]), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR', [],    [1,2]), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR', [1,2], []),    True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR', [],    []),    False)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR', [1,2], [1,2]), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR', [],    [1,2]), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR', [1,2], []),    True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR', [],    []),    False)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND', {"x":1}, {"x":1}), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND', {},      {"x":1}), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND', {"x":1}, {}),      False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND', {},      {}),      False)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR', {"x":1}, {"x":1}), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR', {},      {"x":1}), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR', {"x":1}, {}),      True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR', {},      {}),      False)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR', {"x":1}, {"x":1}), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR', {},      {"x":1}), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR', {"x":1}, {}),      True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR', {},      {}),      False)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND_P', 1,    1),    True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND_P', 0,    1),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND_P', 1,    0),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND_P', 0,    0),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND_P', None, None), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND_P', 0,    None), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND_P', None, 0),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND_P', 0,    0),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND_P', 1,    None), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND_P', None, 1),    False)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR_P', 1,    1),    True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR_P', 0,    1),    True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR_P', 1,    0),    True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR_P', 0,    0),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR_P', None, None), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR_P', 0,    None), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR_P', None, 0),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR_P', 0,    0),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR_P', 1,    None), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR_P', None, 1),    True)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR_P', 1,    1),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR_P', 0,    1),    True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR_P', 1,    0),    True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR_P', 0,    0),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR_P', None, None), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR_P', 0,    None), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR_P', None, 0),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR_P', 0,    0),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR_P', 1,    None), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR_P', None, 1),    True)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND_P', "True", "True"), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND_P', "",     "True"), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND_P', "True", ""),     False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND_P', "",     ""),     False)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR_P', "True", "True"), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR_P', "",     "True"), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR_P', "True", ""),     True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR_P', "",     ""),     False)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR_P', "True", "True"), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR_P', "",     "True"), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR_P', "True", ""),     True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR_P', "",     ""),     False)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND_P', [1,2], [1,2]), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND_P', [],    [1,2]), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND_P', [1,2], []),    False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND_P', [],    []),    False)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR_P', [1,2], [1,2]), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR_P', [],    [1,2]), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR_P', [1,2], []),    True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR_P', [],    []),    False)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR_P', [1,2], [1,2]), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR_P', [],    [1,2]), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR_P', [1,2], []),    True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR_P', [],    []),    False)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND_P', {"x":1}, {"x":1}), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND_P', {},      {"x":1}), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND_P', {"x":1}, {}),      False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_AND_P', {},      {}),      False)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR_P', {"x":1}, {"x":1}), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR_P', {},      {"x":1}), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR_P', {"x":1}, {}),      True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_OR_P', {},      {}),      False)
-
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR_P', {"x":1}, {"x":1}), False)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR_P', {},      {"x":1}), True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR_P', {"x":1}, {}),      True)
-        self.assertEqual(traverser.evaluate_binop_logical('OP_XOR_P', {},      {}),      False)
-
-    def test_02_evaluate_binops_comparison(self):
-        """
-        Test the comparison binary operations evaluations.
-        """
-        self.maxDiff = None
-
-        traverser = RuleTreeTraverser()
-
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_LIKE', 'abcd', 'a'), True)
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_LIKE', 'abcd', 'e'), False)
-
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_IN', 'a', ['a','b','c','d']), True)
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_IN', 'e', ['a','b','c','d']), False)
-
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_IS', ['a','b','c','d'], ['a','b','c','d']), True)
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_IS', ['a','b','c','e'], ['a','b','c','d']), False)
-
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_EQ', 'a', 'a'), True)
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_EQ', 'e', 'a'), False)
-
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_NE', 'e', 'a'), True)
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_NE', 'a', 'a'), False)
-
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_GT', 'ab', 'ab'), False)
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_GT', 'eb', 'ab'), True)
-
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_GE', 'eb', 'ab'), True)
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_GE', 'ab', 'ab'), True)
-
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_LT', 'ab', 'ab'), False)
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_LT', 'eb', 'ab'), False)
-
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_LE', 'eb', 'ab'), False)
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_LE', 'ab', 'ab'), True)
-
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_IN', 1, [1,2,3,4]), True)
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_IN', 5, [1,2,3,4]), False)
-
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_IS', 1, 1), True)
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_IS', 1, 5), False)
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_IS', "Test", "Test"),     True)
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_IS', "Test", ["Test"]),   True)
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_IS', ["Test"], ["Test"]), True)
-
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_EQ', 1, 1), True)
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_EQ', 2, 1), False)
-
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_NE', 2, 1), True)
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_NE', 1, 1), False)
-
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_GT', 1, 1), False)
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_GT', 2, 1), True)
-
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_GE', 1, 1), True)
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_GE', 1, 2), False)
-
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_LT', 1, 1), False)
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_LT', 1, 2), True)
-
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_LE', 2, 1), False)
-        self.assertEqual(traverser.evaluate_binop_comparison('OP_LE', 1, 2), True)
-
-    def test_03_evaluate_binops_math(self):
-        """
-        Test the mathematical binary operations evaluations.
-        """
-        self.maxDiff = None
-
-        traverser = RuleTreeTraverser()
-
-        self.assertEqual(traverser.evaluate_binop_math('OP_PLUS',   10, 10), 20)
-        self.assertEqual(traverser.evaluate_binop_math('OP_MINUS',  10, 10), 0)
-        self.assertEqual(traverser.evaluate_binop_math('OP_TIMES',  10, 10), 100)
-        self.assertEqual(traverser.evaluate_binop_math('OP_MODULO', 10,  3), 1)
-
-        self.assertEqual(traverser.evaluate_binop_math('OP_PLUS',   [10], [10]), 20)
-        self.assertEqual(traverser.evaluate_binop_math('OP_MINUS',  [10], [10]), 0)
-        self.assertEqual(traverser.evaluate_binop_math('OP_TIMES',  [10], [10]), 100)
-        self.assertEqual(traverser.evaluate_binop_math('OP_MODULO', [10],  [3]), 1)
-
-        self.assertEqual(traverser.evaluate_binop_math('OP_PLUS',   [10,20], [10]), [20,30])
-        self.assertEqual(traverser.evaluate_binop_math('OP_MINUS',  [10,20], [10]), [0,10])
-        self.assertEqual(traverser.evaluate_binop_math('OP_TIMES',  [10,20], [10]), [100,200])
-        self.assertEqual(traverser.evaluate_binop_math('OP_MODULO', [10,20],  [3]), [1,2])
-
-        self.assertEqual(traverser.evaluate_binop_math('OP_PLUS',   [10], [10,20]), [20,30])
-        self.assertEqual(traverser.evaluate_binop_math('OP_MINUS',  [10], [10,20]), [0,-10])
-        self.assertEqual(traverser.evaluate_binop_math('OP_TIMES',  [10], [10,20]), [100,200])
-        self.assertEqual(traverser.evaluate_binop_math('OP_MODULO', [10],   [3,4]), [1,2])
-
-class TestMentatPrintingTreeTraverser(unittest.TestCase):
-
-    def test_01_basic(self):
-        """
-        Demonstrate and test the PrintingTreeTraverser object.
-        """
-        self.maxDiff = None
-
-        traverser = PrintingTreeTraverser()
-
-        rule_binop_l = LogicalBinOpRule('OP_OR', VariableRule("Test"), IntegerRule(10))
-        self.assertEqual(rule_binop_l.traverse(traverser), 'LOGBINOP(OP_OR;VARIABLE(Test);INTEGER(10))')
-
-        rule_binop_c = ComparisonBinOpRule('OP_GT', VariableRule("Test"), IntegerRule(15))
-        self.assertEqual(rule_binop_c.traverse(traverser), 'COMPBINOP(OP_GT;VARIABLE(Test);INTEGER(15))')
-
-        rule_binop_m = MathBinOpRule('OP_PLUS', VariableRule("Test"), IntegerRule(10))
-        self.assertEqual(rule_binop_m.traverse(traverser), 'MATHBINOP(OP_PLUS;VARIABLE(Test);INTEGER(10))')
-
-        rule_binop = LogicalBinOpRule('OP_OR', ComparisonBinOpRule('OP_GT', MathBinOpRule('OP_PLUS', VariableRule("Test"), IntegerRule(10)), IntegerRule(20)), ComparisonBinOpRule('OP_LT', VariableRule("Test"), IntegerRule(5)))
-        self.assertEqual(rule_binop.traverse(traverser), 'LOGBINOP(OP_OR;COMPBINOP(OP_GT;MATHBINOP(OP_PLUS;VARIABLE(Test);INTEGER(10));INTEGER(20));COMPBINOP(OP_LT;VARIABLE(Test);INTEGER(5)))')
-
-        rule_unop = UnaryOperationRule('OP_NOT', VariableRule("Test"))
-        self.assertEqual(rule_unop.traverse(traverser), 'UNOP(OP_NOT;VARIABLE(Test))')
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/submodules/pynspect b/submodules/pynspect
new file mode 160000
index 0000000000000000000000000000000000000000..e8c7a578f25ea5a95412fdc7beed28a0603f54ab
--- /dev/null
+++ b/submodules/pynspect
@@ -0,0 +1 @@
+Subproject commit e8c7a578f25ea5a95412fdc7beed28a0603f54ab
diff --git a/submodules/pyzenkit b/submodules/pyzenkit
new file mode 160000
index 0000000000000000000000000000000000000000..c561146e512718d3e59b5bb3b52906393cad3293
--- /dev/null
+++ b/submodules/pyzenkit
@@ -0,0 +1 @@
+Subproject commit c561146e512718d3e59b5bb3b52906393cad3293