From f80456a6983566ae073e477b9033090263c4745d Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 4 Sep 2018 20:49:47 -0400 Subject: [PATCH] configfile: Move config file code from klippy.py to new file Add a klippy/configfile.py file with the code needed to read the main printer config file. Signed-off-by: Kevin O'Connor --- klippy/configfile.py | 116 ++++++++++++++++++++++++++++++++ klippy/extras/display/menu.py | 9 +-- klippy/klippy.py | 121 ++++------------------------------ 3 files changed, 130 insertions(+), 116 deletions(-) create mode 100644 klippy/configfile.py diff --git a/klippy/configfile.py b/klippy/configfile.py new file mode 100644 index 00000000..9c4ad4e1 --- /dev/null +++ b/klippy/configfile.py @@ -0,0 +1,116 @@ +# Code for reading the Klipper config file +# +# Copyright (C) 2016-2018 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import ConfigParser + +error = ConfigParser.Error + +class sentinel: + pass + +class ConfigWrapper: + error = ConfigParser.Error + def __init__(self, printer, fileconfig, access_tracking, section): + self.printer = printer + self.fileconfig = fileconfig + self.access_tracking = access_tracking + self.section = section + def get_printer(self): + return self.printer + def get_name(self): + return self.section + def _get_wrapper(self, parser, option, default, + minval=None, maxval=None, above=None, below=None): + if (default is not sentinel + and not self.fileconfig.has_option(self.section, option)): + return default + self.access_tracking[(self.section.lower(), option.lower())] = 1 + try: + v = parser(self.section, option) + except self.error as e: + raise + except: + raise error("Unable to parse option '%s' in section '%s'" % ( + option, self.section)) + if minval is not None and v < minval: + raise error( + "Option '%s' in section '%s' must have minimum of %s" % ( + option, self.section, minval)) + if maxval is not None and v > maxval: + raise error( + "Option '%s' in section '%s' must have maximum of %s" % ( + option, self.section, maxval)) + if above is not None and v <= above: + raise error("Option '%s' in section '%s' must be above %s" % ( + option, self.section, above)) + if below is not None and v >= below: + raise self.error("Option '%s' in section '%s' must be below %s" % ( + option, self.section, below)) + return v + def get(self, option, default=sentinel): + return self._get_wrapper(self.fileconfig.get, option, default) + def getint(self, option, default=sentinel, minval=None, maxval=None): + return self._get_wrapper( + self.fileconfig.getint, option, default, minval, maxval) + def getfloat(self, option, default=sentinel, + minval=None, maxval=None, above=None, below=None): + return self._get_wrapper(self.fileconfig.getfloat, option, default, + minval, maxval, above, below) + def getboolean(self, option, default=sentinel): + return self._get_wrapper(self.fileconfig.getboolean, option, default) + def getchoice(self, option, choices, default=sentinel): + c = self.get(option, default) + if c not in choices: + raise error("Choice '%s' for option '%s' in section '%s'" + " is not a valid choice" % (c, option, self.section)) + return choices[c] + def getsection(self, section): + return ConfigWrapper(self.printer, self.fileconfig, + self.access_tracking, section) + def has_section(self, section): + return self.fileconfig.has_section(section) + def get_prefix_sections(self, prefix): + return [self.getsection(s) for s in self.fileconfig.sections() + if s.startswith(prefix)] + +class ConfigLogger: + def __init__(self, fileconfig, printer): + self.lines = ["===== Config file ====="] + fileconfig.write(self) + self.lines.append("=======================") + printer.set_rollover_info("config", "\n".join(self.lines)) + def write(self, data): + self.lines.append(data.strip()) + +class PrinterConfig: + def __init__(self, printer): + self.printer = printer + def read_config(self, filename): + fileconfig = ConfigParser.RawConfigParser() + res = fileconfig.read(filename) + if not res: + raise error("Unable to open config file %s" % (filename,)) + return ConfigWrapper(self.printer, fileconfig, {}, 'printer') + def read_main_config(self): + filename = self.printer.get_start_args()['config_file'] + return self.read_config(filename) + def check_unused_options(self, config): + access_tracking = config.access_tracking + fileconfig = config.fileconfig + objects = dict(self.printer.lookup_objects()) + # Validate that there are no undefined parameters in the config file + valid_sections = { s: 1 for s, o in access_tracking } + for section_name in fileconfig.sections(): + section = section_name.lower() + if section not in valid_sections and section not in objects: + raise error("Section '%s' is not a valid config section" % ( + section,)) + for option in fileconfig.options(section_name): + option = option.lower() + if (section, option) not in access_tracking: + raise error("Option '%s' is not valid in section '%s'" % ( + option, section)) + def log_config(self, config): + ConfigLogger(config.fileconfig, self.printer) diff --git a/klippy/extras/display/menu.py b/klippy/extras/display/menu.py index c32d9ed3..a8c74341 100644 --- a/klippy/extras/display/menu.py +++ b/klippy/extras/display/menu.py @@ -5,9 +5,7 @@ # Copyright (C) 2018 Janar Sööt # # This file may be distributed under the terms of the GNU GPLv3 license. -import os, ConfigParser, logging -import sys, ast, re -import klippy +import os, logging, sys, ast, re class error(Exception): @@ -963,10 +961,9 @@ class MenuManager: desc=self.cmd_DO_help) # Parse local config file in same directory as current module - fileconfig = ConfigParser.RawConfigParser() + pconfig = self.printer.lookup_object('configfile') localname = os.path.join(os.path.dirname(__file__), 'menu.cfg') - fileconfig.read(localname) - localconfig = klippy.ConfigWrapper(self.printer, fileconfig, {}, None) + localconfig = pconfig.read_config(localname) # Load items from local config self.load_menuitems(localconfig) diff --git a/klippy/klippy.py b/klippy/klippy.py index e88b54d8..e8f737f7 100644 --- a/klippy/klippy.py +++ b/klippy/klippy.py @@ -4,10 +4,9 @@ # Copyright (C) 2016-2018 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import sys, os, optparse, logging, time, threading -import collections, ConfigParser, importlib +import sys, os, optparse, logging, time, threading, collections, importlib import util, reactor, queuelogger, msgproto -import gcode, pins, heater, mcu, toolhead +import gcode, configfile, pins, heater, mcu, toolhead message_ready = "Printer is ready" @@ -46,89 +45,8 @@ config, and restart the host software. Printer is shutdown """ -class ConfigWrapper: - error = ConfigParser.Error - class sentinel: - pass - def __init__(self, printer, fileconfig, access_tracking, section): - self.printer = printer - self.fileconfig = fileconfig - self.access_tracking = access_tracking - self.section = section - def get_printer(self): - return self.printer - def get_name(self): - return self.section - def _get_wrapper(self, parser, option, default, - minval=None, maxval=None, above=None, below=None): - if (default is not self.sentinel - and not self.fileconfig.has_option(self.section, option)): - return default - self.access_tracking[(self.section.lower(), option.lower())] = 1 - try: - v = parser(self.section, option) - except self.error as e: - raise - except: - raise self.error("Unable to parse option '%s' in section '%s'" % ( - option, self.section)) - if minval is not None and v < minval: - raise self.error( - "Option '%s' in section '%s' must have minimum of %s" % ( - option, self.section, minval)) - if maxval is not None and v > maxval: - raise self.error( - "Option '%s' in section '%s' must have maximum of %s" % ( - option, self.section, maxval)) - if above is not None and v <= above: - raise self.error( - "Option '%s' in section '%s' must be above %s" % ( - option, self.section, above)) - if below is not None and v >= below: - raise self.error( - "Option '%s' in section '%s' must be below %s" % ( - option, self.section, below)) - return v - def get(self, option, default=sentinel): - return self._get_wrapper(self.fileconfig.get, option, default) - def getint(self, option, default=sentinel, minval=None, maxval=None): - return self._get_wrapper( - self.fileconfig.getint, option, default, minval, maxval) - def getfloat(self, option, default=sentinel, - minval=None, maxval=None, above=None, below=None): - return self._get_wrapper(self.fileconfig.getfloat, option, default, - minval, maxval, above, below) - def getboolean(self, option, default=sentinel): - return self._get_wrapper(self.fileconfig.getboolean, option, default) - def getchoice(self, option, choices, default=sentinel): - c = self.get(option, default) - if c not in choices: - raise self.error( - "Choice '%s' for option '%s' in section '%s'" - " is not a valid choice" % (c, option, self.section)) - return choices[c] - def getsection(self, section): - return ConfigWrapper(self.printer, self.fileconfig, - self.access_tracking, section) - def has_section(self, section): - return self.fileconfig.has_section(section) - def get_prefix_sections(self, prefix): - return [self.getsection(s) for s in self.fileconfig.sections() - if s.startswith(prefix)] - -class ConfigLogger(): - def __init__(self, cfg, bglogger): - self.lines = ["===== Config file ====="] - cfg.write(self) - self.lines.append("=======================") - data = "\n".join(self.lines) - logging.info(data) - bglogger.set_rollover_info("config", data) - def write(self, data): - self.lines.append(data.strip()) - class Printer: - config_error = ConfigParser.Error + config_error = configfile.error def __init__(self, input_fd, bglogger, start_args): self.bglogger = bglogger self.start_args = start_args @@ -156,10 +74,10 @@ class Printer: raise self.config_error( "Printer object '%s' already created" % (name,)) self.objects[name] = obj - def lookup_object(self, name, default=ConfigWrapper.sentinel): + def lookup_object(self, name, default=configfile.sentinel): if name in self.objects: return self.objects[name] - if default is ConfigWrapper.sentinel: + if default is configfile.sentinel: raise self.config_error("Unknown config object '%s'" % (name,)) return default def lookup_objects(self, module=None): @@ -196,36 +114,19 @@ class Printer: self.objects[section] = init_func(config.getsection(section)) return self.objects[section] def _read_config(self): - fileconfig = ConfigParser.RawConfigParser() - config_file = self.start_args['config_file'] - res = fileconfig.read(config_file) - if not res: - raise self.config_error("Unable to open config file %s" % ( - config_file,)) + self.objects['configfile'] = pconfig = configfile.PrinterConfig(self) + config = pconfig.read_main_config() if self.bglogger is not None: - ConfigLogger(fileconfig, self.bglogger) + pconfig.log_config(config) # Create printer components - access_tracking = {} - config = ConfigWrapper(self, fileconfig, access_tracking, 'printer') for m in [pins, heater, mcu]: m.add_printer_objects(config) - for section in fileconfig.sections(): - self.try_load_module(config, section) + for section_config in config.get_prefix_sections(''): + self.try_load_module(config, section_config.get_name()) for m in [toolhead]: m.add_printer_objects(config) # Validate that there are no undefined parameters in the config file - valid_sections = { s: 1 for s, o in access_tracking } - for section_name in fileconfig.sections(): - section = section_name.lower() - if section not in valid_sections and section not in self.objects: - raise self.config_error( - "Section '%s' is not a valid config section" % (section,)) - for option in fileconfig.options(section_name): - option = option.lower() - if (section, option) not in access_tracking: - raise self.config_error( - "Option '%s' is not valid in section '%s'" % ( - option, section)) + pconfig.check_unused_options(config) # Determine which printer objects have state callbacks self.state_cb = [o.printer_state for o in self.objects.values() if hasattr(o, 'printer_state')]