#!/usr/bin/python2.7 import json import sys from collections import OrderedDict import fsio __author__ = 'frank.sattelberger@bittwister.com' import collections """ Based on http://code.activestate.com/recipes/576694/ License = MIT """ class OrderedSet(collections.MutableSet): def __init__(self, iterable=None): self.end = end = [] end += [None, end, end] # sentinel node for doubly linked list self.map = {} # key --> [key, prev, next] if iterable is not None: self |= iterable def __len__(self): return len(self.map) def __contains__(self, key): return key in self.map def add(self, key): if key not in self.map: end = self.end curr = end[1] curr[2] = end[1] = self.map[key] = [key, curr, end] def discard(self, key): if key in self.map: key, prev, next = self.map.pop(key) prev[2] = next next[1] = prev def __iter__(self): end = self.end curr = end[2] while curr is not end: yield curr[0] curr = curr[2] def __reversed__(self): end = self.end curr = end[1] while curr is not end: yield curr[0] curr = curr[1] def pop(self, last=True): if not self: raise KeyError('set is empty') key = self.end[1][0] if last else self.end[2][0] self.discard(key) return key def __repr__(self): if not self: return '%s()' % (self.__class__.__name__,) return '%s(%r)' % (self.__class__.__name__, list(self)) def __eq__(self, other): if isinstance(other, OrderedSet): return len(self) == len(other) and list(self) == list(other) return set(self) == set(other) class GuiViews(object): """ Contains the views of the divers versions. Converts versions to numerical version information to compare in an ordered way. Has methods to get the new views since a release / service pack. Or to get all views of a specific release / service pack. The versioning counts ST, FT and SP for the same version depending on the number after the acronym. """ def __init__(self): self.views = OrderedDict([ ("actualValues", 1000000), ("diagnosis", 1200000), ("idcodeviewer", 1000000), ("info", 1000000), ("language", 1000000), ("prgselect", 1000000), ("jobselect", 1300000), ("job", 1300000), ("operationmode", 1300000), ("setdatetime", 1000000), ("themeselect", 1000000), ("wlan", 1200000), ("leave", 1100000), ("exchange", 1000000), ("picviewer", 1100000), ("game", 1000000) ]) def convert_version2numeric(self, version): """ Converts a version as provided in the path to a numerical value """ v = version.split("-") vers = int(v[1][1:]) sp = 0 if v[2] == "Release" else 0 if v[2][0:2] != "SP" else int(v[2][2:]) vers *= 1000 res = vers + sp return res def get_new_views(self, old_version, new_version): """ Get all new views after old_version till new_version """ old_version_num = self.convert_version2numeric(old_version) new_version_num = self.convert_version2numeric(new_version) new_views = [] for k, v in self.views.items(): if old_version_num < v <= new_version_num: new_views.append(k) return new_views def get_views(self, version): """ Get all views of a specific version. """ version_num = self.convert_version2numeric(version) views = [] for k, v in self.views.items(): if v <= version_num: views.append(k) return views class GuiConfigData(object): """ Holds the converted data of a guiconfig.json file. """ def __init__(self, data): self.myglobal = None self.entries = None self.forceread = None self.start = None self.transitions = None view = None if data is not None: self.myglobal = data.get('global') view = data.get('view') if view is not None: main_menu = view.get('MainMenu') if main_menu is not None: self.entries = main_menu.get('entries') warning = view.get('warning') if warning is not None: self.forceread = warning.get('forceread') if data is not None: wizard = data.get('wizard') if wizard is not None: self.start = wizard.get('start'), self.transitions = wizard.get('transitions') class MergeGuiConfig(object): """ Merges two guiconfig.json files. The value of the old config will be used, if an old config entry is available. If the entry is only available in the new config, the new value is provided without change. """ def __init__(self, src, dest): self.src = src self.dest = dest self.old_version = src.strip().split("/")[-1] self.new_version = dest.strip().split("/")[-1] self.newdata = GuiConfigData(None) self.olddata = GuiConfigData(None) def read(self): # read data = json.load(open('{0}/guiconfig.json'.format(self.src))) self.olddata = GuiConfigData(data) data = json.load(open('{0}/guiconfig.json'.format(self.dest))) self.newdata = GuiConfigData(data) def merge(self): """ Public merge method that uses different merge methods for the specific parts of the guiconfig.json file. """ self.merge_global() self.merge_entries() self.merge_forceread() self.merge_start() #self.merge_transitions() """ self.forceread = None self.start = None self.transitions = None """ def merge_global(self): """ Private method to merge the global part of a guiconfig.json file. :return: None """ for k, v in self.olddata.myglobal.items(): if k in self.newdata.myglobal.keys(): self.newdata.myglobal[k] = v def merge_entries(self): """ Private method to merge the entries part of a guiconfig.json file. New entries are appended at end of list. We want to preserve the old order. The user might has reordered it and don't want to get a new entry in the way. :return: None """ gui_views = GuiViews() state_entries = [] state_entries_state = [] state_entries_positive = [] state_entries_negative = [] old_versions_views = gui_views.get_views(self.old_version) for e in self.olddata.entries: if e.get('active') is None or e.get('active') is True: state_entries.append({"state": e.get('state'), "active": True}) state_entries_positive.append(e.get('state')) state_entries_state.append(e.get('state')) elif e.get('active') is False: state_entries.append({"state": e.get('state'), "active": False}) state_entries_negative.append(e.get('state')) state_entries_state.append(e.get('state')) # all old views that are not active: other_old_views = OrderedSet(old_versions_views) - OrderedSet(state_entries_state) state_entries_state = OrderedSet(old_versions_views) | OrderedSet(state_entries_state) # add them to the already negatives: set_state_entries_negative = OrderedSet(state_entries_negative) | OrderedSet(other_old_views) state_entries_negative = list(set_state_entries_negative) for to_append in list(other_old_views): state_entries.append({"state": to_append, "active": False}) state_entries_states = list(state_entries_state) for e in self.newdata.entries: if e.get('state') not in state_entries_state: state_entries.append(e) """ if e.get('state') in state_entries_positive: e['active'] = True elif e.get('state') in state_entries_negative: e['active'] = False else: pass # new entries are propagated as they are """ self.newdata.entries = state_entries # entries must not be sorted, order is significant for menu on tool display! def merge_forceread(self): """ Private method to merge the forceread part of a guiconfig.json file. :return: None """ #view.warning.forceread if self.olddata.forceread is not None: self.newdata.forceread = self.olddata.forceread def merge_start(self): """ Private method to merge the start part of a guiconfig.json file. :return: None """ # wizard.start if self.olddata.start is not None: self.newdata.start = self.olddata.start[0] def merge_transitions(self): """ We do not need to merge these. Transitions are not configurable. :return: None """ raise NotImplementedError() def write(self): """ Writes changes resulting from the merge into the destination file. :return: """ data = fsio.jsonload('{0}/guiconfig.json'.format(self.dest)) data['global'] = self.newdata.myglobal data.get('view').get('MainMenu')['entries'] = self.newdata.entries data.get('view').get('warning')['forceread'] = self.newdata.forceread data.get('wizard')['start'] = self.newdata.start fsio.jsondump_sorted('{0}/guiconfig.json'.format(self.dest), data) if __name__ == "__main__": if len(sys.argv) != 3: print("merge.py needs two arguments SRC and DSC (cp. /etc/init.d/rcS") exit(1) source = sys.argv[1] destination = sys.argv[2] mgc = MergeGuiConfig(source, destination) mgc.read() mgc.merge() mgc.write() """Test for views of a specific version gv = GuiViews() print gv.get_new_views("NxFw-V1100-Release", "NxFw-V1200-SP1") print gv.get_views("NxFw-V1100-Release") """ """ s = OrderedSet('abracadaba') t = OrderedSet('simsalabim') print(s | t) print(s & t) print(list(s - t)) """