Home | History | Annotate | Download | only in scripts
      1 # Copyright (C) 2013 Google Inc. All rights reserved.
      2 #
      3 # Redistribution and use in source and binary forms, with or without
      4 # modification, are permitted provided that the following conditions are
      5 # met:
      6 #
      7 #     * Redistributions of source code must retain the above copyright
      8 # notice, this list of conditions and the following disclaimer.
      9 #     * Redistributions in binary form must reproduce the above
     10 # copyright notice, this list of conditions and the following disclaimer
     11 # in the documentation and/or other materials provided with the
     12 # distribution.
     13 #     * Neither the name of Google Inc. nor the names of its
     14 # contributors may be used to endorse or promote products derived from
     15 # this software without specific prior written permission.
     16 #
     17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28 
     29 import copy
     30 import os
     31 
     32 # NOTE: This has only been used to parse
     33 # core/page/RuntimeEnabledFeatures.in and may not be capable
     34 # of parsing other .in files correctly.
     35 
     36 # .in file format is:
     37 # // comment
     38 # name1 arg=value, arg2=value2, arg2=value3
     39 #
     40 # InFile must be passed a dictionary of default values
     41 # with which to validate arguments against known names.
     42 # Sequence types as default values will produce sequences
     43 # as parse results.
     44 # Bare arguments (no '=') are treated as names with value True.
     45 # The first field will always be labeled 'name'.
     46 #
     47 # InFile.load_from_files(['file.in'], {'arg': None, 'arg2': []})
     48 #
     49 # Parsing produces an array of dictionaries:
     50 # [ { 'name' : 'name1', 'arg' :' value', arg2=['value2', 'value3'] }
     51 
     52 def _is_comment(line):
     53     return line.startswith("//") or line.startswith("#")
     54 
     55 class InFile(object):
     56     def __init__(self, lines, defaults, valid_values=None, default_parameters=None):
     57         self.name_dictionaries = []
     58         self.parameters = copy.deepcopy(default_parameters if default_parameters else {})
     59         self._defaults = defaults
     60         self._valid_values = copy.deepcopy(valid_values if valid_values else {})
     61         self._parse(map(str.strip, lines))
     62 
     63     @classmethod
     64     def load_from_files(self, file_paths, defaults, valid_values, default_parameters):
     65         lines = []
     66         for path in file_paths:
     67             with open(os.path.abspath(path)) as in_file:
     68                 lines += in_file.readlines()
     69         return InFile(lines, defaults, valid_values, default_parameters)
     70 
     71     def _is_sequence(self, arg):
     72         return (not hasattr(arg, "strip")
     73                 and hasattr(arg, "__getitem__")
     74                 or hasattr(arg, "__iter__"))
     75 
     76     def _parse(self, lines):
     77         parsing_parameters = True
     78         indices = {}
     79         for line in lines:
     80             if _is_comment(line):
     81                 continue
     82             if not line:
     83                 parsing_parameters = False
     84                 continue
     85             if parsing_parameters:
     86                 self._parse_parameter(line)
     87             else:
     88                 entry = self._parse_line(line)
     89                 name = entry['name']
     90                 if name in indices:
     91                     entry = self._merge_entries(entry, self.name_dictionaries[indices[name]])
     92                     entry['name'] = name
     93                     self.name_dictionaries[indices[name]] = entry
     94                 else:
     95                     indices[name] = len(self.name_dictionaries)
     96                     self.name_dictionaries.append(entry)
     97 
     98 
     99     def _merge_entries(self, one, two):
    100         merged = {}
    101         for key in one:
    102             if key not in two:
    103                 self._fatal("Expected key '%s' not found in entry: %s" % (key, two))
    104             if one[key] and two[key]:
    105                 val_one = one[key]
    106                 val_two = two[key]
    107                 if isinstance(val_one, list) and isinstance(val_two, list):
    108                     val = val_one + val_two
    109                 elif isinstance(val_one, list):
    110                     val = val_one + [val_two]
    111                 elif isinstance(val_two, list):
    112                     val = [val_one] + val_two
    113                 else:
    114                     val = [val_one, val_two]
    115                 merged[key] = val
    116             elif one[key]:
    117                 merged[key] = one[key]
    118             else:
    119                 merged[key] = two[key]
    120         return merged
    121 
    122 
    123     def _parse_parameter(self, line):
    124         if '=' in line:
    125             name, value = line.split('=')
    126         else:
    127             name, value = line, True
    128         if not name in self.parameters:
    129             self._fatal("Unknown parameter: '%s' in line:\n%s\nKnown parameters: %s" % (name, line, self.parameters.keys()))
    130         self.parameters[name] = value
    131 
    132     def _parse_line(self, line):
    133         args = copy.deepcopy(self._defaults)
    134         parts = line.split(' ')
    135         args['name'] = parts[0]
    136         # re-join the rest of the line and split on ','
    137         args_list = ' '.join(parts[1:]).strip().split(',')
    138         for arg_string in args_list:
    139             arg_string = arg_string.strip()
    140             if not arg_string: # Ignore empty args
    141                 continue
    142             if '=' in arg_string:
    143                 arg_name, arg_value = arg_string.split('=')
    144             else:
    145                 arg_name, arg_value = arg_string, True
    146             if arg_name not in self._defaults:
    147                 self._fatal("Unknown argument: '%s' in line:\n%s\nKnown arguments: %s" % (arg_name, line, self._defaults.keys()))
    148             valid_values = self._valid_values.get(arg_name)
    149             if valid_values and arg_value not in valid_values:
    150                 self._fatal("Unknown value: '%s' in line:\n%s\nKnown values: %s" % (arg_value, line, valid_values))
    151             if self._is_sequence(args[arg_name]):
    152                 args[arg_name].append(arg_value)
    153             else:
    154                 args[arg_name] = arg_value
    155         return args
    156 
    157     def _fatal(self, message):
    158         # FIXME: This should probably raise instead of exit(1)
    159         print message
    160         exit(1)
    161