From: Ismaƫl Bouya Date: Tue, 26 Jul 2016 13:30:02 +0000 (+0200) Subject: Give usable errors when parsing configuration X-Git-Tag: 1.0.0~23^2~2 X-Git-Url: https://git.immae.eu/?p=perso%2FImmae%2FProjets%2FPython%2FMusicSampler.git;a=commitdiff_plain;h=05d0d2ed0672aeb2e056c8af79bebde9c8b27199 Give usable errors when parsing configuration --- diff --git a/helpers/__init__.py b/helpers/__init__.py index f5ad848..534e168 100644 --- a/helpers/__init__.py +++ b/helpers/__init__.py @@ -86,7 +86,7 @@ def parse_args(): by Kivy. Pass \"-- --help\" to get Kivy's usage.") from kivy.logger import Logger - Logger.setLevel(logging.ERROR) + Logger.setLevel(logging.WARN) args = parser.parse_args(argv) @@ -137,10 +137,14 @@ def gain(volume, old_volume=None): 20 * math.log10(max(volume, 0.1) / max(old_volume, 0.1)), max(volume, 0)] -def debug_print(message): +def debug_print(message, with_trace=False): from kivy.logger import Logger - Logger.debug('MusicSampler: ' + message) + Logger.debug('MusicSampler: ' + message, exc_info=with_trace) -def error_print(message): +def error_print(message, with_trace=False): from kivy.logger import Logger - Logger.error('MusicSampler: ' + message) + Logger.error('MusicSampler: ' + message, exc_info=with_trace) + +def warn_print(message, with_trace=False): + from kivy.logger import Logger + Logger.warn('MusicSampler: ' + message, exc_info=with_trace) diff --git a/helpers/mapping.py b/helpers/mapping.py index ba2c340..1f63459 100644 --- a/helpers/mapping.py +++ b/helpers/mapping.py @@ -6,10 +6,11 @@ from kivy.clock import Clock import threading import yaml import sys +from collections import defaultdict from .music_file import MusicFile from .mixer import Mixer -from . import Config, gain, error_print +from . import Config, gain, error_print, warn_print from .action import Action class Mapping(RelativeLayout): @@ -26,7 +27,8 @@ class Mapping(RelativeLayout): try: self.key_config, self.open_files = self.parse_config() except Exception as e: - error_print("Error while loading configuration: {}".format(e)) + error_print("Error while loading configuration: {}".format(e), + with_trace=True) sys.exit() super(Mapping, self).__init__(**kwargs) @@ -108,85 +110,171 @@ class Mapping(RelativeLayout): self.running.remove((key, start_time)) def parse_config(self): + def update_alias(prop_hash, aliases, key): + if isinstance(aliases[key], dict): + prop_hash.update(aliases[key], **prop_hash) + else: + warn_print("Alias {} is not a hash, ignored".format(key)) + + def include_aliases(prop_hash, aliases): + if 'include' not in prop_hash: + return + + included = prop_hash['include'] + del(prop_hash['include']) + if isinstance(included, str): + update_alias(prop_hash, aliases, included) + elif isinstance(included, list): + for included_ in included: + if isinstance(included_, str): + update_alias(prop_hash, aliases, included_) + else: + warn_print("Unkown alias include type, ignored: " + "{} in {}".format(included_, included)) + else: + warn_print("Unkown alias include type, ignored: {}" + .format(included)) + + def check_key_property(key_property, key): + if 'description' in key_property: + desc = key_property['description'] + if not isinstance(desc, list): + warn_print("description in key_property '{}' is not " + "a list, ignored".format(key)) + del(key_property['description']) + if 'color' in key_property: + color = key_property['color'] + if not isinstance(color, list)\ + or len(color) != 3\ + or not all(isinstance(item, int) for item in color)\ + or any(item < 0 or item > 255 for item in color): + warn_print("color in key_property '{}' is not " + "a list of 3 valid integers, ignored".format(key)) + del(key_property['color']) + + def check_key_properties(config): + if 'key_properties' in config: + if isinstance(config['key_properties'], dict): + return config['key_properties'] + else: + warn_print("key_properties config is not a hash, ignored") + return {} + else: + return {} + + def check_mapped_keys(config): + if 'keys' in config: + if isinstance(config['keys'], dict): + return config['keys'] + else: + warn_print("keys config is not a hash, ignored") + return {} + else: + return {} + + def check_mapped_key(mapped_keys, key): + if not isinstance(mapped_keys[key], list): + warn_print("key config '{}' is not an array, ignored" + .format(key)) + return [] + else: + return mapped_keys[key] + + def check_music_property(music_property, filename): + if not isinstance(music_property, dict): + warn_print("music_property config '{}' is not a hash, ignored" + .format(filename)) + return {} + if 'name' in music_property: + music_property['name'] = str(music_property['name']) + if 'gain' in music_property: + try: + music_property['gain'] = float(music_property['gain']) + except ValueError as e: + del(music_property['gain']) + warn_print("gain for music_property '{}' is not " + "a float, ignored".format(filename)) + return music_property + stream = open(Config.yml_file, "r") try: - config = yaml.load(stream) + config = yaml.safe_load(stream) except Exception as e: error_print("Error while loading config file: {}".format(e)) sys.exit() stream.close() - aliases = config['aliases'] + if not isinstance(config, dict): + raise Exception("Top level config is supposed to be a hash") + + if 'aliases' in config and isinstance(config['aliases'], dict): + aliases = config['aliases'] + else: + aliases = defaultdict(dict) + if 'aliases' in config: + warn_print("aliases config is not a hash, ignored") + + music_properties = defaultdict(dict) + if 'music_properties' in config and\ + isinstance(config['music_properties'], dict): + music_properties.update(config['music_properties']) + elif 'music_properties' in config: + warn_print("music_properties config is not a hash, ignored") + seen_files = {} - key_properties = {} + key_properties = defaultdict(lambda: { + "actions": [], + "properties": {}, + "files": [] + }) - for key in config['key_properties']: - if key not in key_properties: - key_prop = config['key_properties'][key] - if 'include' in key_prop: - included = key_prop['include'] - del(key_prop['include']) + for key in check_key_properties(config): + key_prop = config['key_properties'][key] + + if not isinstance(key_prop, dict): + warn_print("key_property '{}' is not a hash, ignored" + .format(key)) + continue + + include_aliases(key_prop, aliases) + check_key_property(key_prop, key) + + key_properties[key]["properties"] = key_prop + + for mapped_key in check_mapped_keys(config): + for index, action in enumerate(check_mapped_key( + config['keys'], mapped_key)): + if not isinstance(action, dict) or\ + not len(action) == 1 or\ + not isinstance(list(action.values())[0] or {}, dict): + warn_print("action number {} of key '{}' is invalid, " + "ignored".format(index + 1, mapped_key)) + continue - if isinstance(included, str): - key_prop.update(aliases[included], **key_prop) - else: - for included_ in included: - key_prop.update(aliases[included_], **key_prop) - - key_properties[key] = { - "actions": [], - "properties": key_prop, - "files": [] - } - - for mapped_key in config['keys']: - if mapped_key not in key_properties: - key_properties[mapped_key] = { - "actions": [], - "properties": {}, - "files": [] - } - for action in config['keys'][mapped_key]: action_name = list(action)[0] action_args = {} if action[action_name] is None: - action[action_name] = [] - - if 'include' in action[action_name]: - included = action[action_name]['include'] - del(action[action_name]['include']) + action[action_name] = {} - if isinstance(included, str): - action[action_name].update( - aliases[included], - **action[action_name]) - else: - for included_ in included: - action[action_name].update( - aliases[included_], - **action[action_name]) + include_aliases(action[action_name], aliases) for argument in action[action_name]: if argument == 'file': - filename = action[action_name]['file'] + filename = str(action[action_name]['file']) if filename not in seen_files: - if filename in config['music_properties']: - seen_files[filename] = MusicFile( - filename, - self, - **config['music_properties'][filename]) - else: - seen_files[filename] = MusicFile( - filename, - self) + music_property = check_music_property( + music_properties[filename], + filename) + + seen_files[filename] = MusicFile( + filename, self, **music_property) if filename not in key_properties[mapped_key]['files']: key_properties[mapped_key]['files'] \ .append(seen_files[filename]) action_args['music'] = seen_files[filename] - else: action_args[argument] = action[action_name][argument]