Mercurial > hg > orthanc-stone
view Resources/CodeGeneration/stonegentool.py @ 470:db093eb6b29d bgo-commands-codegen
Ongoing code generation tool
author | bgo-osimis |
---|---|
date | Tue, 12 Feb 2019 16:49:28 +0100 |
parents | 52549faf47ba |
children | 125c19b294e3 |
line wrap: on
line source
from typing import List,Dict import sys, json def LoadSchema(file_path : str): with open(file_path, 'r') as fp: obj = json.load(fp) return obj class Type: def __init__(self, canonicalTypeName:str, kind:str): allowedTypeKinds = ["primitive","enum","struct","collection"] """dependent type is the list of canonical types this type depends on. For instance, vector<map<string,int32>> depends on map<string,int32> that, in turn, depends on string and int32 that, in turn, depend on nothing""" self.canonicalTypeName = canonicalTypeName assert(kind in allowedTypeKinds) def setDependentTypes(self, dependentTypes:List[Type]) -> None: self.dependentTypes = dependentTypes def getDependentTypes(self) -> List[Type]: return self.dependentTypes def getCppTypeName(self) -> str: # C++: prefix map vector and string with std::map, std::vector and std::string # replace int32 by int32_t # replace float32 by float # replace float64 by double retVal : str = self.canonicalTypeName.replace("map","std::map") retVal : str = self.canonicalTypeName.replace("vector","std::vector") retVal : str = self.canonicalTypeName.replace("int32","int32_t") retVal : str = self.canonicalTypeName.replace("float32","float") retVal : str = self.canonicalTypeName.replace("float64","double") return retVal def getTypeScriptTypeName(self) -> str: # TS: replace vector with Array and map with Map # string remains string # replace int32 by number # replace float32 by number # replace float64 by number retVal : str = self.canonicalTypeName.replace("map","Map") retVal : str = self.canonicalTypeName.replace("vector","Array") retVal : str = self.canonicalTypeName.replace("int32","number") retVal : str = self.canonicalTypeName.replace("float32","number") retVal : str = self.canonicalTypeName.replace("float64","number") retVal : str = self.canonicalTypeName.replace("bool","boolean") return retVal class Schema: def __init__(self, root_prefix : str, defined_types : List[Type]): self.rootName : str = root_prefix self.definedTypes : str = defined_types def CheckTypeSchema(definedType : Dict) -> None: allowedDefinedTypeKinds = ["enum","struct"] if not definedType.has_key('name'): raise Exception("type lacks the 'name' key") name = definedType['name'] if not definedType.has_key('kind'): raise Exception(f"type {name} lacks the 'kind' key") kind = definedType['kind'] if not (kind in allowedDefinedTypeKinds): raise Exception(f"type {name} : kind {kind} is not allowed. It must be one of {allowedDefinedTypeKinds}") if not definedType.has_key('fields'): raise Exception("type {name} lacks the 'fields' key") # generic check on all kinds of types fields = definedType['fields'] for field in fields: fieldName = field['name'] if not field.has_key('name'): raise Exception("field in type {name} lacks the 'name' key") # fields in struct must have types if kind == 'struct': for field in fields: fieldName = field['name'] if not field.has_key('type'): raise Exception(f"field {fieldName} in type {name} lacks the 'type' key") def CheckSchemaSchema(schema : Dict) -> None: if not schema.has_key('root_name'): raise Exception("schema lacks the 'root_name' key") if not schema.has_key('types'): raise Exception("schema lacks the 'types' key") for definedType in schema['types']: CheckTypeSchema(definedType) def CreateAndCacheTypeObject(allTypes : Dict[str,Type], typeDict : Dict) -> None: """This does not set the dependentTypes field""" typeName : str = typeDict['name'] if allTypes.has_key(typeName): raise Exception(f'Type {name} is defined more than once!') else: typeObject = Type(typeName, typeDict['kind']) allTypes[typeName] = typeObject def ParseTemplateType(typeName) -> (bool,str,str): """ If the type is a template like "SOMETHING<SOME<THING,EL<SE>>>", then it returns (true,"SOMETHING","SOME<THING,EL<SE>>") otherwise it returns (false,"","")""" def GetCachedTypeObject(allTypes : Dict[str,Type], typeName : str) -> Type: if allTypes.has_key('typeName') return allTypes['typeName'] # if we reach this point, it means the type is NOT a struct or an enum. # it is another (non directly user-defined) type that we must parse and create # let's do it def ComputeTopTypeTree(allTypes : Dict[str,Type], typeDict : Dict) -> None: """Now that all the types we might be referring to are known (either structs and enums already created OR primitive types OR collections), we can create the whole dependency tree""" if typeDict['kind'] == 'struct': typeName : str = typeDict['name'] typeObject : Type = allTypes[typeName] typeFields : List[Dict] = typeDict['fields'] dependentTypes = [] for typeField : Dict in typeFields: typeFieldObject = CreateTypeObject(allTypes, typeField['name']) dependentTypes.append(typeFieldObject) elif typeDict['kind'] == 'enum': # the enum objects are already created and have no dependencies else: raise Exception("Internal error: ComputeTopTypeTree() can only be called on the defined types (enums and structs)") def ProcessSchema(schema : dict) -> None: CheckSchemaSchema(schema) rootName : str = schema['root_name'] definedTypes : list = schema['types'] # gather defined types allTypes : Dict[str,Type] = {} # gather all defined types (first pass) : structs and enums # we do not parse the subtypes now as to not mandate # a particular declaration order for definedType in definedTypes: CreateAndCacheTypeObject(allTypes,definedType) # now we have objects for all the defined types # let's build the whole type dependency tree for definedType in definedTypes: ComputeTypeTree(allTypes,definedType) if __name__ == '__main__': import argparse parser = argparse.ArgumentParser(usage = """stonegentool.py [-h] [-o OUT_DIR] [-v] input_schemas EXAMPLE: python command_gen.py -o "generated_files/" "mainSchema.json,App Specific Commands.json" """) parser.add_argument("input_schema", type=str, help = "path to the schema file") parser.add_argument("-o", "--out_dir", type=str, default=".", help = """path of the directory where the files will be generated. Default is current working folder""") parser.add_argument("-v", "--verbosity", action="count", default=0, help = """increase output verbosity (0 == errors only, 1 == some verbosity, 2 == nerd mode""") args = parser.parse_args() inputSchemaFilename = args.input_schema outDir = args.out_dir print("input schema = " + str(inputSchemaFilename)) print("out dir = " + str(outDir)) ProcessSchema(LoadSchema(inputSchemaFilename)) ################### ## ATTIC ## ################### # this works if False: obj = json.loads("""{ "firstName": "Alice", "lastName": "Hall", "age": 35 }""") print(obj)