Source code for flexget.plugins.cli.check

import codecs

import yaml
from loguru import logger

from flexget import options
from flexget.event import event
from flexget.terminal import console

logger = logger.bind(name='check')


[docs] @event('manager.before_config_load') def before_config_load(manager): if manager.options.cli_command == 'check': pre_check_config(manager.config_path)
[docs] def pre_check_config(config_path): """Check configuration file for common mistakes that are easily detectable.""" with codecs.open(config_path, 'r', 'utf-8') as config_file: try: config = config_file.read() except UnicodeDecodeError: return def get_indentation(line): i, n = 0, len(line) while i < n and line[i] == ' ': i += 1 return i def isodd(n): return bool(n % 2) line_num = 0 duplicates = {} # flags prev_indentation = 0 prev_mapping = False prev_list = True prev_scalar = True list_open = False # multiline list with [ for line in config.splitlines(): if '# warnings off' in line.strip().lower(): logger.debug('config pre-check warnings off') break line_num += 1 # remove linefeed line = line.rstrip() # empty line if line.strip() == '': continue # comment line if line.strip().startswith('#'): continue indentation = get_indentation(line) if prev_scalar: if indentation <= prev_indentation: prev_scalar = False else: continue cur_list = line.strip().startswith('-') # skipping lines as long as multiline compact list is open if list_open: if line.strip().endswith(']'): list_open = False # print 'closed list at line %s' % line continue list_open = line.strip().endswith(': [') or line.strip().endswith(':[') if list_open: # print 'list open at line %s' % line continue # print '#%i: %s' % (line_num, line) # print 'indentation: %s, prev_ind: %s, prev_mapping: %s, prev_list: %s, cur_list: %s' % \ # (indentation, prev_indentation, prev_mapping, prev_list, cur_list) if ':\t' in line: logger.critical( 'Line {} has TAB character after : character. DO NOT use tab key when editing config!', line_num, ) elif '\t' in line: logger.warning('Line {} has tabs, use only spaces!', line_num) if isodd(indentation): logger.warning('Config line {} has odd (uneven) indentation', line_num) if indentation > prev_indentation and not prev_mapping: # line increases indentation, but previous didn't start mapping logger.warning("Config line {} is likely missing ':' at the end", line_num - 1) if indentation > prev_indentation + 2 and prev_mapping and not prev_list: # mapping value after non list indented more than 2 logger.warning('Config line {} is indented too much', line_num) if indentation <= prev_indentation + (2 * (not cur_list)) and prev_mapping and prev_list: logger.warning('Config line {} is not indented enough', line_num) if prev_mapping and cur_list: # list after opening mapping if indentation < prev_indentation or indentation > prev_indentation + 2 + ( 2 * prev_list ): logger.warning( 'Config line {} containing list element is indented incorrectly', line_num ) elif prev_mapping and indentation <= prev_indentation: # after opening a map, indentation doesn't increase logger.warning( "Config line {} is indented incorrectly (previous line ends with ':')", line_num ) # notify if user is trying to set same key multiple times in a task (a common mistake) for level in duplicates: # when indentation goes down, delete everything indented more than that if indentation < level: duplicates[level] = {} if ':' in line: name = line.split(':', 1)[0].strip() ns = duplicates.setdefault(indentation, {}) if name in ns: logger.warning( 'Trying to set value for `{}` in line {}, but it is already defined in line {}!', name, line_num, ns[name], ) ns[name] = line_num prev_indentation = indentation # this line is a mapping (ends with :) prev_mapping = line[-1] == ':' prev_scalar = line[-1] in '|>' # this line is a list prev_list = line.strip()[0] == '-' if prev_list: # This line is in a list, so clear the duplicates, # as duplicates are not always wrong in a list. see #697 duplicates[indentation] = {} logger.verbose('Pre-checked {} configuration lines', line_num)
[docs] def check(manager, options): logger.verbose('Checking config file `{}`', manager.config_path) if manager.is_daemon: # If we are running in a daemon, check disk config pre_check_config(manager.config_path) with codecs.open(manager.config_path, 'r', encoding='utf-8') as config_file: try: config = yaml.safe_load(config_file) except yaml.error.YAMLError as e: logger.critical('Config file is invalid YAML:') for line in str(e).split('\n'): console(line) return try: manager.validate_config(config) except ValueError as e: for error in getattr(e, 'errors', []): logger.critical('[{}] {}', error.json_pointer, error.message) else: logger.verbose('Config passed check.') else: # If we aren't in a daemon, the config already validated if we got here logger.verbose('Config passed check.')
[docs] @event('options.register') def register_options(): options.register_command('check', check, help='validate configuration file and print errors')