Home | History | Annotate | Download | only in scripts
      1 # Copyright 2014 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """Utility functions (file reading, simple IDL parsing by regexes) for IDL build.
      6 
      7 Design doc: http://www.chromium.org/developers/design-documents/idl-build
      8 """
      9 
     10 import os
     11 import cPickle as pickle
     12 import re
     13 import string
     14 
     15 
     16 class IdlBadFilenameError(Exception):
     17     """Raised if an IDL filename disagrees with the interface name in the file."""
     18     pass
     19 
     20 
     21 def idl_filename_to_interface_name(idl_filename):
     22     # interface name is the root of the basename: InterfaceName.idl
     23     return os.path.splitext(os.path.basename(idl_filename))[0]
     24 
     25 
     26 ################################################################################
     27 # Basic file reading/writing
     28 ################################################################################
     29 
     30 def get_file_contents(filename):
     31     with open(filename) as f:
     32         return f.read()
     33 
     34 
     35 def read_file_to_list(filename):
     36     """Returns a list of (stripped) lines for a given filename."""
     37     with open(filename) as f:
     38         return [line.rstrip('\n') for line in f]
     39 
     40 
     41 def read_pickle_files(pickle_filenames):
     42     for pickle_filename in pickle_filenames:
     43         with open(pickle_filename) as pickle_file:
     44             yield pickle.load(pickle_file)
     45 
     46 
     47 def write_file(new_text, destination_filename, only_if_changed):
     48     if only_if_changed and os.path.isfile(destination_filename):
     49         with open(destination_filename) as destination_file:
     50             if destination_file.read() == new_text:
     51                 return
     52     with open(destination_filename, 'w') as destination_file:
     53         destination_file.write(new_text)
     54 
     55 
     56 def write_pickle_file(pickle_filename, data, only_if_changed):
     57     if only_if_changed and os.path.isfile(pickle_filename):
     58         with open(pickle_filename) as pickle_file:
     59             try:
     60                 if pickle.load(pickle_file) == data:
     61                     return
     62             except (EOFError, pickle.UnpicklingError):
     63                 # If trouble unpickling, overwrite
     64                 pass
     65     with open(pickle_filename, 'w') as pickle_file:
     66         pickle.dump(data, pickle_file)
     67 
     68 
     69 ################################################################################
     70 # IDL parsing
     71 #
     72 # We use regular expressions for parsing; this is incorrect (Web IDL is not a
     73 # regular language), but simple and sufficient in practice.
     74 # Leading and trailing context (e.g. following '{') used to avoid false matches.
     75 ################################################################################
     76 
     77 def get_partial_interface_name_from_idl(file_contents):
     78     match = re.search(r'partial\s+interface\s+(\w+)\s*{', file_contents)
     79     return match and match.group(1)
     80 
     81 
     82 def get_implements_from_idl(file_contents, interface_name):
     83     """Returns lists of implementing and implemented interfaces.
     84 
     85     Rule is: identifier-A implements identifier-B;
     86     i.e., implement*ing* implements implement*ed*;
     87     http://www.w3.org/TR/WebIDL/#idl-implements-statements
     88 
     89     Returns two lists of interfaces: identifier-As and identifier-Bs.
     90     An 'implements' statements can be present in the IDL file for either the
     91     implementing or the implemented interface, but not other files.
     92     """
     93     implements_re = (r'^\s*'
     94                      r'(\w+)\s+'
     95                      r'implements\s+'
     96                      r'(\w+)\s*'
     97                      r';')
     98     implements_matches = re.finditer(implements_re, file_contents, re.MULTILINE)
     99     implements_pairs = [match.groups() for match in implements_matches]
    100 
    101     foreign_implements = [pair for pair in implements_pairs
    102                           if interface_name not in pair]
    103     if foreign_implements:
    104         left, right = foreign_implements.pop()
    105         raise IdlBadFilenameError(
    106                 'implements statement found in unrelated IDL file.\n'
    107                 'Statement is:\n'
    108                 '    %s implements %s;\n'
    109                 'but filename is unrelated "%s.idl"' %
    110                 (left, right, interface_name))
    111 
    112     return (
    113         [left for left, right in implements_pairs if right == interface_name],
    114         [right for left, right in implements_pairs if left == interface_name])
    115 
    116 
    117 def is_callback_interface_from_idl(file_contents):
    118     match = re.search(r'callback\s+interface\s+\w+\s*{', file_contents)
    119     return bool(match)
    120 
    121 
    122 def get_parent_interface(file_contents):
    123     match = re.search(r'interface\s+'
    124                       r'\w+\s*'
    125                       r':\s*(\w+)\s*'
    126                       r'{',
    127                       file_contents)
    128     return match and match.group(1)
    129 
    130 
    131 def get_interface_extended_attributes_from_idl(file_contents):
    132     # Strip comments
    133     # re.compile needed b/c Python 2.6 doesn't support flags in re.sub
    134     single_line_comment_re = re.compile(r'//.*$', flags=re.MULTILINE)
    135     block_comment_re = re.compile(r'/\*.*?\*/', flags=re.MULTILINE | re.DOTALL)
    136     file_contents = re.sub(single_line_comment_re, '', file_contents)
    137     file_contents = re.sub(block_comment_re, '', file_contents)
    138 
    139     match = re.search(r'\[(.*)\]\s*'
    140                       r'((callback|partial)\s+)?'
    141                       r'(interface|exception)\s+'
    142                       r'\w+\s*'
    143                       r'(:\s*\w+\s*)?'
    144                       r'{',
    145                       file_contents, flags=re.DOTALL)
    146     if not match:
    147         return {}
    148 
    149     extended_attributes_string = match.group(1)
    150     extended_attributes = {}
    151     # FIXME: this splitting is WRONG: it fails on ExtendedAttributeArgList like
    152     # 'NamedConstructor=Foo(a, b)'
    153     parts = [extended_attribute.strip()
    154              for extended_attribute in extended_attributes_string.split(',')
    155              # Discard empty parts, which may exist due to trailing comma
    156              if extended_attribute.strip()]
    157     for part in parts:
    158         name, _, value = map(string.strip, part.partition('='))
    159         extended_attributes[name] = value
    160     return extended_attributes
    161 
    162 
    163 def get_put_forward_interfaces_from_idl(file_contents):
    164     put_forwards_pattern = (r'\[[^\]]*PutForwards=[^\]]*\]\s+'
    165                             r'readonly\s+'
    166                             r'attribute\s+'
    167                             r'(\w+)')
    168     return sorted(set(match.group(1)
    169                       for match in re.finditer(put_forwards_pattern,
    170                                                file_contents,
    171                                                flags=re.DOTALL)))
    172