use Moo;
-has observation => (is => 'ro', required => 1);
+has observer => (is => 'ro', required => 1);
has resume => (is => 'ro', required => 1);
--- /dev/null
+package DX::Observer::FromCode;
+
+use Moo;
+
+has code => (is => 'ro', required => 1);
+
+sub run { $_[0]->code->(); }
+
+1;
--- /dev/null
+package DX::Op::Observe;
+
+use DX::ObservationRequired;
+use Moo;
+
+with 'DX::Role::Op';
+
+has vars => (is => 'ro', required => 1);
+has builder => (is => 'ro', required => 1);
+
+has _arg_map => (is => 'lazy', builder => sub {
+ my ($self) = @_;
+ my $name = 'arg0';
+ +{ map +($name++, $_), @{$self->vars} };
+});
+
+sub run {
+ my ($self, $state) = @_;
+ ($state, my %args) = $self->_expand_args($state, %{$self->_arg_map});
+ my @vars = map $_->bound_value, @args{sort keys %args};
+ my $observer = $self->builder->(@vars);
+ $state->return_from_run(
+ DX::ObservationRequired->new(
+ observer => $observer,
+ resume => $state->then($self->next)
+ )
+ );
+}
+
+1;
has is_exhausted => (is => 'rwp');
+has observation_policy => (is => 'ro', default => sub { sub { 0 } });
+
sub next {
my ($self) = @_;
return if $self->is_exhausted;
- my $start_state = do {
+ my $state = do {
if (my $cur = $self->_current_state) {
$cur->push_backtrack;
} else {
$self->for_state
}
};
- my $state = $self->_current_state($start_state->run);
+ STATE: while ($state = $self->_current_state($state->run)) {
+ last if $state->isa('DX::State');
+ if ($state->isa('DX::ObservationRequired')) {
+ if ($self->observation_policy->($state->observer)) {
+ my ($type, $value) = $state->observer->run;
+ $state = $state->resume;
+ $state->facts->{$type}->set_value($value);
+ } else {
+ die "Observation refused";
+ }
+ } else {
+ die "WTF: ".$state;
+ }
+ }
unless ($state) {
$self->_set_is_exhausted(1);
return;
use DX::Op::ApplyConstraint;
use DX::Op::Return;
use DX::Op::Cut;
+use DX::Op::Observe;
use List::Util qw(reduce);
has rules => (is => 'ro', default => sub { {} });
);
}
+sub _expand_observe {
+ my ($self, $vars, $builder) = @_;
+ DX::Op::Observe->new(
+ vars => $vars,
+ builder => $builder,
+ );
+}
+
1;
has facts => (is => 'ro', required => 1);
+has observation_policy => (is => 'ro');
+
sub query {
my ($self, $vars, @terms) = @_;
my $rule_set = $self->rule_set;
rule_set => $rule_set,
)->assign_vars(map +($_ => {}), @$vars);
return DX::ResultStream->new(
- for_state => $state
+ for_state => $state,
+ ($self->observation_policy
+ ? (observation_policy => $self->observation_policy)
+ : ()),
);
}
use Test::More;
use DX::Solver;
use DX::SetOver;
+use DX::Observer::FromCode;
use Test::Exception;
{
%path_status = %protos;
-sub paths_for {
+sub paths_for_simple {
join ' ', map $_->{PS}->path, $solver->query(
[ qw(PS) ], [ path_status => 'PS' ], @_
)->results;
}
-is(paths_for(), '.ssh .ssh/authorized_keys');
+is(paths_for_simple(), '.ssh .ssh/authorized_keys');
-is(paths_for([ is_directory => 'PS' ]), '.ssh');
+is(paths_for_simple([ is_directory => 'PS' ]), '.ssh');
-is(paths_for([ is_file => 'PS' ]), '.ssh/authorized_keys');
+is(paths_for_simple([ is_file => 'PS' ]), '.ssh/authorized_keys');
-is(paths_for([ mode => 'PS', [ value => '0755' ] ]), '.ssh');
+is(paths_for_simple([ mode => 'PS', [ value => '0755' ] ]), '.ssh');
$solver->add_rule(
path_status_at => [ 'PS', 'P' ],
is(join(' ', map $_->{PS}->path, @res), '.ssh');
-#::Dwarn($solver->query([ qw(PS) ], [ path_status_at => 'PS', [ value => '.ssh' ] ])->results);
+delete $solver->rule_set->rules->{'path_status_at/2'};
+
+$solver->add_rule(
+ path_status_at => [ 'PS', 'P' ],
+ [ path_status => 'PS' ],
+ [ path => qw(PS P) ],
+ [ 'cut' ],
+);
+
+my %ob_res;
+
+$solver->add_rule(
+ path_status_at => [ 'PS', 'P' ],
+ [ observe => [ 'P' ],
+ sub {
+ my ($path) = $_[0];
+ DX::Observer::FromCode->new(
+ code => sub { (path_status => $ob_res{$path}) }
+ )
+ }
+ ],
+ [ path_status => 'PS' ],
+ [ path => qw(PS P) ],
+);
+
+%path_status = ('.ssh/authorized_keys' => $protos{'.ssh/authorized_keys'});
+
+$ob_res{'.ssh'} = $protos{'.ssh'};
+
+sub paths_for {
+ join ' ', map $_->{PS}->path, $solver->query([ 'PS' ], @_)->results;
+}
+
+is(
+ paths_for([ path_status => 'PS' ], [ path => 'PS', [ value => '.ssh' ] ]),
+ '',
+ 'no .ssh entry'
+);
+
+throws_ok { paths_for([ path_status_at => 'PS', [ value => '.ssh' ] ]) }
+ qr/refused/;
+
+$solver->{observation_policy} = sub { 1 };
+
+is(
+ paths_for([ path_status_at => 'PS', [ value => '.ssh' ] ]),
+ '.ssh',
+ 'observation'
+);
+
+is($path_status{'.ssh'}, $ob_res{'.ssh'});
+
+delete $solver->{observation_policy};
+
+lives_ok { paths_for([ path_status_at => 'PS', [ value => '.ssh' ] ]) }
+ 'No observation required anymore';
done_testing;
my $path = $state->scope_var('P')->bound_value;
$state->return_from_run(
DX::ObservationRequired->new(
- observation => sub { $paths{$path} = $observe_path{$path} },
+ observer => sub { $paths{$path} = $observe_path{$path} },
resume => $state->then($self->next),
)
);
my $ob_req = $or_state->run;
-$ob_req->observation->();
+$ob_req->observer->();
{
my $res = $ob_req->resume->run;