diff --git a/CMake/CMakeLists_executable.txt b/CMake/CMakeLists_bin.txt
similarity index 96%
rename from CMake/CMakeLists_executable.txt
rename to CMake/CMakeLists_bin.txt
index a20192301a78f7598233639e0ab89363c5c0e304..d67320e18c3cc94d01350cc453b127bd5663679d 100644
--- a/CMake/CMakeLists_executable.txt
+++ b/CMake/CMakeLists_bin.txt
@@ -3,7 +3,8 @@ project({project_name})
 set(PROJECT_NAME {project_name})
 
 set(SOURCE_FILES
-{source_files}        )
+        {source_files}
+        )
 
 # Add executable target
 add_executable(${{PROJECT_NAME}} ${{SOURCE_FILES}})
diff --git a/CMake/CMakeLists_library.txt b/CMake/CMakeLists_lib.txt
similarity index 96%
rename from CMake/CMakeLists_library.txt
rename to CMake/CMakeLists_lib.txt
index dbb5f946f00d9f1d7211eb56c7615d36e6e129a4..af1942d54368c53f8acb200421138e0639b118ac 100644
--- a/CMake/CMakeLists_library.txt
+++ b/CMake/CMakeLists_lib.txt
@@ -5,7 +5,8 @@ set(PROJECT_NAME {project_name})
 {cmake_options}
 
 set(SOURCE_FILES
-{source_files}        )
+        {source_files}
+        )
 
 # Add library target
 add_library(${{PROJECT_NAME}} SHARED ${{SOURCE_FILES}})
@@ -43,7 +44,8 @@ set(CMAKE_AUTOUIC OFF)
 set(PROJECT_NAME_TEST test-{project_name})
 
 set(SOURCE_FILES_TEST
-{source_files_test}        )
+        {source_files_test}
+        )
 
 add_executable(${{PROJECT_NAME_TEST}} ${{SOURCE_FILES_TEST}})
 set_target_properties(${{PROJECT_NAME_TEST}} PROPERTIES
diff --git a/CMake/CMakeLists_root.txt b/CMake/CMakeLists_root.txt
index 3ea58e86b30cff9e7eda75ad8cdbef3ad83eeab5..660769cdc8cd1ffa74eede9fe42b79f5486f3e42 100644
--- a/CMake/CMakeLists_root.txt
+++ b/CMake/CMakeLists_root.txt
@@ -109,3 +109,6 @@ endforeach ()
 #  - enable
 enable_testing()
 # include(CTest) # we do not need full CTest support now
+
+# register bash tests
+# TODO
diff --git a/CMake/__init__.py b/CMake/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/CMake/__main__.py b/CMake/__main__.py
deleted file mode 100644
index 2141f99ff51f476a383fd5e33a8da9088a6ff149..0000000000000000000000000000000000000000
--- a/CMake/__main__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from alib_cmake import main
-
-main()
diff --git a/CMake/alib_cmake.py b/CMake/alib_cmake.py
deleted file mode 100755
index f1c039d60329916b643a1382d7d968905f1f2e77..0000000000000000000000000000000000000000
--- a/CMake/alib_cmake.py
+++ /dev/null
@@ -1,280 +0,0 @@
-#!/usr/bin/env python3
-
-import os
-import sys
-
-import click
-
-alib_path = '..'
-
-alib_modules_lib = [
-    # Libraries
-    'alib2algo',
-    'alib2algo_experimental',
-    'alib2aux',
-    'alib2cli',
-    'alib2common',
-    'alib2data',
-    'alib2data_experimental',
-    'alib2elgo',
-    'alib2measure',
-    'alib2raw',
-    'alib2raw_cli_integration',
-    'alib2std',
-    'alib2str',
-    'alib2str_cli_integration',
-    'alib2xml',
-    'alib2abstraction',
-    'alib2dummy',
-    'alib2graph_data',
-    'alib2graph_algo',
-    'alib2gui',
-]
-
-alib_modules_exe = [
-    # Binaries
-    'aaccess2',
-    'aarbology2',
-    'acast2',
-    'acompaction2',
-    'acompare2',
-    'aconversions2',
-    'aconvert2',
-    'aderivation2',
-    'adeterminize2',
-    'aecho2',
-    'aepsilon2',
-    'agenerate2',
-    'aintegral2',
-    'aintrospection2',
-    'alangop2',
-    'aminimize2',
-    'anormalize2',
-    'aql2',
-    'aquery2',
-    'arand2',
-    'araw2',
-    'arename2',
-    'areverse2',
-    'arun2',
-    'astat2',
-    'astringology2',
-    'atrim2',
-    'tniceprint',
-    'agui2',
-]
-
-root_template = open(os.path.join(os.path.dirname(__file__), "CMakeLists_root.txt"), 'r').read()
-library_template = open(os.path.join(os.path.dirname(__file__), "CMakeLists_library.txt"), 'r').read()
-executable_template = open(os.path.join(os.path.dirname(__file__), "CMakeLists_executable.txt"), 'r').read()
-
-separate_size = 20
-
-
-# ----------------------------------------------------------------------------------------------------------------------
-
-def create_root_cmakelist(debug):
-    alib_modules_exe_str = '\n        '.join(alib_modules_exe)
-    alib_modules_lib_str = '\n        '.join(alib_modules_lib)
-
-    if not debug:
-        print('CMakeLists.txt')
-        print('-' * separate_size)
-        print(root_template.format(
-            alib_modules_lib=alib_modules_lib_str,
-            alib_modules_exe=alib_modules_exe_str
-        ))
-        print('-' * separate_size)
-    else:
-        with open(f'{alib_path}/CMakeLists.txt', 'w') as file:
-            file.write(root_template.format(
-                alib_modules_lib=alib_modules_lib_str,
-                alib_modules_exe=alib_modules_exe_str
-            ))
-
-
-# ----------------------------------------------------------------------------------------------------------------------
-
-def get_source_files(project_name, directory):
-    # Recursively find all source file(s) and directories
-    source_files = []
-    for dp, dn, fn in os.walk(f'{alib_path}/{project_name}/{directory}'):
-        for file in fn:
-            if file.endswith(".cxx") or file.endswith(".hxx") or file.endswith(".txx"):
-                continue
-            source_files.append(os.path.join(dp, file))
-
-    # Create strings from lists for file(s)
-    source_files_str = ""
-    for file in source_files:
-        source_files_str += f'        {file[len(alib_path)+1+len(project_name)+1:]}\n'
-
-    return source_files_str
-
-
-# ----------------------------------------------------------------------------------------------------------------------
-def parse_makeconfig(file):
-    link_libs = []
-    link_test_libs = []
-    include_libs = []
-    include_test_libs = []
-    cmake_options = []
-
-    # Read and parse makefile.conf
-    with open(file, 'r') as file:
-        for line in file:
-            if line.startswith('LINK_LIBRARIES'):
-                link_libs = line[len('LINK_LIBRARIES='):].split()
-            elif line.startswith('SYSTEM_LIBRARIES'):
-                sys_libs = line[len('SYSTEM_LIBRARIES='):].split()
-                for lib in sys_libs:
-                    if lib == 'xml2':
-                        link_libs.append('${LIBXML2_LIBRARIES}')
-                        include_libs.append('PUBLIC ${LIBXML2_INCLUDE_DIR}')
-                    elif lib == 'threads':
-                        link_libs.append('${CMAKE_THREAD_LIBS_INIT}')
-                    elif lib == 'Qt5Widgets':
-                        link_libs.append('Qt5::Widgets')
-                        include_libs.append('${Qt5Widgets_INCLUDE_DIRS}')
-                    elif lib == 'Qt5Xml':
-                        link_libs.append('Qt5::Xml')
-                        include_libs.append('${Qt5Xml_INCLUDE_DIRS}')
-                    elif lib == 'graphviz':
-                        link_libs.append('${GRAPHVIZ_GVC_LIBRARY}')
-                        link_libs.append('${GRAPHVIZ_CGRAPH_LIBRARY}')
-                        include_libs.append('${GRAPHVIZ_INCLUDE_DIR}')
-                    elif lib == 'json':
-                        link_libs.append('${JSONCPP_LIBRARIES}')
-                        include_libs.append('${jsoncpp_INCLUDE_DIRS}')
-                    else:
-                        link_libs.append(lib)
-            elif line.startswith('TEST_LINK_LIBRARIES'):
-                link_test_libs = line[len('TEST_LINK_LIBRARIES='):].split()
-            elif line.startswith('TEST_SYSTEM_LIBRARIES'):
-                sys_libs = line[len('TEST_SYSTEM_LIBRARIES='):].split()
-                for lib in sys_libs:
-                    link_test_libs.append()
-            elif line.startswith('AUTOMOC=ON'):
-                cmake_options.append('set(CMAKE_AUTOMOC ON)')
-            elif line.startswith('AUTOUIC=ON'):
-                cmake_options.append('set(CMAKE_AUTOUIC ON)')
-
-    return ' '.join(link_libs), ' '.join(link_test_libs), ' '.join(include_libs), ' '.join(include_test_libs), '\n'.join(cmake_options)
-
-
-# ----------------------------------------------------------------------------------------------------------------------
-
-def create_library_package(project_name, write):
-    # Parse config file
-    link_libs, link_test_libs, include_libs, include_test_libs, cmake_options = parse_makeconfig(f'{alib_path}/{project_name}/makefile.conf')
-
-    # Get source files
-    source_files = get_source_files(project_name, 'src')
-
-    # Get source files test
-    source_files_test = get_source_files(project_name, 'test-src')
-
-    if not write:
-        print(f'{project_name}/CMakeLists.txt')
-        print('-' * separate_size)
-        print(library_template.format(
-            project_name=project_name,
-            target_libs=link_libs,
-            target_test_libs=link_test_libs,
-            include_paths=include_libs,
-            include_test_paths=include_test_libs,
-            cmake_options=cmake_options,
-            source_files=source_files,
-            source_files_test=source_files_test,
-        ))
-        print('-' * separate_size)
-    else:
-        with open(f'{alib_path}/{project_name}/CMakeLists.txt', 'w') as file:
-            file.write(library_template.format(
-                project_name=project_name,
-                target_libs=link_libs,
-                target_test_libs=link_test_libs,
-                include_paths=include_libs,
-                include_test_paths=include_test_libs,
-                cmake_options=cmake_options,
-                source_files=source_files,
-                source_files_test=source_files_test,
-            ))
-
-
-# ----------------------------------------------------------------------------------------------------------------------
-
-def create_executable_package(project_name, write):
-    # Parse config file
-    link_libs, link_test_libs, include_libs, include_test_libs, cmake_options = parse_makeconfig(f'{alib_path}/{project_name}/makefile.conf')
-
-    # Get source files
-    source_files = get_source_files(project_name, 'src')
-
-    if not write:
-        print(f'{project_name}/CMakeLists.txt')
-        print('-' * separate_size)
-        print(executable_template.format(
-            project_name=project_name,
-            target_libs=link_libs,
-            target_test_libs=link_test_libs,
-            include_paths=include_libs,
-            include_test_paths=include_test_libs,
-            cmake_options=cmake_options,
-            source_files=source_files,
-        ))
-        print('-' * separate_size)
-    else:
-        with open(f'{alib_path}/{project_name}/CMakeLists.txt', 'w') as file:
-            file.write(executable_template.format(
-                project_name=project_name,
-                target_libs=link_libs,
-                target_test_libs=link_test_libs,
-                include_paths=include_libs,
-                include_test_paths=include_test_libs,
-                cmake_options=cmake_options,
-                source_files=source_files,
-            ))
-
-
-# ----------------------------------------------------------------------------------------------------------------------
-
-@click.command()
-@click.option('--packages', '-p', help='Specify packages', default=alib_modules_lib + alib_modules_exe, multiple=True)
-@click.option('--write', '-w', help='Write output to file(s).', is_flag=True, default=False)
-@click.option('--main_file', '-m', help='Generate also main CMakeLists.txt', is_flag=True, default=False)
-def main(write, main_file, packages):
-    packages_cnt = len(packages)
-    libs_cnt = len(list(set(packages) & set(alib_modules_lib)))
-    exes_cnt = len(list(set(packages) & set(alib_modules_exe)))
-
-    # Increment counter
-    if main_file:
-        packages_cnt += 1
-
-    # Generate for library package
-    for i, package in enumerate(list(set(packages) & set(alib_modules_lib)), 1):
-        try:
-            create_library_package(package, write)
-            print(f'[{i}/{packages_cnt}] Generated library package {package}', file=sys.stderr)
-        except FileNotFoundError as e:
-            print(f'[{i}/{packages_cnt}] Skipping library package {package}: {e}', file=sys.stderr)
-
-    # Generate for executable package
-    for i, package in enumerate(list(set(packages) & set(alib_modules_exe)), libs_cnt + 1):
-        try:
-            create_executable_package(package, write)
-            print(f'[{i}/{packages_cnt}] Generated executable package {package}', file=sys.stderr)
-        except FileNotFoundError as e:
-            print(f'[{i}/{packages_cnt}] Skipping executable package {package}: {e}', file=sys.stderr)
-
-    # Generate root file
-    if main_file:
-        create_root_cmakelist(write)
-        print(f'[{packages_cnt}/{packages_cnt}] Generated main CMakeLists.txt', file=sys.stderr)
-
-
-# ----------------------------------------------------------------------------------------------------------------------
-
-if __name__ == '__main__':
-    main()
diff --git a/CMake/config.ini b/CMake/config.ini
new file mode 100644
index 0000000000000000000000000000000000000000..4d15d9b95d0b925d112ece08e3f71bbe087d2dd5
--- /dev/null
+++ b/CMake/config.ini
@@ -0,0 +1,57 @@
+[Templates]
+root = CMakeLists_root.txt
+lib  = CMakeLists_lib.txt
+bin  = CMakeLists_bin.txt
+
+[ModulesLib]
+alib2algo
+alib2algo_experimental
+alib2aux
+alib2cli
+alib2common
+alib2data
+alib2data_experimental
+alib2elgo
+alib2measure
+alib2raw
+alib2raw_cli_integration
+alib2std
+alib2str
+alib2str_cli_integration
+alib2xml
+alib2abstraction
+alib2dummy
+alib2graph_data
+alib2graph_algo
+alib2gui
+
+[ModulesBin]
+aaccess2
+aarbology2
+acast2
+acompaction2
+acompare2
+aconversions2
+aconvert2
+aderivation2
+adeterminize2
+aecho2
+aepsilon2
+agenerate2
+aintegral2
+aintrospection2
+alangop2
+aminimize2
+anormalize2
+aql2
+aquery2
+arand2
+araw2
+arename2
+areverse2
+arun2
+astat2
+astringology2
+atrim2
+tniceprint
+agui2
diff --git a/CMake/generate.py b/CMake/generate.py
new file mode 100755
index 0000000000000000000000000000000000000000..6ec1dd79332cf6c825d6b02efea5fa73839c2662
--- /dev/null
+++ b/CMake/generate.py
@@ -0,0 +1,227 @@
+#!/usr/bin/env python3
+
+import os
+import sys
+import configparser
+import argparse
+
+# ----------------------------------------------------------------------------------------------------------------------
+
+
+class SmartOpen:
+    def __init__(self, filename, mode='r', *, directory='.', encoding=None, dry_run=False):
+        self._dry_run = dry_run
+        self._filename = filename
+        self._mode = mode
+
+        if self._dry_run:
+            self._fd = sys.stdout
+        else:
+            self._fd = open(os.path.join(directory, filename), mode=mode, encoding=encoding)
+
+    def read(self):
+        return self._fd.read()
+
+    def __enter__(self):
+        if self._dry_run and 'w' in self._mode:
+            self._fd.write(f'{self._filename}\n' + '-' * SEPARATOR_SIZE + '\n')
+
+        return self._fd
+
+    def __exit__(self, d_type, value, traceback):
+        if self._dry_run and 'w' in self._mode:
+            self._fd.write('-' * SEPARATOR_SIZE + '\n')
+
+        if self._fd != sys.stdout:
+            self._fd.close()
+
+# ----------------------------------------------------------------------------------------------------------------------
+
+
+SCRIPT_DIRECTORY = os.path.join(os.path.dirname(__file__))
+ALIB_DIRECTORY = os.path.join(SCRIPT_DIRECTORY, "..")
+SEPARATOR_SIZE = 20
+
+SOURCES_EXCLUDE = ('.cxx', '.hxx', '.txx')
+
+config = configparser.ConfigParser(allow_no_value=True)
+config.read(os.path.join(SCRIPT_DIRECTORY, 'config.ini'))
+
+# ----------------------------------------------------------------------------------------------------------------------
+
+
+PACKAGES = {
+    'lib': {key for key in config['ModulesLib']},
+    'bin': {key for key in config['ModulesBin']},
+}
+
+TEMPLATES = {
+    tpl: SmartOpen(config['Templates'][tpl], directory=SCRIPT_DIRECTORY, mode='r').read() for tpl in ['root', 'lib', 'bin']
+}
+
+
+# ----------------------------------------------------------------------------------------------------------------------
+
+
+def to_rows(lst, sep='\n        '):
+    return sep.join(lst)
+
+# ----------------------------------------------------------------------------------------------------------------------
+
+
+def get_source_files(project_name, directory):
+    # Recursively find all source file(s) and directories
+    source_files = []
+    relpath_ref = os.path.join(ALIB_DIRECTORY, project_name)
+
+    for dp, dn, fn in os.walk(os.path.join(relpath_ref, directory)):
+        source_files = source_files + [os.path.relpath(os.path.join(dp, file), relpath_ref)
+                                       for file in fn if not file.endswith(SOURCES_EXCLUDE)]
+
+    return source_files
+
+# ----------------------------------------------------------------------------------------------------------------------
+
+
+def parse_makeconfig(file):
+    link_libs = []
+    link_test_libs = []
+    include_libs = []
+    include_test_libs = []
+    cmake_options = []
+
+    # Read and parse makefile.conf
+    with open(file, 'r') as file:
+        for line in file:
+            if line.startswith('LINK_LIBRARIES'):
+                link_libs = line[len('LINK_LIBRARIES='):].split()
+            elif line.startswith('SYSTEM_LIBRARIES'):
+                sys_libs = line[len('SYSTEM_LIBRARIES='):].split()
+                for lib in sys_libs:
+                    if lib == 'xml2':
+                        link_libs.append('${LIBXML2_LIBRARIES}')
+                        include_libs.append('PUBLIC ${LIBXML2_INCLUDE_DIR}')
+                    elif lib == 'threads':
+                        link_libs.append('${CMAKE_THREAD_LIBS_INIT}')
+                    elif lib == 'Qt5Widgets':
+                        link_libs.append('Qt5::Widgets')
+                        include_libs.append('${Qt5Widgets_INCLUDE_DIRS}')
+                    elif lib == 'Qt5Xml':
+                        link_libs.append('Qt5::Xml')
+                        include_libs.append('${Qt5Xml_INCLUDE_DIRS}')
+                    elif lib == 'graphviz':
+                        link_libs.append('${GRAPHVIZ_GVC_LIBRARY}')
+                        link_libs.append('${GRAPHVIZ_CGRAPH_LIBRARY}')
+                        include_libs.append('${GRAPHVIZ_INCLUDE_DIR}')
+                    elif lib == 'json':
+                        link_libs.append('${JSONCPP_LIBRARIES}')
+                        include_libs.append('${jsoncpp_INCLUDE_DIRS}')
+                    else:
+                        link_libs.append(lib)
+            elif line.startswith('TEST_LINK_LIBRARIES'):
+                link_test_libs = line[len('TEST_LINK_LIBRARIES='):].split()
+            elif line.startswith('TEST_SYSTEM_LIBRARIES'):
+                sys_libs = line[len('TEST_SYSTEM_LIBRARIES='):].split()
+                for lib in sys_libs:
+                    link_test_libs.append()
+            elif line.startswith('AUTOMOC=ON'):
+                cmake_options.append('set(CMAKE_AUTOMOC ON)')
+            elif line.startswith('AUTOUIC=ON'):
+                cmake_options.append('set(CMAKE_AUTOUIC ON)')
+
+    return ' '.join(link_libs), ' '.join(link_test_libs), ' '.join(include_libs), ' '.join(
+        include_test_libs), '\n'.join(cmake_options)
+
+# ----------------------------------------------------------------------------------------------------------------------
+
+
+def create_root_cmakelist(dry_run, packages):
+    with SmartOpen("CMakeLists.txt", 'w', directory=ALIB_DIRECTORY, dry_run=dry_run) as f:
+        f.write(TEMPLATES['root'].format(
+            alib_modules_lib=to_rows(packages['library']),
+            alib_modules_exe=to_rows(packages['executable']),
+        ))
+
+        if dry_run:
+            f.write('-' * SEPARATOR_SIZE + '\n')
+
+
+def create_library_package(project_name, dry_run):
+    link_libs, link_test_libs, include_libs, include_test_libs, cmake_options = parse_makeconfig(
+        os.path.join(ALIB_DIRECTORY, project_name, 'makefile.conf'))
+
+    with SmartOpen("CMakeLists.txt", 'w', directory=os.path.join(ALIB_DIRECTORY, project_name), dry_run=dry_run) as f:
+        f.write(TEMPLATES['lib'].format(
+            project_name=project_name,
+            target_libs=link_libs,
+            target_test_libs=link_test_libs,
+            include_paths=include_libs,
+            include_test_paths=include_test_libs,
+            cmake_options=cmake_options,
+            source_files=to_rows(get_source_files(project_name, 'src')),
+            source_files_test=to_rows(get_source_files(project_name, 'test-src')),
+        ))
+
+
+def create_executable_package(project_name, dry_run):
+    link_libs, link_test_libs, include_libs, include_test_libs, cmake_options = parse_makeconfig(
+        os.path.join(ALIB_DIRECTORY, project_name, 'makefile.conf'))
+
+    with SmartOpen("CMakeLists.txt", 'w', directory=os.path.join(ALIB_DIRECTORY, project_name), dry_run=dry_run) as f:
+        f.write(TEMPLATES['bin'].format(
+            project_name=project_name,
+            target_libs=link_libs,
+            target_test_libs=link_test_libs,
+            include_paths=include_libs,
+            include_test_paths=include_test_libs,
+            cmake_options=cmake_options,
+            source_files=to_rows(get_source_files(project_name, 'src')),
+        ))
+
+# ----------------------------------------------------------------------------------------------------------------------
+
+
+def main(dry_run, main_file, packages):
+    packages = {
+        'library': set(packages) & PACKAGES['lib'],
+        'executable': set(packages) & PACKAGES['bin'],
+    }
+
+    packages_functions = {
+        'library':    create_library_package,
+        'executable': create_executable_package,
+    }
+
+    packages_cnt = sum([len(v) for k, v in packages.items()]) + (main_file is True)
+
+    i = 1
+    for type, packages_set in packages.items():
+        for package in packages_set:
+            try:
+                packages_functions[type](package, dry_run)
+                print(f'[{i}/{packages_cnt}] Generated {type} package {package}', file=sys.stderr)
+            except FileNotFoundError as e:
+                print(f'[{i}/{packages_cnt}] Skipping library package {package}: {e}', file=sys.stderr)
+
+            i += 1
+
+    # Generate root file
+    if main_file:
+        create_root_cmakelist(dry_run, packages)
+        print(f'[{i}/{packages_cnt}] Generated main CMakeLists.txt', file=sys.stderr)
+
+# ----------------------------------------------------------------------------------------------------------------------
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(description='CMakeLists generator for Algorithms Library Toolkit')
+    parser.add_argument('-w', '--write', action='store_true', default=False, help="Write files")
+    parser.add_argument('-m', '--main', action='store_true', default=False, help="Generate main CMakeLists.txt")
+    parser.add_argument('-p', '--packages', action='append', help='Specify packages to build. For multiple packages, use -p multiple times')
+
+    args = parser.parse_args()
+
+    if args.packages is None:
+        args.packages = list(PACKAGES['lib'] | PACKAGES['bin'])
+
+    main(not args.write, args.main, args.packages)
diff --git a/CMake/requirements.txt b/CMake/requirements.txt
deleted file mode 100644
index 3af12f5c89c416fd8c1820c52d7f870c42414e50..0000000000000000000000000000000000000000
--- a/CMake/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-click==6.7