- RESUME is defined only in print/pause_resume.cfg; heat_soak.cfg's duplicate replaced it (no unretract, no fan restore). It now runs HEAT_SOAK_RESUME -> _RESUME_PRINT -> RESUME_BASE. - PAUSE runs PAUSE_BASE first, then retracts and parks once (X20 Y345); a parked flag makes resume undo only what PAUSE did. - New _PRINT_CLEANUP shared by PRINT_END and CANCEL_PRINT: heaters incl. iHEATER_OFF, fan, M221 S100, bdwidth off, mesh and skew cleared. In PRINT_END it runs after RESTORE_GCODE_STATE so the flow reset sticks. - Add CLAUDE.md and tools/effective_config.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
83 lines
2.9 KiB
Python
83 lines
2.9 KiB
Python
"""Show or compare the config Klipper builds from macros/klipper-macros.cfg.
|
|
|
|
Klipper expands [include ...] in order and merges sections with the same
|
|
name (the last value of an option wins). This script does the same, so a
|
|
refactor can be checked for unintended changes.
|
|
|
|
python tools/effective_config.py list sections of the working tree
|
|
python tools/effective_config.py HEAD compare HEAD with the working tree
|
|
python tools/effective_config.py A B compare two git refs
|
|
python tools/effective_config.py HEAD -v also print old/new values
|
|
"""
|
|
import configparser
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
BASE = os.path.join('macros', 'klipper-macros.cfg')
|
|
|
|
|
|
def expand(path):
|
|
lines = []
|
|
with open(path, encoding='utf-8') as f:
|
|
for line in f.read().replace('\r\n', '\n').split('\n'):
|
|
m = re.match(r'^\[include ([^\]]+)\]', line.split('#')[0])
|
|
if m:
|
|
inc = os.path.join(os.path.dirname(path), m.group(1).strip())
|
|
lines += expand(inc)
|
|
else:
|
|
lines.append(line)
|
|
return lines
|
|
|
|
|
|
def load(root):
|
|
cp = configparser.RawConfigParser(strict=False,
|
|
inline_comment_prefixes=(';', '#'))
|
|
cp.read_string('\n'.join(expand(os.path.join(root, BASE))))
|
|
return {s: dict(cp.items(s)) for s in cp.sections()}
|
|
|
|
|
|
def checkout(ref, tmp):
|
|
archive = subprocess.run(['git', '-C', ROOT, 'archive', ref, 'macros'],
|
|
check=True, capture_output=True).stdout
|
|
subprocess.run(['tar', '-x', '-C', tmp], input=archive, check=True)
|
|
return tmp
|
|
|
|
|
|
def main(argv):
|
|
verbose = '-v' in argv
|
|
refs = [a for a in argv if a != '-v']
|
|
with tempfile.TemporaryDirectory() as t1, tempfile.TemporaryDirectory() as t2:
|
|
if not refs:
|
|
for s in sorted(load(ROOT)):
|
|
print(s)
|
|
return 0
|
|
old = load(checkout(refs[0], t1))
|
|
new = load(checkout(refs[1], t2) if len(refs) > 1 else ROOT)
|
|
added, removed = sorted(set(new) - set(old)), sorted(set(old) - set(new))
|
|
changed = 0
|
|
for s in added:
|
|
print('added ', s)
|
|
for s in removed:
|
|
print('removed ', s)
|
|
for s in sorted(set(old) & set(new)):
|
|
keys = sorted(k for k in set(old[s]) | set(new[s])
|
|
if old[s].get(k) != new[s].get(k))
|
|
if keys:
|
|
changed += 1
|
|
print('changed ', s, keys)
|
|
if verbose:
|
|
for k in keys:
|
|
print(' old %s: %r' % (k, old[s].get(k)))
|
|
print(' new %s: %r' % (k, new[s].get(k)))
|
|
print('%d sections, %d added, %d removed, %d changed'
|
|
% (len(new), len(added), len(removed), changed))
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main(sys.argv[1:]))
|