fritzkit 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. package/.claude-plugin/marketplace.json +12 -0
  2. package/.claude-plugin/plugin.json +13 -0
  3. package/CHANGELOG.md +8 -0
  4. package/LICENSE +21 -0
  5. package/README.md +429 -2
  6. package/dist/cli/commands/doctor.d.ts +4 -0
  7. package/dist/cli/commands/doctor.js +7 -0
  8. package/dist/cli/commands/doctor.js.map +1 -0
  9. package/dist/cli/commands/export.d.ts +17 -0
  10. package/dist/cli/commands/export.js +81 -0
  11. package/dist/cli/commands/export.js.map +1 -0
  12. package/dist/cli/commands/kicad.d.ts +10 -0
  13. package/dist/cli/commands/kicad.js +22 -0
  14. package/dist/cli/commands/kicad.js.map +1 -0
  15. package/dist/cli/commands/parts.d.ts +22 -0
  16. package/dist/cli/commands/parts.js +46 -0
  17. package/dist/cli/commands/parts.js.map +1 -0
  18. package/dist/cli/commands/prune.d.ts +14 -0
  19. package/dist/cli/commands/prune.js +67 -0
  20. package/dist/cli/commands/prune.js.map +1 -0
  21. package/dist/cli/commands/render.d.ts +24 -0
  22. package/dist/cli/commands/render.js +62 -0
  23. package/dist/cli/commands/render.js.map +1 -0
  24. package/dist/cli/commands/schema.d.ts +10 -0
  25. package/dist/cli/commands/schema.js +35 -0
  26. package/dist/cli/commands/schema.js.map +1 -0
  27. package/dist/cli/commands/ui.d.ts +14 -0
  28. package/dist/cli/commands/ui.js +67 -0
  29. package/dist/cli/commands/ui.js.map +1 -0
  30. package/dist/cli/commands/validate.d.ts +24 -0
  31. package/dist/cli/commands/validate.js +91 -0
  32. package/dist/cli/commands/validate.js.map +1 -0
  33. package/dist/cli/context.d.ts +81 -0
  34. package/dist/cli/context.js +139 -0
  35. package/dist/cli/context.js.map +1 -0
  36. package/dist/cli/main.d.ts +2 -0
  37. package/dist/cli/main.js +8 -0
  38. package/dist/cli/main.js.map +1 -0
  39. package/dist/cli/program.d.ts +21 -0
  40. package/dist/cli/program.js +158 -0
  41. package/dist/cli/program.js.map +1 -0
  42. package/dist/cli/raster.d.ts +4 -0
  43. package/dist/cli/raster.js +9 -0
  44. package/dist/cli/raster.js.map +1 -0
  45. package/dist/core/compile/breadboard-util.d.ts +12 -0
  46. package/dist/core/compile/breadboard-util.js +107 -0
  47. package/dist/core/compile/breadboard-util.js.map +1 -0
  48. package/dist/core/compile/breadboard.d.ts +60 -0
  49. package/dist/core/compile/breadboard.js +513 -0
  50. package/dist/core/compile/breadboard.js.map +1 -0
  51. package/dist/core/compile/fzp.d.ts +12 -0
  52. package/dist/core/compile/fzp.js +183 -0
  53. package/dist/core/compile/fzp.js.map +1 -0
  54. package/dist/core/compile/icon.d.ts +17 -0
  55. package/dist/core/compile/icon.js +27 -0
  56. package/dist/core/compile/icon.js.map +1 -0
  57. package/dist/core/compile/index.d.ts +4 -0
  58. package/dist/core/compile/index.js +62 -0
  59. package/dist/core/compile/index.js.map +1 -0
  60. package/dist/core/compile/pcb.d.ts +39 -0
  61. package/dist/core/compile/pcb.js +164 -0
  62. package/dist/core/compile/pcb.js.map +1 -0
  63. package/dist/core/compile/schematic.d.ts +69 -0
  64. package/dist/core/compile/schematic.js +240 -0
  65. package/dist/core/compile/schematic.js.map +1 -0
  66. package/dist/core/compile/style.d.ts +52 -0
  67. package/dist/core/compile/style.js +72 -0
  68. package/dist/core/compile/style.js.map +1 -0
  69. package/dist/core/compile/types.d.ts +50 -0
  70. package/dist/core/compile/types.js +15 -0
  71. package/dist/core/compile/types.js.map +1 -0
  72. package/dist/core/export/bin.d.ts +61 -0
  73. package/dist/core/export/bin.js +221 -0
  74. package/dist/core/export/bin.js.map +1 -0
  75. package/dist/core/export/fzpz.d.ts +37 -0
  76. package/dist/core/export/fzpz.js +121 -0
  77. package/dist/core/export/fzpz.js.map +1 -0
  78. package/dist/core/export/index.d.ts +5 -0
  79. package/dist/core/export/index.js +6 -0
  80. package/dist/core/export/index.js.map +1 -0
  81. package/dist/core/export/install.d.ts +77 -0
  82. package/dist/core/export/install.js +183 -0
  83. package/dist/core/export/install.js.map +1 -0
  84. package/dist/core/export/prune.d.ts +72 -0
  85. package/dist/core/export/prune.js +165 -0
  86. package/dist/core/export/prune.js.map +1 -0
  87. package/dist/core/fritzing.d.ts +22 -0
  88. package/dist/core/fritzing.js +77 -0
  89. package/dist/core/fritzing.js.map +1 -0
  90. package/dist/core/identity.d.ts +29 -0
  91. package/dist/core/identity.js +70 -0
  92. package/dist/core/identity.js.map +1 -0
  93. package/dist/core/image/bbox.d.ts +27 -0
  94. package/dist/core/image/bbox.js +24 -0
  95. package/dist/core/image/bbox.js.map +1 -0
  96. package/dist/core/image/index.d.ts +8 -0
  97. package/dist/core/image/index.js +9 -0
  98. package/dist/core/image/index.js.map +1 -0
  99. package/dist/core/image/key.d.ts +72 -0
  100. package/dist/core/image/key.js +317 -0
  101. package/dist/core/image/key.js.map +1 -0
  102. package/dist/core/image/prepare.d.ts +31 -0
  103. package/dist/core/image/prepare.js +98 -0
  104. package/dist/core/image/prepare.js.map +1 -0
  105. package/dist/core/image/remover.d.ts +17 -0
  106. package/dist/core/image/remover.js +51 -0
  107. package/dist/core/image/remover.js.map +1 -0
  108. package/dist/core/index.d.ts +16 -0
  109. package/dist/core/index.js +17 -0
  110. package/dist/core/index.js.map +1 -0
  111. package/dist/core/kicad/footprint.d.ts +104 -0
  112. package/dist/core/kicad/footprint.js +317 -0
  113. package/dist/core/kicad/footprint.js.map +1 -0
  114. package/dist/core/kicad/index.d.ts +25 -0
  115. package/dist/core/kicad/index.js +197 -0
  116. package/dist/core/kicad/index.js.map +1 -0
  117. package/dist/core/kicad/sexpr.d.ts +35 -0
  118. package/dist/core/kicad/sexpr.js +163 -0
  119. package/dist/core/kicad/sexpr.js.map +1 -0
  120. package/dist/core/resolve.d.ts +113 -0
  121. package/dist/core/resolve.js +301 -0
  122. package/dist/core/resolve.js.map +1 -0
  123. package/dist/core/schema/part.d.ts +995 -0
  124. package/dist/core/schema/part.js +278 -0
  125. package/dist/core/schema/part.js.map +1 -0
  126. package/dist/core/schema/part.schema.json +1122 -0
  127. package/dist/core/svg.d.ts +20 -0
  128. package/dist/core/svg.js +56 -0
  129. package/dist/core/svg.js.map +1 -0
  130. package/dist/core/units.d.ts +28 -0
  131. package/dist/core/units.js +42 -0
  132. package/dist/core/units.js.map +1 -0
  133. package/dist/core/validate/checker.d.ts +46 -0
  134. package/dist/core/validate/checker.js +142 -0
  135. package/dist/core/validate/checker.js.map +1 -0
  136. package/dist/core/validate/doctor.d.ts +25 -0
  137. package/dist/core/validate/doctor.js +156 -0
  138. package/dist/core/validate/doctor.js.map +1 -0
  139. package/dist/core/validate/explain.d.ts +10 -0
  140. package/dist/core/validate/explain.js +135 -0
  141. package/dist/core/validate/explain.js.map +1 -0
  142. package/dist/core/validate/index.d.ts +21 -0
  143. package/dist/core/validate/index.js +96 -0
  144. package/dist/core/validate/index.js.map +1 -0
  145. package/dist/core/validate/structural.d.ts +74 -0
  146. package/dist/core/validate/structural.js +664 -0
  147. package/dist/core/validate/structural.js.map +1 -0
  148. package/dist/core/workspace.d.ts +69 -0
  149. package/dist/core/workspace.js +213 -0
  150. package/dist/core/workspace.js.map +1 -0
  151. package/dist/mcp/jsonpatch.d.ts +30 -0
  152. package/dist/mcp/jsonpatch.js +145 -0
  153. package/dist/mcp/jsonpatch.js.map +1 -0
  154. package/dist/mcp/main.d.ts +2 -0
  155. package/dist/mcp/main.js +5 -0
  156. package/dist/mcp/main.js.map +1 -0
  157. package/dist/mcp/server.d.ts +28 -0
  158. package/dist/mcp/server.js +310 -0
  159. package/dist/mcp/server.js.map +1 -0
  160. package/dist/server/api.d.ts +72 -0
  161. package/dist/server/api.js +507 -0
  162. package/dist/server/api.js.map +1 -0
  163. package/dist/server/events.d.ts +28 -0
  164. package/dist/server/events.js +134 -0
  165. package/dist/server/events.js.map +1 -0
  166. package/dist/server/http.d.ts +36 -0
  167. package/dist/server/http.js +127 -0
  168. package/dist/server/http.js.map +1 -0
  169. package/dist/server/index.d.ts +65 -0
  170. package/dist/server/index.js +193 -0
  171. package/dist/server/index.js.map +1 -0
  172. package/dist/ui/assets/index-DLU8opyI.js +63 -0
  173. package/dist/ui/assets/index-GhUA-T3b.css +1 -0
  174. package/dist/ui/index.html +14 -0
  175. package/package.json +89 -4
  176. package/skills/fritzkit/SKILL.md +690 -0
  177. package/skills/fritzkit/reference/examples.md +275 -0
  178. package/skills/fritzkit/reference/part-json.md +333 -0
  179. package/skills/fritzkit/scripts/measure.py +197 -0
  180. package/tools/fzp-checker/.python-version +1 -0
  181. package/tools/fzp-checker/README.md +45 -0
  182. package/tools/fzp-checker/check_fzp.py +48 -0
  183. package/tools/fzp-checker/pyproject.toml +26 -0
  184. package/tools/fzp-checker/uv.lock +266 -0
  185. package/tools/fzp-checker/vendor/LICENSE.txt +8 -0
  186. package/tools/fzp-checker/vendor/UPSTREAM.md +25 -0
  187. package/tools/fzp-checker/vendor/fzp_checker.py +14 -0
  188. package/tools/fzp-checker/vendor/scripts/checks/__init__.py +26 -0
  189. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/__init__.cpython-313.pyc +0 -0
  190. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/check_missing_leg_ids.cpython-313.pyc +0 -0
  191. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/connector_numbering.cpython-313.pyc +0 -0
  192. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/fzp_checker_runner.cpython-313.pyc +0 -0
  193. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/fzp_checkers.cpython-313.pyc +0 -0
  194. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/fzp_svg_checkers.cpython-313.pyc +0 -0
  195. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/fzp_utils.cpython-313.pyc +0 -0
  196. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/svg_checkers.cpython-313.pyc +0 -0
  197. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/svg_utils.cpython-313.pyc +0 -0
  198. package/tools/fzp-checker/vendor/scripts/checks/check_missing_leg_ids.py +44 -0
  199. package/tools/fzp-checker/vendor/scripts/checks/checkascii.py +27 -0
  200. package/tools/fzp-checker/vendor/scripts/checks/checkcase.py +156 -0
  201. package/tools/fzp-checker/vendor/scripts/checks/checkcopies.py +104 -0
  202. package/tools/fzp-checker/vendor/scripts/checks/connector_numbering.py +79 -0
  203. package/tools/fzp-checker/vendor/scripts/checks/connectors_misnumbered.py +228 -0
  204. package/tools/fzp-checker/vendor/scripts/checks/explain_errors.md +95 -0
  205. package/tools/fzp-checker/vendor/scripts/checks/fzp_checker_runner.py +717 -0
  206. package/tools/fzp-checker/vendor/scripts/checks/fzp_checkers.py +1069 -0
  207. package/tools/fzp-checker/vendor/scripts/checks/fzp_svg_checkers.py +62 -0
  208. package/tools/fzp-checker/vendor/scripts/checks/fzp_utils.py +222 -0
  209. package/tools/fzp-checker/vendor/scripts/checks/inspector_fc51.png +0 -0
  210. package/tools/fzp-checker/vendor/scripts/checks/invisibleconnectors.py +118 -0
  211. package/tools/fzp-checker/vendor/scripts/checks/nolayeridcheck.txt +758 -0
  212. package/tools/fzp-checker/vendor/scripts/checks/requirements.txt +3 -0
  213. package/tools/fzp-checker/vendor/scripts/checks/svg_checkers.py +953 -0
  214. package/tools/fzp-checker/vendor/scripts/checks/svg_utils.py +96 -0
  215. package/tools/fzp-checker/vendor/scripts/checks/test_checkers.py +839 -0
  216. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/author_missing.fzp.test +3 -0
  217. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/author_present.fzp.test +4 -0
  218. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/buses_missing_id.fzp.test +8 -0
  219. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/buses_missing_node_members.fzp.test +7 -0
  220. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/buses_present_valid.fzp.test +8 -0
  221. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/connector_layers_missing_attributes.fzp.test +16 -0
  222. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/connector_layers_present_valid.fzp.test +14 -0
  223. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/connector_numbering_invalid.fzp.test +70 -0
  224. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/connector_numbering_valid.fzp.test +70 -0
  225. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/connector_refs_valid.fzp.test +53 -0
  226. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_invalid_mixed.fzp.test +11 -0
  227. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_invalid_silkscreen_nested.fzp.test +11 -0
  228. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_invalid_style_attr.fzp.test +11 -0
  229. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_invalid_white_stroke.fzp.test +11 -0
  230. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_invalid_wrong_color.fzp.test +11 -0
  231. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_inherited.fzp.test +11 -0
  232. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_legacy.fzp.test +12 -0
  233. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_nested_copper.fzp.test +12 -0
  234. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_none.fzp.test +11 -0
  235. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_standard.fzp.test +12 -0
  236. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_uppercase.fzp.test +11 -0
  237. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/css_connector.fzp.test +100 -0
  238. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/description_missing.fzp.test +3 -0
  239. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/description_present.fzp.test +4 -0
  240. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/duplicate_ids.fzp.test +27 -0
  241. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/family_property_missing.fzp.test +6 -0
  242. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/family_property_present_valid.fzp.test +6 -0
  243. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/font_size.fzp.test +46 -0
  244. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/fritzing_version_invalid_format.fzp.test +4 -0
  245. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/fritzing_version_missing.fzp.test +4 -0
  246. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/fritzing_version_present_valid.fzp.test +4 -0
  247. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/gorn_absent.fzp.test +15 -0
  248. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/gorn_present.fzp.test +15 -0
  249. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/hybrid_connectors.fzp.test +232 -0
  250. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/invalid_xml.fzp.test +12 -0
  251. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/layer_ids_match.fzp.test +29 -0
  252. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/layer_ids_mismatch.fzp.test +30 -0
  253. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/layer_nesting_invalid.fzp.test +11 -0
  254. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/layer_nesting_valid.fzp.test +11 -0
  255. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/matrix_transform.fzp.test +8 -0
  256. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/missing_connector_refs.fzp.test +63 -0
  257. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/missing_leg_ids.fzp.test +34 -0
  258. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_missing.fzp.test +4 -0
  259. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_non_alnum.fzp.test +4 -0
  260. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_present.fzp.test +4 -0
  261. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_special_chars_absent.fzp.test +4 -0
  262. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_special_chars_present.fzp.test +4 -0
  263. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_too_short.fzp.test +4 -0
  264. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/no_layer_invalid.fzp.test +19 -0
  265. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/no_layer_namedview.fzp.test +19 -0
  266. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/no_layer_valid.fzp.test +19 -0
  267. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/pcb_only.fzp.test +48 -0
  268. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/property_fields_missing_name.fzp.test +6 -0
  269. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/property_fields_missing_value.fzp.test +6 -0
  270. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/property_fields_present.fzp.test +6 -0
  271. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/required_tags_missing.fzp.test +3 -0
  272. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/required_tags_present.fzp.test +11 -0
  273. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/stroke_test.fzp.test +60 -0
  274. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/template_svgs.fzp.test +35 -0
  275. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/title_missing.fzp.test +3 -0
  276. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/title_present.fzp.test +4 -0
  277. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/unique_ids.fzp.test +27 -0
  278. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/unique_property_names_duplicates.fzp.test +7 -0
  279. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/unique_property_names_unique.fzp.test +7 -0
  280. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/valid_xml.fzp.test +249 -0
  281. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/version_invalid_format.fzp.test +4 -0
  282. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/version_missing.fzp.test +3 -0
  283. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/version_present_valid.fzp.test +4 -0
  284. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/views_missing.fzp.test +3 -0
  285. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/views_present.fzp.test +8 -0
  286. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/connector_refs_valid_breadboard.svg +7 -0
  287. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/duplicate_ids_breadboard.svg +13 -0
  288. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/gorn_absent_breadboard.svg +7 -0
  289. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/gorn_present_breadboard.svg +7 -0
  290. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/hybrid_connectors_breadboard.svg +102 -0
  291. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/layer_nesting_invalid.svg +13 -0
  292. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/layer_nesting_valid.svg +13 -0
  293. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/matrix_transform.svg +11 -0
  294. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/missing_connector_refs_breadboard.svg +8 -0
  295. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/missing_leg_ids_breadboard.svg +9 -0
  296. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/test_layer_ids_match_breadboard.svg +9 -0
  297. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/test_layer_ids_mismatch_breadboard.svg +9 -0
  298. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/unique_ids_breadboard.svg +13 -0
  299. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/icon/hybrid_connectors_icon.svg +78 -0
  300. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/icon/no_layer_invalid.svg +4 -0
  301. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/icon/no_layer_namedview.svg +9 -0
  302. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/icon/no_layer_valid.svg +7 -0
  303. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/icon/plain_pcb.svg +10 -0
  304. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/connector_refs_valid_pcb.svg +10 -0
  305. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_invalid_mixed.svg +8 -0
  306. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_invalid_silkscreen_nested.svg +10 -0
  307. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_invalid_style_attr.svg +6 -0
  308. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_invalid_white_stroke.svg +6 -0
  309. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_invalid_wrong_color.svg +6 -0
  310. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_inherited.svg +7 -0
  311. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_legacy.svg +10 -0
  312. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_nested_copper.svg +9 -0
  313. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_none.svg +7 -0
  314. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_standard.svg +10 -0
  315. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_uppercase.svg +7 -0
  316. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/css_connector.svg +25 -0
  317. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/font_size.svg +54 -0
  318. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/hybrid_connectors_pcb.svg +35 -0
  319. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/missing_connector_refs_pcb.svg +14 -0
  320. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/pcb-arduino-shield.svg +25 -0
  321. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/stroke_test.svg +12 -0
  322. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/test_layer_ids_match_pcb.svg +12 -0
  323. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/test_layer_ids_mismatch_pcb.svg +12 -0
  324. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/schematic/connector_refs_valid_schematic.svg +7 -0
  325. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/schematic/hybrid_connectors_schematic.svg +59 -0
  326. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/schematic/missing_connector_refs_schematic.svg +8 -0
  327. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/schematic/test_layer_ids_match_schematic.svg +6 -0
  328. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/schematic/test_layer_ids_mismatch_schematic.svg +6 -0
@@ -0,0 +1,717 @@
1
+ from lxml import etree
2
+ from .fzp_checkers import *
3
+ from .check_missing_leg_ids import *
4
+ from .connector_numbering import *
5
+ from .svg_checkers import *
6
+ from .fzp_svg_checkers import FZPMissingConnectorRefsChecker
7
+ from .fzp_checkers import ValidationIssue
8
+ from .fzp_utils import FZPUtils
9
+ import json
10
+ import re
11
+ import os
12
+ import sys
13
+ import logging
14
+
15
+ class FZPCheckerRunner:
16
+ def __init__(self, path):
17
+ self.path = path
18
+ self.total_errors = 0
19
+ self.total_warnings = 0
20
+ self.extracted_dir = None # For fzpz cleanup
21
+ self.checks_run = 0
22
+ self.errors_fixed = 0
23
+ self.logger = logging.getLogger(self.__class__.__name__)
24
+ self.all_issues = [] # ValidationIssue objects from FZP checks
25
+ self.all_fixes = [] # FixResult objects from FZP checks
26
+ self.svg_file_results = {} # SVG file path -> {'issues': [], 'fixes': []}
27
+
28
+ def check(self, check_types, svg_check_types, fix=False):
29
+ self.total_errors = 0
30
+ self.total_warnings = 0
31
+ self.checks_run = 0
32
+ self.errors_fixed = 0
33
+ self.all_issues = []
34
+ self.all_fixes = []
35
+ self.svg_file_results = {}
36
+
37
+ # Handle fzpz files
38
+ original_path = self.path
39
+ if self.path.endswith('.fzpz'):
40
+ self.path = FZPUtils.extract_fzpz(self.path)
41
+ self.extracted_dir = os.path.dirname(self.path)
42
+ self.logger.debug(f"Extracted FZPZ to: {self.extracted_dir}")
43
+ # Debug: list extracted files
44
+ extracted_files = os.listdir(self.extracted_dir)
45
+ self.logger.debug(f"Extracted files: {extracted_files}")
46
+
47
+ try:
48
+ fzp_doc = self._parse_fzp()
49
+ except etree.XMLSyntaxError as e:
50
+ error_msg = f"Invalid XML: {str(e)}"
51
+ self.logger.error(error_msg)
52
+ # Create ValidationIssue for XML syntax error
53
+ xml_error = ValidationIssue(error_msg, severity='error', node=None)
54
+ self.all_issues.append(xml_error)
55
+ self.total_errors += 1
56
+ self._cleanup_if_needed()
57
+ return
58
+
59
+ # Pre-load all SVG XMLs
60
+ svg_docs = self._load_svg_docs(fzp_doc)
61
+ svg_paths = self._get_svg_paths(fzp_doc)
62
+
63
+ self.logger.info(f"Scanning file: {self.path}")
64
+
65
+ for check_type in check_types:
66
+ checker = self._get_checker(check_type, fzp_doc, svg_docs)
67
+ self.logger.debug(f"Running check: {checker.get_name()}")
68
+
69
+ errors, warnings = checker.check()
70
+ self.total_errors += errors
71
+ self.total_warnings += warnings
72
+ self.checks_run += 1
73
+
74
+ # Collect issues from this checker
75
+ self.all_issues.extend(checker.issues)
76
+
77
+ # Apply fixes if requested and available
78
+ if fix and (errors > 0 or warnings > 0) and hasattr(checker, 'fix'):
79
+ if checker.fix(self.path):
80
+ fixes_count = checker.get_fixes_count()
81
+ self.errors_fixed += fixes_count
82
+ # Collect fixes from this checker
83
+ self.all_fixes.extend(checker.fixes)
84
+
85
+ if svg_check_types:
86
+ self._run_svg_checkers(fzp_doc, svg_docs, svg_paths, svg_check_types, fix)
87
+
88
+ if self.total_errors > 0 or self.total_warnings > 0:
89
+ self.logger.info(f"Total errors in {self.path}: {self.total_errors}")
90
+ if self.total_warnings > 0:
91
+ self.logger.info(f"Total warnings in {self.path}: {self.total_warnings}")
92
+
93
+ fzp_doc.getroot().clear()
94
+ for svg_doc in svg_docs.values():
95
+ if svg_doc is not None:
96
+ svg_doc.getroot().clear()
97
+ self._cleanup_if_needed()
98
+
99
+ def _parse_fzp(self):
100
+ fzp_doc = etree.parse(self.path)
101
+ return fzp_doc
102
+
103
+ def _get_checker(self, check_type, fzp_doc, svg_docs):
104
+ for checker in AVAILABLE_CHECKERS:
105
+ if checker.get_name() == check_type:
106
+ if checker in [
107
+ FZPConnectorTerminalChecker,
108
+ FZPConnectorSvgRefChecker,
109
+ FZPPCBConnectorStrokeChecker,
110
+ FZPLayerIDsChecker,
111
+ FZPMissingConnectorRefsChecker,
112
+ FZPMissingLegIDsChecker
113
+ ]:
114
+ return checker(fzp_doc, svg_docs)
115
+ else:
116
+ return checker(fzp_doc)
117
+ raise ValueError(f"Invalid check type: {check_type}")
118
+
119
+ def _load_svg_docs(self, fzp_doc):
120
+ """Pre-load all SVG documents for the four views."""
121
+ svg_docs = {}
122
+ views_elements = fzp_doc.xpath("//views")
123
+ if not views_elements:
124
+ return svg_docs
125
+ views = views_elements[0]
126
+ for view in views.xpath("*"):
127
+ if view.tag == "defaultUnits":
128
+ continue
129
+ layers_elements = view.xpath("layers")
130
+ if layers_elements:
131
+ layers = layers_elements[0]
132
+ image = layers.get("image")
133
+ if image:
134
+ svg_path = FZPUtils.get_svg_path(self.path, image, view.tag)
135
+ if svg_path and os.path.isfile(svg_path):
136
+ try:
137
+ svg_docs[view.tag] = etree.parse(svg_path)
138
+ except etree.XMLSyntaxError as e:
139
+ error_msg = f"Invalid XML in SVG {svg_path}: {str(e)}"
140
+ self.logger.error(error_msg)
141
+ # Create ValidationIssue for SVG XML syntax error
142
+ xml_error = ValidationIssue(error_msg, severity='error', node=None)
143
+ self.all_issues.append(xml_error)
144
+ self.total_errors += 1
145
+ svg_docs[view.tag] = None
146
+ else:
147
+ if svg_path:
148
+ error_msg = f"Missing SVG file referenced by FZP: {image}"
149
+ xml_error = ValidationIssue(error_msg, severity='error', node=None)
150
+ self.all_issues.append(xml_error)
151
+ self.total_errors += 1
152
+ svg_docs[view.tag] = None
153
+ return svg_docs
154
+
155
+ def _get_svg_paths(self, fzp_doc):
156
+ """Get SVG file paths for fix operations."""
157
+ svg_paths = {}
158
+ views_elements = fzp_doc.xpath("//views")
159
+ if not views_elements:
160
+ return svg_paths
161
+ views = views_elements[0]
162
+ for view in views.xpath("*"):
163
+ if view.tag == "defaultUnits":
164
+ continue
165
+ layers_elements = view.xpath("layers")
166
+ if layers_elements:
167
+ layers = layers_elements[0]
168
+ image = layers.get("image")
169
+ if image:
170
+ svg_path = FZPUtils.get_svg_path(self.path, image, view.tag)
171
+ if svg_path and os.path.isfile(svg_path):
172
+ svg_paths[view.tag] = svg_path
173
+ return svg_paths
174
+
175
+ def _run_svg_checkers(self, fzp_doc, svg_docs, svg_paths, svg_check_types, fix):
176
+ views_elements = fzp_doc.xpath("//views")
177
+ if not views_elements:
178
+ return
179
+ views = views_elements[0]
180
+ for view in views.xpath("*"):
181
+ if view.tag == "defaultUnits":
182
+ continue
183
+
184
+ svg_doc = svg_docs.get(view.tag)
185
+ if not svg_doc:
186
+ continue
187
+
188
+ # Show which SVG file is being checked for this view
189
+ svg_path = svg_paths.get(view.tag)
190
+ if svg_path:
191
+ self.logger.info(f"Checking SVG file: {svg_path}")
192
+ # Initialize results for this SVG file if not exists
193
+ if svg_path not in self.svg_file_results:
194
+ self.svg_file_results[svg_path] = {'issues': [], 'fixes': []}
195
+
196
+ layers_elements = view.xpath("layers")
197
+ if layers_elements:
198
+ layers = layers_elements[0]
199
+ layer_ids = []
200
+ layer_elements = layers.xpath("layer")
201
+ for layer_element in layer_elements:
202
+ layer_id = layer_element.get("layerId")
203
+ if layer_id:
204
+ layer_ids.append(layer_id)
205
+
206
+ for check_type in svg_check_types:
207
+ checker = self._get_svg_checker(check_type, svg_doc, layer_ids)
208
+ self.logger.debug(f"Running SVG check: {checker.get_name()} for {view.tag}")
209
+ errors, warnings = checker.check()
210
+ self.total_errors += errors
211
+ self.total_warnings += warnings
212
+ self.checks_run += 1
213
+
214
+ # Collect issues from this SVG checker for the specific SVG file
215
+ if svg_path and svg_path in self.svg_file_results:
216
+ self.svg_file_results[svg_path]['issues'].extend(checker.issues)
217
+
218
+ if fix and errors > 0 and hasattr(checker, 'fix'):
219
+ if svg_path and checker.fix(svg_path):
220
+ fixes_count = checker.get_fixes_count()
221
+ self.errors_fixed += fixes_count
222
+ # Collect fixes from this SVG checker for the specific SVG file
223
+ if svg_path in self.svg_file_results:
224
+ self.svg_file_results[svg_path]['fixes'].extend(checker.fixes)
225
+
226
+ def _get_svg_checker(self, check_type, svg_doc, layer_ids):
227
+ for checker in SVG_AVAILABLE_CHECKERS:
228
+ if checker.get_name() == check_type:
229
+ return checker(svg_doc, layer_ids)
230
+ raise ValueError(f"Invalid SVG check type: {check_type}")
231
+
232
+ def _cleanup_if_needed(self):
233
+ """Clean up extracted fzpz contents if needed."""
234
+ if self.extracted_dir:
235
+ FZPUtils.cleanup_extraction(self.extracted_dir)
236
+ self.extracted_dir = None
237
+
238
+ def check_svg_file(self, svg_path, selected_svg_checks, fix=False):
239
+ """Check a single SVG file directly with SVG-specific checks."""
240
+ from lxml import etree
241
+
242
+ if not os.path.isfile(svg_path):
243
+ print(f"Error: SVG file '{svg_path}' does not exist")
244
+ return 1
245
+
246
+ try:
247
+ svg_doc = etree.parse(svg_path)
248
+ except etree.XMLSyntaxError as e:
249
+ print(f"Error: Invalid XML in SVG file '{svg_path}': {str(e)}")
250
+ return 1
251
+
252
+ all_issues = []
253
+ all_fixes = []
254
+
255
+ self.logger.info(f"Checking SVG file: {svg_path}")
256
+
257
+ for check_type in selected_svg_checks:
258
+ checker = self._get_svg_checker(check_type, svg_doc, [])
259
+ self.logger.debug(f"Running SVG check: {checker.get_name()}")
260
+
261
+ errors, warnings = checker.check()
262
+
263
+ # Collect issues from this checker
264
+ all_issues.extend(checker.issues)
265
+
266
+ # Apply fixes if requested
267
+ if fix and errors > 0 and hasattr(checker, 'fix'):
268
+ if checker.fix(svg_path):
269
+ # Collect fixes from this checker
270
+ all_fixes.extend(checker.fixes)
271
+
272
+ # Create file result structure for SVG-only check
273
+ # Treat the SVG file as the main file (like an FZP file would be)
274
+ file_result = {
275
+ 'file': svg_path,
276
+ 'checks': len(selected_svg_checks),
277
+ 'issues': all_issues[:], # SVG issues as main issues
278
+ 'fix_results': all_fixes[:] # SVG fixes as main fixes
279
+ # No 'svg_files' key since this IS the SVG file
280
+ }
281
+
282
+ # Show hint before the report
283
+ print(f"\n💡 Hint: To find FZP files that use this SVG and run additional checks, try:")
284
+ print(f" python fzp_checker.py -s {svg_path} /path/to/fzp/directory")
285
+ print(f" Example: python fzp_checker.py -s {svg_path} contrib/")
286
+
287
+ # Use the unified reporting method
288
+ self.generate_report([file_result], verbose=self.logger.isEnabledFor(logging.DEBUG))
289
+
290
+ # Return error count for exit code
291
+ return len([issue for issue in all_issues if issue.severity == 'error'])
292
+
293
+ def generate_github_summary(self, file_results):
294
+ """Generate a markdown report for GitHub Actions summary"""
295
+ # Calculate totals
296
+ total_files_checked = len(file_results)
297
+ total_checks_run = sum(result['checks'] for result in file_results)
298
+ total_errors = 0
299
+ total_warnings = 0
300
+ total_errors_fixed = 0
301
+
302
+ for result in file_results:
303
+ fzp_errors = len([issue for issue in result['issues'] if issue.severity == 'error'])
304
+ fzp_warnings = len([issue for issue in result['issues'] if issue.severity == 'warning'])
305
+ fzp_fixes = len(result['fix_results'])
306
+
307
+ total_errors += fzp_errors
308
+ total_warnings += fzp_warnings
309
+ total_errors_fixed += fzp_fixes
310
+
311
+ if 'svg_files' in result:
312
+ for svg_path, svg_result in result['svg_files'].items():
313
+ svg_errors = len([issue for issue in svg_result['issues'] if issue.severity == 'error'])
314
+ svg_warnings = len([issue for issue in svg_result['issues'] if issue.severity == 'warning'])
315
+ svg_fixes = len(svg_result['fix_results'])
316
+
317
+ total_errors += svg_errors
318
+ total_warnings += svg_warnings
319
+ total_errors_fixed += svg_fixes
320
+
321
+ # Generate markdown
322
+ md = []
323
+ md.append("# FZP Checker Results\n")
324
+
325
+ # Summary table
326
+ if total_errors == 0 and total_warnings == 0:
327
+ md.append("## ✅ All checks passed!\n")
328
+ elif total_errors > 0:
329
+ md.append("## ❌ Issues found\n")
330
+ else:
331
+ md.append("## ⚠️ Warnings found\n")
332
+
333
+ md.append("| Metric | Count |")
334
+ md.append("|--------|-------|")
335
+ md.append(f"| Files checked | {total_files_checked} |")
336
+ md.append(f"| Checks run | {total_checks_run} |")
337
+ md.append(f"| Errors | {total_errors} |")
338
+ md.append(f"| Warnings | {total_warnings} |")
339
+ md.append(f"| Fixed | {total_errors_fixed} |")
340
+ md.append("")
341
+
342
+ # File details
343
+ if file_results:
344
+ md.append("## File Details\n")
345
+ for result in file_results:
346
+ fzp_errors = len([issue for issue in result['issues'] if issue.severity == 'error'])
347
+ fzp_warnings = len([issue for issue in result['issues'] if issue.severity == 'warning'])
348
+ fzp_fixes = len(result['fix_results'])
349
+
350
+ if fzp_errors == 0 and fzp_warnings == 0:
351
+ status = "✅"
352
+ elif fzp_errors > 0:
353
+ status = "❌"
354
+ else:
355
+ status = "⚠️"
356
+
357
+ md.append(f"### {status} `{result['file']}`")
358
+
359
+ if fzp_errors > 0 or fzp_warnings > 0:
360
+ if result['issues']:
361
+ for issue in result['issues']:
362
+ icon = "🔴" if issue.severity == 'error' else "🟡"
363
+ md.append(f"- {icon} {issue.message}")
364
+
365
+ if fzp_fixes > 0:
366
+ md.append(f"\n**Fixed:** {fzp_fixes} issue(s)")
367
+
368
+ # SVG files
369
+ if result.get('svg_files'):
370
+ for svg_file, svg_result in sorted(result['svg_files'].items()):
371
+ svg_errors = len([issue for issue in svg_result['issues'] if issue.severity == 'error'])
372
+ svg_warnings = len([issue for issue in svg_result['issues'] if issue.severity == 'warning'])
373
+
374
+ if svg_errors > 0 or svg_warnings > 0:
375
+ md.append(f"\n**SVG:** `{svg_file}`")
376
+ if svg_result['issues']:
377
+ for issue in svg_result['issues']:
378
+ icon = "🔴" if issue.severity == 'error' else "🟡"
379
+ md.append(f" - {icon} {issue.message}")
380
+
381
+ md.append("")
382
+
383
+ return "\n".join(md)
384
+
385
+ def generate_report(self, file_results, verbose=False):
386
+ """Generate a formatted report for checker results"""
387
+ # Calculate totals from the collected data
388
+ total_files_checked = len(file_results)
389
+ total_checks_run = sum(result['checks'] for result in file_results)
390
+ total_errors = 0
391
+ total_warnings = 0
392
+ total_errors_fixed = 0
393
+
394
+ # Calculate totals and add counts to each result
395
+ for result in file_results:
396
+ # Count FZP errors/warnings/fixes
397
+ fzp_errors = len([issue for issue in result['issues'] if issue.severity == 'error'])
398
+ fzp_warnings = len([issue for issue in result['issues'] if issue.severity == 'warning'])
399
+ fzp_fixes = len(result['fix_results'])
400
+
401
+ result['errors'] = fzp_errors
402
+ result['warnings'] = fzp_warnings
403
+ result['fixes'] = fzp_fixes
404
+
405
+ total_errors += fzp_errors
406
+ total_warnings += fzp_warnings
407
+ total_errors_fixed += fzp_fixes
408
+
409
+ # Process SVG results for this FZP
410
+ if 'svg_files' in result:
411
+ for svg_path, svg_result in result['svg_files'].items():
412
+ svg_errors = len([issue for issue in svg_result['issues'] if issue.severity == 'error'])
413
+ svg_warnings = len([issue for issue in svg_result['issues'] if issue.severity == 'warning'])
414
+ svg_fixes = len(svg_result['fix_results'])
415
+
416
+ svg_result['errors'] = svg_errors
417
+ svg_result['warnings'] = svg_warnings
418
+ svg_result['fixes'] = svg_fixes
419
+
420
+ total_errors += svg_errors
421
+ total_warnings += svg_warnings
422
+ total_errors_fixed += svg_fixes
423
+
424
+ # Print detailed summary
425
+ print(f"\nSummary:")
426
+ print(f" Files checked: {total_files_checked}")
427
+ print(f" Checks run: {total_checks_run}")
428
+ print(f" Errors found: {total_errors}")
429
+ if total_warnings > 0:
430
+ print(f" Warnings found: {total_warnings}")
431
+ print(f" Errors fixed: {total_errors_fixed}")
432
+
433
+ print(f"\nFile Details:")
434
+
435
+ # Show FZP files with their associated SVG files
436
+ for result in file_results:
437
+ # FZP file status
438
+ status_parts = []
439
+ if result['errors'] == 0 and result['warnings'] == 0:
440
+ status = "✓ CLEAN"
441
+ else:
442
+ if result['errors'] > 0:
443
+ status_parts.append(f"{result['errors']} error{'s' if result['errors'] > 1 else ''}")
444
+ if result['warnings'] > 0:
445
+ status_parts.append(f"{result['warnings']} warning{'s' if result['warnings'] > 1 else ''}")
446
+ status = "✗ " + ", ".join(status_parts)
447
+
448
+ fixes_info = ""
449
+ if result['fixes'] > 0:
450
+ fixes_info = f" (fixed: {result['fixes']})"
451
+
452
+ print(f" {result['file']}: {status}{fixes_info}")
453
+
454
+ # Show FZP-specific issues
455
+ if result['issues']:
456
+ for issue in result['issues']:
457
+ severity_icon = "✗" if issue.severity == 'error' else "⚠"
458
+ print(f" {severity_icon} {issue.message}")
459
+
460
+ if result['fix_results']:
461
+ for fix in result['fix_results']:
462
+ print(f" ✓ {fix.message}")
463
+
464
+ # Show associated SVG files
465
+ if result.get('svg_files'):
466
+ for svg_file, svg_result in sorted(result['svg_files'].items()):
467
+ svg_status_parts = []
468
+ if svg_result['errors'] == 0 and svg_result['warnings'] == 0:
469
+ svg_status = "✓ CLEAN"
470
+ else:
471
+ if svg_result['errors'] > 0:
472
+ svg_status_parts.append(f"{svg_result['errors']} error{'s' if svg_result['errors'] > 1 else ''}")
473
+ if svg_result['warnings'] > 0:
474
+ svg_status_parts.append(f"{svg_result['warnings']} warning{'s' if svg_result['warnings'] > 1 else ''}")
475
+ svg_status = "✗ " + ", ".join(svg_status_parts)
476
+
477
+ svg_fixes_info = ""
478
+ if svg_result['fixes'] > 0:
479
+ svg_fixes_info = f" (fixed: {svg_result['fixes']})"
480
+
481
+ print(f" └── {svg_file}: {svg_status}{svg_fixes_info}")
482
+
483
+ # Show SVG-specific issues
484
+ if svg_result['issues']:
485
+ for issue in svg_result['issues']:
486
+ severity_icon = "✗" if issue.severity == 'error' else "⚠"
487
+ print(f" {severity_icon} {issue.message}")
488
+
489
+ if svg_result['fix_results']:
490
+ for fix in svg_result['fix_results']:
491
+ print(f" ✓ {fix.message}")
492
+
493
+ if total_files_checked > 1:
494
+ print() # Extra line break after file details when multiple files
495
+
496
+ if verbose or total_errors > 0:
497
+ print(f"Total errors: {total_errors}")
498
+
499
+ def search_and_check_fzp_files(self, svg_file, fzp_dir, check_types, svg_check_types):
500
+ errors = 0
501
+ fzp_files = self._search_fzp_files_with_svg(svg_file, fzp_dir)
502
+ for fzp_file in fzp_files:
503
+ self.path = fzp_file
504
+ self.check(check_types, svg_check_types)
505
+ errors += self.total_errors
506
+ return errors
507
+
508
+ def _search_fzp_files_with_svg(self, svg_file, fzp_dir):
509
+ fzp_files = []
510
+ svg_filename = os.path.basename(svg_file)
511
+ is_obsolete = 'obsolete' in svg_file.split(os.sep)
512
+ for root, dirs, files in os.walk(fzp_dir):
513
+ if not is_obsolete and 'obsolete' in root.split(os.sep):
514
+ continue
515
+
516
+ for file in files:
517
+ if file.endswith(".fzp"):
518
+ fzp_path = os.path.join(root, file)
519
+ with open(fzp_path, 'r') as f:
520
+ fzp_content = f.read()
521
+ if svg_filename in fzp_content:
522
+ fzp_files.append(fzp_path)
523
+ return fzp_files
524
+
525
+ AVAILABLE_CHECKERS = [FZPMissingTagsChecker, FZPConnectorTerminalChecker, FZPConnectorSvgRefChecker, FZPPCBConnectorStrokeChecker, FZPModuleIDSpecialCharsChecker, FZPMissingLegIDsChecker, FZPMissingConnectorRefsChecker, FZPDateFormatChecker, FZPConnectorNumberingChecker]
526
+
527
+ SVG_AVAILABLE_CHECKERS = [SVGFontSizeChecker, SVGFontTypeChecker, SVGViewBoxChecker, SVGIdsChecker, SVGMatrixChecker, SVGLayerNestingChecker, SVGNoLayerChecker, SVGGornChecker, SVGCopperLayerContentChecker]
528
+ AVAILABLE_CHECKERS_FROM_GOLANG = [
529
+ FZPFritzingVersionChecker,
530
+ FZPFritzingVersionRangeChecker,
531
+ FZPModuleIDChecker,
532
+ FZPVersionChecker,
533
+ FZPTitleChecker,
534
+ FZPDescriptionChecker,
535
+ FZPAuthorChecker,
536
+ FZPViewsChecker,
537
+ FZPBusIDChecker,
538
+ FZPBusNodesChecker,
539
+ FZPConnectorLayersChecker,
540
+ FZPFamilyPropertyChecker,
541
+ FZPUniquePropertyNamesChecker,
542
+ FZPPropertyFieldsChecker,
543
+ FZPRequiredTagsChecker,
544
+ FZPBusesChecker,
545
+ FZPLayerIDsChecker,
546
+ ]
547
+ AVAILABLE_CHECKERS += AVAILABLE_CHECKERS_FROM_GOLANG
548
+
549
+ def main():
550
+ import argparse
551
+ import sys
552
+
553
+ # Simple formatting
554
+ BOLD = '\033[1m'
555
+ RESET = '\033[0m'
556
+
557
+ all_checkers = AVAILABLE_CHECKERS + SVG_AVAILABLE_CHECKERS
558
+
559
+ # TODOs
560
+ # Cleanup arguments: Remove path, --svg, replace with:
561
+ # --basedir : directory to use as fritzing-parts dir (contains core and svg subdirs)
562
+ # --file : Automatically detect .json, .txt, .fzp and .svg
563
+ # Add support for directly checking .fzpz files
564
+ parser = argparse.ArgumentParser(description="Scan FZP files for various checks")
565
+ parser.add_argument("path", nargs='?', help="Path to FZP/FZPZ file or directory to scan")
566
+ parser.add_argument("-c", "--checks", nargs="*", default=["all"],
567
+ choices=["all"] + [checker.get_name() for checker in all_checkers],
568
+ help="Type(s) of check to run (default: all)")
569
+ parser.add_argument("-s", "--svg", help="Path to an SVG file. If path provided, searches for FZP files using this SVG. If no path, checks SVG directly.")
570
+ parser.add_argument("-f", "--file", help="Path to a file containing a list of SVG and FZP files to check")
571
+ parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose output")
572
+ parser.add_argument("--fix", action="store_true", help="Try to automatically fix errors when possible")
573
+
574
+ # Check for help flag to show detailed checker info
575
+ if "-h" in sys.argv or "--help" in sys.argv:
576
+ parser.print_help()
577
+ print(f"\n{BOLD}Examples:{RESET}")
578
+ print(" python fzp_checker.py mypart.fzpz # Check an FZPZ file")
579
+ print(" python fzp_checker.py contrib/ # Check all FZP files in directory")
580
+ print(" python fzp_checker.py -s myfile.svg # Check SVG file directly")
581
+ print(f"\n{BOLD}Available FZP checks:{RESET}")
582
+ for checker in AVAILABLE_CHECKERS:
583
+ print(f"{BOLD}{checker.get_name()}{RESET}:\n{checker.get_description()}\n")
584
+ print(f"{BOLD}Available SVG checks:{RESET}")
585
+ for checker in SVG_AVAILABLE_CHECKERS:
586
+ print(f"{BOLD}{checker.get_name()}{RESET}:\n{checker.get_description()}\n")
587
+ sys.exit(os.EX_OK)
588
+
589
+ args = parser.parse_args()
590
+
591
+ # Configure logging based on verbose flag
592
+ if args.verbose:
593
+ logging.basicConfig(level=logging.DEBUG, format='%(message)s')
594
+ else:
595
+ logging.basicConfig(level=logging.INFO, format='%(message)s')
596
+
597
+ # Show standard help if no path provided (unless using --svg which doesn't need path)
598
+ if not args.path and not args.svg:
599
+ parser.print_help()
600
+ print(f"\n{BOLD}Examples:{RESET}")
601
+ print(" python fzp_checker.py mypart.fzpz # Check an FZPZ file")
602
+ print(" python fzp_checker.py contrib/ # Check all FZP files in directory")
603
+ print(" python fzp_checker.py -s myfile.svg # Check SVG file directly")
604
+ print(" python fzp_checker.py -s myfile.svg contrib/ # Find FZP files using myfile.svg")
605
+ sys.exit(os.EX_USAGE)
606
+
607
+ fzp_checks = [checker.get_name() for checker in AVAILABLE_CHECKERS]
608
+ svg_checks = [checker.get_name() for checker in SVG_AVAILABLE_CHECKERS]
609
+
610
+ if args.checks == ["all"]:
611
+ args.checks = fzp_checks + svg_checks
612
+
613
+ selected_fzp_checks = [check for check in args.checks if check in fzp_checks]
614
+ selected_svg_checks = [check for check in args.checks if check in svg_checks]
615
+
616
+ try:
617
+ if not selected_fzp_checks and not selected_svg_checks:
618
+ raise ValueError("No valid check types specified.")
619
+
620
+ file_results = [] # Store results for each FZP file (including its SVG files)
621
+
622
+ checker_runner = FZPCheckerRunner(None)
623
+
624
+ fzp_files = set()
625
+ file_list = []
626
+
627
+ if args.file:
628
+ if args.file.endswith(".json"):
629
+ # List of strings in json format
630
+ with open(args.file, "r") as file:
631
+ file_list = json.load(file)
632
+ else:
633
+ # Textfile, each filename on a new line
634
+ with open(args.file, "r") as file:
635
+ file_list = [line.strip() for line in file]
636
+
637
+ for filepath in file_list:
638
+ if filepath.endswith(".fzp") or filepath.endswith(".fzpz"):
639
+ fzp_files.add(os.path.join(args.path, filepath))
640
+ elif filepath.endswith(".svg"):
641
+ fzp_files.update(checker_runner._search_fzp_files_with_svg(filepath, args.path))
642
+ elif args.svg and args.path and os.path.isdir(args.path):
643
+ # Search for FZP files that reference the SVG file
644
+ fzp_files.update(checker_runner._search_fzp_files_with_svg(args.svg, args.path))
645
+ elif args.svg and not args.path:
646
+ # For SVG files without path, run SVG checks directly
647
+ exit_code = checker_runner.check_svg_file(args.svg, selected_svg_checks, fix=args.fix)
648
+ sys.exit(os.EX_DATAERR if exit_code > 0 else os.EX_OK)
649
+ elif os.path.isfile(args.path):
650
+ if args.path.endswith(".fzp") or args.path.endswith(".fzpz"):
651
+ fzp_files.add(args.path)
652
+ else:
653
+ print(f"Error: File {args.path} is not an FZP or FZPZ file")
654
+ sys.exit(os.EX_DATAERR)
655
+ elif os.path.isdir(args.path):
656
+ for filename in sorted(os.listdir(args.path)):
657
+ if filename.endswith(".fzp") or filename.endswith(".fzpz"):
658
+ fzp_files.add(os.path.join(args.path, filename))
659
+ else:
660
+ print(f"Error: Path '{args.path}' does not exist or is not accessible")
661
+ sys.exit(os.EX_NOINPUT)
662
+
663
+ # Create a logger for main function messages
664
+ logger = logging.getLogger('fzp_checker_main')
665
+ logger.info(f"Checking {len(fzp_files)} FZP files")
666
+
667
+ total_errors = 0
668
+ for fzp_file in sorted(fzp_files):
669
+ checker_runner.path = fzp_file
670
+ checker_runner.check(selected_fzp_checks, selected_svg_checks, fix=args.fix)
671
+
672
+ # Process SVG results for this FZP - just store raw data
673
+ svg_results_for_fzp = {}
674
+ for svg_path, svg_result in checker_runner.svg_file_results.items():
675
+ svg_results_for_fzp[svg_path] = {
676
+ 'issues': svg_result['issues'][:],
677
+ 'fix_results': svg_result['fixes'][:]
678
+ }
679
+
680
+ # Store results for this FZP file (including its SVG results) - just raw data
681
+ file_result = {
682
+ 'file': fzp_file,
683
+ 'checks': checker_runner.checks_run,
684
+ 'issues': checker_runner.all_issues[:], # FZP issues only
685
+ 'fix_results': checker_runner.all_fixes[:], # FZP fixes only
686
+ 'svg_files': svg_results_for_fzp # SVG results associated with this FZP
687
+ }
688
+ file_results.append(file_result)
689
+
690
+ total_errors += checker_runner.total_errors
691
+
692
+ # Generate report using the extracted method
693
+ checker_runner.generate_report(file_results, verbose=args.verbose)
694
+
695
+ # Write to GitHub Actions summary if running in CI
696
+ github_summary_file = os.environ.get('GITHUB_STEP_SUMMARY')
697
+ if github_summary_file:
698
+ try:
699
+ markdown_report = checker_runner.generate_github_summary(file_results)
700
+ with open(github_summary_file, 'a') as f:
701
+ f.write(markdown_report)
702
+ logger.info("Report written to GitHub Actions summary")
703
+ except Exception as e:
704
+ logger.warning(f"Failed to write GitHub Actions summary: {e}")
705
+
706
+ if total_errors > 0:
707
+ sys.exit(os.EX_DATAERR)
708
+ elif args.verbose:
709
+ sys.exit(os.EX_OK)
710
+
711
+ except ValueError as e:
712
+ print(str(e))
713
+ parser.print_help()
714
+ sys.exit(os.EX_USAGE)
715
+
716
+ if __name__ == "__main__":
717
+ main()