153e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# Copyright (C) 2013 Google Inc. All rights reserved.
253e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)#
353e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# Redistribution and use in source and binary forms, with or without
453e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# modification, are permitted provided that the following conditions are
553e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# met:
653e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)#
753e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)#     * Redistributions of source code must retain the above copyright
853e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# notice, this list of conditions and the following disclaimer.
953e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)#     * Redistributions in binary form must reproduce the above
1053e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# copyright notice, this list of conditions and the following disclaimer
1153e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# in the documentation and/or other materials provided with the
1253e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# distribution.
1353e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)#     * Neither the name of Google Inc. nor the names of its
1453e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# contributors may be used to endorse or promote products derived from
1553e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# this software without specific prior written permission.
1653e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)#
1753e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1853e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1953e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2053e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
2153e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2253e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2353e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2453e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2553e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2653e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2753e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2853e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
2953e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)import copy
3053e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)import os
3153e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
3253e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# NOTE: This has only been used to parse
3353e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# core/page/RuntimeEnabledFeatures.in and may not be capable
3453e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# of parsing other .in files correctly.
3553e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
3653e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# .in file format is:
3753e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# // comment
3853e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# name1 arg=value, arg2=value2, arg2=value3
3953e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)#
4053e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# InFile must be passed a dictionary of default values
4153e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# with which to validate arguments against known names.
4253e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# Sequence types as default values will produce sequences
4353e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# as parse results.
4453e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# Bare arguments (no '=') are treated as names with value True.
4553e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# The first field will always be labeled 'name'.
4653e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)#
4793ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)# InFile.load_from_files(['file.in'], {'arg': None, 'arg2': []})
4853e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)#
4953e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# Parsing produces an array of dictionaries:
5053e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)# [ { 'name' : 'name1', 'arg' :' value', arg2=['value2', 'value3'] }
5153e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
5253e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)def _is_comment(line):
5353e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)    return line.startswith("//") or line.startswith("#")
5453e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
5553e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)class InFile(object):
5681a5157921f1d2a7ff6aae115bfe3c139b38a5c8Torne (Richard Coles)    def __init__(self, lines, defaults, valid_values=None, default_parameters=None):
5753e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        self.name_dictionaries = []
5853e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        self.parameters = copy.deepcopy(default_parameters if default_parameters else {})
5953e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        self._defaults = defaults
6081a5157921f1d2a7ff6aae115bfe3c139b38a5c8Torne (Richard Coles)        self._valid_values = copy.deepcopy(valid_values if valid_values else {})
6153e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        self._parse(map(str.strip, lines))
6253e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
6353e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)    @classmethod
6493ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)    def load_from_files(self, file_paths, defaults, valid_values, default_parameters):
6593ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)        lines = []
6693ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)        for path in file_paths:
67197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch            assert path.endswith(".in")
6893ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)            with open(os.path.abspath(path)) as in_file:
6993ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)                lines += in_file.readlines()
7093ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)        return InFile(lines, defaults, valid_values, default_parameters)
7153e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
7253e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)    def _is_sequence(self, arg):
7353e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        return (not hasattr(arg, "strip")
7453e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                and hasattr(arg, "__getitem__")
7553e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                or hasattr(arg, "__iter__"))
7653e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
7753e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)    def _parse(self, lines):
7853e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        parsing_parameters = True
79591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch        indices = {}
8053e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        for line in lines:
8153e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            if _is_comment(line):
8253e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                continue
8353e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            if not line:
8453e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                parsing_parameters = False
8553e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                continue
8653e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            if parsing_parameters:
8753e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                self._parse_parameter(line)
8853e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            else:
89591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                entry = self._parse_line(line)
90591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                name = entry['name']
91591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                if name in indices:
92591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                    entry = self._merge_entries(entry, self.name_dictionaries[indices[name]])
93591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                    entry['name'] = name
94591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                    self.name_dictionaries[indices[name]] = entry
95591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                else:
96591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                    indices[name] = len(self.name_dictionaries)
97591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                    self.name_dictionaries.append(entry)
98591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch
99591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch
100591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch    def _merge_entries(self, one, two):
101591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch        merged = {}
102591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch        for key in one:
103591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch            if key not in two:
104591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                self._fatal("Expected key '%s' not found in entry: %s" % (key, two))
105591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch            if one[key] and two[key]:
106591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                val_one = one[key]
107591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                val_two = two[key]
108591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                if isinstance(val_one, list) and isinstance(val_two, list):
109591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                    val = val_one + val_two
110591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                elif isinstance(val_one, list):
111591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                    val = val_one + [val_two]
112591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                elif isinstance(val_two, list):
113591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                    val = [val_one] + val_two
114591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                else:
115591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                    val = [val_one, val_two]
116591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                merged[key] = val
117591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch            elif one[key]:
118591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                merged[key] = one[key]
119591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch            else:
120591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch                merged[key] = two[key]
121591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch        return merged
122591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch
12353e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
12453e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)    def _parse_parameter(self, line):
12553e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        if '=' in line:
12653e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            name, value = line.split('=')
12753e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        else:
12853e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            name, value = line, True
12953e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        if not name in self.parameters:
13053e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            self._fatal("Unknown parameter: '%s' in line:\n%s\nKnown parameters: %s" % (name, line, self.parameters.keys()))
13153e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        self.parameters[name] = value
13253e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
13353e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)    def _parse_line(self, line):
13453e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        args = copy.deepcopy(self._defaults)
13553e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        parts = line.split(' ')
13653e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        args['name'] = parts[0]
13753e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        # re-join the rest of the line and split on ','
13853e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        args_list = ' '.join(parts[1:]).strip().split(',')
13953e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        for arg_string in args_list:
14053e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            arg_string = arg_string.strip()
14153e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            if not arg_string: # Ignore empty args
14253e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                continue
14353e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            if '=' in arg_string:
14453e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                arg_name, arg_value = arg_string.split('=')
14553e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            else:
14653e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                arg_name, arg_value = arg_string, True
14753e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            if arg_name not in self._defaults:
14853e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                self._fatal("Unknown argument: '%s' in line:\n%s\nKnown arguments: %s" % (arg_name, line, self._defaults.keys()))
14981a5157921f1d2a7ff6aae115bfe3c139b38a5c8Torne (Richard Coles)            valid_values = self._valid_values.get(arg_name)
15081a5157921f1d2a7ff6aae115bfe3c139b38a5c8Torne (Richard Coles)            if valid_values and arg_value not in valid_values:
15181a5157921f1d2a7ff6aae115bfe3c139b38a5c8Torne (Richard Coles)                self._fatal("Unknown value: '%s' in line:\n%s\nKnown values: %s" % (arg_value, line, valid_values))
15253e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            if self._is_sequence(args[arg_name]):
15353e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                args[arg_name].append(arg_value)
15453e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            else:
15553e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                args[arg_name] = arg_value
15653e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        return args
15753e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
15853e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)    def _fatal(self, message):
15953e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        # FIXME: This should probably raise instead of exit(1)
16053e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        print message
16153e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        exit(1)
162