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 29import copy 30import 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 52def _is_comment(line): 53 return line.startswith("//") or line.startswith("#") 54 55class 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