5 use DX::Observer::FromCode;
6 use DX::Action::FromCode;
11 package My::PathStatus;
15 with 'DX::Role::Fact';
17 has path => (is => 'ro', required => 1);
18 has info => (is => 'ro', predicate => 1);
20 package My::PathStatusInfo;
24 with 'DX::Role::Fact';
26 has is_directory => (is => 'ro', default => 0);
27 has is_file => (is => 'ro', default => 0);
28 has mode => (is => 'ro', required => 1);
31 @INC{qw(My/PathStatus.pm My/PathStatusInfo.pm)} = (__FILE__,__FILE__);
34 '.ssh' => My::PathStatus->new(
36 info => My::PathStatusInfo->new(is_directory => 1, mode => '0755')
38 '.ssh/authorized_keys' => My::PathStatus->new(
39 path => '.ssh/authorized_keys',
40 info => My::PathStatusInfo->new(is_file => 1, mode => '0644')
45 '.ssh' => My::PathStatus->new(
48 '.ssh/authorized_keys' => My::PathStatus->new(
49 path => '.ssh/authorized_keys'
55 my $solver = DX::Solver->new(
56 facts => { path_status => DX::SetOver->new(
58 values => \%path_status,
62 $solver->add_rule(@$_) for (
63 [ path_status => [ qw(PS) ],
64 [ member_of => 'PS', \'path_status' ] ],
65 [ path => [ qw(PS P) ],
66 [ prop => 'PS', \'path', 'P' ] ],
67 [ info_prop => [ qw(PS N V) ],
68 [ prop => 'PS', \'info', 'PSI' ],
69 [ prop => 'PSI', 'N', 'V' ] ],
70 [ mode => [ qw(PS M) ],
71 [ info_prop => 'PS', \'mode', 'M' ] ],
72 [ exists_path => [ qw(PS) ],
73 [ info_prop => 'PS', \'is_directory', \1 ] ],
74 [ exists_path => [ qw(PS) ],
75 [ info_prop => 'PS', \'is_file', \1 ] ],
76 [ is_directory => [ qw(PS) ],
77 [ info_prop => 'PS', \'is_directory', \1 ] ],
78 [ is_file => [ qw(PS) ],
79 [ info_prop => 'PS', \'is_file', \1 ] ],
82 %path_status = %protos;
84 sub paths_for_simple {
85 join ' ', map $_->value_for('PS')->path, $solver->query(
86 [ qw(PS) ], [ path_status => 'PS' ], @_
90 is(paths_for_simple(), '.ssh .ssh/authorized_keys');
92 is(paths_for_simple([ is_directory => 'PS' ]), '.ssh');
94 is(paths_for_simple([ is_file => 'PS' ]), '.ssh/authorized_keys');
96 is(paths_for_simple([ mode => 'PS', [ value => '0755' ] ]), '.ssh');
99 path_status_at => [ 'PS', 'P' ],
100 [ path_status => 'PS' ],
101 [ path => qw(PS P) ],
104 path_status_at => [ 'PS', 'P' ],
105 [ constrain => [] => sub { die "ARGH" } ]
111 [ path_status_at => 'PS', [ value => '.ssh' ] ]
115 delete $solver->rule_set->rules->{'path_status_at/2'};
118 path_status_at => [ 'PS', 'P' ],
119 [ path_status => 'PS' ],
120 [ path => qw(PS P) ],
124 path_status_at => [ 'PS', 'P' ],
125 [ constrain => [] => sub { die "ARGH" } ]
131 @res = $solver->query(
133 [ path_status_at => 'PS', [ value => '.ssh' ] ]
137 is(join(' ', map $_->value_for('PS')->path, @res), '.ssh');
139 delete $solver->rule_set->rules->{'path_status_at/2'};
142 path_status_at => [ 'PS', 'P' ],
143 [ path_status => 'PS' ],
144 [ path => qw(PS P) ],
151 path_status_at => [ 'PS', 'P' ],
152 [ observe => [ 'P' ],
155 DX::Observer::FromCode->new(
156 code => sub { (path_status => $ob_res{$path}) }
160 [ path_status => 'PS' ],
161 [ path => qw(PS P) ],
166 $ob_res{'.ssh'} = $protos{'.ssh'};
169 join ' ', map $_->value_for('PS')->path, $solver->query([ 'PS' ], @_)->results;
173 paths_for([ path_status => 'PS' ], [ path => 'PS', [ value => '.ssh' ] ]),
178 throws_ok { paths_for([ path_status_at => 'PS', [ value => '.ssh' ] ]) }
181 $solver->{observation_policy} = sub { 1 };
184 paths_for([ path_status_at => 'PS', [ value => '.ssh' ] ]),
189 is($path_status{'.ssh'}, $ob_res{'.ssh'});
191 delete $solver->{observation_policy};
193 lives_ok { paths_for([ path_status_at => 'PS', [ value => '.ssh' ] ]) }
194 'No observation required anymore';
196 $path_status{'.ssh/authorized_keys'} = $protos{'.ssh/authorized_keys'};
199 paths_for([ path_status => 'PS' ], [ not => [ is_directory => 'PS' ] ]),
200 '.ssh/authorized_keys',
204 $solver->add_rule(@$_) for (
205 [ directory_at => [ qw(PS P) ],
206 [ path_status_at => qw(PS P) ],
207 [ is_directory => 'PS' ] ],
208 [ file_at => [ qw(PS P) ],
209 [ path_status_at => qw(PS P) ],
210 [ is_file => 'PS' ] ],
215 $ob_res{'.ssh'} = $empty{'.ssh'};
217 #%path_status = %protos;
219 $solver->{observation_policy} = sub { 1 };
222 $solver->query([ 'PS' ], [ directory_at => 'PS' => [ value => '.ssh' ] ]);
226 [ dot_ssh_query()->results ],
230 #::Dwarn(paths_for([ directory_at => 'PS', [ value => '.ssh' ] ]));
232 $solver->add_rule(@$_) for (
233 [ is_directory => [ qw(PS) ],
234 [ not => [ exists_path => 'PS' ] ],
238 DX::Action::FromCode->new(
240 (path_status => My::PathStatus->new(
241 path => $value->path,
242 info => My::PathStatusInfo->new(
243 is_directory => 1, mode => ''
248 $ob_res{$value->path} = $protos{$value->path};
249 (path_status => $value);
257 @res = dot_ssh_query()->results;
259 is(scalar(@res),1,'Single result');
261 is($path_status{'.ssh'}, $empty{'.ssh'}, 'Empty observed');
264 scalar(my ($action) = $res[0]->actions), 1
267 $solver->run_action($action);
269 ok(!$path_status{'.ssh'}, 'Empty retracted');
271 @res = dot_ssh_query()->results;
273 is(scalar(@res),1,'Single result');
275 is($path_status{'.ssh'}, $protos{'.ssh'}, 'Created observed');
277 ok(!$res[0]->actions, 'No action');
279 $solver->add_predicate(
280 catfile => [ qw(DirPath FileName FilePath) ],
281 [ qw(+ + -) ] => sub {
282 +(FilePath => [ value => File::Spec->catfile($_{DirPath}, $_{FileName}) ])
286 $solver->add_rule(@$_) for (
287 [ file_in => [ qw(DirStatus FileName FileStatus) ],
288 [ is_directory => qw(DirStatus) ],
289 [ path => qw(DirStatus DirPath) ],
290 [ catfile => qw(DirPath FileName FilePath) ],
291 [ file_at => qw(FileStatus FilePath) ] ],
292 [ is_file => [ qw(PS) ],
293 [ not => [ exists_path => 'PS' ] ],
297 DX::Action::FromCode->new(
299 (path_status => My::PathStatus->new(
300 path => $value->path,
301 info => My::PathStatusInfo->new(
302 is_file => 1, mode => ''
307 $ob_res{$value->path} = $protos{$value->path};
308 (path_status => $value);
318 $solver->query([ qw(D F) ],
319 [ directory_at => 'D' => \'.ssh' ],
320 [ file_in => 'D' => \'authorized_keys' => 'F' ],
324 @res = keys_file()->results;
326 is(scalar @res, 1, 'One result');
328 is(scalar(my @act = $res[0]->actions), 2, 'Two actions');
332 is(scalar(my ($poss) = grep !@{$_->dependencies}, @act), 1, 'One possible');
334 $solver->run_action($poss);
336 @res = keys_file()->results;
338 is(scalar @res, 1, 'One result');
341 scalar(($poss) = grep !@{$_->dependencies}, $res[0]->actions), 1,
345 $solver->run_action($poss);
347 @res = keys_file()->results;
349 is(scalar @res, 1, 'One result');
351 is(scalar($res[0]->actions), 0, 'No actions');