Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configuration reference interpolation #448

Merged
merged 2 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions docs/Config_Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,30 @@ A collection of Kalico-specific system options
#log_webhook_method_register_messages: False
```

## ⚠️ Configuration references

In your configuration, you can reference other values to share
configuration between multiple sections. References take the form of
`${option}` to copy a value in the current section, or
`${section.option}` to look up a value elsewhere in your configuration.

Optionally, a `[constants]` section may be used specifically to store
these values. Unlike the rest of your configuration, unused constants
will show a warning instead of causing an error.

```
[constants]
run_current_ab: 1.0
i_am_not_used: True # Will show "Constant 'i_am_not_used' is unused"

[tmc5160 stepper_x]
run_current: ${constants.run_current_ab}

[tmc5160 stepper_y]
run_current: ${tmc5160 stepper_x.run_current}
# Nested references work, but are not advised
```

## Common kinematic settings

### [printer]
Expand Down
1 change: 1 addition & 0 deletions docs/Danger_Features.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- `--rotate-log-at-restart` can be added to your Kalico start script or service to force log rotation every restart.
- [`[virtual_sdcard] with_subdirs`](./Config_Reference.md#virtual_sdcard) enables scanning of subdirectories for .gcode files, for the menu and M20/M23 commands
- [`[firmware_retraction] z_hop_height`](./Config_Reference.md#firmware_retraction) adds an automatic z hop when using firmware retraction
- [`[constants]` and `${constants.value}`](./Config_Reference.md#configuration-references) allow re-using values in your configuration

## Enhanced behavior

Expand Down
58 changes: 48 additions & 10 deletions klippy/configfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,38 @@ class sentinel:
pass


class SectionInterpolation(configparser.Interpolation):
"""
variable interpolation replacing ${[section.]option}
"""

_KEYCRE = re.compile(
r"\$\{(?:(?P<section>[^.:${}]+)[.:])?(?P<option>[^${}]+)\}"
)

def __init__(self, access_tracking):
self.access_tracking = access_tracking

def before_get(self, parser, section, option, value, defaults):
depth = configparser.MAX_INTERPOLATION_DEPTH
while depth:
depth -= 1

match = self._KEYCRE.search(value)
if not match:
break

sect = match.group("section") or section
opt = match.group("option")

const = parser.get(sect, opt)
self.access_tracking.setdefault((sect, opt), const)

value = value[: match.start()] + const + value[match.end() :]

return value


class ConfigWrapper:
error = configparser.Error

Expand Down Expand Up @@ -416,14 +448,17 @@ def _parse_config(self, data, filename, fileconfig, visited):
visited.remove(path)

def _build_config_wrapper(self, data, filename):
if sys.version_info.major >= 3:
fileconfig = configparser.RawConfigParser(
strict=False, inline_comment_prefixes=(";", "#")
)
else:
fileconfig = configparser.RawConfigParser()
access_tracking = {}
fileconfig = configparser.RawConfigParser(
strict=False,
inline_comment_prefixes=(";", "#"),
interpolation=SectionInterpolation(access_tracking),
)

self._parse_config(data, filename, fileconfig, set())
return ConfigWrapper(self.printer, fileconfig, {}, "printer")
return ConfigWrapper(
self.printer, fileconfig, access_tracking, "printer"
)

def _build_config_string(self, config):
sfile = io.StringIO()
Expand Down Expand Up @@ -454,7 +489,7 @@ def check_unused_options(self, config, error_on_unused):
for option in self.autosave.fileconfig.options(section):
access_tracking[(section.lower(), option.lower())] = 1
# Validate that there are no undefined parameters in the config file
valid_sections = {s: 1 for s, o in access_tracking}
valid_sections = {s 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:
Expand All @@ -468,7 +503,7 @@ def check_unused_options(self, config, error_on_unused):
for option in fileconfig.options(section_name):
option = option.lower()
if (section, option) not in access_tracking:
if error_on_unused:
if error_on_unused and section != "constants":
raise error(
"Option '%s' is not valid in section '%s'"
% (option, section)
Expand Down Expand Up @@ -524,7 +559,10 @@ def _build_status(self, config):

for section, option in self.unused_options:
_type = "unused_option"
msg = f"Option '{option}' in section '{section}' is invalid"
if section == "constants":
msg = f"Constant '{option}' is unused"
else:
msg = f"Option '{option}' in section '{section}' is invalid"
self.warn(_type, msg, section, option)
for section in self.unused_sections:
_type = "unused_section"
Expand Down
4 changes: 3 additions & 1 deletion test/klippy/danger_options.cfg
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
[constants]
false: False

[poopybutt]
[danger_options]
log_statistics: False
log_config_file_at_startup: False
error_on_unused_config_options: False
error_on_unused_config_options: ${constants.false}
log_bed_mesh_at_startup: False
log_shutdown_info: False
allow_plugin_override: True
Expand Down
Loading