check_setting_visibility.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. #!/usr/bin/env python3
  2. #
  3. # This script checks the correctness of the list of visibility settings
  4. #
  5. import collections
  6. import configparser
  7. import json
  8. import os
  9. import sys
  10. from typing import Any, Dict, List
  11. # Directory where this python file resides
  12. SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
  13. #
  14. # This class
  15. #
  16. class SettingVisibilityInspection:
  17. def __init__(self) -> None:
  18. # The order of settings type. If the setting is in basic list then it also should be in expert
  19. self._setting_visibility_order = ["basic", "advanced", "expert"]
  20. # This is dictionary with categories as keys and all setting keys as values.
  21. self.all_settings_keys = {} # type: Dict[str, List[str]]
  22. # Load all Cura setting keys from the given fdmprinter.json file
  23. def loadAllCuraSettingKeys(self, fdmprinter_json_path: str) -> None:
  24. with open(fdmprinter_json_path, "r", encoding = "utf-8") as f:
  25. json_data = json.load(f)
  26. # Get all settings keys in each category
  27. for key, data in json_data["settings"].items(): # top level settings are categories
  28. if "type" in data and data["type"] == "category":
  29. self.all_settings_keys[key] = []
  30. self._flattenSettings(data["children"], key) # actual settings are children of top level category-settings
  31. def _flattenSettings(self, settings: Dict[str, str], category: str) -> None:
  32. for key, setting in settings.items():
  33. if "type" in setting and setting["type"] != "category":
  34. self.all_settings_keys[category].append(key)
  35. if "children" in setting:
  36. self._flattenSettings(setting["children"], category)
  37. # Loads the given setting visibility file and returns a dict with categories as keys and a list of setting keys as
  38. # values.
  39. def _loadSettingVisibilityConfigFile(self, file_name: str) -> Dict[str, List[str]]:
  40. with open(file_name, "r", encoding = "utf-8") as f:
  41. parser = configparser.ConfigParser(allow_no_value = True)
  42. parser.read_file(f)
  43. data_dict = {}
  44. for category, option_dict in parser.items():
  45. if category in (parser.default_section, "general"):
  46. continue
  47. data_dict[category] = []
  48. for key in option_dict:
  49. data_dict[category].append(key)
  50. return data_dict
  51. def validateSettingsVisibility(self, setting_visibility_files: Dict[str, str]) -> Dict[str, Dict[str, Any]]:
  52. # First load all setting visibility files into the dict "setting_visibility_dict" in the following structure:
  53. # <visibility_name> -> <category> -> <list-fo-setting-keys>
  54. # "basic" -> "info"
  55. setting_visibility_dict = {} # type: Dict[str, Dict[str, List[str]]]
  56. for visibility_name, file_path in setting_visibility_files.items():
  57. setting_visibility_dict[visibility_name] = self._loadSettingVisibilityConfigFile(file_path)
  58. # The result is in the format:
  59. # <visibility_name> -> dict
  60. # "basic" -> "file_name": "basic.cfg"
  61. # "is_valid": True / False
  62. # "invalid_categories": List[str]
  63. # "invalid_settings": Dict[category -> List[str]]
  64. # "missing_categories_from_previous": List[str]
  65. # "missing_settings_from_previous": Dict[category -> List[str]]
  66. all_result_dict = dict() # type: Dict[str, Dict[str, Any]]
  67. previous_result = None
  68. previous_visibility_dict = None
  69. is_all_valid = True
  70. for visibility_name in self._setting_visibility_order:
  71. invalid_categories = []
  72. invalid_settings = collections.defaultdict(list)
  73. this_visibility_dict = setting_visibility_dict[visibility_name]
  74. # Check if categories and keys exist at all
  75. for category, key_list in this_visibility_dict.items():
  76. if category not in self.all_settings_keys:
  77. invalid_categories.append(category)
  78. continue # If this category doesn't exist at all, not need to check for details
  79. for key in key_list:
  80. if key not in self.all_settings_keys[category]:
  81. invalid_settings[category].append(key)
  82. is_settings_valid = len(invalid_categories) == 0 and len(invalid_settings) == 0
  83. file_path = setting_visibility_files[visibility_name]
  84. result_dict = {"file_name": os.path.basename(file_path),
  85. "is_valid": is_settings_valid,
  86. "invalid_categories": invalid_categories,
  87. "invalid_settings": invalid_settings,
  88. "missing_categories_from_previous": list(),
  89. "missing_settings_from_previous": dict(),
  90. }
  91. # If this is not the first item in the list, check if the settings are defined in the previous
  92. # visibility file.
  93. # A visibility with more details SHOULD add more settings. It SHOULD NOT remove any settings defined
  94. # in the less detailed visibility.
  95. if previous_visibility_dict is not None:
  96. missing_categories_from_previous = []
  97. missing_settings_from_previous = collections.defaultdict(list)
  98. for prev_category, prev_key_list in previous_visibility_dict.items():
  99. # Skip the categories that are invalid
  100. if prev_category in previous_result["invalid_categories"]:
  101. continue
  102. if prev_category not in this_visibility_dict:
  103. missing_categories_from_previous.append(prev_category)
  104. continue
  105. this_key_list = this_visibility_dict[prev_category]
  106. for key in prev_key_list:
  107. # Skip the settings that are invalid
  108. if key in previous_result["invalid_settings"][prev_category]:
  109. continue
  110. if key not in this_key_list:
  111. missing_settings_from_previous[prev_category].append(key)
  112. result_dict["missing_categories_from_previous"] = missing_categories_from_previous
  113. result_dict["missing_settings_from_previous"] = missing_settings_from_previous
  114. is_settings_valid = len(missing_categories_from_previous) == 0 and len(missing_settings_from_previous) == 0
  115. result_dict["is_valid"] = result_dict["is_valid"] and is_settings_valid
  116. # Update the complete result dict
  117. all_result_dict[visibility_name] = result_dict
  118. previous_result = result_dict
  119. previous_visibility_dict = this_visibility_dict
  120. is_all_valid = is_all_valid and result_dict["is_valid"]
  121. all_result_dict["all_results"] = {"is_valid": is_all_valid}
  122. return all_result_dict
  123. def printResults(self, all_result_dict: Dict[str, Dict[str, Any]]) -> None:
  124. print("")
  125. print("Setting Visibility Check Results:")
  126. prev_visibility_name = None
  127. for visibility_name in self._setting_visibility_order:
  128. if visibility_name not in all_result_dict:
  129. continue
  130. result_dict = all_result_dict[visibility_name]
  131. print("=============================")
  132. result_str = "OK" if result_dict["is_valid"] else "INVALID"
  133. print("[%s] : [%s] : %s" % (visibility_name, result_dict["file_name"], result_str))
  134. if result_dict["is_valid"]:
  135. continue
  136. # Print details of invalid settings
  137. if result_dict["invalid_categories"]:
  138. print("It has the following non-existing CATEGORIES:")
  139. for category in result_dict["invalid_categories"]:
  140. print(" - [%s]" % category)
  141. if result_dict["invalid_settings"]:
  142. print("")
  143. print("It has the following non-existing SETTINGS:")
  144. for category, key_list in result_dict["invalid_settings"].items():
  145. for key in key_list:
  146. print(" - [%s / %s]" % (category, key))
  147. if prev_visibility_name is not None:
  148. if result_dict["missing_categories_from_previous"]:
  149. print("")
  150. print("The following CATEGORIES are defined in the previous visibility [%s] but not here:" % prev_visibility_name)
  151. for category in result_dict["missing_categories_from_previous"]:
  152. print(" - [%s]" % category)
  153. if result_dict["missing_settings_from_previous"]:
  154. print("")
  155. print("The following SETTINGS are defined in the previous visibility [%s] but not here:" % prev_visibility_name)
  156. for category, key_list in result_dict["missing_settings_from_previous"].items():
  157. for key in key_list:
  158. print(" - [%s / %s]" % (category, key))
  159. print("")
  160. prev_visibility_name = visibility_name
  161. #
  162. # Returns a dictionary of setting visibility .CFG files in the given search directory.
  163. # The dict has the name of the visibility type as the key (such as "basic", "advanced", "expert"), and
  164. # the actual file path (absolute path).
  165. #
  166. def getAllSettingVisiblityFiles(search_dir: str) -> Dict[str, str]:
  167. visibility_file_dict = dict()
  168. extension = ".cfg"
  169. for file_name in os.listdir(search_dir):
  170. file_path = os.path.join(search_dir, file_name)
  171. # Only check files that has the .cfg extension
  172. if not os.path.isfile(file_path):
  173. continue
  174. if not file_path.endswith(extension):
  175. continue
  176. base_filename = os.path.basename(file_name)[:-len(extension)]
  177. visibility_file_dict[base_filename] = file_path
  178. return visibility_file_dict
  179. def main() -> None:
  180. setting_visibility_files_dir = os.path.abspath(os.path.join(SCRIPT_DIR, "..", "resources", "setting_visibility"))
  181. fdmprinter_def_path = os.path.abspath(os.path.join(SCRIPT_DIR, "..", "resources", "definitions", "fdmprinter.def.json"))
  182. setting_visibility_files_dict = getAllSettingVisiblityFiles(setting_visibility_files_dir)
  183. inspector = SettingVisibilityInspection()
  184. inspector.loadAllCuraSettingKeys(fdmprinter_def_path)
  185. check_result = inspector.validateSettingsVisibility(setting_visibility_files_dict)
  186. is_result_valid = check_result["all_results"]["is_valid"]
  187. inspector.printResults(check_result)
  188. sys.exit(0 if is_result_valid else 1)
  189. if __name__ == "__main__":
  190. main()