xref: /aosp_15_r20/external/sl4a/Docs/generate_api_reference_md.py (revision 456ef56af69dcf0481dd36cc45216c4002d72fa3)
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