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.
- package/.claude-plugin/marketplace.json +12 -0
- package/.claude-plugin/plugin.json +13 -0
- package/CHANGELOG.md +8 -0
- package/LICENSE +21 -0
- package/README.md +429 -2
- package/dist/cli/commands/doctor.d.ts +4 -0
- package/dist/cli/commands/doctor.js +7 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/export.d.ts +17 -0
- package/dist/cli/commands/export.js +81 -0
- package/dist/cli/commands/export.js.map +1 -0
- package/dist/cli/commands/kicad.d.ts +10 -0
- package/dist/cli/commands/kicad.js +22 -0
- package/dist/cli/commands/kicad.js.map +1 -0
- package/dist/cli/commands/parts.d.ts +22 -0
- package/dist/cli/commands/parts.js +46 -0
- package/dist/cli/commands/parts.js.map +1 -0
- package/dist/cli/commands/prune.d.ts +14 -0
- package/dist/cli/commands/prune.js +67 -0
- package/dist/cli/commands/prune.js.map +1 -0
- package/dist/cli/commands/render.d.ts +24 -0
- package/dist/cli/commands/render.js +62 -0
- package/dist/cli/commands/render.js.map +1 -0
- package/dist/cli/commands/schema.d.ts +10 -0
- package/dist/cli/commands/schema.js +35 -0
- package/dist/cli/commands/schema.js.map +1 -0
- package/dist/cli/commands/ui.d.ts +14 -0
- package/dist/cli/commands/ui.js +67 -0
- package/dist/cli/commands/ui.js.map +1 -0
- package/dist/cli/commands/validate.d.ts +24 -0
- package/dist/cli/commands/validate.js +91 -0
- package/dist/cli/commands/validate.js.map +1 -0
- package/dist/cli/context.d.ts +81 -0
- package/dist/cli/context.js +139 -0
- package/dist/cli/context.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +8 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/program.d.ts +21 -0
- package/dist/cli/program.js +158 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/cli/raster.d.ts +4 -0
- package/dist/cli/raster.js +9 -0
- package/dist/cli/raster.js.map +1 -0
- package/dist/core/compile/breadboard-util.d.ts +12 -0
- package/dist/core/compile/breadboard-util.js +107 -0
- package/dist/core/compile/breadboard-util.js.map +1 -0
- package/dist/core/compile/breadboard.d.ts +60 -0
- package/dist/core/compile/breadboard.js +513 -0
- package/dist/core/compile/breadboard.js.map +1 -0
- package/dist/core/compile/fzp.d.ts +12 -0
- package/dist/core/compile/fzp.js +183 -0
- package/dist/core/compile/fzp.js.map +1 -0
- package/dist/core/compile/icon.d.ts +17 -0
- package/dist/core/compile/icon.js +27 -0
- package/dist/core/compile/icon.js.map +1 -0
- package/dist/core/compile/index.d.ts +4 -0
- package/dist/core/compile/index.js +62 -0
- package/dist/core/compile/index.js.map +1 -0
- package/dist/core/compile/pcb.d.ts +39 -0
- package/dist/core/compile/pcb.js +164 -0
- package/dist/core/compile/pcb.js.map +1 -0
- package/dist/core/compile/schematic.d.ts +69 -0
- package/dist/core/compile/schematic.js +240 -0
- package/dist/core/compile/schematic.js.map +1 -0
- package/dist/core/compile/style.d.ts +52 -0
- package/dist/core/compile/style.js +72 -0
- package/dist/core/compile/style.js.map +1 -0
- package/dist/core/compile/types.d.ts +50 -0
- package/dist/core/compile/types.js +15 -0
- package/dist/core/compile/types.js.map +1 -0
- package/dist/core/export/bin.d.ts +61 -0
- package/dist/core/export/bin.js +221 -0
- package/dist/core/export/bin.js.map +1 -0
- package/dist/core/export/fzpz.d.ts +37 -0
- package/dist/core/export/fzpz.js +121 -0
- package/dist/core/export/fzpz.js.map +1 -0
- package/dist/core/export/index.d.ts +5 -0
- package/dist/core/export/index.js +6 -0
- package/dist/core/export/index.js.map +1 -0
- package/dist/core/export/install.d.ts +77 -0
- package/dist/core/export/install.js +183 -0
- package/dist/core/export/install.js.map +1 -0
- package/dist/core/export/prune.d.ts +72 -0
- package/dist/core/export/prune.js +165 -0
- package/dist/core/export/prune.js.map +1 -0
- package/dist/core/fritzing.d.ts +22 -0
- package/dist/core/fritzing.js +77 -0
- package/dist/core/fritzing.js.map +1 -0
- package/dist/core/identity.d.ts +29 -0
- package/dist/core/identity.js +70 -0
- package/dist/core/identity.js.map +1 -0
- package/dist/core/image/bbox.d.ts +27 -0
- package/dist/core/image/bbox.js +24 -0
- package/dist/core/image/bbox.js.map +1 -0
- package/dist/core/image/index.d.ts +8 -0
- package/dist/core/image/index.js +9 -0
- package/dist/core/image/index.js.map +1 -0
- package/dist/core/image/key.d.ts +72 -0
- package/dist/core/image/key.js +317 -0
- package/dist/core/image/key.js.map +1 -0
- package/dist/core/image/prepare.d.ts +31 -0
- package/dist/core/image/prepare.js +98 -0
- package/dist/core/image/prepare.js.map +1 -0
- package/dist/core/image/remover.d.ts +17 -0
- package/dist/core/image/remover.js +51 -0
- package/dist/core/image/remover.js.map +1 -0
- package/dist/core/index.d.ts +16 -0
- package/dist/core/index.js +17 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/kicad/footprint.d.ts +104 -0
- package/dist/core/kicad/footprint.js +317 -0
- package/dist/core/kicad/footprint.js.map +1 -0
- package/dist/core/kicad/index.d.ts +25 -0
- package/dist/core/kicad/index.js +197 -0
- package/dist/core/kicad/index.js.map +1 -0
- package/dist/core/kicad/sexpr.d.ts +35 -0
- package/dist/core/kicad/sexpr.js +163 -0
- package/dist/core/kicad/sexpr.js.map +1 -0
- package/dist/core/resolve.d.ts +113 -0
- package/dist/core/resolve.js +301 -0
- package/dist/core/resolve.js.map +1 -0
- package/dist/core/schema/part.d.ts +995 -0
- package/dist/core/schema/part.js +278 -0
- package/dist/core/schema/part.js.map +1 -0
- package/dist/core/schema/part.schema.json +1122 -0
- package/dist/core/svg.d.ts +20 -0
- package/dist/core/svg.js +56 -0
- package/dist/core/svg.js.map +1 -0
- package/dist/core/units.d.ts +28 -0
- package/dist/core/units.js +42 -0
- package/dist/core/units.js.map +1 -0
- package/dist/core/validate/checker.d.ts +46 -0
- package/dist/core/validate/checker.js +142 -0
- package/dist/core/validate/checker.js.map +1 -0
- package/dist/core/validate/doctor.d.ts +25 -0
- package/dist/core/validate/doctor.js +156 -0
- package/dist/core/validate/doctor.js.map +1 -0
- package/dist/core/validate/explain.d.ts +10 -0
- package/dist/core/validate/explain.js +135 -0
- package/dist/core/validate/explain.js.map +1 -0
- package/dist/core/validate/index.d.ts +21 -0
- package/dist/core/validate/index.js +96 -0
- package/dist/core/validate/index.js.map +1 -0
- package/dist/core/validate/structural.d.ts +74 -0
- package/dist/core/validate/structural.js +664 -0
- package/dist/core/validate/structural.js.map +1 -0
- package/dist/core/workspace.d.ts +69 -0
- package/dist/core/workspace.js +213 -0
- package/dist/core/workspace.js.map +1 -0
- package/dist/mcp/jsonpatch.d.ts +30 -0
- package/dist/mcp/jsonpatch.js +145 -0
- package/dist/mcp/jsonpatch.js.map +1 -0
- package/dist/mcp/main.d.ts +2 -0
- package/dist/mcp/main.js +5 -0
- package/dist/mcp/main.js.map +1 -0
- package/dist/mcp/server.d.ts +28 -0
- package/dist/mcp/server.js +310 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/server/api.d.ts +72 -0
- package/dist/server/api.js +507 -0
- package/dist/server/api.js.map +1 -0
- package/dist/server/events.d.ts +28 -0
- package/dist/server/events.js +134 -0
- package/dist/server/events.js.map +1 -0
- package/dist/server/http.d.ts +36 -0
- package/dist/server/http.js +127 -0
- package/dist/server/http.js.map +1 -0
- package/dist/server/index.d.ts +65 -0
- package/dist/server/index.js +193 -0
- package/dist/server/index.js.map +1 -0
- package/dist/ui/assets/index-DLU8opyI.js +63 -0
- package/dist/ui/assets/index-GhUA-T3b.css +1 -0
- package/dist/ui/index.html +14 -0
- package/package.json +89 -4
- package/skills/fritzkit/SKILL.md +690 -0
- package/skills/fritzkit/reference/examples.md +275 -0
- package/skills/fritzkit/reference/part-json.md +333 -0
- package/skills/fritzkit/scripts/measure.py +197 -0
- package/tools/fzp-checker/.python-version +1 -0
- package/tools/fzp-checker/README.md +45 -0
- package/tools/fzp-checker/check_fzp.py +48 -0
- package/tools/fzp-checker/pyproject.toml +26 -0
- package/tools/fzp-checker/uv.lock +266 -0
- package/tools/fzp-checker/vendor/LICENSE.txt +8 -0
- package/tools/fzp-checker/vendor/UPSTREAM.md +25 -0
- package/tools/fzp-checker/vendor/fzp_checker.py +14 -0
- package/tools/fzp-checker/vendor/scripts/checks/__init__.py +26 -0
- package/tools/fzp-checker/vendor/scripts/checks/__pycache__/__init__.cpython-313.pyc +0 -0
- package/tools/fzp-checker/vendor/scripts/checks/__pycache__/check_missing_leg_ids.cpython-313.pyc +0 -0
- package/tools/fzp-checker/vendor/scripts/checks/__pycache__/connector_numbering.cpython-313.pyc +0 -0
- package/tools/fzp-checker/vendor/scripts/checks/__pycache__/fzp_checker_runner.cpython-313.pyc +0 -0
- package/tools/fzp-checker/vendor/scripts/checks/__pycache__/fzp_checkers.cpython-313.pyc +0 -0
- package/tools/fzp-checker/vendor/scripts/checks/__pycache__/fzp_svg_checkers.cpython-313.pyc +0 -0
- package/tools/fzp-checker/vendor/scripts/checks/__pycache__/fzp_utils.cpython-313.pyc +0 -0
- package/tools/fzp-checker/vendor/scripts/checks/__pycache__/svg_checkers.cpython-313.pyc +0 -0
- package/tools/fzp-checker/vendor/scripts/checks/__pycache__/svg_utils.cpython-313.pyc +0 -0
- package/tools/fzp-checker/vendor/scripts/checks/check_missing_leg_ids.py +44 -0
- package/tools/fzp-checker/vendor/scripts/checks/checkascii.py +27 -0
- package/tools/fzp-checker/vendor/scripts/checks/checkcase.py +156 -0
- package/tools/fzp-checker/vendor/scripts/checks/checkcopies.py +104 -0
- package/tools/fzp-checker/vendor/scripts/checks/connector_numbering.py +79 -0
- package/tools/fzp-checker/vendor/scripts/checks/connectors_misnumbered.py +228 -0
- package/tools/fzp-checker/vendor/scripts/checks/explain_errors.md +95 -0
- package/tools/fzp-checker/vendor/scripts/checks/fzp_checker_runner.py +717 -0
- package/tools/fzp-checker/vendor/scripts/checks/fzp_checkers.py +1069 -0
- package/tools/fzp-checker/vendor/scripts/checks/fzp_svg_checkers.py +62 -0
- package/tools/fzp-checker/vendor/scripts/checks/fzp_utils.py +222 -0
- package/tools/fzp-checker/vendor/scripts/checks/inspector_fc51.png +0 -0
- package/tools/fzp-checker/vendor/scripts/checks/invisibleconnectors.py +118 -0
- package/tools/fzp-checker/vendor/scripts/checks/nolayeridcheck.txt +758 -0
- package/tools/fzp-checker/vendor/scripts/checks/requirements.txt +3 -0
- package/tools/fzp-checker/vendor/scripts/checks/svg_checkers.py +953 -0
- package/tools/fzp-checker/vendor/scripts/checks/svg_utils.py +96 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_checkers.py +839 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/author_missing.fzp.test +3 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/author_present.fzp.test +4 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/buses_missing_id.fzp.test +8 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/buses_missing_node_members.fzp.test +7 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/buses_present_valid.fzp.test +8 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/connector_layers_missing_attributes.fzp.test +16 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/connector_layers_present_valid.fzp.test +14 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/connector_numbering_invalid.fzp.test +70 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/connector_numbering_valid.fzp.test +70 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/connector_refs_valid.fzp.test +53 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_invalid_mixed.fzp.test +11 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_invalid_silkscreen_nested.fzp.test +11 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_invalid_style_attr.fzp.test +11 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_invalid_white_stroke.fzp.test +11 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_invalid_wrong_color.fzp.test +11 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_inherited.fzp.test +11 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_legacy.fzp.test +12 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_nested_copper.fzp.test +12 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_none.fzp.test +11 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_standard.fzp.test +12 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_uppercase.fzp.test +11 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/css_connector.fzp.test +100 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/description_missing.fzp.test +3 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/description_present.fzp.test +4 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/duplicate_ids.fzp.test +27 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/family_property_missing.fzp.test +6 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/family_property_present_valid.fzp.test +6 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/font_size.fzp.test +46 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/fritzing_version_invalid_format.fzp.test +4 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/fritzing_version_missing.fzp.test +4 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/fritzing_version_present_valid.fzp.test +4 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/gorn_absent.fzp.test +15 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/gorn_present.fzp.test +15 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/hybrid_connectors.fzp.test +232 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/invalid_xml.fzp.test +12 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/layer_ids_match.fzp.test +29 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/layer_ids_mismatch.fzp.test +30 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/layer_nesting_invalid.fzp.test +11 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/layer_nesting_valid.fzp.test +11 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/matrix_transform.fzp.test +8 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/missing_connector_refs.fzp.test +63 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/missing_leg_ids.fzp.test +34 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_missing.fzp.test +4 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_non_alnum.fzp.test +4 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_present.fzp.test +4 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_special_chars_absent.fzp.test +4 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_special_chars_present.fzp.test +4 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_too_short.fzp.test +4 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/no_layer_invalid.fzp.test +19 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/no_layer_namedview.fzp.test +19 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/no_layer_valid.fzp.test +19 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/pcb_only.fzp.test +48 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/property_fields_missing_name.fzp.test +6 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/property_fields_missing_value.fzp.test +6 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/property_fields_present.fzp.test +6 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/required_tags_missing.fzp.test +3 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/required_tags_present.fzp.test +11 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/stroke_test.fzp.test +60 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/template_svgs.fzp.test +35 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/title_missing.fzp.test +3 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/title_present.fzp.test +4 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/unique_ids.fzp.test +27 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/unique_property_names_duplicates.fzp.test +7 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/unique_property_names_unique.fzp.test +7 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/valid_xml.fzp.test +249 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/version_invalid_format.fzp.test +4 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/version_missing.fzp.test +3 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/version_present_valid.fzp.test +4 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/views_missing.fzp.test +3 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/core/views_present.fzp.test +8 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/connector_refs_valid_breadboard.svg +7 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/duplicate_ids_breadboard.svg +13 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/gorn_absent_breadboard.svg +7 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/gorn_present_breadboard.svg +7 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/hybrid_connectors_breadboard.svg +102 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/layer_nesting_invalid.svg +13 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/layer_nesting_valid.svg +13 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/matrix_transform.svg +11 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/missing_connector_refs_breadboard.svg +8 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/missing_leg_ids_breadboard.svg +9 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/test_layer_ids_match_breadboard.svg +9 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/test_layer_ids_mismatch_breadboard.svg +9 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/unique_ids_breadboard.svg +13 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/icon/hybrid_connectors_icon.svg +78 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/icon/no_layer_invalid.svg +4 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/icon/no_layer_namedview.svg +9 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/icon/no_layer_valid.svg +7 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/icon/plain_pcb.svg +10 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/connector_refs_valid_pcb.svg +10 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_invalid_mixed.svg +8 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_invalid_silkscreen_nested.svg +10 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_invalid_style_attr.svg +6 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_invalid_white_stroke.svg +6 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_invalid_wrong_color.svg +6 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_inherited.svg +7 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_legacy.svg +10 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_nested_copper.svg +9 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_none.svg +7 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_standard.svg +10 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_uppercase.svg +7 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/css_connector.svg +25 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/font_size.svg +54 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/hybrid_connectors_pcb.svg +35 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/missing_connector_refs_pcb.svg +14 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/pcb-arduino-shield.svg +25 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/stroke_test.svg +12 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/test_layer_ids_match_pcb.svg +12 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/test_layer_ids_mismatch_pcb.svg +12 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/schematic/connector_refs_valid_schematic.svg +7 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/schematic/hybrid_connectors_schematic.svg +59 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/schematic/missing_connector_refs_schematic.svg +8 -0
- package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/schematic/test_layer_ids_match_schematic.svg +6 -0
- 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()
|