
# task.check – Soft checks with generic patterns, final hard checks for type, masters, and listen-on
source: named.conf

# 1. Convert original BIND config to dot‑flat
regexp: .*
code: <<CODE
!raku
my @lines = matched();
my $original = @lines.join("\n");

my $section = '';
my @flat;
for $original.lines -> $line {
    next if $line ~~ /^ \s* $/ || $line ~~ /^ \s* '#'/ || $line ~~ /^ \s* ';'/;
    if $line ~~ /^ \s* '[' \s* ( <-[ \] ]>+ ) \s* ']' \s* $/ {
        $section = $0.subst(/ \s* '"' | '"' \s* /, '', :g).subst(/\s+/, '.');
    }
    elsif $line ~~ /^ \s* ( <-[ = ; ]>+ ) \s* '=' \s* ( <-[ ; ]>*? ) \s* ';'? \s* $/ {
        if $section {
            @flat.push: "$section.{$0.trim} = {$1.trim}";
        }
    }
}
my $flat-file = '/tmp/bind-flat.conf';
spurt $flat-file, @flat.join("\n");
CODE

source: /tmp/bind-flat.conf

# 2. options.directory – soft check
~regexp: options \. directory \s* \= \s* ( <-[ ; ]>+ ) \s* \;?
code: <<CODE
!raku
if matched() {
    update_state({ directory => capture()[0] });
}
CODE
generator: <<CODE
!raku
if captures() -> @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: <<CODE
!raku
# capture handled in generator
CODE
generator: <<CODE
!raku
if captures() -> @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.<name>.type – soft, collect zone names and types
~regexp: zone \. ( <-[ \= ; ]>+ ) \. type \s* \= \s* ( <-[ ; ]>+ ) \s* \;?
code: <<CODE
!raku
if captures() -> @all {
    my $state = get_state();
    $state<all-zones> //= [];
    $state<zone-types> //= {};
    for @all -> $c {
        my $zone = $c[0];
        $state<all-zones>.push($zone) unless $state<all-zones>.grep($zone);
        $state<zone-types>{$zone} = $c[1];
    }
    update_state($state);
}
CODE
generator: <<CODE
!raku
if captures() -> @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.<name>.file – soft
~regexp: zone \. ( <-[ \= ; ]>+ ) \. file \s* \= \s* ( <-[ ; ]>+ ) \s* \;?
code: <<CODE
!raku
# soft, no state needed
CODE
generator: <<CODE
!raku
if captures() -> @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.<name>.masters – soft, mark zones having a masters field
~regexp: zone \. ( <-[ \= ; ]>+ ) \. masters \s* \= \s* ( <-[ ; ]>+ ) \s* \;?
code: <<CODE
!raku
if captures() -> @all {
    my $state = get_state();
    $state<masters-zones> //= [];
    for @all -> $c {
        my $zone = $c[0];
        $state<masters-zones>.push($zone) unless $state<masters-zones>.grep($zone);
    }
    update_state($state);
}
CODE
generator: <<CODE
!raku
if captures() -> @c {
    for @c -> $c {
        say "assert: 1 zone $c[0] masters defined";
    }
}
CODE

# 7. Final hard check: all zones must be master or slave
generator: <<CODE
!raku
my $state = get_state();
my %types = $state<zone-types> // {};
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: all zones must have a masters field
generator: <<CODE
!raku
my $state = get_state();
my @all     = $state<all-zones> // [];
my @with    = $state<masters-zones> // [];
my @missing = @all.grep: { $_ ∉ @with };

if @missing {
    say "assert: 0 zones missing masters: {@missing.join(', ')}";
} else {
    say "assert: 1 all zones have masters field set";
}
CODE