• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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