1#!/usr/bin/env python3 2# 3# Copyright (C) 2020 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17 18import os, sys, shutil 19 20def usage(): 21 print("Usage: explode.py [--consolidate-leaves] [--remove-leaves] <inputFile> <outputPath>") 22 sys.exit(1) 23 24# Miscellaneous file utilities 25class FileIo(object): 26 def __init__(self): 27 return 28 29 def ensureDirExists(self, filePath): 30 if not os.path.isdir(filePath): 31 if os.path.isfile(filePath) or os.path.islink(filePath): 32 os.remove(filePath) 33 os.makedirs(filePath) 34 35 def removePath(self, filePath): 36 if len(os.path.split(filePath)) < 2: 37 raise Exception("Will not remove path at " + filePath + "; is too close to the root of the filesystem") 38 if os.path.islink(filePath): 39 os.remove(filePath) 40 elif os.path.isdir(filePath): 41 shutil.rmtree(filePath) 42 elif os.path.isfile(filePath): 43 os.remove(filePath) 44 45 def copyFile(self, fromPath, toPath): 46 self.ensureDirExists(os.path.dirname(toPath)) 47 self.removePath(toPath) 48 if os.path.islink(fromPath): 49 linkText = os.readlink(fromPath) 50 os.symlink(linkText, toPath) 51 else: 52 shutil.copy2(fromPath, toPath) 53 54 def writeFile(self, path, text): 55 f = open(path, "w+") 56 f.write(text) 57 f.close() 58 59fileIo = FileIo() 60 61def countStartingSpaces(text): 62 for i in range(len(text)): 63 if text[i] not in (" ", "\t", "\n"): 64 return i 65 return len(text) 66 67# A Block represents some section of text from a source file and stores it as a file on disk 68# The text of each child block is expected to have text starting with <numSpaces> * " " 69class Block(object): 70 def __init__(self, numSpaces, fileName): 71 self.numSpaces = numSpaces 72 self.children = [] 73 self.fileName = fileName 74 75 # Adds more text into this section 76 def addChild(self, newChild): 77 self.children.append(newChild) 78 79 # Displays the text represented by this Block 80 def display(self): 81 prefix = " " * (self.numSpaces + 1) 82 for i in range(len(self.children)): 83 print(prefix + str(i) + ":") 84 child = self.children[i] 85 child.display() 86 87 # Generates files at <filePath> representing this Block 88 def apply(self, filePath): 89 if not os.path.isdir(filePath): 90 os.mkdir(filePath) 91 for child in self.children: 92 child.apply(os.path.join(filePath, child.fileName)) 93 94 def hasChildren(self): 95 return len(self.children) > 0 96 97 def startsFunction(self): 98 if len(self.children) < 1: 99 return False 100 return self.children[0].startsFunction() 101 102 # Removes any nodes that seem to be function bodies 103 def consolidateFunctionBodies(self, emitOptionalFunctionBodies): 104 consolidated = False 105 for i in range(1, len(self.children)): 106 prev = self.children[i - 1] 107 if (not prev.hasChildren()) and prev.startsFunction(): 108 child = self.children[i] 109 if child.hasChildren(): 110 child.consolidateSelf(emitOptionalFunctionBodies) 111 consolidated = True 112 for child in self.children: 113 if child.hasChildren(): 114 child.consolidateFunctionBodies(emitOptionalFunctionBodies) 115 116 def consolidateSelf(self, emitOptionalFunctionBodies): 117 text = self.getText() 118 if emitOptionalFunctionBodies or " return " in text: 119 self.children = [TextBlock(text, "0")] 120 else: 121 self.children = [] 122 123 def getText(self): 124 texts = [child.getText() for child in self.children] 125 return "".join(texts) 126 127# A TextBlock stores text for inclusion in a parent Block 128# Together, they store the result of parsing text from a file 129class TextBlock(object): 130 def __init__(self, text, fileName): 131 self.text = text 132 self.fileName = fileName 133 134 def getText(self): 135 return self.text 136 137 def display(self): 138 print(self.text) 139 140 def apply(self, filePath): 141 fileIo.writeFile(filePath, self.text) 142 143 def hasChildren(self): 144 return False 145 146 def startsFunction(self): 147 if "}" in self.text: 148 return False 149 if "class " in self.text: 150 return False 151 parenIndex = self.text.find(")") 152 curlyIndex = self.text.find("{") 153 if parenIndex >= 0 and curlyIndex >= parenIndex: 154 return True 155 return False 156 157def getLineName(lineNumber, numLines): 158 longestLineNumber = len(str(numLines - 1)) 159 thisLineNumber = len(str(lineNumber)) 160 extraZeros = "0" * (longestLineNumber - thisLineNumber) 161 return extraZeros + str(lineNumber) 162 163def main(args): 164 if len(args) < 2: 165 usage() 166 consolidateLeaves = False 167 emitLeaves = True 168 if args[0] == "--remove-leaves": 169 consolidateLeaves = True 170 emitLeaves = False 171 args = args[1:] 172 if args[0] == "--consolidate-leaves": 173 consolidateLeaves = True 174 args = args[1:] 175 if len(args) != 2: 176 usage() 177 stack = [Block(0, -1)] 178 inputPath = args[0] 179 outputPath = args[1] 180 inputFile = open(inputPath) 181 lines = [] 182 try: 183 lines = inputFile.readlines() 184 except UnicodeDecodeError: 185 fileIo.copyFile(inputPath, outputPath + "/binary") 186 numLines = len(lines) 187 for i in range(numLines): 188 lineName = getLineName(i, numLines) 189 line = lines[i] 190 numSpaces = countStartingSpaces(line) 191 if line.strip() == "*/" and line.startswith(" "): 192 numSpaces -= 1 193 ignore = (numSpaces == len(line)) 194 if not ignore: 195 # pop back to a previous scope 196 while numSpaces < stack[-1].numSpaces: 197 stack = stack[:-1] 198 if numSpaces > stack[-1].numSpaces: 199 newChild = Block(numSpaces, lineName) 200 stack[-1].addChild(newChild) 201 stack.append(newChild) 202 stack[-1].addChild(TextBlock(line, lineName)) 203 if consolidateLeaves: 204 # Remove the nodes that the user considers to be leaf nodes 205 stack[0].consolidateFunctionBodies(emitLeaves) 206 #stack[0].display() 207 stack[0].apply(outputPath) 208 209 210if __name__ == "__main__": 211 main(sys.argv[1:]) 212