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:
parent
5144c5f01e
commit
f80456a698
|
@ -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)
|
|
@ -5,9 +5,7 @@
|
||||||
# Copyright (C) 2018 Janar Sööt <janar.soot@gmail.com>
|
# Copyright (C) 2018 Janar Sööt <janar.soot@gmail.com>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import os, ConfigParser, logging
|
import os, logging, sys, ast, re
|
||||||
import sys, ast, re
|
|
||||||
import klippy
|
|
||||||
|
|
||||||
|
|
||||||
class error(Exception):
|
class error(Exception):
|
||||||
|
@ -963,10 +961,9 @@ class MenuManager:
|
||||||
desc=self.cmd_DO_help)
|
desc=self.cmd_DO_help)
|
||||||
|
|
||||||
# Parse local config file in same directory as current module
|
# 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')
|
localname = os.path.join(os.path.dirname(__file__), 'menu.cfg')
|
||||||
fileconfig.read(localname)
|
localconfig = pconfig.read_config(localname)
|
||||||
localconfig = klippy.ConfigWrapper(self.printer, fileconfig, {}, None)
|
|
||||||
|
|
||||||
# Load items from local config
|
# Load items from local config
|
||||||
self.load_menuitems(localconfig)
|
self.load_menuitems(localconfig)
|
||||||
|
|
121
klippy/klippy.py
121
klippy/klippy.py
|
@ -4,10 +4,9 @@
|
||||||
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import sys, os, optparse, logging, time, threading
|
import sys, os, optparse, logging, time, threading, collections, importlib
|
||||||
import collections, ConfigParser, importlib
|
|
||||||
import util, reactor, queuelogger, msgproto
|
import util, reactor, queuelogger, msgproto
|
||||||
import gcode, pins, heater, mcu, toolhead
|
import gcode, configfile, pins, heater, mcu, toolhead
|
||||||
|
|
||||||
message_ready = "Printer is ready"
|
message_ready = "Printer is ready"
|
||||||
|
|
||||||
|
@ -46,89 +45,8 @@ config, and restart the host software.
|
||||||
Printer is shutdown
|
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:
|
class Printer:
|
||||||
config_error = ConfigParser.Error
|
config_error = configfile.error
|
||||||
def __init__(self, input_fd, bglogger, start_args):
|
def __init__(self, input_fd, bglogger, start_args):
|
||||||
self.bglogger = bglogger
|
self.bglogger = bglogger
|
||||||
self.start_args = start_args
|
self.start_args = start_args
|
||||||
|
@ -156,10 +74,10 @@ class Printer:
|
||||||
raise self.config_error(
|
raise self.config_error(
|
||||||
"Printer object '%s' already created" % (name,))
|
"Printer object '%s' already created" % (name,))
|
||||||
self.objects[name] = obj
|
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:
|
if name in self.objects:
|
||||||
return self.objects[name]
|
return self.objects[name]
|
||||||
if default is ConfigWrapper.sentinel:
|
if default is configfile.sentinel:
|
||||||
raise self.config_error("Unknown config object '%s'" % (name,))
|
raise self.config_error("Unknown config object '%s'" % (name,))
|
||||||
return default
|
return default
|
||||||
def lookup_objects(self, module=None):
|
def lookup_objects(self, module=None):
|
||||||
|
@ -196,36 +114,19 @@ class Printer:
|
||||||
self.objects[section] = init_func(config.getsection(section))
|
self.objects[section] = init_func(config.getsection(section))
|
||||||
return self.objects[section]
|
return self.objects[section]
|
||||||
def _read_config(self):
|
def _read_config(self):
|
||||||
fileconfig = ConfigParser.RawConfigParser()
|
self.objects['configfile'] = pconfig = configfile.PrinterConfig(self)
|
||||||
config_file = self.start_args['config_file']
|
config = pconfig.read_main_config()
|
||||||
res = fileconfig.read(config_file)
|
|
||||||
if not res:
|
|
||||||
raise self.config_error("Unable to open config file %s" % (
|
|
||||||
config_file,))
|
|
||||||
if self.bglogger is not None:
|
if self.bglogger is not None:
|
||||||
ConfigLogger(fileconfig, self.bglogger)
|
pconfig.log_config(config)
|
||||||
# Create printer components
|
# Create printer components
|
||||||
access_tracking = {}
|
|
||||||
config = ConfigWrapper(self, fileconfig, access_tracking, 'printer')
|
|
||||||
for m in [pins, heater, mcu]:
|
for m in [pins, heater, mcu]:
|
||||||
m.add_printer_objects(config)
|
m.add_printer_objects(config)
|
||||||
for section in fileconfig.sections():
|
for section_config in config.get_prefix_sections(''):
|
||||||
self.try_load_module(config, section)
|
self.try_load_module(config, section_config.get_name())
|
||||||
for m in [toolhead]:
|
for m in [toolhead]:
|
||||||
m.add_printer_objects(config)
|
m.add_printer_objects(config)
|
||||||
# Validate that there are no undefined parameters in the config file
|
# Validate that there are no undefined parameters in the config file
|
||||||
valid_sections = { s: 1 for s, o in access_tracking }
|
pconfig.check_unused_options(config)
|
||||||
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))
|
|
||||||
# Determine which printer objects have state callbacks
|
# Determine which printer objects have state callbacks
|
||||||
self.state_cb = [o.printer_state for o in self.objects.values()
|
self.state_cb = [o.printer_state for o in self.objects.values()
|
||||||
if hasattr(o, 'printer_state')]
|
if hasattr(o, 'printer_state')]
|
||||||
|
|
Loading…
Reference in New Issue