#!/usr/bin/env python3 ###############################################################################/ # Copyright (c) 2021 Nerian Vision GmbH # # 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. ###############################################################################/ # # This helper script auto-generates adapters for all current # Nerian stereo device parameters directly from the C++ header file. # import pathlib import sys import os class Generator(object): def __init__(self): self.pxdcode = \ ''' # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # !! CAUTION !! # !! !! # !! This file is autogenerated from the libvisiontransfer headers !! # !! using autogen.py - manual changes are not permanent! !! # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! cdef extern from "visiontransfer/deviceparameters.h" namespace "visiontransfer": cdef cppclass DeviceParameters: DeviceParameters(const DeviceInfo &) except +'''.split('\n') self.pyxcode = \ '''# distutils: language=c++ # cython: language_level=3 # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # !! CAUTION !! # !! !! # !! This file is autogenerated from the libvisiontransfer headers !! # !! using autogen.py - manual changes are not permanent! !! # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from libcpp.string cimport string from libcpp.vector cimport vector from libcpp cimport bool from cython cimport view cdef class DeviceParameters: '''.split('\n') self.pyxcode2 = \ ''' cdef cpp.DeviceParameters* c_obj def __cinit__(self, DeviceInfo device_info): self.c_obj = new cpp.DeviceParameters(device_info.c_obj) def __dealloc__(self): del self.c_obj '''.split('\n') def add_pxd(self, ret, fnname, argstr): args = [p.strip().split() for p in argstr.split(',')] # remove default arguments in pxd (present in pyx) for a in args: if len(a)>1: a[1] = a[1].split('=')[0] self.pxdcode.append(' '*8 + ret + ' ' + fnname + ' ('+(', '.join((a[0]+' '+a[1]) for a in args if len(a)>1))+') except +') def add_pyx(self, ret, fnname, argstr, comment): # Generate function name reference also used by doc extractor args_just_names = [(a.split('=')[0].strip().split()[-1] if a.strip()!='' else '') for a in argstr.split(',')] currentname = 'visiontransfer::DeviceParameters::' + fnname + '(' + (', '.join(args_just_names)) + ')' fnname_snake = self.snake_case(fnname) args = [p.strip().split() for p in argstr.split(',')] for i in range(len(args)): if len(args[i])>0: if args[i][0] in ['int', 'float', 'double', 'bool', 'int&', 'float&', 'double&', 'bool&']: pass else: args[i][0] = "cpp." + str(args[i][0]) if fnname.startswith('set'): argstr = ', '.join(' '.join(a) for a in args if len(a)>0) self.pyxcode.append(' '*4 + 'def '+ fnname_snake + '(self' + (', ' if len(argstr) else '') + argstr + '):') self.pyxcode.append(' '*8 + '_SUBSTITUTE_DOCSTRING_FOR_("' + currentname + '")') self.pyxcode.append(' '*8 + 'self.c_obj.'+ fnname + '(' + ', '.join(a[1].split('=')[0] for a in args if len(a)>1) + ')') self.pyxcode.append(' '*0) # extra newline to visually separate blocks pass else: argstr = '' #', '.join(' '.join(a) for a in args if len(a)>0) newargstr_defaults = ', '.join(a[1] for a in args if len(a)>0) newargstr_nodefaults = ', '.join(a[1].split('=')[0] for a in args if len(a)>0) if all(' '.join(a).find('&')<0 for a in args): #len(args)==0 or len(args[0])==0: if ret in ['int', 'float', 'double', 'bool', 'int&', 'float&', 'double&', 'bool&']: ret = '' ret_post = '' else: ret += '(' ret_post = ')' self.pyxcode.append(' '*4 + 'def '+ fnname_snake + '(self' + (', ' if len(newargstr_defaults) else '') + newargstr_defaults + '):') self.pyxcode.append(' '*8 + '_SUBSTITUTE_DOCSTRING_FOR_("' + currentname + '")') self.pyxcode.append(' '*8 + 'return '+ret+'self.c_obj.'+ fnname + '(' + newargstr_nodefaults + ')' + ret_post) else: self.pyxcode.append(' '*4 + 'def '+ fnname_snake + '(self' + (', ' if len(argstr) else '') + argstr + '):') self.pyxcode.append(' '*8 + '_SUBSTITUTE_DOCSTRING_FOR_("' + currentname + '")') for a in args: rawtype = a[0].replace('&', '') var = a[1] if a[1].find('=')>0 else (a[1]+' = 0') self.pyxcode.append(' '*8 + 'cdef '+rawtype+' '+var) self.pyxcode.append(' '*8 + 'self.c_obj.'+ fnname + '(' + newargstr_nodefaults + ')') self.pyxcode.append(' '*8 + 'return '+newargstr_nodefaults) self.pyxcode.append(' '*0) # extra newline to visually separate blocks def snake_case(self, fnname): '''Convert mixed case to Python methods' snake case''' fnname_snake = '' for c in fnname: if c.isupper(): fnname_snake += '_' + c.lower() else: fnname_snake += c # Some conventional exceptions :) fnname_snake = fnname_snake.replace('r_o_i', 'roi') return fnname_snake def generate(self, basedir): with open(basedir + '/visiontransfer/deviceparameters.h', 'r') as f: in_comment = False comment = '' level = 0 for l in [ll.strip() for ll in f.readlines()]: if in_comment: end = l.find('*/') thisline = (l if end<0 else l[:end]).lstrip('*').strip() if thisline != '': comment += '\n' + thisline if end >= 0: in_comment = False else: start = l.find('/**') if start >= 0: in_comment = True comment = l[start+3:] else: if level==1 and l.find(' DeviceParameters {') >= 0: # insert class docstring self.pyxcode.append(' '*4 + '_SUBSTITUTE_DOCSTRING_FOR_("visiontransfer::DeviceParameters")') self.pyxcode.extend(self.pyxcode2) self.pyxcode2 = [] comment = '' elif level==2 and l.find('(') >= 0 and l.find('{') > 0 and (l.find('get') > 0 or l.find('set') > 0): ret = l.split()[0] fnname = l.split()[1].split('(')[0] args = l.split('(')[1].split(')')[0] self.add_pxd(ret, fnname, args) self.add_pyx(ret, fnname, args, comment) comment = '' else: pass level += l.count('{') level -= l.count('}') if __name__=='__main__': basedir = os.getenv("LIBVISIONTRANSFER_SRCDIR", '../..') if os.path.isdir(basedir): g = Generator() g.generate(basedir) pathlib.Path("visiontransfer").mkdir(parents=True, exist_ok=True) with open('visiontransfer/visiontransfer_parameters_cpp_autogen.pxd', 'w') as f: f.write('\n'.join(g.pxdcode)) with open('visiontransfer/visiontransfer_parameters_autogen.pyx.in', 'w') as f: f.write('\n'.join(g.pyxcode)) else: print("Could not open library base dir, please set a correct LIBVISIONTRANSFER_SRCDIR")