text: #!/usr/bin/env python3
  
  import collections
  import os
  import argparse
  import re
  import sys
  
  from textwrap import dedent, indent
  
  parser = argparse.ArgumentParser()
  parser.add_argument('config')
  parser.add_argument('config_path')
  parser.add_argument('out_path')
  parser.add_argument('--print_deps', action='store_true')
  parser.add_argument('--generate_files', action='store_true')
  args = parser.parse_args()
  
  pyconfig = args.config
  config_path = args.config_path
  out_path = args.out_path
  
  root_filename = os.path.join(out_path, 'root.cpp')
  
  def eval_config(path, globs):
      file0 = open(path, 'r')
      content = file0.read()
      file0.flush()
      file0.close()
      exec(content, globs)
  
  def config_match(pattern):
      textfile = open(config_path, 'r')
      filetext = textfile.read()
      textfile.close()
      pattern = re.compile(pattern)
      for line in filetext.splitlines():
          for match in re.finditer(pattern, line):
              return True
      return False
  
  AlgorithmConfig = collections.namedtuple(
      'AlgorithmConfig', ['name', 'header', 'sub'])
  AlgorithmConfig.__new__.__defaults__ = ('', '', [])
  
  class RegistryConfig:
      compressors = []
      decompressors = []
      generators  = []
  
      def get_kinds(self):
          return [
              ("Compressor", self.compressors),
              ("Decompressor", self.decompressors),
              ("Generator",  self.generators),
          ]
  
  def code(s, i = 0, r = {}):
      s = indent(dedent(s), '    ' * i)
      for key in r:
          s = s.replace(key, r[key])
      return s
  
  class Code:
      def __init__(self, file):
          self.file = file
      def code(self, s, i = 0, r = {}):
          self.file.write(code(s, i, r) + '\n')
      def emptyline(self):
          self.file.write('\n')
  
  autogen_disclaimer = '''
      /* Autogenerated file by genregistry.py */
  '''
  
  tdc = RegistryConfig()
  eval_config(pyconfig, {
      "config_match": config_match,
      "AlgorithmConfig": AlgorithmConfig,
      "tdc": tdc
  })
  
  def cpp_template_instance(instance):
      str = ""
  
      level = 0
      level_num = [1]
      level_rank = [0]
  
      for (name, nsubs) in instance:
          if level_rank[level] > 0:
              str += ", "
  
          str += name
          level_rank[level] += 1
  
          if nsubs > 0:
              str += '<'
              level += 1
              level_num.append(nsubs)
              level_rank.append(0)
  
          while level > 0 and level_rank[level] >= level_num[level]:
              del level_rank[level]
              del level_num[level]
              level -= 1
              str += '>'
  
      return str
  
  def dfs(algo, f, q = [], instance = [], headers = []):
      (name, header, sub) = algo
  
      instance_ = instance + [(name,len(sub))]
      headers_  = headers + [header]
  
      q_ = sub + q
      if len(q_) > 0:
          next = q_[0]
          q_ = q_[1:]
          for sub_algo in next:
              dfs(sub_algo, f, q_, instance_, headers_)
  
      else:
          # reached a leaf
          f(instance_, headers_)
  
  def print_instance(instance, headers):
      print(cpp_template_instance(instance))
  
  
  #######################
  ### Instance groups ###
  #######################
  groups = []
  max_groupsize = 100;
  
  for (classname, algos) in tdc.get_kinds():
      class_groups = []
  
      for algo in algos:
          (name, _, _) = algo
  
          group_name = name.replace(':', '_')
          group_rank = 1
          group_count = 0
          group_headers = []
          group_instances = []
  
          def write_group():
              global class_groups
  
              class_groups.append(group_name)
  
              if args.generate_files:
                  group_headers.sort()
  
                  with open(os.path.join(out_path, group_name + '.cpp'), 'w') as f:
                      cpp = Code(f)
                      cpp.code(autogen_disclaimer)
                      cpp.code('#include <tudocomp_driver/Registry.hpp>')
                      for h in group_headers:
                          cpp.code('#include <tudocomp/' + h + '>')
  
                      cpp.emptyline()
                      cpp.code('namespace tdc_algorithms {')
                      cpp.emptyline()
                      cpp.code('using namespace tdc;')
                      cpp.emptyline()
                      cpp.code('void register_$GROUP(RegistryOf<$TYPE>& r) {',
                          0, { '$GROUP': group_name, '$TYPE': classname})
  
                      for i in group_instances:
                          cpp.code('r.register_algorithm<$INSTANCE>();',
                              1, { '$INSTANCE': i })
  
                      cpp.code('}')
                      cpp.emptyline()
                      cpp.code('} // namespace')
  
          def process_instance(instance, headers):
              global group_name, group_rank, group_count, group_headers, group_instances
  
              group_headers = list(set(group_headers + headers))
              group_instances.append(cpp_template_instance(instance))
              group_count += 1
  
              if group_count > max_groupsize:
                  write_group()
                  group_headers = []
                  group_instances = []
                  group_count = 0
                  group_rank += 1
                  group_name = name + '_' + str(group_rank)
  
          dfs(algo, process_instance)
  
          if group_count > 0:
              write_group()
  
      groups.append((classname, class_groups))
  
  ################
  ### root.cpp ###
  ################
  if args.generate_files:
      with open(root_filename, 'w') as f:
          root = Code(f)
          root.code(autogen_disclaimer)
          root.code('#include <tudocomp_driver/Registry.hpp>')
          root.emptyline()
          root.code('namespace tdc_algorithms {')
          root.emptyline()
          root.code('using namespace tdc;')
          root.emptyline()
          root.code('// forward declarations')
          for (classname, class_groups) in groups:
              for group_name in class_groups:
                  root.code('void register_$GROUP(RegistryOf<$TYPE>&);',
                      0, { '$GROUP': group_name, '$TYPE': classname })
          root.emptyline()
          root.code('void register_algorithms() {', 0)
          for (classname, class_groups) in groups:
              root.code('// $TYPEs', 1, { '$TYPE': classname })
              root.code('{', 1)
              if len(class_groups) > 0:
                  root.code('auto& r = Registry::of<$TYPE>();',
                      2, { '$TYPE': classname })
  
              for group_name in class_groups:
                  root.code('register_$GROUP(r);', 2, { '$GROUP': group_name })
  
              root.code('}', 1)
          root.code('}')
          root.emptyline()
          root.code('} //namespace')
  
  if args.print_deps:
      files = [root_filename]
      for (_, class_groups) in groups:
          for group_name in class_groups:
              files.append(os.path.join(out_path, group_name + '.cpp'))
  
      sys.stdout.write(';'.join(files))
      sys.stdout.flush()
  
  os.sync()
  