From f4a671b4dac477775b71ea6c9bf6a067d5a042a7 Mon Sep 17 00:00:00 2001 From: Nico Lube Date: Fri, 17 Apr 2026 11:31:46 +0200 Subject: [PATCH] Support deprecated Keepout file function + add attribute roundtrip test. Parser now accepts `%TF.FileFunction,Keepout,*%`, the pre-2017.11 name for what the spec now calls `Profile`. Adds a text->parse->serialize test covering every FileAttribute variant so drift between the parser and the gerber-types serializer surfaces as a textual diff. Fixes: #19 --- src/parser.rs | 2 + tests/component_tests.rs | 80 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/src/parser.rs b/src/parser.rs index d894407..32ae058 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1841,6 +1841,8 @@ fn parse_file_attribute(line: Chars) -> Result { ("Soldermask", args, len) if len <= 2 => { with_side_and_optional_index!(SolderMask, args) } + // `Keepout` is the serialized name per gerber-types; spec 11.15 (Rev 2017.11) prefers `Profile` now. + ("Keepout", args, 1) => with_side!(KeepOut, args), ("Legend", args, len) if len <= 2 => with_side_and_optional_index!(Legend, args), ("Component", args, 2) => with_layer_and_side!(Component, args), ("Paste", args, 1) => with_side!(Paste, args), diff --git a/tests/component_tests.rs b/tests/component_tests.rs index 94475d8..78b877f 100644 --- a/tests/component_tests.rs +++ b/tests/component_tests.rs @@ -17,7 +17,7 @@ use std::collections::HashMap; use strum::VariantArray; mod util; use gerber_parser::util::{ - coordinates_from_gerber, gerber_to_reader, partial_coordinates_from_gerber, + coordinates_from_gerber, gerber_doc_as_str, gerber_to_reader, partial_coordinates_from_gerber, partial_coordinates_offset_from_gerber, }; use util::testing::logging_init; @@ -2025,6 +2025,84 @@ fn TF_file_attributes() { assert_eq!(filtered_commands, expected_commands,) } +/// Guards against silent drift between the parser and the gerber-types serializer +/// for every `FileAttribute` variant. Each TF line is written in the exact form the +/// serializer produces, so any divergence surfaces as a textual diff. +#[test] +#[allow(non_snake_case)] +fn TF_file_attributes_serialize_roundtrip() { + logging_init(); + + const GERBER: &str = "%FSLAX23Y23*% +%MOMM*% +%TF.Part,Single*% +%TF.Part,Array*% +%TF.Part,FabricationPanel*% +%TF.Part,Coupon*% +%TF.Part,Other,Value 1*% +%TF.FileFunction,Copper,L1,Top*% +%TF.FileFunction,Copper,L2,Inr,Plane*% +%TF.FileFunction,Copper,L3,Inr,Signal*% +%TF.FileFunction,Copper,L4,Bot,Mixed*% +%TF.FileFunction,Copper,L5,Bot,Hatched*% +%TF.FileFunction,Plated,1,2,PTH*% +%TF.FileFunction,Plated,1,6,Blind,Drill*% +%TF.FileFunction,Plated,3,4,Buried,Rout*% +%TF.FileFunction,NonPlated,1,2,NPTH*% +%TF.FileFunction,NonPlated,1,6,Blind,Mixed*% +%TF.FileFunction,NonPlated,3,4,Buried,Drill*% +%TF.FileFunction,Profile*% +%TF.FileFunction,Profile,P*% +%TF.FileFunction,Profile,NP*% +%TF.FileFunction,Keepout,Top*% +%TF.FileFunction,Keepout,Bot*% +%TF.FileFunction,Soldermask,Top*% +%TF.FileFunction,Soldermask,Bot,1*% +%TF.FileFunction,Legend,Top*% +%TF.FileFunction,Legend,Bot,2*% +%TF.FileFunction,Component,L1,Top*% +%TF.FileFunction,Paste,Top*% +%TF.FileFunction,Paste,Bot*% +%TF.FileFunction,Glue,Top*% +%TF.FileFunction,Carbonmask,Top*% +%TF.FileFunction,Goldmask,Top,1*% +%TF.FileFunction,Heatsinkmask,Bot*% +%TF.FileFunction,Peelablemask,Top*% +%TF.FileFunction,Silvermask,Bot,2*% +%TF.FileFunction,Tinmask,Top*% +%TF.FileFunction,Depthrout,Top*% +%TF.FileFunction,Vcut*% +%TF.FileFunction,Vcut,Top*% +%TF.FileFunction,Viafill*% +%TF.FileFunction,Pads,Top*% +%TF.FileFunction,Other,Value 1*% +%TF.FileFunction,Drillmap*% +%TF.FileFunction,FabricationDrawing*% +%TF.FileFunction,Vcutmap*% +%TF.FileFunction,AssemblyDrawing,Top*% +%TF.FileFunction,AssemblyDrawing,Bot*% +%TF.FileFunction,ArrayDrawing*% +%TF.FileFunction,OtherDrawing,Value 1*% +%TF.FilePolarity,Positive*% +%TF.FilePolarity,Negative*% +%TF.SameCoordinates*% +%TF.SameCoordinates,ident*% +%TF.SameCoordinates,ffffffff-ffff-ffff-ffff-ffffffffffff*% +%TF.CreationDate,2015-02-23T15:59:51+01:00*% +%TF.GenerationSoftware,MakerPnP,gerber-types*% +%TF.GenerationSoftware,MakerPnP,gerber-types,0.4.0*% +%TF.ProjectId,My PCB,ffffffff-ffff-ffff-ffff-ffffffffffff,2.0*% +%TF.MD5,6ab9e892830469cdff7e3e346331d404*% +%TFNonStandardAttribute,Value 1,Value 2*% +M02* +"; + + let doc = parse(gerber_to_reader(GERBER)).unwrap(); + let errors = doc.errors(); + assert!(errors.is_empty(), "parse produced errors: {:?}", errors); + assert_eq!(gerber_doc_as_str(&doc), GERBER); +} + #[test] #[should_panic] fn conflicting_aperture_codes() {