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