1*456ef56aSSadaf Ebrahimi#!/usr/bin/env python 2*456ef56aSSadaf Ebrahimi# 3*456ef56aSSadaf Ebrahimi# Copyright (C) 2016 Google, Inc. 4*456ef56aSSadaf Ebrahimi# 5*456ef56aSSadaf Ebrahimi# Licensed under the Apache License, Version 2.0 (the "License"); 6*456ef56aSSadaf Ebrahimi# you may not use this file except in compliance with the License. 7*456ef56aSSadaf Ebrahimi# You may obtain a copy of the License at: 8*456ef56aSSadaf Ebrahimi# 9*456ef56aSSadaf Ebrahimi# http://www.apache.org/licenses/LICENSE-2.0 10*456ef56aSSadaf Ebrahimi# 11*456ef56aSSadaf Ebrahimi# Unless required by applicable law or agreed to in writing, software 12*456ef56aSSadaf Ebrahimi# distributed under the License is distributed on an "AS IS" BASIS, 13*456ef56aSSadaf Ebrahimi# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*456ef56aSSadaf Ebrahimi# See the License for the specific language governing permissions and 15*456ef56aSSadaf Ebrahimi# limitations under the License. 16*456ef56aSSadaf Ebrahimi 17*456ef56aSSadaf Ebrahimiimport collections 18*456ef56aSSadaf Ebrahimiimport itertools 19*456ef56aSSadaf Ebrahimiimport os 20*456ef56aSSadaf Ebrahimiimport re 21*456ef56aSSadaf Ebrahimiimport subprocess 22*456ef56aSSadaf Ebrahimi 23*456ef56aSSadaf Ebrahimi# Parsing states: 24*456ef56aSSadaf Ebrahimi# STATE_INITIAL: looking for rpc or function defintion 25*456ef56aSSadaf Ebrahimi# STATE_RPC_DECORATOR: in the middle of a multi-line rpc definition 26*456ef56aSSadaf Ebrahimi# STATE_FUNCTION_DECORATOR: in the middle of a multi-line function definition 27*456ef56aSSadaf Ebrahimi# STATE_COMPLETE: done parsing a function 28*456ef56aSSadaf EbrahimiSTATE_INITIAL = 1 29*456ef56aSSadaf EbrahimiSTATE_RPC_DECORATOR = 2 30*456ef56aSSadaf EbrahimiSTATE_FUNCTION_DEFINITION = 3 31*456ef56aSSadaf EbrahimiSTATE_COMPLETE = 4 32*456ef56aSSadaf Ebrahimi 33*456ef56aSSadaf Ebrahimi# RE to match key=value tuples with matching quoting on value. 34*456ef56aSSadaf EbrahimiKEY_VAL_RE = re.compile(r''' 35*456ef56aSSadaf Ebrahimi (?P<key>\w+)\s*=\s* # Key consists of only alphanumerics 36*456ef56aSSadaf Ebrahimi (?P<quote>["']?) # Optional quote character. 37*456ef56aSSadaf Ebrahimi (?P<value>.*?) # Value is a non greedy match 38*456ef56aSSadaf Ebrahimi (?P=quote) # Closing quote equals the first. 39*456ef56aSSadaf Ebrahimi ($|,) # Entry ends with comma or end of string 40*456ef56aSSadaf Ebrahimi ''', re.VERBOSE) 41*456ef56aSSadaf Ebrahimi 42*456ef56aSSadaf Ebrahimi# RE to match a function definition and extract out the function name. 43*456ef56aSSadaf EbrahimiFUNC_RE = re.compile(r'.+\s+(\w+)\s*\(.*') 44*456ef56aSSadaf Ebrahimi 45*456ef56aSSadaf Ebrahimi 46*456ef56aSSadaf Ebrahimiclass Function(object): 47*456ef56aSSadaf Ebrahimi """Represents a RPC-exported function.""" 48*456ef56aSSadaf Ebrahimi 49*456ef56aSSadaf Ebrahimi def __init__(self, rpc_def, func_def): 50*456ef56aSSadaf Ebrahimi """Constructs a function object given its RPC and function signature.""" 51*456ef56aSSadaf Ebrahimi self._function = '' 52*456ef56aSSadaf Ebrahimi self._signature = '' 53*456ef56aSSadaf Ebrahimi self._description = '' 54*456ef56aSSadaf Ebrahimi self._returns = '' 55*456ef56aSSadaf Ebrahimi 56*456ef56aSSadaf Ebrahimi self._ParseRpcDefinition(rpc_def) 57*456ef56aSSadaf Ebrahimi self._ParseFunctionDefinition(func_def) 58*456ef56aSSadaf Ebrahimi 59*456ef56aSSadaf Ebrahimi def _ParseRpcDefinition(self, s): 60*456ef56aSSadaf Ebrahimi """Parse RPC definition.""" 61*456ef56aSSadaf Ebrahimi # collapse string concatenation 62*456ef56aSSadaf Ebrahimi s = s.replace('" + "', '') 63*456ef56aSSadaf Ebrahimi s = s.strip('()') 64*456ef56aSSadaf Ebrahimi for m in KEY_VAL_RE.finditer(s): 65*456ef56aSSadaf Ebrahimi if m.group('key') == 'description': 66*456ef56aSSadaf Ebrahimi self._description = m.group('value') 67*456ef56aSSadaf Ebrahimi if m.group('key') == 'returns': 68*456ef56aSSadaf Ebrahimi self._returns = m.group('value') 69*456ef56aSSadaf Ebrahimi 70*456ef56aSSadaf Ebrahimi def _ParseFunctionDefinition(self, s): 71*456ef56aSSadaf Ebrahimi """Parse function definition.""" 72*456ef56aSSadaf Ebrahimi # Remove some keywords we don't care about. 73*456ef56aSSadaf Ebrahimi s = s.replace('public ', '') 74*456ef56aSSadaf Ebrahimi s = s.replace('synchronized ', '') 75*456ef56aSSadaf Ebrahimi # Remove any throw specifications. 76*456ef56aSSadaf Ebrahimi s = re.sub('\s+throws.*', '', s) 77*456ef56aSSadaf Ebrahimi s = s.strip('{') 78*456ef56aSSadaf Ebrahimi # Remove all the RPC parameter annotations. 79*456ef56aSSadaf Ebrahimi s = s.replace('@RpcOptional ', '') 80*456ef56aSSadaf Ebrahimi s = s.replace('@RpcOptional() ', '') 81*456ef56aSSadaf Ebrahimi s = re.sub('@RpcParameter\s*\(.+?\)\s+', '', s) 82*456ef56aSSadaf Ebrahimi s = re.sub('@RpcDefault\s*\(.+?\)\s+', '', s) 83*456ef56aSSadaf Ebrahimi m = FUNC_RE.match(s) 84*456ef56aSSadaf Ebrahimi if m: 85*456ef56aSSadaf Ebrahimi self._function = m.group(1) 86*456ef56aSSadaf Ebrahimi self._signature = s.strip() 87*456ef56aSSadaf Ebrahimi 88*456ef56aSSadaf Ebrahimi @property 89*456ef56aSSadaf Ebrahimi def function(self): 90*456ef56aSSadaf Ebrahimi return self._function 91*456ef56aSSadaf Ebrahimi 92*456ef56aSSadaf Ebrahimi @property 93*456ef56aSSadaf Ebrahimi def signature(self): 94*456ef56aSSadaf Ebrahimi return self._signature 95*456ef56aSSadaf Ebrahimi 96*456ef56aSSadaf Ebrahimi @property 97*456ef56aSSadaf Ebrahimi def description(self): 98*456ef56aSSadaf Ebrahimi return self._description 99*456ef56aSSadaf Ebrahimi 100*456ef56aSSadaf Ebrahimi @property 101*456ef56aSSadaf Ebrahimi def returns(self): 102*456ef56aSSadaf Ebrahimi return self._returns 103*456ef56aSSadaf Ebrahimi 104*456ef56aSSadaf Ebrahimi 105*456ef56aSSadaf Ebrahimiclass DocGenerator(object): 106*456ef56aSSadaf Ebrahimi """Documentation genereator.""" 107*456ef56aSSadaf Ebrahimi 108*456ef56aSSadaf Ebrahimi def __init__(self, basepath): 109*456ef56aSSadaf Ebrahimi """Construct based on all the *Facade.java files in the given basepath.""" 110*456ef56aSSadaf Ebrahimi self._functions = collections.defaultdict(list) 111*456ef56aSSadaf Ebrahimi 112*456ef56aSSadaf Ebrahimi for path, dirs, files in os.walk(basepath): 113*456ef56aSSadaf Ebrahimi for f in files: 114*456ef56aSSadaf Ebrahimi if f.endswith('Facade.java'): 115*456ef56aSSadaf Ebrahimi self._Parse(os.path.join(path, f)) 116*456ef56aSSadaf Ebrahimi 117*456ef56aSSadaf Ebrahimi def _Parse(self, filename): 118*456ef56aSSadaf Ebrahimi """Parser state machine for a single file.""" 119*456ef56aSSadaf Ebrahimi state = STATE_INITIAL 120*456ef56aSSadaf Ebrahimi self._current_rpc = '' 121*456ef56aSSadaf Ebrahimi self._current_function = '' 122*456ef56aSSadaf Ebrahimi 123*456ef56aSSadaf Ebrahimi with open(filename, 'r') as f: 124*456ef56aSSadaf Ebrahimi for line in f.readlines(): 125*456ef56aSSadaf Ebrahimi line = line.strip() 126*456ef56aSSadaf Ebrahimi if state == STATE_INITIAL: 127*456ef56aSSadaf Ebrahimi state = self._ParseLineInitial(line) 128*456ef56aSSadaf Ebrahimi elif state == STATE_RPC_DECORATOR: 129*456ef56aSSadaf Ebrahimi state = self._ParseLineRpcDecorator(line) 130*456ef56aSSadaf Ebrahimi elif state == STATE_FUNCTION_DEFINITION: 131*456ef56aSSadaf Ebrahimi state = self._ParseLineFunctionDefinition(line) 132*456ef56aSSadaf Ebrahimi 133*456ef56aSSadaf Ebrahimi if state == STATE_COMPLETE: 134*456ef56aSSadaf Ebrahimi self._EmitFunction(filename) 135*456ef56aSSadaf Ebrahimi state = STATE_INITIAL 136*456ef56aSSadaf Ebrahimi 137*456ef56aSSadaf Ebrahimi def _ParseLineInitial(self, line): 138*456ef56aSSadaf Ebrahimi """Parse a line while in STATE_INITIAL.""" 139*456ef56aSSadaf Ebrahimi if line.startswith('@Rpc('): 140*456ef56aSSadaf Ebrahimi self._current_rpc = line[4:] 141*456ef56aSSadaf Ebrahimi if not line.endswith(')'): 142*456ef56aSSadaf Ebrahimi # Multi-line RPC definition 143*456ef56aSSadaf Ebrahimi return STATE_RPC_DECORATOR 144*456ef56aSSadaf Ebrahimi elif line.startswith('public'): 145*456ef56aSSadaf Ebrahimi self._current_function = line 146*456ef56aSSadaf Ebrahimi if not line.endswith('{'): 147*456ef56aSSadaf Ebrahimi # Multi-line function definition 148*456ef56aSSadaf Ebrahimi return STATE_FUNCTION_DEFINITION 149*456ef56aSSadaf Ebrahimi else: 150*456ef56aSSadaf Ebrahimi return STATE_COMPLETE 151*456ef56aSSadaf Ebrahimi return STATE_INITIAL 152*456ef56aSSadaf Ebrahimi 153*456ef56aSSadaf Ebrahimi def _ParseLineRpcDecorator(self, line): 154*456ef56aSSadaf Ebrahimi """Parse a line while in STATE_RPC_DECORATOR.""" 155*456ef56aSSadaf Ebrahimi self._current_rpc += ' ' + line 156*456ef56aSSadaf Ebrahimi if line.endswith(')'): 157*456ef56aSSadaf Ebrahimi # Done with RPC definition 158*456ef56aSSadaf Ebrahimi return STATE_INITIAL 159*456ef56aSSadaf Ebrahimi else: 160*456ef56aSSadaf Ebrahimi # Multi-line RPC definition 161*456ef56aSSadaf Ebrahimi return STATE_RPC_DECORATOR 162*456ef56aSSadaf Ebrahimi 163*456ef56aSSadaf Ebrahimi def _ParseLineFunctionDefinition(self, line): 164*456ef56aSSadaf Ebrahimi """Parse a line while in STATE_FUNCTION_DEFINITION.""" 165*456ef56aSSadaf Ebrahimi self._current_function += ' ' + line 166*456ef56aSSadaf Ebrahimi if line.endswith('{'): 167*456ef56aSSadaf Ebrahimi # Done with function definition 168*456ef56aSSadaf Ebrahimi return STATE_COMPLETE 169*456ef56aSSadaf Ebrahimi else: 170*456ef56aSSadaf Ebrahimi # Multi-line function definition 171*456ef56aSSadaf Ebrahimi return STATE_FUNCTION_DEFINITION 172*456ef56aSSadaf Ebrahimi 173*456ef56aSSadaf Ebrahimi def _EmitFunction(self, filename): 174*456ef56aSSadaf Ebrahimi """Store a function definition from the current parse state.""" 175*456ef56aSSadaf Ebrahimi if self._current_rpc and self._current_function: 176*456ef56aSSadaf Ebrahimi module = os.path.basename(filename)[0:-5] 177*456ef56aSSadaf Ebrahimi f = Function(self._current_rpc, self._current_function) 178*456ef56aSSadaf Ebrahimi if f.function: 179*456ef56aSSadaf Ebrahimi self._functions[module].append(f) 180*456ef56aSSadaf Ebrahimi 181*456ef56aSSadaf Ebrahimi self._current_rpc = None 182*456ef56aSSadaf Ebrahimi self._current_function = None 183*456ef56aSSadaf Ebrahimi 184*456ef56aSSadaf Ebrahimi def WriteOutput(self, filename): 185*456ef56aSSadaf Ebrahimi git_rev = None 186*456ef56aSSadaf Ebrahimi try: 187*456ef56aSSadaf Ebrahimi git_rev = subprocess.check_output('git rev-parse HEAD', 188*456ef56aSSadaf Ebrahimi shell=True).strip() 189*456ef56aSSadaf Ebrahimi except subprocess.CalledProcessError as e: 190*456ef56aSSadaf Ebrahimi # Getting the commit ID is optional; we continue if we cannot get it 191*456ef56aSSadaf Ebrahimi pass 192*456ef56aSSadaf Ebrahimi 193*456ef56aSSadaf Ebrahimi with open(filename, 'w') as f: 194*456ef56aSSadaf Ebrahimi if git_rev: 195*456ef56aSSadaf Ebrahimi f.write('Generated at commit `%s`\n\n' % git_rev) 196*456ef56aSSadaf Ebrahimi # Write table of contents 197*456ef56aSSadaf Ebrahimi for module in sorted(self._functions.keys()): 198*456ef56aSSadaf Ebrahimi f.write('**%s**\n\n' % module) 199*456ef56aSSadaf Ebrahimi for func in self._functions[module]: 200*456ef56aSSadaf Ebrahimi f.write(' * [%s](#%s)\n' % 201*456ef56aSSadaf Ebrahimi (func.function, func.function.lower())) 202*456ef56aSSadaf Ebrahimi f.write('\n') 203*456ef56aSSadaf Ebrahimi 204*456ef56aSSadaf Ebrahimi f.write('# Method descriptions\n\n') 205*456ef56aSSadaf Ebrahimi for func in itertools.chain.from_iterable( 206*456ef56aSSadaf Ebrahimi self._functions.values()): 207*456ef56aSSadaf Ebrahimi f.write('## %s\n\n' % func.function) 208*456ef56aSSadaf Ebrahimi f.write('```\n') 209*456ef56aSSadaf Ebrahimi f.write('%s\n\n' % func.signature) 210*456ef56aSSadaf Ebrahimi f.write('%s\n' % func.description) 211*456ef56aSSadaf Ebrahimi if func.returns: 212*456ef56aSSadaf Ebrahimi if func.returns.lower().startswith('return'): 213*456ef56aSSadaf Ebrahimi f.write('\n%s\n' % func.returns) 214*456ef56aSSadaf Ebrahimi else: 215*456ef56aSSadaf Ebrahimi f.write('\nReturns %s\n' % func.returns) 216*456ef56aSSadaf Ebrahimi f.write('```\n\n') 217*456ef56aSSadaf Ebrahimi 218*456ef56aSSadaf Ebrahimi# Main 219*456ef56aSSadaf Ebrahimibasepath = os.path.abspath(os.path.join(os.path.dirname( 220*456ef56aSSadaf Ebrahimi os.path.realpath(__file__)), '..')) 221*456ef56aSSadaf Ebrahimig = DocGenerator(basepath) 222*456ef56aSSadaf Ebrahimig.WriteOutput(os.path.join(basepath, 'Docs/ApiReference.md')) 223