Index: tools/nixysa/nixysa/js_header_generator.py |
=================================================================== |
--- tools/nixysa/nixysa/js_header_generator.py (revision 0) |
+++ tools/nixysa/nixysa/js_header_generator.py (revision 0) |
@@ -0,0 +1,534 @@ |
+#!/usr/bin/python2.4 |
+# |
+# Copyright 2008 Google Inc. |
+# |
+# Licensed under the Apache License, Version 2.0 (the "License"); |
+# you may not use this file except in compliance with the License. |
+# You may obtain a copy of the License at |
+# |
+# http://www.apache.org/licenses/LICENSE-2.0 |
+# |
+# Unless required by applicable law or agreed to in writing, software |
+# distributed under the License is distributed on an "AS IS" BASIS, |
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
+# See the License for the specific language governing permissions and |
+# limitations under the License. |
+ |
+"""Javascript JSCompiler and JSDocToolkit file generator. |
+ |
+This module generates JSCompiler and JSDocToolkit file for a |
+javascript documentation file from the parsed syntax tree. |
+""" |
+ |
+import gflags |
+import re |
+import cpp_utils |
+import js_utils |
+import java_utils |
+import naming |
+import log |
+import syntax_tree |
+ |
+ |
+class UndocumentedError(Exception): |
+ """Error raised when a member is undocumented.""" |
+ |
+ def __init__(self, obj): |
+ Exception.__init__(self) |
+ self.object = obj |
+ |
+ |
+class JSHeaderGenerator(object): |
+ """Header generator class. |
+ |
+ This class takes care of the details of generating JavaScript JSDocToolkit |
+ format files suitable for either documenation generation or for externs for |
+ the JSCompiler. |
+ |
+ It contains a list of functions named after each of the Definition classes in |
+ syntax_tree, with a common signature. The appropriate function will be called |
+ for each definition, to generate the code for that definition. |
+ |
+ Attributes: |
+ _output_dir: output directory |
+ force_documentation: whether to force all members to have documentation |
+ """ |
+ _start_whitespace_re = re.compile(r'^(\s+)') |
+ _param_re = re.compile(r'\\param (\w+) ') |
+ _return_re = re.compile(r'\\return ') |
+ _code_re = re.compile(r'\\code') |
+ _li_re = re.compile(r'\\li') |
+ _var_re = re.compile(r'\\var') |
+ _sa_re = re.compile(r'\\sa') |
+ _endcode_re = re.compile(r'\\endcode') |
+ |
+ def __init__(self, output_dir): |
+ self._output_dir = output_dir |
+ |
+ def GetSectionFromAttributes(self, parent_section, defn): |
+ """Gets the code section appropriate for a given definition. |
+ |
+ Classes have 3 definition sections: private, protected and public. This |
+ function will pick one of the sections, based on the attributes of the |
+ definition, if its parent is a class. For other scopes (namespaces) it will |
+ return the parent scope main section. |
+ |
+ Args: |
+ parent_section: the main section for the parent scope. |
+ defn: the definition. |
+ |
+ Returns: |
+ the appropriate section. |
+ """ |
+ if defn.parent and defn.parent.defn_type == 'Class': |
+ if 'private' in defn.attributes: |
+ return parent_section.GetSection('private:') or parent_section |
+ elif 'protected' in defn.attributes: |
+ return parent_section.GetSection('protected:') or parent_section |
+ else: |
+ return parent_section.GetSection('public:') or parent_section |
+ else: |
+ return parent_section |
+ |
+ def Verbatim(self, parent_section, scope, obj): |
+ """Generates the code for a Verbatim definition. |
+ |
+ Verbatim definitions being written for a particular type of output file, |
+ this function will check the 'verbatim' attribute, and only emit the |
+ verbatim code if it is 'js_header'. |
+ |
+ Args: |
+ parent_section: the main section of the parent scope. |
+ scope: the parent scope. |
+ obj: the Verbatim definition. |
+ |
+ Returns: |
+ a list of (boolean, Definition) pairs, of all the types that need |
+ to be declared (boolean is False) or defined (boolean is True) before |
+ this definition. |
+ """ |
+ scope = scope # silence gpylint. |
+ try: |
+ if obj.attributes['verbatim'] == 'js_header': |
+ section = self.GetSectionFromAttributes(parent_section, obj) |
+ section.EmitCode(obj.text) |
+ except KeyError: |
+ log.SourceWarning(obj.source, |
+ 'ignoring verbatim with no verbatim attribute') |
+ |
+ def Typedef(self, parent_section, scope, obj): |
+ """Generates the code for a Typedef definition. |
+ |
+ Args: |
+ parent_section: the main section of the parent scope. |
+ scope: the parent scope. |
+ obj: the Typedef definition. |
+ """ |
+ # typedefs do not get exported for JavaScript. |
+ # silence lint |
+ parent_section, scope, obj = parent_section, scope, obj |
+ |
+ def Variable(self, parent_section, scope, obj): |
+ """Generates the code for a Variable definition. |
+ |
+ This function will generate the member/global variable declaration, as well |
+ as the setter/getter functions if specified in the attributes. |
+ |
+ Args: |
+ parent_section: the main section of the parent scope. |
+ scope: the parent scope. |
+ obj: the Variable definition. |
+ """ |
+ member_section = self.GetSectionFromAttributes(parent_section, obj) |
+ |
+ bm = obj.type_defn.binding_model |
+ id_prefix = js_utils.GetFullyQualifiedScopePrefix(scope) |
+ proto = 'prototype.' |
+ # Note: There is no static in javascript |
+ field_name = naming.Normalize(obj.name, naming.Java) |
+ extra = '' |
+ if 'getter' in obj.attributes and 'setter' not in obj.attributes: |
+ extra = '\n\nThis property is read-only.' |
+ elif 'getter' not in obj.attributes and 'setter' in obj.attributes: |
+ extra = '\n\nThis property is write-only.' |
+ type_string = '\n@type {%s}' % js_utils.GetFullyQualifiedTypeName( |
+ obj.type_defn) |
+ self.Documentation(member_section, obj, extra + type_string) |
+ undef = '' |
+ if gflags.FLAGS['properties-equal-undefined'].value: |
+ undef = ' = undefined' |
+ member_section.EmitCode('%s%s%s%s;' % (id_prefix, proto, field_name, undef)) |
+ # Note: There are no getter/setter in javascript |
+ |
+ def Function(self, parent_section, scope, obj): |
+ """Generates the code for a Function definition. |
+ |
+ Args: |
+ parent_section: the main section of the parent scope. |
+ scope: the parent scope. |
+ obj: the Function definition. |
+ """ |
+ section = self.GetSectionFromAttributes(parent_section, obj) |
+ return_type = '**not defined**' |
+ if obj.type_defn: |
+ return_type = js_utils.GetFullyQualifiedTypeName(obj.type_defn) |
+ doc_info = self.Documentation(section, obj, '') |
+ if doc_info: |
+ # there was a return statement so the return type better NOT be void |
+ if return_type == 'void': |
+ log.SourceError(obj.source, |
+ 'return found for void function: %s' % obj.name) |
+ else: |
+ # there was no return statement so the return type better be void |
+ if (not return_type == 'void') and (not return_type == '**not defined**'): |
+ log.SourceError(obj.source, |
+ 'return missing for non void function: %s' % obj.name) |
+ prototype = js_utils.GetFunctionPrototype(scope, obj, True) |
+ section.EmitCode(prototype) |
+ |
+ def OverloadedFunction(self, parent_section, scope, func_array): |
+ """Generates the code for an Overloaded Function. |
+ |
+ Args: |
+ parent_section: the main section of the parent scope. |
+ scope: the parent scope. |
+ func_array: an array of function definition objects. |
+ """ |
+ if gflags.FLAGS['overloaded-function-docs'].value: |
+ count = 0 |
+ for func in func_array: |
+ old_name = func.name |
+ func.name = "%sxxxOVERLOADED%dxxx" % (old_name, count) |
+ self.Function(parent_section, scope, func) |
+ func.name = old_name |
+ count += 1 |
+ return |
+ |
+ # merge the params. |
+ params = [] |
+ min_params = len(func_array[0].params) |
+ for func in func_array: |
+ if len(func.params) < min_params: |
+ min_params = len(func.params) |
+ index = 0 |
+ # we only take the comment from the first function that documents |
+ # a parameter at this position. |
+ comments, param_comments = js_utils.GetCommentsForParams(func) |
+ for param in func.params: |
+ if len(params) <= index: |
+ params.append({'orig_name': param.name, |
+ 'new_name': param.name, |
+ 'docs' : param_comments[param.name], |
+ 'params': [{'param': param, 'func': func}]}) |
+ else: |
+ params[index]['params'].append({'param': param, 'func': func}) |
+ index += 1 |
+ |
+ # rename the params. |
+ index = 0 |
+ opt = '' |
+ for param in params: |
+ if index >= min_params: |
+ opt = 'opt_' |
+ param['new_name'] = '%sparam%d' % (opt, index + 1) |
+ index += 1 |
+ |
+ # generate param comments. |
+ param_comments = [] |
+ for param in params: |
+ if len(param['params']) == 1: |
+ param_string = js_utils.GetFunctionParamType( |
+ param['params'][0]['func'], |
+ param['params'][0]['param'].name) |
+ else: |
+ union_strings = set() |
+ for option in param['params']: |
+ union_strings.add(js_utils.GetFunctionParamType(option['func'], |
+ option['param'].name)) |
+ param_string = '|'.join(union_strings) |
+ if len(union_strings) > 1: |
+ param_string = '(' + param_string + ')' |
+ param_comments += ['@param {%s} %s %s' % (param_string, param['new_name'], |
+ param['docs'])] |
+ |
+ first_func = func_array[0] |
+ |
+ # use just the first function's comments. |
+ func_comments = (js_utils.GetCommentsForParams(first_func)[0] + |
+ '\n'.join(param_comments)) |
+ |
+ first_func.attributes['__docs'] = func_comments |
+ section = self.GetSectionFromAttributes(parent_section, first_func) |
+ self.Documentation(section, first_func, '') |
+ |
+ param_strings = [] |
+ for param in params: |
+ param_strings += [param['new_name']] |
+ |
+ param_string = ', '.join(param_strings) |
+ id_prefix = js_utils.GetFullyQualifiedScopePrefix(scope) |
+ proto = 'prototype.' |
+ prototype = '%s%s%s = function(%s) { };' % ( |
+ id_prefix, proto, naming.Normalize(first_func.name, naming.Java), |
+ param_string) |
+ section.EmitCode(prototype) |
+ |
+ def Callback(self, parent_section, scope, obj): |
+ """Generates the code for a Callback definition. |
+ |
+ Args: |
+ parent_section: the main section of the parent scope. |
+ scope: the parent scope. |
+ obj: the Class definition. |
+ """ |
+ parent_section, scope, obj = parent_section, scope, obj |
+ |
+ def Class(self, parent_section, scope, obj): |
+ """Generates the code for a Class definition. |
+ |
+ This function will recursively generate the code for all the definitions |
+ inside the class. These definitions will be output into one of 3 sections |
+ (private, protected, public), depending on their attributes. These |
+ individual sections will only be output if they are not empty. |
+ |
+ Args: |
+ parent_section: the main section of the parent scope. |
+ scope: the parent scope. |
+ obj: the Class definition. |
+ """ |
+ section = self.GetSectionFromAttributes(parent_section, obj).CreateSection( |
+ obj.name) |
+ id_prefix = js_utils.GetFullyQualifiedScopePrefix(scope) |
+ if 'marshaled' in obj.attributes and not ('no_marshaled_docs' in obj.attributes): |
+ found = False |
+ for member_defn in obj.defn_list: |
+ if member_defn.name == 'marshaled': |
+ if isinstance(member_defn, syntax_tree.Variable): |
+ type_name = js_utils.GetFullyQualifiedTypeName( |
+ member_defn.type_defn) |
+ self.Documentation(section, obj, '\n@type {%s}' % type_name) |
+ section.EmitCode('%s%s = goog.typedef;' % (id_prefix, obj.name)) |
+ found = True |
+ break |
+ if not found: |
+ log.SourceError(obj.source, |
+ ('no marshaled function found for %s' % obj.name)) |
+ else: |
+ extends = '' |
+ if obj.base_type: |
+ base = js_utils.GetFullyQualifiedTypeName(obj.base_type) |
+ if base[0] == '!': |
+ base = base[1:] |
+ extends = '\n@extends {%s}' % base |
+ self.Documentation(section, obj, '\n@constructor' + extends) |
+ section.EmitCode('%s%s = function() { };' % (id_prefix, obj.name)) |
+ |
+ public_section = section.CreateSection('public:') |
+ protected_section = section.CreateSection('protected:') |
+ private_section = section.CreateSection('private:') |
+ self.DefinitionList(section, obj, obj.defn_list) |
+ # TODO(gman): Delete protected and private sections. Those docs |
+ # are not needed for javascript. |
+ |
+ def Namespace(self, parent_section, scope, obj): |
+ """Generates the code for a Namespace definition. |
+ |
+ This function will recursively generate the code for all the definitions |
+ inside the namespace. |
+ |
+ Args: |
+ parent_section: the main section of the parent scope. |
+ scope: the parent scope. |
+ obj: the Namespace definition. |
+ """ |
+ self.DefinitionList(parent_section, obj, obj.defn_list) |
+ |
+ def Typename(self, parent_section, scope, obj): |
+ """Generates the code for a Typename definition. |
+ |
+ Since typenames (undefined types) cannot be expressed in C++, this function |
+ will not output code. The definition may be output with a verbatim section. |
+ |
+ Args: |
+ parent_section: the main section of the parent scope. |
+ scope: the parent scope. |
+ obj: the Typename definition. |
+ """ |
+ # silence gpylint. |
+ parent_section, scope, obj = parent_section, scope, obj |
+ |
+ def Enum(self, parent_section, scope, obj): |
+ """Generates the code for an Enum definition. |
+ |
+ Args: |
+ parent_section: the main section of the parent scope. |
+ scope: the parent scope. |
+ obj: the Enum definition. |
+ """ |
+ section = self.GetSectionFromAttributes(parent_section, obj) |
+ type_string = 'number' |
+ id_prefix = js_utils.GetFullyQualifiedScopePrefix(scope) |
+ self.Documentation(parent_section, obj, '\n@enum {%s}' % type_string) |
+ section.EmitCode('%s%s = {' % (id_prefix, obj.name)) |
+ count = 0 |
+ for ii in range(0, len(obj.values)): |
+ value = obj.values[ii] |
+ comma = ',' |
+ if ii == len(obj.values) - 1: |
+ comma = '' |
+ if value.value is None: |
+ section.EmitCode('%s: %d%s' % (value.name, count, comma)) |
+ else: |
+ section.EmitCode('%s: %s%s' % (value.name, value.value, comma)) |
+ count = int(value.value) |
+ count += 1 |
+ section.EmitCode('};') |
+ |
+ def DefinitionList(self, parent_section, scope, defn_list): |
+ """Generates the code for all the definitions in a list. |
+ |
+ Args: |
+ parent_section: the main section of the parent scope. |
+ scope: the parent scope. |
+ defn_list: the list of definitions. |
+ Returns: |
+ True if there was a '\returns' tag. |
+ """ |
+ # extract functions and merge functions of the same name and process all |
+ # non-functions. |
+ function_by_name = {} |
+ for obj in defn_list: |
+ if 'nojs' in obj.attributes or 'nodocs' in obj.attributes: |
+ continue |
+ if obj.defn_type == 'Function': |
+ if obj.name in function_by_name: |
+ function_by_name[obj.name].append(obj) |
+ else: |
+ function_by_name[obj.name] = [obj] |
+ else: |
+ func = getattr(self, obj.defn_type) |
+ func(parent_section, scope, obj) |
+ |
+ # process functions. |
+ for func_array in function_by_name.values(): |
+ if len(func_array) == 1: |
+ self.Function(parent_section, scope, func_array[0]) |
+ else: |
+ self.OverloadedFunction(parent_section, scope, func_array) |
+ |
+ def Documentation(self, parent_section, obj, extra_doc): |
+ """Generates the documentation code. |
+ |
+ Args: |
+ parent_section: the main section of the parent scope. |
+ obj: the object to be documented; may be class, function, enum or field. |
+ extra_doc: extra documenation information to be put in comments |
+ Raises: |
+ UndocumentedError: an error if there is no documentation |
+ """ |
+ try: |
+ section = self.GetSectionFromAttributes(parent_section, obj) |
+ comment_lines = (obj.attributes['__docs'] + extra_doc).splitlines() |
+ # Break up text and insert comment formatting |
+ section.EmitCode('/**') |
+ # move all blank lines at start of docs. |
+ while comment_lines and comment_lines[0].strip() == '': |
+ comment_lines = comment_lines[1:] |
+ # figure out how much whitespace is on first line and remove |
+ # the same amount from all lines |
+ start_index = 0 |
+ if comment_lines: |
+ match = self._start_whitespace_re.search(comment_lines[0]) |
+ if match: |
+ start_index = len(match.group(1)) |
+ flags = {'eat_lines': False}; |
+ found_returns = False |
+ for line in comment_lines: |
+ if line[0:start_index].strip() == '': |
+ line = line[start_index:] |
+ if line.startswith('\\'): |
+ flags['eat_lines'] = False |
+ if self._return_re.match(line): |
+ found_returns = True |
+ line = self._param_re.sub( |
+ lambda match: js_utils.GetParamSpec(obj, match.group(1)), |
+ line) |
+ line = self._return_re.sub( |
+ lambda match: js_utils.GetReturnSpec(obj, flags) + ' ', |
+ line) |
+ line = self._code_re.sub('<pre>', line) |
+ line = self._li_re.sub('<li>', line) |
+ line = self._var_re.sub('', line) |
+ line = self._sa_re.sub('@see', line) |
+ line = self._endcode_re.sub('</pre>', line) |
+ if not flags['eat_lines']: |
+ section.EmitCode(' * %s' % (line)) |
+ section.EmitCode(' */') |
+ return found_returns |
+ |
+ except KeyError: |
+ log.SourceError(obj.source, 'Documentation not found') |
+ |
+ def FieldFunctionDocumentation(self, member_section, description, |
+ type_string, field_name): |
+ """Automatically generate the get/set function documentation code. |
+ |
+ Args: |
+ member_section: the main section of the getter/setter scope. |
+ description: describes the field function. |
+ type_string: string defining field type. |
+ field_name: getter/setter field name. |
+ """ |
+ member_section.EmitCode('/*!') |
+ member_section.EmitCode('* %s for %s %s' % |
+ (description, type_string, field_name)) |
+ member_section.EmitCode('*/') |
+ |
+ def Generate(self, idl_file, namespace, defn_list): |
+ """Generates the header file. |
+ |
+ Args: |
+ idl_file: the source IDL file containing the definitions, as a |
+ idl_parser.File instance. |
+ namespace: a Definition for the global namespace. |
+ defn_list: the list of top-level definitions. |
+ |
+ Returns: |
+ a js_utils.JavascriptFileWriter that contains the javascript header file |
+ code. |
+ |
+ Raises: |
+ CircularDefinition: circular definitions were found in the file. |
+ """ |
+ filename = idl_file.basename + '.js' |
+ writer = js_utils.JavascriptFileWriter('%s/%s' % (self._output_dir, |
+ filename), True) |
+ code_section = writer.CreateSection('defns') |
+ self.DefinitionList(code_section, namespace, defn_list) |
+ return writer |
+ |
+ |
+def ProcessFiles(output_dir, pairs, namespace): |
+ """Generates the headers for all input files. |
+ |
+ Args: |
+ output_dir: the output directory. |
+ pairs: a list of (idl_parser.File, syntax_tree.Definition list) describing |
+ the list of top-level definitions in each source file. |
+ namespace: a syntax_tree.Namespace for the global namespace. |
+ |
+ Returns: |
+ a list of js_utils.JavascriptFileWriter, one for each output header file. |
+ """ |
+ generator = JSHeaderGenerator(output_dir) |
+ writer_list = [] |
+ for (f, defn) in pairs: |
+ writer_list.append(generator.Generate(f, namespace, defn)) |
+ return writer_list |
+ |
+ |
+def main(): |
+ pass |
+ |
+if __name__ == '__main__': |
+ main() |
Property changes on: tools/nixysa/nixysa/js_header_generator.py |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |