#!/usr/bin/env python3 """ mc-apply.py Apply firmware configuration from a JSON file (marlin_config.json). usage: mc-apply.py [-h] [--opt] [--verbose] [config_file] Process Marlin firmware configuration. positional arguments: config_file Path to the configuration file. optional arguments: -h, --help Show this help message and exit --opt Output as an option setting script. --verbose Enable verbose logging (0-2) """ import json, sys, os, configuration import config import argparse verbose = 0 def blab(msg, level=1): if verbose >= level: print(f"[mc-apply] {msg}") def normalize_value(v): """ Normalize configuration values to consistent format. Returns tuple: (action, value) where action is 'enable', 'disable', or 'set' - "on", "true", True, "" -> ('enable', '') - Enable without value - "off", "false", False -> ('disable', '') - Disable/comment out - Any other value -> ('set', value) - Enable with value """ # Convert to string for comparison, handle JSON booleans if isinstance(v, bool): v_str = 'true' if v else 'false' else: v_str = str(v).strip().lower() # Check for enable values if v_str in ('on', 'true', ''): return ('enable', '') # Check for disable values elif v_str in ('off', 'false'): return ('disable', '') # Everything else is a value to set else: return ('set', v if not isinstance(v, bool) else v_str) def report_version(conf): if 'VERSION' in conf: blab("Configuration version information:") for k, v in sorted(conf['VERSION'].items()): print(k + ': ' + v) def write_opt_file(conf, outpath='Marlin/apply_config.sh'): blab(f"Writing configuration script to {outpath}") with open(outpath, 'w', encoding='utf-8') as outfile: for key, val in conf.items(): if key in ('__INITIAL_HASH', '__directives__', 'VERSION'): continue # Other keys are assumed to be configs if not isinstance(val, dict): continue # Write config commands to the script file lines = [] for k, v in sorted(val.items()): action, norm_val = normalize_value(v) if action == 'enable': lines += [f'opt_enable {k}'] blab(f" opt_enable {k}", 2) elif action == 'disable': lines += [f'opt_disable {k}'] blab(f" opt_disable {k}", 2) else: # action == 'set' norm_val = str(norm_val).replace('"', '\\"').replace("'", "\\'").replace(' ', '\\ ') lines += [f'opt_set {k} {norm_val}'] blab(f" opt_set {k} {norm_val}", 2) outfile.write('\n'.join(lines)) print('Config script written to: ' + outpath) def back_up_config(name): # Back up the existing file before modifying it conf_path = 'Marlin/' + name if not os.path.exists(conf_path): blab(f"Config file not found: {conf_path}", 0) return with open(conf_path, 'r', encoding='utf-8') as f: # Write a filename.bak#.ext retaining the original extension parts = conf_path.split('.') nr = '' while True: bak_path = '.'.join(parts[:-1]) + f'.bak{nr}.' + parts[-1] if os.path.exists(bak_path): nr = 1 if nr == '' else nr + 1 continue with open(bak_path, 'w', encoding='utf-8', newline='') as b: b.writelines(f.readlines()) blab(f"Backed up {conf_path} to {bak_path}", 2) break def process_directives(directives): """Process special directives before applying config options""" if not isinstance(directives, list): directives = [directives] for directive in directives: directive = directive.strip() blab(f"Processing directive: {directive}") # Handle [disable] directive if directive == "[disable]": configuration.disable_all_options() # Handle example fetching (examples/path or example/path) elif directive.startswith('examples/') or directive.startswith('example/'): if directive.startswith('example/'): directive = 'examples' + directive[7:] configuration.fetch_example(directive) # Handle direct URLs elif directive.startswith('http://') or directive.startswith('https://'): configuration.fetch_example(directive) else: blab(f"Unknown directive: {directive}", 0) def apply_config(conf): # Process directives first if they exist if '__directives__' in conf: blab("=" * 20 + " Processing directives...") process_directives(conf['__directives__']) # Apply configuration options blab("=" * 20 + " Applying configuration options...") for key in conf: if key in ('__INITIAL_HASH', '__directives__', 'VERSION'): continue # Skip non-dict values if not isinstance(conf[key], dict): continue back_up_config(key) for k, v in conf[key].items(): action, norm_val = normalize_value(v) conf_file = 'Marlin/' + key if action == 'enable': blab(f"Enabling {k}", 2) config.enable(conf_file, k, True) elif action == 'disable': blab(f"Disabling {k}", 2) config.enable(conf_file, k, False) else: # action == 'set' blab(f"Setting {k} = {norm_val}", 2) config.set(conf_file, k, norm_val) def main(): global verbose parser = argparse.ArgumentParser(description='Process Marlin firmware configuration.') parser.add_argument('--opt', action='store_true', help='Output as an option setting script.') parser.add_argument('--verbose', '-v', type=int, default=0, help='Verbose logging level (0-2, default: 0)') parser.add_argument('config_file', nargs='?', default='marlin_config.json', help='Path to the configuration file.') args = parser.parse_args() # Set verbose level verbose = args.verbose try: infile = open(args.config_file, 'r', encoding='utf-8') except: print(f'No {args.config_file} found.') sys.exit(1) conf = json.load(infile) report_version(conf) if args.opt: write_opt_file(conf) else: apply_config(conf) if __name__ == '__main__': main()