xref: /aosp_15_r20/external/aws-sdk-java-v2/scripts/doc_crosslinks/generate_cross_link_data.py (revision 8a52c7834d808308836a99fc2a6e0ed8db339086)
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