Mercurial > hg > orthanc-stone
comparison Resources/CodeGeneration/stonegentool.py @ 513:dea3787a8f4b bgo-commands-codegen
Added support for struct metadata, to disable/enable handler support in Typescript or C++
author | Benjamin Golinvaux <bgo@osimis.io> |
---|---|
date | Mon, 11 Mar 2019 13:02:46 +0100 |
parents | 7105a0bad250 |
children | 1dbf2d9ed1e4 |
comparison
equal
deleted
inserted
replaced
510:97a16b694321 | 513:dea3787a8f4b |
---|---|
37 while trimmed and not trimmed[0]: | 37 while trimmed and not trimmed[0]: |
38 trimmed.pop(0) | 38 trimmed.pop(0) |
39 # Return a single string: | 39 # Return a single string: |
40 return '\n'.join(trimmed) | 40 return '\n'.join(trimmed) |
41 | 41 |
42 | |
43 class GenCode: | |
44 def __init__(self): | |
45 | |
46 # file-wide preamble (#include directives, comment...) | |
47 self.cppPreamble = StringIO() | |
48 | |
49 self.cppEnums = StringIO() | |
50 self.cppStructs = StringIO() | |
51 self.cppDispatcher = StringIO() | |
52 self.cppHandler = StringIO() | |
53 | |
54 # file-wide preamble (module directives, comment...) | |
55 self.tsPreamble = StringIO() | |
56 | |
57 self.tsEnums = StringIO() | |
58 self.tsStructs = StringIO() | |
59 self.tsDispatcher = StringIO() | |
60 self.tsHandler = StringIO() | |
61 | |
62 def FlattenToFiles(self, outputDir): | |
63 raise NotImplementedError() | |
64 | |
65 | |
66 class JsonHelpers: | 42 class JsonHelpers: |
67 """A set of utilities to perform JSON operations""" | 43 """A set of utilities to perform JSON operations""" |
68 | 44 |
69 @staticmethod | 45 @staticmethod |
70 def removeCommentsFromJsonContent(string): | 46 def removeCommentsFromJsonContent(string): |
232 # we need to split with the commas that are outside of the | 208 # we need to split with the commas that are outside of the |
233 # defined types. Simply splitting at commas won't work | 209 # defined types. Simply splitting at commas won't work |
234 listOfDependentTypes = SplitListOfTypes(m.group(2)) | 210 listOfDependentTypes = SplitListOfTypes(m.group(2)) |
235 return (True, m.group(1), listOfDependentTypes) | 211 return (True, m.group(1), listOfDependentTypes) |
236 | 212 |
213 def GetStructFields(struct): | |
214 """This filters out the __meta__ key from the struct fields""" | |
215 return [k for k in struct.keys() if k != '__meta__'] | |
237 | 216 |
238 def ComputeOrderFromTypeTree( | 217 def ComputeOrderFromTypeTree( |
239 ancestors, | 218 ancestors, |
240 genOrder, | 219 genOrder, |
241 shortTypename, schema): | 220 shortTypename, schema): |
266 struct = schema[GetLongTypename(shortTypename, schema)] | 245 struct = schema[GetLongTypename(shortTypename, schema)] |
267 # The keys in the struct dict are the member names | 246 # The keys in the struct dict are the member names |
268 # The values in the struct dict are the member types | 247 # The values in the struct dict are the member types |
269 if struct: | 248 if struct: |
270 # we reach this if struct is not None AND not empty | 249 # we reach this if struct is not None AND not empty |
271 for field in struct.keys(): | 250 for field in GetStructFields(struct): |
272 # we fill the chain of dependent types (starting here) | 251 # we fill the chain of dependent types (starting here) |
273 ancestors.append(shortTypename) | 252 ancestors.append(shortTypename) |
274 ComputeOrderFromTypeTree( | 253 ComputeOrderFromTypeTree( |
275 ancestors, genOrder, struct[field], schema) | 254 ancestors, genOrder, struct[field], schema) |
276 # don't forget to restore it! | 255 # don't forget to restore it! |
338 | 317 |
339 # TODO: check enum fields are unique (in whole namespace) | 318 # TODO: check enum fields are unique (in whole namespace) |
340 # TODO: check struct fields are unique (in each struct) | 319 # TODO: check struct fields are unique (in each struct) |
341 # TODO: check that in the source schema, there are spaces after each colon | 320 # TODO: check that in the source schema, there are spaces after each colon |
342 | 321 |
322 nonTypeKeys = ['rootName'] | |
323 def GetTypesInSchema(schema): | |
324 """Returns the top schema keys that are actual type names""" | |
325 typeList = [k for k in schema if k not in nonTypeKeys] | |
326 return typeList | |
327 | |
343 # +-----------------------+ | 328 # +-----------------------+ |
344 # | Main processing logic | | 329 # | Main processing logic | |
345 # +-----------------------+ | 330 # +-----------------------+ |
346 | 331 |
347 def ComputeRequiredDeclarationOrder(schema): | 332 def ComputeRequiredDeclarationOrder(schema): |
355 # where they must be defined. | 340 # where they must be defined. |
356 # We do not care about the enums here... They do not depend upon | 341 # We do not care about the enums here... They do not depend upon |
357 # anything and we'll handle them, in their original declaration | 342 # anything and we'll handle them, in their original declaration |
358 # order, at the start | 343 # order, at the start |
359 genOrder = [] | 344 genOrder = [] |
360 for fullName in schema.keys(): | 345 for fullName in GetTypesInSchema(schema): |
361 if IsStructType(fullName): | 346 if IsStructType(fullName): |
362 realName = GetShortTypename(fullName) | 347 realName = GetShortTypename(fullName) |
363 ancestors = [] | 348 ancestors = [] |
364 ComputeOrderFromTypeTree(ancestors, genOrder, realName, schema) | 349 ComputeOrderFromTypeTree(ancestors, genOrder, realName, schema) |
365 return genOrder | 350 return genOrder |
351 | |
352 def GetStructFields(fieldDict): | |
353 """Returns the regular (non __meta__) struct fields""" | |
354 # the following happens for empty structs | |
355 if fieldDict == None: | |
356 return fieldDict | |
357 ret = {} | |
358 for k,v in fieldDict.items(): | |
359 if k != "__meta__": | |
360 ret[k] = v | |
361 return ret | |
362 | |
363 def GetStructMetadata(fieldDict): | |
364 """Returns the __meta__ struct fields (there are default values that | |
365 can be overridden by entries in the schema | |
366 Not tested because it's a fail-safe: if something is broken in meta, | |
367 dependent projects will not build.""" | |
368 metadataDict = {} | |
369 metadataDict['handleInCpp'] = True | |
370 metadataDict['handleInTypescript'] = True | |
371 | |
372 # Empty types are allowed | |
373 if fieldDict != None: | |
374 for k,v in fieldDict.items(): | |
375 if k == "__meta__": | |
376 # let's examine the various metadata entries | |
377 for metaKey,metaValue in v.items(): | |
378 # we only accept overriding EXISTING entries | |
379 if metaKey in metadataDict: | |
380 # simple check, valid for now | |
381 if type(metaValue) != bool: | |
382 raise RuntimeError("Wrong value for metadata key") | |
383 metadataDict[metaKey] = metaValue | |
384 else: | |
385 raise RuntimeError("Wrong key \"{metaKey}\" in metadata. Allowed keys are \"{metadataDict.keys()}\"") | |
386 return metadataDict | |
366 | 387 |
367 def ProcessSchema(schema, genOrder): | 388 def ProcessSchema(schema, genOrder): |
368 # sanity check | 389 # sanity check |
369 CheckSchemaSchema(schema) | 390 CheckSchemaSchema(schema) |
370 | 391 |
406 # this is already the short name | 427 # this is already the short name |
407 typename = genOrder[i] | 428 typename = genOrder[i] |
408 fieldDict = schema["struct " + typename] | 429 fieldDict = schema["struct " + typename] |
409 struct = {} | 430 struct = {} |
410 struct['name'] = typename | 431 struct['name'] = typename |
411 struct['fields'] = fieldDict | 432 struct['fields'] = GetStructFields(fieldDict) |
433 struct['__meta__'] = GetStructMetadata(fieldDict) | |
412 structs.append(struct) | 434 structs.append(struct) |
413 | 435 |
414 templatingDict = {} | 436 templatingDict = {} |
415 templatingDict['enums'] = enums | 437 templatingDict['enums'] = enums |
416 templatingDict['structs'] = structs | 438 templatingDict['structs'] = structs |
440 for i in range(len(schemaText)-1): | 462 for i in range(len(schemaText)-1): |
441 ch = schemaText[i] | 463 ch = schemaText[i] |
442 nextCh = schemaText[i+1] | 464 nextCh = schemaText[i+1] |
443 if ch == ':': | 465 if ch == ':': |
444 if not (nextCh == ' ' or nextCh == '\n'): | 466 if not (nextCh == ' ' or nextCh == '\n'): |
445 assert(False) | 467 lineNumber = schemaText.count("\n",0,i) + 1 |
468 raise RuntimeError(f"Error at line {lineNumber} in the schema: colons must be followed by a space or a newline!") | |
446 schema = yaml.load(schemaText) | 469 schema = yaml.load(schemaText) |
447 return schema | 470 return schema |
448 | 471 |
449 def GetTemplatingDictFromSchemaFilename(fn): | 472 def GetTemplatingDictFromSchemaFilename(fn): |
450 obj = LoadSchema(fn) | 473 obj = LoadSchema(fn) |
455 return templatingDict | 478 return templatingDict |
456 | 479 |
457 # +-----------------------+ | 480 # +-----------------------+ |
458 # | ENTRY POINT | | 481 # | ENTRY POINT | |
459 # +-----------------------+ | 482 # +-----------------------+ |
483 def Process(schemaFile, outDir): | |
484 tdico = GetTemplatingDictFromSchemaFilename(schemaFile) | |
485 | |
486 tsTemplateFile = \ | |
487 os.path.join(os.path.dirname(__file__), 'template.in.ts') | |
488 template = MakeTemplateFromFile(tsTemplateFile) | |
489 renderedTsCode = template.render(**tdico) | |
490 outputTsFile = os.path.join( \ | |
491 outDir,str(tdico['rootName']) + "_generated.ts") | |
492 with open(outputTsFile,"wt",encoding='utf8') as outFile: | |
493 outFile.write(renderedTsCode) | |
494 | |
495 cppTemplateFile = \ | |
496 os.path.join(os.path.dirname(__file__), 'template.in.h') | |
497 template = MakeTemplateFromFile(cppTemplateFile) | |
498 renderedCppCode = template.render(**tdico) | |
499 outputCppFile = os.path.join( \ | |
500 outDir, str(tdico['rootName']) + "_generated.hpp") | |
501 with open(outputCppFile,"wt",encoding='utf8') as outFile: | |
502 outFile.write(renderedCppCode) | |
460 | 503 |
461 if __name__ == "__main__": | 504 if __name__ == "__main__": |
462 import argparse | 505 import argparse |
463 | 506 |
464 parser = argparse.ArgumentParser( | 507 parser = argparse.ArgumentParser( |
488 ) | 531 ) |
489 | 532 |
490 args = parser.parse_args() | 533 args = parser.parse_args() |
491 schemaFile = args.input_schema | 534 schemaFile = args.input_schema |
492 outDir = args.out_dir | 535 outDir = args.out_dir |
493 | 536 Process(schemaFile, outDir) |
494 tdico = GetTemplatingDictFromSchemaFilename(schemaFile) | |
495 | |
496 tsTemplateFile = \ | |
497 os.path.join(os.path.dirname(__file__), 'template.in.ts') | |
498 template = MakeTemplateFromFile(tsTemplateFile) | |
499 renderedTsCode = template.render(**tdico) | |
500 outputTsFile = os.path.join( \ | |
501 outDir,str(tdico['rootName']) + "_generated.ts") | |
502 with open(outputTsFile,"wt",encoding='utf8') as outFile: | |
503 outFile.write(renderedTsCode) | |
504 | |
505 cppTemplateFile = \ | |
506 os.path.join(os.path.dirname(__file__), 'template.in.h') | |
507 template = MakeTemplateFromFile(cppTemplateFile) | |
508 renderedCppCode = template.render(**tdico) | |
509 outputCppFile = os.path.join( \ | |
510 outDir, str(tdico['rootName']) + "_generated.hpp") | |
511 with open(outputCppFile,"wt",encoding='utf8') as outFile: | |
512 outFile.write(renderedCppCode) | |
513 | |
514 # def GenEnumDecl(genc: GenCode, fullName: str, schema: Dict) -> None: | |
515 # """Writes the enumerations in genc""" | |
516 # enumDict:Dict=schema[fullName] | |
517 # # jinja2 template | |
518 # j2cppEnum = Template(trim( | |
519 # """ {{fullName}} | |
520 # { | |
521 # {% for key in enumDict.keys()%} | |
522 # {{key}}, | |
523 # {%endfor%} | |
524 # }; | |
525 # """)) | |
526 # j2cppEnumR = j2cppEnum.render(locals()) | |
527 # genc.cppEnums.write(j2cppEnumR) | |
528 | |
529 # j2tsEnum = Template(trim( | |
530 # """ export {{fullName}} | |
531 # { | |
532 # {% for key in enumDict.keys()%} | |
533 # {{key}}, | |
534 # {%endfor%} | |
535 # }; | |
536 # """)) | |
537 # j2cppEnumR = j2cppEnum.render(locals()) | |
538 # genc.tsEnums.write(j2cppEnumR) | |
539 | |
540 | |
541 | |
542 # def GetSerializationCode(typename: str,valueName: str, tempName: str) | |
543 # if IsPrimitiveType(typename) or IsTemplateCollection(typename): | |
544 # # no need to write code for the primitive types or collections. | |
545 # # It is handled in C++ by the template functions and in TS by | |
546 # # the JSON.stringify code. | |
547 # elif IsStructType(typename): | |
548 # pass | |
549 | |
550 # def GenStructTypeDeclAndSerialize(genc: GenCode, type, schema) -> None: | |
551 # ###### | |
552 # # CPP | |
553 # ###### | |
554 # sampleCpp = """ struct Message1 | |
555 # { | |
556 # int32_t a; | |
557 # std::string b; | |
558 # EnumMonth0 c; | |
559 # bool d; | |
560 # }; | |
561 | |
562 # Json::Value StoneSerialize(const Message1& value) | |
563 # { | |
564 # Json::Value result(Json::objectValue); | |
565 # result["a"] = StoneSerialize(value.a); | |
566 # result["b"] = StoneSerialize(value.b); | |
567 # result["c"] = StoneSerialize(value.c); | |
568 # result["d"] = StoneSerialize(value.d); | |
569 # return result; | |
570 # } | |
571 # """ | |
572 | |
573 | |
574 # ###### | |
575 # # TS | |
576 # ###### | |
577 # sampleTs = """ | |
578 # { | |
579 # export class Message1 { | |
580 # a: number; | |
581 # b: string; | |
582 # c: EnumMonth0; | |
583 # d: boolean; | |
584 # public StoneSerialize(): string { | |
585 # let container: object = {}; | |
586 # container['type'] = 'Message1'; | |
587 # container['value'] = this; | |
588 # return JSON.stringify(container); | |
589 # } | |
590 # }; | |
591 # } | |
592 # """ | |
593 | |
594 | |
595 | |
596 | |
597 # tsText: StringIO = StringIO() | |
598 # cppText: StringIO = StringIO() | |
599 | |
600 # tsText.write("class %s\n" % typeDict["name"]) | |
601 # tsText.write("{\n") | |
602 | |
603 # cppText.write("struct %s\n" % typeDict["name"]) | |
604 # cppText.write("{\n") | |
605 | |
606 # """ | |
607 | |
608 # GenerateSerializationCode(typename,valueName) | |
609 | |
610 # primitives: | |
611 # ----------- | |
612 # int | |
613 # jsonValue val(objectInt); | |
614 # val.setValue("$name") | |
615 # parent.add(("$name",$name) | |
616 # double | |
617 # ... | |
618 # string | |
619 # ... | |
620 | |
621 # collections: | |
622 # ----------- | |
623 # dict { } | |
624 | |
625 # serializeValue() | |
626 # """ | |
627 | |
628 # for i in range(len(typeDict["fields"])): | |
629 # field = typeDict["fields"][i] | |
630 # name = field["name"] | |
631 # tsType = GetTypeScriptTypenameFromCanonical(field["type"]) | |
632 # tsText.write(" public %s %s;\n" % (tsType, name)) | |
633 # cppType = GetCppTypenameFromCanonical(field["type"]) | |
634 # cppText.write(" %s %s;\n" % (cppType, name)) | |
635 | |
636 # tsText.write("};\n\n") | |
637 # cppText.write("};\n\n") | |
638 | |
639 # genc.tsStructs.write(tsText.getvalue()) | |
640 # genc.cppStructs.write(cppText.getvalue()) | |
641 | |
642 | |
643 # def GenerateCodeFromTsTemplate(genc) | |
644 | |
645 | |
646 # +-----------------------+ | |
647 # | CODE GENERATION | | |
648 # +-----------------------+ | |
649 | |
650 # def GenPreambles(rootName: str, genc: GenCode) -> None: | |
651 # cppPreambleT = Template(trim( | |
652 # """// autogenerated by stonegentool on {{time.ctime()}} | |
653 # // for module {{rootName}} | |
654 # #include <cstdint> | |
655 # #include <string> | |
656 # #include <vector> | |
657 # #include <map> | |
658 # namespace {{rootName}} | |
659 # { | |
660 # Json::Value StoneSerialize(int32_t value) | |
661 # { | |
662 # Json::Value result(value); | |
663 # return result; | |
664 # } | |
665 # Json::Value StoneSerialize(double value) | |
666 # { | |
667 # Json::Value result(value); | |
668 # return result; | |
669 # } | |
670 # Json::Value StoneSerialize(bool value) | |
671 # { | |
672 # Json::Value result(value); | |
673 # return result; | |
674 # } | |
675 # Json::Value StoneSerialize(const std::string& value) | |
676 # { | |
677 # // the following is better than | |
678 # Json::Value result(value.data(),value.data()+value.size()); | |
679 # return result; | |
680 # } | |
681 # template<typename T> | |
682 # Json::Value StoneSerialize(const std::map<std::string,T>& value) | |
683 # { | |
684 # Json::Value result(Json::objectValue); | |
685 | |
686 # for (std::map<std::string, T>::const_iterator it = value.cbegin(); | |
687 # it != value.cend(); ++it) | |
688 # { | |
689 # // it->first it->second | |
690 # result[it->first] = StoneSerialize(it->second); | |
691 # } | |
692 # return result; | |
693 # } | |
694 # template<typename T> | |
695 # Json::Value StoneSerialize(const std::vector<T>& value) | |
696 # { | |
697 # Json::Value result(Json::arrayValue); | |
698 # for (size_t i = 0; i < value.size(); ++i) | |
699 # { | |
700 # result.append(StoneSerialize(value[i])); | |
701 # } | |
702 # return result; | |
703 # } | |
704 # """ | |
705 # cppPreambleR = cppPreambleT.render(locals()) | |
706 # genc.cppPreamble.write(cppPreambleR) | |
707 | |
708 # tsPreambleT = Template(trim( | |
709 # """// autogenerated by stonegentool on {{time.ctime()}} | |
710 # // for module {{rootName}} | |
711 | |
712 # namespace {{rootName}} | |
713 # { | |
714 # """ | |
715 # tsPreambleR = tsPreambleT.render(locals()) | |
716 # genc.tsPreamble.write(tsPreambleR) | |
717 | |
718 # def ComputeOrder_ProcessStruct( \ | |
719 # genOrder: List[str], name:str, schema: Dict[str, str]) -> None: | |
720 # # let's generate the code according to the | |
721 # struct = schema[name] | |
722 | |
723 # if not IsStructType(name): | |
724 # raise Exception(f'{typename} should start with "struct "') | |
725 | |
726 |