470
|
1 from typing import List,Dict
|
469
|
2 import sys, json
|
|
3
|
|
4 def LoadSchema(file_path : str):
|
|
5 with open(file_path, 'r') as fp:
|
|
6 obj = json.load(fp)
|
|
7 return obj
|
|
8
|
|
9 class Type:
|
470
|
10 def __init__(self, canonicalTypeName:str, kind:str):
|
|
11 allowedTypeKinds = ["primitive","enum","struct","collection"]
|
469
|
12 """dependent type is the list of canonical types this type depends on.
|
|
13 For instance, vector<map<string,int32>> depends on map<string,int32>
|
|
14 that, in turn, depends on string and int32 that, in turn, depend on
|
|
15 nothing"""
|
|
16 self.canonicalTypeName = canonicalTypeName
|
470
|
17 assert(kind in allowedTypeKinds)
|
|
18
|
|
19 def setDependentTypes(self, dependentTypes:List[Type]) -> None:
|
469
|
20 self.dependentTypes = dependentTypes
|
470
|
21
|
|
22 def getDependentTypes(self) -> List[Type]:
|
469
|
23 return self.dependentTypes
|
470
|
24
|
|
25 def getCppTypeName(self) -> str:
|
469
|
26 # C++: prefix map vector and string with std::map, std::vector and std::string
|
|
27 # replace int32 by int32_t
|
|
28 # replace float32 by float
|
|
29 # replace float64 by double
|
|
30 retVal : str = self.canonicalTypeName.replace("map","std::map")
|
|
31 retVal : str = self.canonicalTypeName.replace("vector","std::vector")
|
|
32 retVal : str = self.canonicalTypeName.replace("int32","int32_t")
|
|
33 retVal : str = self.canonicalTypeName.replace("float32","float")
|
|
34 retVal : str = self.canonicalTypeName.replace("float64","double")
|
|
35 return retVal
|
470
|
36
|
|
37 def getTypeScriptTypeName(self) -> str:
|
469
|
38 # TS: replace vector with Array and map with Map
|
|
39 # string remains string
|
|
40 # replace int32 by number
|
|
41 # replace float32 by number
|
|
42 # replace float64 by number
|
|
43 retVal : str = self.canonicalTypeName.replace("map","Map")
|
|
44 retVal : str = self.canonicalTypeName.replace("vector","Array")
|
|
45 retVal : str = self.canonicalTypeName.replace("int32","number")
|
|
46 retVal : str = self.canonicalTypeName.replace("float32","number")
|
|
47 retVal : str = self.canonicalTypeName.replace("float64","number")
|
|
48 retVal : str = self.canonicalTypeName.replace("bool","boolean")
|
|
49 return retVal
|
|
50
|
|
51 class Schema:
|
|
52 def __init__(self, root_prefix : str, defined_types : List[Type]):
|
470
|
53 self.rootName : str = root_prefix
|
|
54 self.definedTypes : str = defined_types
|
|
55
|
|
56 def CheckTypeSchema(definedType : Dict) -> None:
|
|
57 allowedDefinedTypeKinds = ["enum","struct"]
|
|
58 if not definedType.has_key('name'):
|
|
59 raise Exception("type lacks the 'name' key")
|
|
60 name = definedType['name']
|
|
61 if not definedType.has_key('kind'):
|
|
62 raise Exception(f"type {name} lacks the 'kind' key")
|
|
63 kind = definedType['kind']
|
|
64 if not (kind in allowedDefinedTypeKinds):
|
|
65 raise Exception(f"type {name} : kind {kind} is not allowed. It must be one of {allowedDefinedTypeKinds}")
|
|
66
|
|
67 if not definedType.has_key('fields'):
|
|
68 raise Exception("type {name} lacks the 'fields' key")
|
|
69
|
|
70 # generic check on all kinds of types
|
|
71 fields = definedType['fields']
|
|
72 for field in fields:
|
|
73 fieldName = field['name']
|
|
74 if not field.has_key('name'):
|
|
75 raise Exception("field in type {name} lacks the 'name' key")
|
469
|
76
|
470
|
77 # fields in struct must have types
|
|
78 if kind == 'struct':
|
|
79 for field in fields:
|
|
80 fieldName = field['name']
|
|
81 if not field.has_key('type'):
|
|
82 raise Exception(f"field {fieldName} in type {name} lacks the 'type' key")
|
|
83
|
|
84 def CheckSchemaSchema(schema : Dict) -> None:
|
|
85 if not schema.has_key('root_name'):
|
|
86 raise Exception("schema lacks the 'root_name' key")
|
|
87 if not schema.has_key('types'):
|
|
88 raise Exception("schema lacks the 'types' key")
|
|
89 for definedType in schema['types']:
|
|
90 CheckTypeSchema(definedType)
|
|
91
|
|
92 def CreateAndCacheTypeObject(allTypes : Dict[str,Type], typeDict : Dict) -> None:
|
|
93 """This does not set the dependentTypes field"""
|
|
94 typeName : str = typeDict['name']
|
|
95 if allTypes.has_key(typeName):
|
|
96 raise Exception(f'Type {name} is defined more than once!')
|
|
97 else:
|
|
98 typeObject = Type(typeName, typeDict['kind'])
|
|
99 allTypes[typeName] = typeObject
|
468
|
100
|
|
101
|
470
|
102 def ParseTemplateType(typeName) -> (bool,str,str):
|
|
103 """ If the type is a template like "SOMETHING<SOME<THING,EL<SE>>>", then
|
|
104 it returns (true,"SOMETHING","SOME<THING,EL<SE>>")
|
|
105 otherwise it returns (false,"","")"""
|
|
106
|
|
107
|
|
108
|
|
109
|
|
110 def GetCachedTypeObject(allTypes : Dict[str,Type], typeName : str) -> Type:
|
|
111 if allTypes.has_key('typeName')
|
|
112 return allTypes['typeName']
|
|
113 # if we reach this point, it means the type is NOT a struct or an enum.
|
|
114 # it is another (non directly user-defined) type that we must parse and create
|
|
115 # let's do it
|
|
116
|
|
117
|
|
118 def ComputeTopTypeTree(allTypes : Dict[str,Type], typeDict : Dict) -> None:
|
|
119 """Now that all the types we might be referring to are known (either structs
|
|
120 and enums already created OR primitive types OR collections), we can create
|
|
121 the whole dependency tree"""
|
|
122
|
|
123 if typeDict['kind'] == 'struct':
|
|
124 typeName : str = typeDict['name']
|
|
125 typeObject : Type = allTypes[typeName]
|
|
126 typeFields : List[Dict] = typeDict['fields']
|
|
127 dependentTypes = []
|
|
128 for typeField : Dict in typeFields:
|
|
129 typeFieldObject = CreateTypeObject(allTypes, typeField['name'])
|
|
130 dependentTypes.append(typeFieldObject)
|
|
131 elif typeDict['kind'] == 'enum':
|
|
132 # the enum objects are already created and have no dependencies
|
|
133 else:
|
|
134 raise Exception("Internal error: ComputeTopTypeTree() can only be called on the defined types (enums and structs)")
|
|
135
|
|
136
|
|
137
|
|
138
|
|
139 def ProcessSchema(schema : dict) -> None:
|
|
140 CheckSchemaSchema(schema)
|
|
141 rootName : str = schema['root_name']
|
|
142 definedTypes : list = schema['types']
|
|
143
|
|
144 # gather defined types
|
|
145 allTypes : Dict[str,Type] = {}
|
|
146
|
|
147 # gather all defined types (first pass) : structs and enums
|
|
148 # we do not parse the subtypes now as to not mandate
|
|
149 # a particular declaration order
|
|
150 for definedType in definedTypes:
|
|
151 CreateAndCacheTypeObject(allTypes,definedType)
|
|
152
|
|
153 # now we have objects for all the defined types
|
|
154 # let's build the whole type dependency tree
|
|
155 for definedType in definedTypes:
|
|
156 ComputeTypeTree(allTypes,definedType)
|
|
157
|
|
158
|
|
159
|
|
160
|
468
|
161
|
|
162 if __name__ == '__main__':
|
|
163 import argparse
|
|
164 parser = argparse.ArgumentParser(usage = """stonegentool.py [-h] [-o OUT_DIR] [-v] input_schemas
|
|
165 EXAMPLE: python command_gen.py -o "generated_files/" "mainSchema.json,App Specific Commands.json" """)
|
470
|
166 parser.add_argument("input_schema", type=str,
|
|
167 help = "path to the schema file")
|
468
|
168 parser.add_argument("-o", "--out_dir", type=str, default=".",
|
|
169 help = """path of the directory where the files
|
|
170 will be generated. Default is current
|
|
171 working folder""")
|
|
172 parser.add_argument("-v", "--verbosity", action="count", default=0,
|
|
173 help = """increase output verbosity (0 == errors
|
|
174 only, 1 == some verbosity, 2 == nerd
|
|
175 mode""")
|
|
176
|
|
177 args = parser.parse_args()
|
470
|
178 inputSchemaFilename = args.input_schema
|
469
|
179 outDir = args.out_dir
|
|
180
|
470
|
181 print("input schema = " + str(inputSchemaFilename))
|
469
|
182 print("out dir = " + str(outDir))
|
|
183
|
470
|
184 ProcessSchema(LoadSchema(inputSchemaFilename))
|
|
185
|
468
|
186
|
469
|
187 ###################
|
|
188 ## ATTIC ##
|
|
189 ###################
|
|
190
|
|
191 # this works
|
468
|
192
|
469
|
193 if False:
|
|
194 obj = json.loads("""{
|
|
195 "firstName": "Alice",
|
|
196 "lastName": "Hall",
|
|
197 "age": 35
|
|
198 }""")
|
|
199 print(obj)
|