blob: e008d799c73eec2ff7e3d9d55986b5dd465eeb4d [file] [log] [blame]
# Copyright (C) 2013 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.
import copy
import os
# NOTE: This has only been used to parse
# core/page/RuntimeEnabledFeatures.in and may not be capable
# of parsing other .in files correctly.
# .in file format is:
# // comment
# name1 arg=value, arg2=value2, arg2=value3
#
# InFile must be passed a dictionary of default values
# with which to validate arguments against known names.
# Sequence types as default values will produce sequences
# as parse results.
# Bare arguments (no '=') are treated as names with value True.
# The first field will always be labeled 'name'.
#
# InFile.load_from_files(['file.in'], {'arg': None, 'arg2': []})
#
# Parsing produces an array of dictionaries:
# [ { 'name' : 'name1', 'arg' :' value', arg2=['value2', 'value3'] }
def _is_comment(line):
return line.startswith("//") or line.startswith("#")
class InFile(object):
def __init__(self, file_paths, lines, defaults, valid_values=None, default_parameters=None):
self.file_paths = file_paths
self.name_dictionaries = []
self.parameters = copy.deepcopy(default_parameters if default_parameters else {})
self._defaults = defaults
self._valid_values = copy.deepcopy(valid_values if valid_values else {})
self._parse(map(str.strip, lines))
@classmethod
def load_from_files(self, file_paths, defaults, valid_values, default_parameters):
lines = []
for path in file_paths:
assert path.endswith(".in")
with open(os.path.abspath(path)) as in_file:
lines += in_file.readlines()
return InFile(file_paths, lines, defaults, valid_values, default_parameters)
def _is_sequence(self, arg):
return (not hasattr(arg, "strip")
and hasattr(arg, "__getitem__")
or hasattr(arg, "__iter__"))
def _parse(self, lines):
parsing_parameters = True
indices = {}
for line in lines:
if _is_comment(line):
continue
if not line:
parsing_parameters = False
continue
if parsing_parameters:
self._parse_parameter(line)
else:
entry = self._parse_line(line)
name = entry['name']
if name in indices:
entry = self._merge_entries(entry, self.name_dictionaries[indices[name]])
entry['name'] = name
self.name_dictionaries[indices[name]] = entry
else:
indices[name] = len(self.name_dictionaries)
self.name_dictionaries.append(entry)
def _merge_entries(self, one, two):
merged = {}
for key in one:
if key not in two:
self._fatal("Expected key '%s' not found in entry: %s" % (key, two))
if one[key] and two[key]:
val_one = one[key]
val_two = two[key]
if isinstance(val_one, list) and isinstance(val_two, list):
val = val_one + val_two
elif isinstance(val_one, list):
val = val_one + [val_two]
elif isinstance(val_two, list):
val = [val_one] + val_two
else:
val = [val_one, val_two]
merged[key] = val
elif one[key]:
merged[key] = one[key]
else:
merged[key] = two[key]
return merged
def _parse_parameter(self, line):
if '=' in line:
name, value = line.split('=')
else:
name, value = line, True
if not name in self.parameters:
self._fatal("Unknown parameter: '%s' in line:\n%s\nKnown parameters: %s" % (name, line, self.parameters.keys()))
self.parameters[name] = value
def _parse_line(self, line):
args = copy.deepcopy(self._defaults)
parts = line.split(' ')
args['name'] = parts[0]
# re-join the rest of the line and split on ','
args_list = ' '.join(parts[1:]).strip().split(',')
for arg_string in args_list:
arg_string = arg_string.strip()
if not arg_string: # Ignore empty args
continue
if '=' in arg_string:
arg_name, arg_value = arg_string.split('=')
else:
arg_name, arg_value = arg_string, True
if arg_name not in self._defaults:
self._fatal("Unknown argument: '%s' in line:\n%s\nKnown arguments: %s" % (arg_name, line, self._defaults.keys()))
valid_values = self._valid_values.get(arg_name)
if valid_values and arg_value not in valid_values:
self._fatal("Unknown value: '%s' in line:\n%s\nKnown values: %s" % (arg_value, line, valid_values))
if self._is_sequence(args[arg_name]):
args[arg_name].append(arg_value)
else:
args[arg_name] = arg_value
return args
def _fatal(self, message):
# FIXME: This should probably raise instead of exit(1)
print message
exit(1)