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 <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2018-09-04 20:49:47 -04:00 committed by KevinOConnor
parent 5144c5f01e
commit f80456a698
3 changed files with 130 additions and 116 deletions

116
klippy/configfile.py Normal file
View File

@ -0,0 +1,116 @@
# Code for reading the Klipper config file
#
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
#
# 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)

View File

@ -5,9 +5,7 @@
# Copyright (C) 2018 Janar Sööt <janar.soot@gmail.com>
#
# 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)

View File

@ -4,10 +4,9 @@
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
#
# 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')]