Coverage for src / zooc / config / config.py: 0%

78 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-11 21:45 +0000

1"""Post-install and uninstall script for ZOOC. 

2 

3Run this script after the ZOOC package is installed: 

4 

5.. code-block:: shell 

6 

7 ~/klippy-env/bin/zooc-setup 

8 

9Script copies the necessary configuration files and modifies the printer configuration to include ZOOC: 

10 

11- ~/klipper/klippy/extras/zooc.py for klipper to load ZOOC module. 

12- ~/klipper_config/zooc.cfg for the default ZOOC configuration, if it does not exist yet. 

13- Modify ~/printer.cfg to include the above ZOOC configuration. 

14 

15To uninstall ZOOC, run the teardown script first to undo the zooc-setup changes: 

16 

17.. code-block:: shell 

18 

19 ~/klippy-env/bin/zooc-teardown 

20 

21""" 

22 

23import os 

24import re 

25import shutil 

26from importlib.resources import files 

27from typing import IO # noqa: F401 

28 

29# Define filenames and paths once to avoid repetition 

30KLIPPER_EXTRAS_DIR = os.path.expanduser("~/klipper/klippy/extras") 

31KLIPPER_CFG_DIR = os.path.expanduser("~/klipper_config") 

32PRINTER_CFG_PATH = os.path.expanduser("~/printer.cfg") 

33ZOOC_CFG_INCLUDE_LINE = "[include klipper_config/zooc.cfg]" 

34 

35def _insert_line_before(filename: str, match_line: str, new_line: str) -> bool: 

36 """Insert a new line before a matching line in a file, or if not found, at the end of the file. 

37 

38 :param filename: Path to the file to modify. 

39 :param match_line: Regular expression to match the line before which the new line should be inserted. 

40 :param new_line: The new line to insert. 

41 :return: True if the new line was inserted, False if it already exists. 

42 """ 

43 with open(file=filename, encoding="utf-8") as f: 

44 lines = f.readlines() 

45 if any(new_line in line for line in lines): 

46 return False # Line already exists, do nothing 

47 pattern = re.compile(match_line) 

48 inserted = False 

49 with open(file=filename, mode="w", encoding="utf-8") as f: 

50 for line in lines: 

51 if not inserted and pattern.match(line): 

52 f.write(new_line + "\n") 

53 inserted = True 

54 f.write(line) 

55 return True 

56 

57 

58def _remove_line_from(filename: str, line_to_remove: str) -> bool: 

59 """Remove a specific line from a file. 

60 

61 :param filename: Path to the file to modify. 

62 :param line_to_remove: The line to remove. 

63 :return: True if the line was removed, False if it was not found. 

64 """ 

65 with open(file=filename, encoding="utf-8") as f: 

66 lines = f.readlines() 

67 

68 line_removed = False 

69 with open(file=filename, mode="w", encoding="utf-8") as f: 

70 for line in lines: 

71 if line.strip() == line_to_remove.strip(): 

72 line_removed = True 

73 continue 

74 f.write(line) 

75 return line_removed 

76 

77 

78def copy_resource_file(input_pkg: str, input_file: str, output_path: str, mkdir: bool = True, overwrite: bool = True) -> bool: 

79 """Copy a file from a package to a destination path. 

80 

81 :param input_pkg: Package name where the file is located. 

82 :param input_file: File name to copy. 

83 :param output_path: Destination path where the file should be copied. 

84 :param mkdir: If True, create the directory if it does not exist. 

85 :param overwrite: If True, overwrite the file if it already exists. 

86 :return: True if the file was copied, False if it already exists and overwrite is False. 

87 :raises FileNotFoundError: If the output path does not exist and mkdir is False 

88 """ 

89 if mkdir: 

90 os.makedirs(output_path, exist_ok=True) 

91 

92 if os.path.exists(output_path): 

93 # Ensure the output path is a directory 

94 output_file = os.path.join(output_path, input_file) 

95 if overwrite or not os.path.exists(output_file): 

96 source = files(input_pkg).joinpath(input_file) 

97 with source.open("rb") as src: # type: IO[bytes] 

98 with open(output_file, "wb") as dst: # type: IO[bytes] 

99 # noinspection PyTypeChecker 

100 shutil.copyfileobj(src, dst) 

101 os.chmod(output_file, 0o644) 

102 print(f"Copied {input_file} to {output_file}") 

103 return True 

104 return False 

105 raise FileNotFoundError(f"File {input_file} was not copied due to directory {output_path} does not exist.") 

106 

107# ----------------- 

108# Setup and Teardown Functions 

109# ----------------- 

110 

111def setup() -> int: 

112 """Post-install script for ZOOC. 

113 

114 :return: 0 on success, 1 on failure. 

115 """ 

116 print("Running post-install steps for ZOOC...") 

117 

118 # Copy zooc.py to the Klipper extras directory. 

119 copy_resource_file(input_pkg='extras', input_file='zooc.py', output_path=KLIPPER_EXTRAS_DIR, mkdir=False) 

120 

121 # Copy zooc.cfg to the Klipper configuration if it does not exist. 

122 cfg_copied = copy_resource_file(input_pkg='zooc.config', input_file='zooc.cfg', output_path=KLIPPER_CFG_DIR, mkdir=True, overwrite=False) 

123 

124 # Include ZOOC in printer configuration 

125 if os.path.exists(PRINTER_CFG_PATH): 

126 if (_insert_line_before( 

127 PRINTER_CFG_PATH, 

128 r"^#\*\#", 

129 ZOOC_CFG_INCLUDE_LINE, 

130 )): 

131 print(f"Modified '{PRINTER_CFG_PATH} 'to include ZOOC configuration.") 

132 else: 

133 print(f"'{PRINTER_CFG_PATH}' already includes the ZOOC configuration") 

134 else: 

135 print(f"Warning: '{PRINTER_CFG_PATH}' does not exist. Please add '{ZOOC_CFG_INCLUDE_LINE}' manually if needed.") 

136 

137 if cfg_copied: 

138 print(f"Default configuration copied to '{KLIPPER_CFG_DIR}'. Check and modify the zooc.cfg if needed.") 

139 else: 

140 print(f"Keeping existing '{KLIPPER_CFG_DIR}'. Make sure it is compatible with the current version.") 

141 

142 return 0 

143 

144 

145def teardown() -> int: 

146 """Uninstalls ZOOC post-install files. 

147 

148 :return: 0 on success, 1 on failure. 

149 """ 

150 print("Running uninstallation steps for ZOOC...") 

151 

152 # Remove zooc.py from the Klipper extras directory. 

153 zooc_py_path = os.path.join(KLIPPER_EXTRAS_DIR, "zooc.py") 

154 if os.path.exists(zooc_py_path): 

155 os.remove(zooc_py_path) 

156 print(f"Removed {zooc_py_path}.") 

157 else: 

158 print(f"{zooc_py_path} not found, nothing to remove.") 

159 

160 # Remove ZOOC from printer.cfg. 

161 if os.path.exists(PRINTER_CFG_PATH): 

162 if _remove_line_from(PRINTER_CFG_PATH, ZOOC_CFG_INCLUDE_LINE): 

163 print(f"Removed ZOOC configuration from '{PRINTER_CFG_PATH}'.") 

164 else: 

165 print(f"ZOOC configuration was not found in '{PRINTER_CFG_PATH}'.") 

166 else: 

167 print(f"Warning: '{PRINTER_CFG_PATH}' does not exist. Please remove '{ZOOC_CFG_INCLUDE_LINE}' manually if needed.") 

168 print(f"Please, remove `ZOOC_ADJUST` and related commands from `{PRINTER_CFG_PATH}` manually.") 

169 

170 # zooc.cfg is *not* removed to avoid deleting user modifications. 

171 # The user should remove it manually if they wish. 

172 zooc_cfg_path = os.path.join(KLIPPER_CFG_DIR, "zooc.cfg") 

173 print(f"Skipping '{zooc_cfg_path}' removal to preserve user data. Please remove it manually if you no longer need it.") 

174 

175 return 0