| #!/usr/bin/python | 
 | # | 
 | # Copyright 2012 Google Inc. All Rights Reserved. | 
 | # | 
 | # Redistribution and use in source and binary forms, with or without | 
 | # modification, are permitted provided that the following conditions are | 
 | # met: | 
 | # | 
 | #    * Redistributions of source code must retain the above copyright | 
 | # notice, this list of conditions and the following disclaimer. | 
 | #    * Redistributions in binary form must reproduce the above | 
 | # copyright notice, this list of conditions and the following disclaimer | 
 | # in the documentation and/or other materials provided with the | 
 | # distribution. | 
 | #    * Neither the name of Google Inc. nor the names of its | 
 | # contributors may be used to endorse or promote products derived from | 
 | # this software without specific prior written permission. | 
 | # | 
 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 
 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 
 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 
 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 
 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
 |  | 
 | """Generates default implementations of operator<< for enum types.""" | 
 |  | 
 | import codecs | 
 | import os | 
 | import re | 
 | import string | 
 | import sys | 
 |  | 
 |  | 
 | _ENUM_START_RE = re.compile(r'\benum\b\s+(\S+)\s+\{') | 
 | _ENUM_VALUE_RE = re.compile(r'([A-Za-z0-9_]+)(.*)') | 
 | _ENUM_END_RE = re.compile(r'^\s*\};$') | 
 | _ENUMS = {} | 
 | _NAMESPACES = {} | 
 |  | 
 | def Confused(filename, line_number, line): | 
 |   sys.stderr.write('%s:%d: confused by:\n%s\n' % (filename, line_number, line)) | 
 |   raise Exception("giving up!") | 
 |   sys.exit(1) | 
 |  | 
 |  | 
 | def ProcessFile(filename): | 
 |   lines = codecs.open(filename, 'r', 'utf8', 'replace').read().split('\n') | 
 |   in_enum = False | 
 |   line_number = 0 | 
 |  | 
 |   namespaces = [] | 
 |   enclosing_classes = [] | 
 |  | 
 |   for raw_line in lines: | 
 |     line_number += 1 | 
 |  | 
 |     if not in_enum: | 
 |       # Is this the start of a new enum? | 
 |       m = _ENUM_START_RE.search(raw_line) | 
 |       if m: | 
 |         # Yes, so add an empty entry to _ENUMS for this enum. | 
 |         enum_name = m.group(1) | 
 |         if len(enclosing_classes) > 0: | 
 |           enum_name = '::'.join(enclosing_classes) + '::' + enum_name | 
 |         _ENUMS[enum_name] = [] | 
 |         _NAMESPACES[enum_name] = '::'.join(namespaces) | 
 |         in_enum = True | 
 |         continue | 
 |  | 
 |       # Is this the start or end of a namespace? | 
 |       m = re.compile(r'^namespace (\S+) \{').search(raw_line) | 
 |       if m: | 
 |         namespaces.append(m.group(1)) | 
 |         continue | 
 |       m = re.compile(r'^\}\s+// namespace').search(raw_line) | 
 |       if m: | 
 |         namespaces = namespaces[0:len(namespaces) - 1] | 
 |         continue | 
 |  | 
 |       # Is this the start or end of an enclosing class or struct? | 
 |       m = re.compile(r'^(?:class|struct)(?: MANAGED)? (\S+).* \{').search(raw_line) | 
 |       if m: | 
 |         enclosing_classes.append(m.group(1)) | 
 |         continue | 
 |       m = re.compile(r'^\};').search(raw_line) | 
 |       if m: | 
 |         enclosing_classes = enclosing_classes[0:len(enclosing_classes) - 1] | 
 |         continue | 
 |  | 
 |       continue | 
 |  | 
 |     # Is this the end of the current enum? | 
 |     m = _ENUM_END_RE.search(raw_line) | 
 |     if m: | 
 |       if not in_enum: | 
 |         Confused(filename, line_number, raw_line) | 
 |       in_enum = False | 
 |       continue | 
 |  | 
 |     # The only useful thing in comments is the <<alternate text>> syntax for | 
 |     # overriding the default enum value names. Pull that out... | 
 |     enum_text = None | 
 |     m_comment = re.compile(r'// <<(.*?)>>').search(raw_line) | 
 |     if m_comment: | 
 |       enum_text = m_comment.group(1) | 
 |     # ...and then strip // comments. | 
 |     line = re.sub(r'//.*', '', raw_line) | 
 |  | 
 |     # Strip whitespace. | 
 |     line = line.strip() | 
 |  | 
 |     # Skip blank lines. | 
 |     if len(line) == 0: | 
 |       continue | 
 |  | 
 |     # Since we know we're in an enum type, and we're not looking at a comment | 
 |     # or a blank line, this line should be the next enum value... | 
 |     m = _ENUM_VALUE_RE.search(line) | 
 |     if not m: | 
 |       Confused(filename, line_number, raw_line) | 
 |     enum_value = m.group(1) | 
 |  | 
 |     # By default, we turn "kSomeValue" into "SomeValue". | 
 |     if enum_text == None: | 
 |       enum_text = enum_value | 
 |       if enum_text.startswith('k'): | 
 |         enum_text = enum_text[1:] | 
 |  | 
 |     # Lose literal values because we don't care; turn "= 123, // blah" into ", // blah". | 
 |     rest = m.group(2).strip() | 
 |     m_literal = re.compile(r'= (0x[0-9a-f]+|-?[0-9]+|\'.\')').search(rest) | 
 |     if m_literal: | 
 |       rest = rest[(len(m_literal.group(0))):] | 
 |  | 
 |     # With "kSomeValue = kOtherValue," we take the original and skip later synonyms. | 
 |     # TODO: check that the rhs is actually an existing value. | 
 |     if rest.startswith('= k'): | 
 |       continue | 
 |  | 
 |     # Remove any trailing comma and whitespace | 
 |     if rest.startswith(','): | 
 |       rest = rest[1:] | 
 |     rest = rest.strip() | 
 |  | 
 |     # There shouldn't be anything left. | 
 |     if len(rest): | 
 |       Confused(filename, line_number, raw_line) | 
 |  | 
 |     if len(enclosing_classes) > 0: | 
 |       enum_value = '::'.join(enclosing_classes) + '::' + enum_value | 
 |  | 
 |     _ENUMS[enum_name].append((enum_value, enum_text)) | 
 |  | 
 | def main(): | 
 |   local_path = sys.argv[1] | 
 |   header_files = [] | 
 |   for header_file in sys.argv[2:]: | 
 |     header_files.append(header_file) | 
 |     ProcessFile(header_file) | 
 |  | 
 |   print '#include <iostream>' | 
 |   print | 
 |  | 
 |   for header_file in header_files: | 
 |     header_file = header_file.replace(local_path + '/', '') | 
 |     print '#include "%s"' % header_file | 
 |  | 
 |   print | 
 |  | 
 |   for enum_name in _ENUMS: | 
 |     print '// This was automatically generated by %s --- do not edit!' % sys.argv[0] | 
 |  | 
 |     namespaces = _NAMESPACES[enum_name].split('::') | 
 |     for namespace in namespaces: | 
 |       print 'namespace %s {' % namespace | 
 |  | 
 |     print 'std::ostream& operator<<(std::ostream& os, const %s& rhs) {' % enum_name | 
 |     print '  switch (rhs) {' | 
 |     for (enum_value, enum_text) in _ENUMS[enum_name]: | 
 |       print '    case %s: os << "%s"; break;' % (enum_value, enum_text) | 
 |     print '    default: os << "%s[" << static_cast<int>(rhs) << "]"; break;' % enum_name | 
 |     print '  }' | 
 |     print '  return os;' | 
 |     print '}' | 
 |  | 
 |     for namespace in reversed(namespaces): | 
 |       print '}  // namespace %s' % namespace | 
 |     print | 
 |  | 
 |   sys.exit(0) | 
 |  | 
 |  | 
 | if __name__ == '__main__': | 
 |   main() |