def _migrate_light_scripts(self): if 'light_scripts' not in self.fc: return YamlRoundtrip.rename_key('light_scripts', 'shows', self.fc, self.log) for show_contents in self.fc['shows'].values(): self._convert_tocks_to_time(show_contents) for step in show_contents: if 'color' in step: step['color'] = self._get_color(step['color']) if len(str(step['color'])) > 2: YamlRoundtrip.rename_key('color', '(leds)', step, self.log) step['leds'] = CommentedMap() YamlRoundtrip.copy_with_comments(step, '(leds)', step['leds'], '(leds)', True, self.log) else: YamlRoundtrip.rename_key('color', '(lights)', step, self.log) step['lights'] = CommentedMap() YamlRoundtrip.copy_with_comments(step, '(lights)', step['lights'], '(lights)', True, self.log)
def _remove_key(self, first_key, key): # actually removes the key from the dict, with nested dicts only # (no lists in there) if not key: # single item if first_key in self.fc: YamlRoundtrip.del_key_with_comments(self.fc, first_key, self.log) return True try: if self.fc[first_key].mlget(key, list_ok=True) is not None: # mlget just verifies with a nested dict / list # index that the key is found. final_key = key.pop(-1) dic = self.fc[first_key] while key: # Loop to get the parent container of the # lowest level key dic = dic[key.pop(0)] YamlRoundtrip.del_key_with_comments(dic, final_key, self.log) return True except (KeyError, IndexError): pass return False
def _create_window_slide(self): if 'window' in self.fc and 'elements' in self.fc['window']: elements = self.fc['window']['elements'] if isinstance(elements, dict): elements = [elements] if 'slides' not in self.fc: self.log.debug("Creating 'slides:' section") self.fc['slides'] = CommentedMap() slide_name = V4Migrator._get_slide_name('window') self.log.debug("Creating slide: %s with %s display widget(s) from " "the old window: config", slide_name, len(elements)) self.log.debug("Adding '%s' slide", slide_name) self.fc['slides'][slide_name] = CommentedMap() self.fc['slides'][slide_name] = ( self._migrate_elements(elements, 'window')) YamlRoundtrip.del_key_with_comments(self.fc['window'], 'elements', self.log) if 'slide_player' not in self.fc: self.fc['slide_player'] = CommentedMap() self.log.debug("Creating slide_player: section") self.log.debug("Creating slide_player:machine_reset_phase3: entry" "to show slide '%s' on boot", slide_name) self.fc['slide_player']['machine_reset_phase_3'] = CommentedMap() self.fc['slide_player']['machine_reset_phase_3'][slide_name] = \ CommentedMap() self.fc['slide_player']['machine_reset_phase_3'][slide_name][ 'target'] = 'window'
def _migrate_show_file(self): self.log.debug("Migrating show file: %s", self.file_name) show_name_stub = os.path.splitext(os.path.split(self.file_name)[1])[0] self._add_show_version() # Convert tocks to time self._convert_tocks_to_time(self.fc) # migrate the components in each step self.log.debug("Converting settings for each show step") slide_num = 0 for i, step in enumerate(self.fc): self._remove_tags(step) if 'display' in step: self.log.debug("Show step %s: Converting 'display' section", i + 1) found_transition = False for widget in step['display']: if 'transition' in widget: found_transition = True break if found_transition: step['display'] = CommentedMap( widgets=self._migrate_elements(step['display'])) for widget in step['display']['widgets']: self._convert_tokens(widget) if 'transition' in widget: YamlRoundtrip.copy_with_comments( widget, 'transition', step['display'], 'transition', True, self.log) else: step['display'] = self._migrate_elements(step['display']) self._convert_tokens(step['display']) YamlRoundtrip.rename_key('display', 'slides', step) slide_num += 1 old_slides = step['slides'] step['slides'] = CommentedMap() step['slides']['{}_slide_{}'.format(show_name_stub, slide_num)] = old_slides return True
def _migrate_shot_profiles(self): if 'shot_profiles' not in self.fc: return for settings in self.fc['shot_profiles'].values(): if 'states' in settings: for dummy_i, state_settings in enumerate(settings['states']): if 'loops' in state_settings and state_settings['loops']: state_settings['loops'] = -1 YamlRoundtrip.rename_key('light_script', 'show', state_settings)
def _convert_show_call_to_tokens(cls, settings): token_list = ['light', 'lights', 'leds', 'led'] for token in token_list: if token in settings: if 'show_tokens' not in settings: settings['show_tokens'] = CommentedMap() YamlRoundtrip.copy_with_comments(settings, token, settings['show_tokens'], token, True)
def _recursive_rename(self, old, new, target): if isinstance(target, list): for item in target: self._recursive_rename(old, new, item) elif isinstance(target, dict): if old in target: YamlRoundtrip.rename_key(old, new, target, self.log) for item in target.values(): self._recursive_rename(old, new, item)
def _remove_tags(self, dic): found = False for v in dic.values(): if isinstance(v, dict): for k1 in v.keys(): if k1.startswith('tag|'): YamlRoundtrip.rename_key(k1, k1.strip('tag|'), v) found = True break if found: self._remove_tags(dic)
def _migrate_mode_timers(self): if 'timers' in self.fc: for timer_name in self.fc['timers']: if "start_paused" in self.fc['timers'][timer_name]: if not self.fc['timers'][timer_name]['start_paused']: self.fc['timers'][timer_name]["start_running"] = True elif ("start_running" not in self.fc['timers'][timer_name] or not self.fc['timers'][timer_name]["start_running"]): self.fc['timers'][timer_name]["start_running"] = False else: raise ValueError("Both start_paused and start_running are true. This is impossible") YamlRoundtrip.del_key_with_comments(self.fc['timers'][timer_name], 'start_paused', self.log)
def _migrate_fonts(self): # Fonts to widget_styles was already renamed, now update contents if 'widget_styles' in self.fc: self.log.debug("Converting widget_styles: from the old fonts: " "settings") for settings in self.fc['widget_styles'].values(): YamlRoundtrip.rename_key('size', 'font_size', settings, self.log) YamlRoundtrip.rename_key('file', 'font_name', settings, self.log) YamlRoundtrip.rename_key('crop_top', 'adjust_top', settings, self.log) YamlRoundtrip.rename_key('crop_bottom', 'adjust_bottom', settings, self.log) if 'font_name' in settings: self.log.debug("Converting font_name: from file to name") settings['font_name'] = os.path.splitext( settings['font_name'])[0] if self.base_name == V4Migrator.MAIN_CONFIG_FILE: if 'widget_styles' not in self.fc: self.log.debug("Creating old default font settings as " "widget_styles: section") self.fc['widget_styles'] = self._get_old_default_widget_styles() else: for k, v in self._get_old_default_widget_styles().items(): if k not in self.fc['widget_styles']: self.fc['widget_styles'][k] = v self.log.debug("Merging old built-in font settings '%s' " "into widget_styles: section", k)
def _migrate_animation_assets(self): if 'animations' in self.fc: self.log.debug("Converting assets:animations to assets:images") if 'images' in self.fc: self.log.debug("Merging animations: into current " "asset:images:") YamlRoundtrip.copy_with_comments(self.fc, 'animations', self.fc, 'images', True, self.log) else: YamlRoundtrip.rename_key('animations', 'images', self.fc, self.log)
def __init__(self, uri, config_type, source=None, version=None, local=True, extra_sys_path=None): self.uri = uri self.version = version self.path = uris.to_fs_path(uri) self.filename = os.path.basename(self.path) self._local = local self._source = source self._extra_sys_path = extra_sys_path or [] self._config_simple = {} self._config_roundtrip = {} self._last_config_simple = {} self._last_config_roundtrip = {} self._loader_roundtrip = YamlRoundtrip() self._loader_simple = YamlInterface() self.config_type = config_type
def test_yaml_patches(self): # tests our patches to the yaml processor config = """ str_1: +1 str_2: 032 str_3: on str_4: off str_5: 123e45 bool_1: yes bool_2: no bool_3: true bool_4: false str_6: hi int_1: 123 float_1: 1.0 """ parsed_config = YamlRoundtrip.process(config) for k, v in parsed_config.items(): if not type(v) is eval(k.split('_')[0]): raise AssertionError('YAML value "{}" is {}, not {}'.format(v, type(v), eval(k.split('_')[0])))
def _migrate_animation(self, element): self.log.debug("Converting 'animation' display_element to animated " "'image' widget") element['type'] = 'image' YamlRoundtrip.rename_key('play_now', 'auto_play', element, self.log) YamlRoundtrip.rename_key('animation', 'image', element, self.log) element.pop('drop_frames', None) self.log.debug('Converting animated image loops: setting') if element['loops']: element['loops'] = -1 else: element['loops'] = 0 return element
def _migrate_switches(self): if 'switches' not in self.fc: return for switch_settings in self.fc['switches'].values(): YamlRoundtrip.rename_key('activation_events', 'events_when_activated', switch_settings, self.log) YamlRoundtrip.rename_key('deactivation_events', 'events_when_deactivated', switch_settings, self.log) if 'debounce' in switch_settings: if switch_settings['debounce']: switch_settings['debounce'] = 'normal' else: switch_settings['debounce'] = 'quick'
def _do_rename(self): for rename in self.renames: if len(rename['old']) > 1: # searching for nested key found_section = Util.get_from_dict(self.fc, rename['old'][:-1]) if not found_section: continue self.log.debug('Renaming key: %s: -> %s:', ':'.join(rename['old']), rename['new']) YamlRoundtrip.rename_key(rename['old'][-1], rename['new'], found_section) else: # searching for a single key anywhere self._recursive_rename(rename['old'][0], rename['new'], self.fc)
def _do_lowercase(self, dic=None): # recurcisely converts all keys in dicts and nested dicts if not dic: dic = self.fc key_list = list(dic.keys()) for key in key_list: try: YamlRoundtrip.rename_key(key, key.lower(), dic, self.log) except AttributeError: pass try: if isinstance(dic[key.lower()], dict): self._do_lowercase(dic[key.lower()]) except AttributeError: if isinstance(dic[key], dict): self._do_lowercase(dic[key])
def _convert_tocks_to_time(self, show_steps): self.log.debug('Converting "tocks:" to "time:" and cascading entries ' 'to the next step (since time: is for the current ' 'step versus tocks: being for the previous step)') previous_tocks = 0 for i, step in enumerate(show_steps): previous_tocks = step['tocks'] if not i: step['tocks'] = 0 else: step['tocks'] = '+{}'.format(previous_tocks) YamlRoundtrip.rename_key('tocks', 'time', step, self.log) if len(show_steps) > 1: show_steps.append(CommentedMap()) show_steps[-1]['time'] = '+{}'.format(previous_tocks) return show_steps
def verify_yaml(self, yaml_string, config_name, show_file=False): formatted_yaml = YamlRoundtrip.reformat_yaml(yaml_string, show_file=show_file) if formatted_yaml == "null\n...\n": # special case: empty config return if yaml_string.strip() != formatted_yaml.strip(): #self.maxDiff = None #self.assertEqual(yaml_string, formatted_yaml) diff = self.unidiff_output(yaml_string.strip() + "\n", formatted_yaml.strip() + "\n") self.fail("Config {} unformatted. Diff:\n{}".format( config_name, diff))
def _migrate_element_y_and_anchor(self, element, display, height): if 'y' in element: old_y = element['y'] element['y'] *= -1 else: old_y = 'None' if 'anchor_y' not in element and 'y' in element: element['anchor_y'] = 'top' try: if 'anchor_y' not in element or element['anchor_y'] == 'bottom': element['y'] = self._format_anchor_and_value( 'bottom', element['y']) elif element['anchor_y'] in ('middle', 'center'): element['y'] = self._format_anchor_and_value( 'middle', element['y']) elif element['anchor_y'] == 'top': element['y'] = self._format_anchor_and_value( 'top', element['y']) self.log.debug( "Changing y:%s to y:%s (Based on anchor_y:%s" "and %s height:%s)", old_y, element['y'], element['anchor_y'], display, height) except KeyError: pass try: if element['anchor_y'] in ('middle', 'center'): YamlRoundtrip.del_key_with_comments(element, 'anchor_y', self.log) except KeyError: pass if ('anchor_y' in element and 'y' not in element and element['anchor_y'] != 'middle'): element['y'] = element['anchor_y'] return element
def _migrate_element_x_and_anchor(self, element, display, width): if 'x' in element: old_x = element['x'] else: old_x = 'None' if 'anchor_x' not in element and 'x' in element: element['anchor_x'] = 'left' try: if 'anchor_x' not in element or element['anchor_x'] == 'left': element['x'] = self._format_anchor_and_value( 'left', element['x']) elif element['anchor_x'] in ('middle', 'center'): element['x'] = self._format_anchor_and_value( 'center', element['x']) elif element['anchor_x'] == 'right': element['x'] = self._format_anchor_and_value( 'right', element['x']) self.log.debug( "Changing x:%s to x:%s (Based on anchor_x:%s" "and %s width:%s)", old_x, element['x'], element['anchor_x'], display, width) except KeyError: pass try: if element['anchor_x'] in ('middle', 'center'): YamlRoundtrip.del_key_with_comments(element, 'anchor_x', self.log) except KeyError: pass if ('anchor_x' in element and 'x' not in element and element['anchor_x'] != 'center'): element['x'] = element['anchor_x'] return element
def _migrate_assets(self, section_name): if section_name in self.fc: keys_to_keep = set(self.mpf_config_spec[section_name].keys()) empty_entries = set() self.log.debug("Converting %s: section", section_name) if self.fc[section_name]: for name, settings in self.fc[section_name].items(): self.log.debug("Converting %s:%s:", section_name, name) if isinstance(settings, dict): keys = set(settings.keys()) keys_to_remove = keys - keys_to_keep for key in keys_to_remove: YamlRoundtrip.del_key_with_comments(settings, key, self.log) if not settings: self.log.debug("%s:%s: is now empty. Will remove it.", section_name, name) empty_entries.add(name) for name in empty_entries: YamlRoundtrip.del_key_with_comments(self.fc[section_name], name, self.log) if not self.fc[section_name]: self.log.debug("%s: is now empty. Will remove it.", section_name) YamlRoundtrip.del_key_with_comments(self.fc, section_name, self.log)
def test_yaml_patches(self): # tests our patches to the yaml processor config = """ str_1: +1 str_2: 032 str_3: on str_4: off str_5: 123e45 str_6: hi str_7: 2:10 str_8: 2:10.1 bool_1: yes bool_2: no bool_3: true bool_4: false bool_5: True bool_6: False int_1: 123 yaml.scalarfloat.ScalarFloat_1: 1.0 """ values = { "str_1": "+1", "str_2": "032", "str_3": "on", "str_4": "off", "str_5": "123e45", "str_6": "hi", "str_7": "2:10", "str_8": "2:10.1", "bool_1": True, "bool_2": False, "bool_3": True, "bool_4": False, "bool_5": True, "bool_6": False, "int_1": 123, "yaml.scalarfloat.ScalarFloat_1": 1.0, } parsed_config = YamlRoundtrip.process(config) for k, v in parsed_config.items(): if not type(v) is eval(k.split('_')[0]): raise AssertionError('YAML value "{}" is {}, not {}'.format(v, type(v), eval(k.split('_')[0]))) self.assertEqual(values[k], v)
def _migrate_light_player(self): # light_player: section in v3 was used for both playing light scripts # and playing shows if 'light_player' not in self.fc: return self.log.debug("Migrating light_player: section") for event, actions in self.fc['light_player'].items(): if not isinstance(actions, list): actions = [actions] this_events_shows = CommentedMap() for dummy_i, action in enumerate(actions): if 'show' in action: show_name = action.pop('show') elif 'script' in action: show_name = action.pop('script') elif 'key' in action: show_name = action.pop('key') else: continue if 'action' in action and action['action'] == 'start': del action['action'] this_events_shows[show_name] = action self._add_to_show_player(event, this_events_shows) YamlRoundtrip.del_key_with_comments(self.fc, 'light_player', self.log)
def do_copy(self, args): """Copy light positions from monitor to your config.""" arguments = args.split(" ") if not arguments or len( arguments) != 2 or arguments[0] != "light_positions": self.stdout.write( "Usage: copy light_positions your_light_config_file\n") return config_loader = YamlRoundtrip() config_name = arguments[1] try: monitor_config = config_loader.load("monitor/monitor.yaml") except Exception as e: # pylint: disable-msg=broad-except self.stdout.write( "Error while loading monitor/monitor.yaml: {}.\n".format(e)) return try: lights_config = config_loader.load(config_name) except Exception as e: # pylint: disable-msg=broad-except self.stdout.write("Error while loading {}: {}.\n".format( config_name, e)) return if "light" not in monitor_config: self.stdout.write( "Error: Monitor config does not contain a light section.\n") return if "lights" not in lights_config: self.stdout.write( "Error: Config does not contain a lights section.\n") return lights_found = 0 for light_name, light_config in lights_config['lights'].items(): if light_name in monitor_config['light']: monitor_light_config = monitor_config['light'][light_name] lights_found += 1 light_config['x'] = monitor_light_config['x'] light_config['y'] = monitor_light_config['y'] config_loader.save(config_name, lights_config) self.stdout.write("Success: Found {} lights.\n".format(lights_found))
def _migrate_layer(self, element): # Migrate layer YamlRoundtrip.rename_key('layer', 'z', element, self.log) YamlRoundtrip.rename_key('h_pos', 'anchor_x', element, self.log) YamlRoundtrip.rename_key('v_pos', 'anchor_y', element, self.log) YamlRoundtrip.rename_key('font', 'style', element, self.log) YamlRoundtrip.rename_key('shade', 'brightness', element, self.log) YamlRoundtrip.del_key_with_comments(element, 'pixel_spacing', self.log) YamlRoundtrip.del_key_with_comments(element, 'antialias', self.log) YamlRoundtrip.del_key_with_comments(element, 'thickness', self.log) YamlRoundtrip.del_key_with_comments(element, 'bg_shade', self.log) YamlRoundtrip.del_key_with_comments(element, 'slide', self.log) return element
def test_round_trip(self): self.maxDiff = None orig_config = """\ hardware: platform: smart_virtual driverboards: virtual dmd: smartmatrix config: - portconfig.yaml - switches.yaml - coils.yaml - devices.yaml - keyboard.yaml - virtual.yaml - images.yaml dmd: physical: false width: 128 height: 32 type: color window: elements: - type: virtualdmd width: 512 height: 128 h_pos: center v_pos: center pixel_color: ff6600 dark_color: 220000 pixel_spacing: 1 - type: shape shape: box width: 516 height: 132 color: aaaaaa thickness: 2 modes: - base - airlock_multiball sound_system: buffer: 512 frequency: 44100 channels: 1 initial_volume: 1 volume_steps: 20 tracks: voice: volume: 1 priority: 2 simultaneous_sounds: 1 preload: false sfx: volume: 1 priority: 1 preload: false simultaneous_sounds: 3 stream: name: music priority: 0 """ parsed_config = YamlRoundtrip.process(orig_config) saved_config = YamlRoundtrip.save_to_str(parsed_config) # print(saved_config) self.assertEqual(orig_config, saved_config)
def _migrate_asset_defaults(self): # convert asset_defaults to assets: if 'asset_defaults' in self.fc: self.log.debug('Renaming key: asset_defaults -> assets:') YamlRoundtrip.rename_key('asset_defaults', 'assets', self.fc, self.log) assets = self.fc['assets'] if 'animations' in assets: self.log.debug("Converting assets:animations to assets:images") if 'images' in assets: self.log.debug("Merging animations: into current " "asset:images:") YamlRoundtrip.copy_with_comments(assets, 'animations', assets, 'images', True, self.log) else: YamlRoundtrip.rename_key('animations', 'images', assets, self.log) YamlRoundtrip.del_key_with_comments(self.fc, 'animations', self.log) if 'movies' in assets: YamlRoundtrip.rename_key('movies', 'videos', assets, self.log) if 'images' in assets: self.log.debug("Converting assets:images:") for settings in assets['images'].values(): YamlRoundtrip.del_key_with_comments(settings, 'target', self.log) if 'sounds' in assets: self.log.debug("Converting assets:sounds:")
def _element_to_widget(self, element, display): # takes an element dict, returns a widget dict width, height = self._get_width_and_height_for_display(element, display) try: element_type = element['type'].lower() except KeyError: return False element = self._migrate_layer(element) type_map = dict(virtualdmd='dmd', text='text', shape='shape', animation='animation', image='image', movie='video', character_picker='character_picker', entered_chars='entered_chars') # Migrate the element type element['type'] = type_map[element_type] self.log.debug('Converting "%s" display_element to "%s" widget', element_type, element['type']) if element_type == 'text': YamlRoundtrip.rename_key('size', 'font_size', element, self.log) if element_type != 'dmd': YamlRoundtrip.del_key_with_comments(element, 'bg_color', self.log) if element_type == 'virtualdmd' and V4Migrator.color_dmd: YamlRoundtrip.del_key_with_comments(element, 'pixel_color', self.log) self.log.debug('Changing widget type from "dmd" to "color_dmd"') element['type'] = 'color_dmd' element = self._migrate_element_x_and_anchor(element, display, height) element = self._migrate_element_y_and_anchor(element, display, width) if element_type == 'animation': element = self._migrate_animation(element) elif element_type == 'shape': element = self._migrate_shape(element) if 'decorators' in element: element = self._migrate_decorators(element, 'decorators', 'animations') if 'cursor_decorators' in element: element = self._migrate_decorators(element, 'cursor_decorators', 'cursor_animations') if 'color' in element: element['color'] = self._get_color(element['color']) if 'movie' in element: YamlRoundtrip.rename_key('movie', 'video', element, self.log) if 'repeat' in element: # indented on purpose YamlRoundtrip.rename_key('repeat', 'loop', element, self.log) if 'loops' in element: # indented on purpose YamlRoundtrip.rename_key('loops', 'loop', element, self.log) self._convert_tokens(element) return element
def _migrate_physical_dmd(self): if ('dmd' in self.fc and 'physical' in self.fc['dmd'] and self.fc['dmd']['physical']): self.log.debug("Converting physical dmd: settings") YamlRoundtrip.del_key_with_comments(self.fc['dmd'], 'physical', self.log) YamlRoundtrip.del_key_with_comments(self.fc['dmd'], 'fps', self.log) if 'type' in self.fc['dmd'] and self.fc['dmd']['type'] == 'color': # physical color DMD YamlRoundtrip.del_key_with_comments(self.fc['dmd'], 'type', self.log) YamlRoundtrip.rename_key('dmd', 'physical_rgb_dmd', self.fc, self.log) else: # physical mono DMD YamlRoundtrip.del_key_with_comments(self.fc['dmd'], 'type', self.log) YamlRoundtrip.rename_key('dmd', 'physical_dmd', self.fc, self.log) YamlRoundtrip.del_key_with_comments(self.fc['displays']['dmd'], 'physical', self.log) YamlRoundtrip.del_key_with_comments(self.fc['displays']['dmd'], 'shades', self.log) YamlRoundtrip.del_key_with_comments(self.fc['displays']['dmd'], 'fps', self.log)