| #!/usr/bin/env python | 
 | # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
 | # Use of this source code is governed by a BSD-style license that can be | 
 | # found in the LICENSE file. | 
 |  | 
 | import sys | 
 |  | 
 | from idl_log import ErrOut, InfoOut, WarnOut | 
 | from idl_option import GetOption, Option, ParseOptions | 
 | from idl_parser import ParseFiles | 
 |  | 
 | GeneratorList = [] | 
 |  | 
 | Option('out', 'List of output files', default='') | 
 | Option('release', 'Which release to generate.', default='') | 
 | Option('range', 'Which ranges in the form of MIN,MAX.', default='start,end') | 
 |  | 
 | class Generator(object): | 
 |   """Base class for generators. | 
 |  | 
 |   This class provides a mechanism for adding new generator objects to the IDL | 
 |   driver.  To use this class override the GenerateRelease and GenerateRange | 
 |   members, and instantiate one copy of the class in the same module which | 
 |   defines it to register the generator.  After the AST is generated, call the | 
 |   static Run member which will check every registered generator to see which | 
 |   ones have been enabled through command-line options.  To enable a generator | 
 |   use the switches: | 
 |     --<sname> : To enable with defaults | 
 |     --<sname>_opt=<XXX,YYY=y> : To enable with generator specific options. | 
 |  | 
 |   NOTE:  Generators still have access to global options | 
 |   """ | 
 |  | 
 |   def __init__(self, name, sname, desc): | 
 |     self.name = name | 
 |     self.run_switch = Option(sname, desc) | 
 |     self.opt_switch = Option(sname + '_opt', 'Options for %s.' % sname, | 
 |                              default='') | 
 |     GeneratorList.append(self) | 
 |     self.errors = 0 | 
 |     self.skip_list = [] | 
 |  | 
 |   def Error(self, msg): | 
 |     ErrOut.Log('Error %s : %s' % (self.name, msg)) | 
 |     self.errors += 1 | 
 |  | 
 |   def GetRunOptions(self): | 
 |     options = {} | 
 |     option_list = self.opt_switch.Get() | 
 |     if option_list: | 
 |       option_list = option_list.split(',') | 
 |       for opt in option_list: | 
 |         offs = opt.find('=') | 
 |         if offs > 0: | 
 |           options[opt[:offs]] = opt[offs+1:] | 
 |         else: | 
 |           options[opt] = True | 
 |       return options | 
 |     if self.run_switch.Get(): | 
 |       return options | 
 |     return None | 
 |  | 
 |   def Generate(self, ast, options): | 
 |     self.errors = 0 | 
 |  | 
 |     rangestr = GetOption('range') | 
 |     releasestr = GetOption('release') | 
 |  | 
 |     print "Found releases: %s" % ast.releases | 
 |  | 
 |     # Generate list of files to ignore due to errors | 
 |     for filenode in ast.GetListOf('File'): | 
 |       # If this file has errors, skip it | 
 |       if filenode.GetProperty('ERRORS') > 0: | 
 |         self.skip_list.append(filenode) | 
 |         continue | 
 |  | 
 |     # Check for a range option which over-rides a release option | 
 |     if not releasestr and rangestr: | 
 |       range_list = rangestr.split(',') | 
 |       if len(range_list) != 2: | 
 |         self.Error('Failed to generate for %s, incorrect range: "%s"' % | 
 |                    (self.name, rangestr)) | 
 |       else: | 
 |         vmin = range_list[0] | 
 |         vmax = range_list[1] | 
 |  | 
 |         # Generate 'start' and 'end' represent first and last found. | 
 |         if vmin == 'start': | 
 |             vmin = ast.releases[0] | 
 |         if vmax == 'end': | 
 |             vmax = ast.releases[-1] | 
 |  | 
 |         vmin = ast.releases.index(vmin) | 
 |         vmax = ast.releases.index(vmax) + 1 | 
 |         releases = ast.releases[vmin:vmax] | 
 |         InfoOut.Log('Generate range %s of %s.' % (rangestr, self.name)) | 
 |         ret = self.GenerateRange(ast, releases, options) | 
 |         if ret < 0: | 
 |           self.Error('Failed to generate range %s : %s.' %(vmin, vmax)) | 
 |         else: | 
 |           InfoOut.Log('%s wrote %d files.' % (self.name, ret)) | 
 |     # Otherwise this should be a single release generation | 
 |     else: | 
 |       if releasestr == 'start': | 
 |         releasestr = ast.releases[0] | 
 |       if releasestr == 'end': | 
 |         releasestr = ast.releases[-1] | 
 |  | 
 |       if releasestr > ast.releases[-1]: | 
 |         InfoOut.Log('There is no unique release for %s, using last release.' % | 
 |                     releasestr) | 
 |         releasestr = ast.releases[-1] | 
 |  | 
 |       if releasestr not in ast.releases: | 
 |         self.Error('Release %s not in [%s].' % | 
 |                    (releasestr, ', '.join(ast.releases))) | 
 |  | 
 |       if releasestr: | 
 |         InfoOut.Log('Generate release %s of %s.' % (releasestr, self.name)) | 
 |         ret = self.GenerateRelease(ast, releasestr, options) | 
 |         if ret < 0: | 
 |           self.Error('Failed to generate release %s.' % releasestr) | 
 |         else: | 
 |           InfoOut.Log('%s wrote %d files.' % (self.name, ret)) | 
 |  | 
 |       else: | 
 |         self.Error('No range or release specified for %s.' % releasestr) | 
 |     return self.errors | 
 |  | 
 |   def GenerateRelease(self, ast, release, options): | 
 |     __pychecker__ = 'unusednames=ast,release,options' | 
 |     self.Error("Undefined release generator.") | 
 |     return 0 | 
 |  | 
 |   def GenerateRange(self, ast, releases, options): | 
 |     __pychecker__ = 'unusednames=ast,releases,options' | 
 |     self.Error("Undefined range generator.") | 
 |     return 0 | 
 |  | 
 |   @staticmethod | 
 |   def Run(ast): | 
 |     fail_count = 0 | 
 |  | 
 |     # Check all registered generators if they should run. | 
 |     for gen in GeneratorList: | 
 |       options = gen.GetRunOptions() | 
 |       if options is not None: | 
 |         if gen.Generate(ast, options): | 
 |           fail_count += 1 | 
 |     return fail_count | 
 |  | 
 |  | 
 | class GeneratorByFile(Generator): | 
 |   """A simplified generator that generates one output file per IDL source file. | 
 |  | 
 |   A subclass of Generator for use of generators which have a one to one | 
 |   mapping between IDL sources and output files. | 
 |  | 
 |   Derived classes should define GenerateFile. | 
 |   """ | 
 |  | 
 |   def GenerateFile(self, filenode, releases, options): | 
 |     """Generates an output file from the IDL source. | 
 |  | 
 |     Returns true if the generated file is different than the previously | 
 |     generated file. | 
 |     """ | 
 |     __pychecker__ = 'unusednames=filenode,releases,options' | 
 |     self.Error("Undefined release generator.") | 
 |     return 0 | 
 |  | 
 |   def GenerateRelease(self, ast, release, options): | 
 |     return self.GenerateRange(ast, [release], options) | 
 |  | 
 |   def GenerateRange(self, ast, releases, options): | 
 |     # Get list of out files | 
 |     outlist = GetOption('out') | 
 |     if outlist: outlist = outlist.split(',') | 
 |  | 
 |     skipList = [] | 
 |     cnt = 0 | 
 |     for filenode in ast.GetListOf('File'): | 
 |       # Ignore files with errors | 
 |       if filenode in self.skip_list: | 
 |         continue | 
 |  | 
 |       # Skip this file if not required | 
 |       if outlist and filenode.GetName() not in outlist: | 
 |         continue | 
 |  | 
 |       # Create the output file and increment out count if there was a delta | 
 |       if self.GenerateFile(filenode, releases, options): | 
 |         cnt = cnt + 1 | 
 |  | 
 |     for filenode in skipList: | 
 |       errcnt = filenode.GetProperty('ERRORS') | 
 |       ErrOut.Log('%s : Skipped because of %d errors.' % ( | 
 |           filenode.GetName(), errcnt)) | 
 |  | 
 |     if skipList: | 
 |       return -len(skipList) | 
 |  | 
 |     if GetOption('diff'): | 
 |       return -cnt | 
 |     return cnt | 
 |  | 
 |  | 
 | check_release = 0 | 
 | check_range = 0 | 
 |  | 
 | class GeneratorReleaseTest(Generator): | 
 |   def GenerateRelease(self, ast, release, options = {}): | 
 |     __pychecker__ = 'unusednames=ast,release,options' | 
 |     global check_release | 
 |     check_map = { | 
 |       'so_long': True, | 
 |       'MyOpt': 'XYZ', | 
 |       'goodbye': True | 
 |     } | 
 |     check_release = 1 | 
 |     for item in check_map: | 
 |       check_item = check_map[item] | 
 |       option_item = options.get(item, None) | 
 |       if check_item != option_item: | 
 |         print 'Option %s is %s, expecting %s' % (item, option_item, check_item) | 
 |         check_release = 0 | 
 |  | 
 |     if release != 'M14': | 
 |       check_release = 0 | 
 |     return check_release == 1 | 
 |  | 
 |   def GenerateRange(self, ast, releases, options): | 
 |     __pychecker__ = 'unusednames=ast,releases,options' | 
 |     global check_range | 
 |     check_range = 1 | 
 |     return True | 
 |  | 
 | def Test(): | 
 |   __pychecker__ = 'unusednames=args' | 
 |   global check_release | 
 |   global check_range | 
 |  | 
 |   ParseOptions(['--testgen_opt=so_long,MyOpt=XYZ,goodbye']) | 
 |   if Generator.Run('AST') != 0: | 
 |     print 'Generate release: Failed.\n' | 
 |     return -1 | 
 |  | 
 |   if check_release != 1 or check_range != 0: | 
 |     print 'Gererate release: Failed to run.\n' | 
 |     return -1 | 
 |  | 
 |   check_release = 0 | 
 |   ParseOptions(['--testgen_opt="HELLO"', '--range=M14,M16']) | 
 |   if Generator.Run('AST') != 0: | 
 |     print 'Generate range: Failed.\n' | 
 |     return -1 | 
 |  | 
 |   if check_release != 0 or check_range != 1: | 
 |     print 'Gererate range: Failed to run.\n' | 
 |     return -1 | 
 |  | 
 |   print 'Generator test: Pass' | 
 |   return 0 | 
 |  | 
 |  | 
 | def Main(args): | 
 |   if not args: return Test() | 
 |   filenames = ParseOptions(args) | 
 |   ast = ParseFiles(filenames) | 
 |  | 
 |   return Generator.Run(ast) | 
 |  | 
 |  | 
 | if __name__ == '__main__': | 
 |   GeneratorReleaseTest('Test Gen', 'testgen', 'Generator Class Test.') | 
 |   sys.exit(Main(sys.argv[1:])) |