Home | History | Annotate | Download | only in desktopui_SonicExtension
      1 # Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import collections
      6 import json
      7 import os
      8 
      9 
     10 class ConfigJsonIteratorError(Exception):
     11     """"Exception for config json iterator"""
     12     pass
     13 
     14 
     15 class ConfigJsonIterator(object):
     16     """Class to consolidate multiple config json files.
     17 
     18     This class reads and combines input JSON instances into one based on the
     19     following rules:
     20     1. "_deps" value in the root config file contains a list of common config
     21        file paths. Each path represents a RELATIVE path to
     22        the root config file.
     23        For example (common.config is in the same directory as root.config):
     24        root.config:
     25            { "a": "123",
     26              "_deps": ["../common.config"]}
     27        common.config:
     28            { "b": "xxx" }
     29        End output:
     30            { "a": "123",
     31              "b": "xxx" }
     32     2. common config files defined in "_deps" MUST NOT contain identical keys
     33        (otherwise an exception will be thrown), for example (invalid - common1
     34        and common2.config are in the same directory as root.config):
     35        root.config:
     36            { "a": "123",
     37              "_deps": ["../common1.config",
     38                        "../common2.config"]}
     39        common1.config:
     40            { "b": "xxx" }
     41        common2.config:
     42            { "b": "yyy" }
     43     3. values in the root config will override the ones in the common config
     44        files. This logic applies to any dependency config file (imagine that
     45        the common config also has "_deps"), thus is recursive.
     46        For example (common.config is in the same directory as root.config):
     47        root.config:
     48            { "a": "123",
     49              "_deps": ["../common.config"]}
     50        common.config:
     51            { "a": "456",
     52              "b": "xxx" }
     53        End output:
     54            { "a": "123",
     55              "b": "xxx" }
     56     """
     57     DEPS = '_deps'
     58 
     59 
     60     def __init__(self, config_path=None):
     61         """Constructor.
     62 
     63         @param config_path: String of root config file path.
     64         """
     65         if config_path:
     66             self.set_config_dir(config_path)
     67 
     68 
     69     def set_config_dir(self, config_path):
     70         """Sets config dictionary.
     71 
     72         @param config_path: String of config file path.
     73         @raises ConfigJsonIteratorError if config does not exist.
     74         """
     75         if not os.path.isfile(config_path):
     76             raise ConfigJsonIteratorError('config file does not exist %s'
     77                                           % config_path)
     78         self._config_dir = os.path.abspath(os.path.dirname(config_path))
     79 
     80 
     81     def _load_config(self, config_path):
     82         """Iterate the base config file.
     83 
     84         @param config_path: String of config file path.
     85         @return Dictionary of the config file.
     86         @raises ConfigJsonIteratorError: if config file is not found or invalid.
     87         """
     88         if not os.path.isfile(config_path):
     89             raise ConfigJsonIteratorError('config file does not exist %s'
     90                                           % config_path)
     91         with open(config_path, 'r') as config_file:
     92             try:
     93                 return json.load(config_file)
     94             except ValueError:
     95                 raise ConfigJsonIteratorError(
     96                         'invalid JSON file %s' % config_file)
     97 
     98 
     99     def aggregated_config(self, config_path):
    100         """Returns dictionary of aggregated config files.
    101         The dependency list contains the RELATIVE path to the root config.
    102 
    103         @param config_path: String of config file path.
    104         @return Dictionary containing the aggregated config files.
    105         @raises ConfigJsonIteratorError: if dependency config list
    106             does not exist.
    107         """
    108         ret_dict = self._load_config(config_path)
    109         if ConfigJsonIterator.DEPS not in ret_dict:
    110             return ret_dict
    111         else:
    112             deps_list = ret_dict[ConfigJsonIterator.DEPS]
    113             if not isinstance(deps_list, list):
    114                 raise ConfigJsonIteratorError('dependency must be a list %s'
    115                                               % deps_list)
    116             del ret_dict[ConfigJsonIterator.DEPS]
    117             common_dict = {}
    118             for dep in deps_list:
    119                 common_config_path = os.path.join(self._config_dir, dep)
    120                 dep_dict = self.aggregated_config(common_config_path)
    121                 common_dict = self._merge_dict(common_dict, dep_dict,
    122                                                allow_override=False)
    123             return self._merge_dict(common_dict, ret_dict, allow_override=True)
    124 
    125 
    126     def _merge_dict(self, dict_one, dict_two, allow_override=True):
    127         """Returns a merged dictionary.
    128 
    129         @param dict_one: Dictionary to merge (first).
    130         @param dict_two: Dictionary to merge (second).
    131         @param allow_override: Boolean to allow override or not.
    132         @return Dictionary containing merged result.
    133         @raises ConfigJsonIteratorError: if no dictionary given.
    134         """
    135         if not isinstance(dict_one, dict) or not isinstance(dict_two, dict):
    136             raise ConfigJsonIteratorError('Input is not a dictionary')
    137         if allow_override:
    138             return dict(dict_one.items() + dict_two.items())
    139         else:
    140             merge = collections.Counter(
    141                     dict_one.keys() + dict_two.keys()).most_common()[0]
    142             if merge[1] > 1:
    143                 raise ConfigJsonIteratorError(
    144                     'Duplicate key %s found', merge[0])
    145             return dict_one
    146