1import os 2import argparse 3import io 4import codecs 5import json 6import re 7import pathlib 8from pathlib import Path 9from os import listdir 10from os.path import isdir, exists, join 11from re import split 12 13sdks = {} 14clientClassPrefix = {} 15 16# Eventstreaming operations are only available on the async clients 17asyncOnlyOperations = {} 18 19def generateDocsMap(apiDefinitionsPath, apiDefinitionsRelativeFilePath): 20 21 rootPath = pathlib.Path(r'./services') 22 for serviceModelPaths in rootPath.rglob('service-2.json'): 23 tokenizePath = str(Path(serviceModelPaths).parent).split("/") 24 getServiceName = tokenizePath[len(tokenizePath)-1] 25 if (getServiceName == "codegen-resources"): 26 getServiceName = str(serviceModelPaths).split("services/")[1].split("/src/main/resources")[0] 27 with codecs.open(serviceModelPaths, 'rb', 'utf-8') as apiDefinition: 28 apiContent = json.loads(apiDefinition.read()) 29 if "uid" in apiContent["metadata"].keys(): 30 sdks[apiContent["metadata"]["uid"]] = getServiceName 31 clientClassPrefix[apiContent["metadata"]["uid"]] = getClientClassNameFromMetadata(apiContent["metadata"]) 32 asyncOnlyOps = generateAsyncOnlyOps(apiContent) 33 if len(asyncOnlyOps.keys()) > 0: 34 asyncOnlyOperations[apiContent["metadata"]["uid"]] = asyncOnlyOps 35 36 return sdks 37 38def generateAsyncOnlyOps(apiContent): 39 asyncOnlyOps = {} 40 for op in apiContent['operations'].keys(): 41 eventStreamInOut = hasEventStreamInputOutput(apiContent, op) 42 if len(eventStreamInOut.keys()) > 0: 43 asyncOnlyOps[op] = eventStreamInOut 44 return asyncOnlyOps 45 46def hasEventStreamInputOutput(serviceModel, operationName): 47 inOut = {} 48 opModel = serviceModel['operations'][operationName] 49 if 'input' in opModel.keys(): 50 inputShapeName = opModel['input']['shape'] 51 if hasEventStreamMember(serviceModel, inputShapeName): 52 inOut['input'] = True 53 if 'output' in opModel.keys(): 54 outputShapeName = opModel['output']['shape'] 55 if hasEventStreamMember(serviceModel, outputShapeName): 56 inOut['output'] = True 57 return inOut 58 59 60def hasEventStreamMember(serviceModel, shapeName): 61 shapeModel = serviceModel['shapes'][shapeName] 62 if 'members' in shapeModel.keys(): 63 for name,memberModel in shapeModel['members'].items(): 64 if isEventStream(serviceModel,memberModel['shape']): 65 return True 66 return False 67 68def isEventStream(serviceModel, shapeName): 69 shapeModel = serviceModel['shapes'][shapeName] 70 return 'eventstream' in shapeModel and shapeModel['eventstream'] 71 72 73def splitOnWordBoundaries(toSplit) : 74 result = toSplit 75 result = re.sub(r'[^A-Za-z0-9]+', " ", result) 76 result = re.sub(r'([^a-z]{2,})v([0-9]+)', r'\g<1> v\g<2>' , result) 77 result = re.sub(r'([^A-Z]{2,})V([0-9]+)', r'\g<1> V\g<2>', result) 78 result = re.sub(r'(?<=[a-z])(?=[A-Z]([a-zA-Z]|[0-9]))', r' ', result) 79 result = re.sub(r'([A-Z]+)([A-Z][a-z])', r'\g<1> \g<2>', result) 80 result = re.sub(r'([0-9])([a-zA-Z])', r'\g<1> \g<2>', result) 81 result = re.sub(r' +', ' ', result) 82 return result.split(" "); 83 84 85def capitalize(str): 86 if(str is None or len(str) == 0): 87 return str 88 strFirstCaps = str[0].title() + str[1:] 89 return strFirstCaps 90 91 92def lowerCase(str): 93 if(str is None or len(str) == 0): 94 return str 95 return str.lower() 96 97def pascalCase(str): 98 splits = splitOnWordBoundaries(str) 99 modifiedStr = "" 100 for i in range(0, len(splits)) : 101 modifiedStr += capitalize(lowerCase(splits[i])) 102 return modifiedStr 103 104def getClientClassNameFromMetadata(metadataNode): 105 toSanitize = "" 106 if "serviceId" in metadataNode.keys(): 107 toSanitize = metadataNode["serviceId"] 108 clientName = pascalCase(toSanitize) 109 clientName = removeLeading(clientName, "Amazon") 110 clientName = removeLeading(clientName, "Aws") 111 clientName = removeTrailing(clientName, "Service" ) 112 return clientName 113 114def removeLeading(str, toRemove) : 115 if(str is None) : 116 return str 117 if(str.startswith(toRemove)): 118 return str.replace(toRemove, "") 119 return str 120 121def removeTrailing(str, toRemove) : 122 if(str is None) : 123 return str 124 if(str.endswith(toRemove)): 125 return str.replace(toRemove, "") 126 return str 127 128def insertDocsMapToRedirect(apiDefinitionsBasePath, apiDefinitionsRelativeFilePath, templateFilePath, outputFilePath): 129 generateDocsMap(apiDefinitionsBasePath, apiDefinitionsRelativeFilePath) 130 output = "" 131 with codecs.open(templateFilePath, 'rb', 'utf-8') as redirect_template: 132 current_template = redirect_template.read(); 133 output = current_template.replace("${UID_SERVICE_MAPPING}", json.dumps(sdks, ensure_ascii=False)) 134 output = output.replace("${UID_CLIENT_CLASS_MAPPING}", json.dumps(clientClassPrefix, ensure_ascii=False)) 135 output = output.replace("${SERVICE_NAME_TO_ASYNC_ONLY_OPERATION_MAPPING}", json.dumps(asyncOnlyOperations, ensure_ascii=False)) 136 with open(outputFilePath, 'w') as redirect_output: 137 redirect_output.write(output) 138 139def Main(): 140 parser = argparse.ArgumentParser(description="Generates a Cross-link redirect file.") 141 parser.add_argument("--apiDefinitionsBasePath", action="store") 142 parser.add_argument("--apiDefinitionsRelativeFilePath", action="store") 143 parser.add_argument("--templateFilePath", action="store") 144 parser.add_argument("--outputFilePath", action="store") 145 146 args = vars( parser.parse_args() ) 147 argMap = {} 148 argMap[ "apiDefinitionsBasePath" ] = args[ "apiDefinitionsBasePath" ] or "./../services/" 149 argMap[ "apiDefinitionsRelativeFilePath" ] = args[ "apiDefinitionsRelativeFilePath" ] or "/src/main/resources/codegen-resources/service-2.json" 150 argMap[ "templateFilePath" ] = args[ "templateFilePath" ] or "./scripts/doc_crosslinks/crosslink_redirect.html" 151 argMap[ "outputFilePath" ] = args[ "outputFilePath" ] or "./crosslink_redirect.html" 152 153 insertDocsMapToRedirect(argMap["apiDefinitionsBasePath"], argMap["apiDefinitionsRelativeFilePath"], argMap["templateFilePath"], argMap["outputFilePath"]) 154 print("Generated Cross link at " + argMap["outputFilePath"]) 155 156Main() 157