# 1. Capture all lines and convert to dot‑flat using Python regexp: .* code: < "zone.example.com" section = section_raw.replace('"', '').replace(' ', '.') continue # key = value pair (semicolon optional) if '=' in line and section: parts = line.split('=', 1) key = parts[0].strip() value = parts[1].strip().rstrip(';').strip() flat_lines.append(f"{section}.{key} = {value}") flat_file = '/tmp/bind-flat.conf' with open(flat_file, 'w') as f: f.write('\n'.join(flat_lines)) CODE # Now all subsequent checks read the freshly generated flat file source: /tmp/bind-flat.conf # 2. options.directory – soft check ~regexp: options \. directory \s* \= \s* ( <-[ \= \; ]>+ ) \s* \;? code: < capture()[0] }); } CODE generator: < @c { for @c -> $c { my $ok = $c[0] ne ''; say "assert: {$ok} options.directory is '$c[0]'"; } } CODE # 3. options.listen-on – soft check, must be 127.0.0.1 ~regexp: options \. listen\-on \s* \= \s* \{ \s* ( <-[ } ; ]>+ ) \s* [ \; \s* ]? \} \s* \;? code: < @c { for @c -> $c { my $ip = $c[0].trim; my $ok = $ip eq '127.0.0.1'; say "assert: {$ok} options.listen-on is '$ip' (must be 127.0.0.1)"; } } else { say "assert: 0 options.listen-on not defined"; } CODE # 4. zone..type – soft, collect zone names and types ~regexp: zone \. ( <-[ \= \; ]>+ ) \. type \s* \= \s* ( <-[ \; ]>+ ) \s* \;? code: < @all { my $state = get_state(); $state //= []; $state //= {}; for @all -> $c { my $zone = $c[0]; $state.push($zone) unless $state.grep($zone); $state{$zone} = $c[1]; } update_state($state); } CODE generator: < @c { for @c -> $c { my ($zone, $type) = $c[0], $c[1]; my $valid = $type eq 'master' || $type eq 'slave'; say "assert: {$valid} zone $zone type is '$type'"; } } CODE # 5. zone..file – soft ~regexp: zone \. ( <-[ \= \; ]>+ ) \. file \s* \= \s* ( <-[ \; ]>+ ) \s* \;? code: < @c { for @c -> $c { my ($zone, $file) = $c[0], $c[1]; my $nonempty = $file ne ''; say "assert: {$nonempty} zone $zone file is '$file'"; } } CODE # 6. zone..masters – soft, mark zones having a masters field ~regexp: zone \. ( <-[ \= \; ]>+ ) \. masters \s* \= \s* ( <-[ \; ]>+ ) \s* \;? code: < @all { my $state = get_state(); $state //= []; for @all -> $c { my $zone = $c[0]; $state.push($zone) unless $state.grep($zone); } update_state($state); } CODE generator: < @c { for @c -> $c { say "assert: 1 zone $c[0] masters defined"; } } CODE # 7. Final hard check #1: all zones must be master or slave generator: < // {}; if %types { my @invalid = %types.keys.grep: { %types{$_} ne 'master' && %types{$_} ne 'slave' }; if @invalid { say "assert: 0 zones with invalid type: {@invalid.join(', ')}"; } else { say "assert: 1 all zones have type master or slave"; } } else { say "assert: 1 no zones defined"; } CODE # 8. Final hard check #2: all zones must have a masters field generator: < // []; my @masters-zones = $state // []; my $all-ok = True; for @all-zones -> $zone { if $zone (elem) @masters-zones { say "assert: 1 zone $zone has masters"; } else { say "assert: 0 zone $zone missing masters"; $all-ok = False; } } # Optional overall assert, but the individual asserts already cover each zone CODE