File Coverage

lib/Data/Hopen/G/GraphBuilder.pm
Criterion Covered Total %
statement 25 67 37.3
branch 0 28 0.0
condition 0 6 0.0
subroutine 9 15 60.0
pod 5 5 100.0
total 39 121 32.2


line stmt bran cond sub pod time code
1             # Data::Hopen::G::GraphBuilder - fluent interface for building graphs
2             package Data::Hopen::G::GraphBuilder;
3 1     1   116976 use Data::Hopen;
  1         3  
  1         76  
4 1     1   6 use strict;
  1         2  
  1         24  
5 1     1   5 use Data::Hopen::Base;
  1         1  
  1         6  
6 1     1   280 use Exporter 'import';
  1         2  
  1         68  
7              
8 1     1   49 our @EXPORT; BEGIN { @EXPORT=qw(make_GraphBuilder); }
9              
10             our $VERSION = '0.000021';
11              
12             use Class::Tiny {
13 1         7 name => 'ANON', # Name is optional; it's here so the
14             # constructor won't croak if you use one.
15             dag => undef, # The current G::DAG instance
16             node => undef, # The last node added
17 1     1   724 };
  1         2344  
18              
19 1     1   1407 use Class::Method::Modifiers qw(install_modifier);
  1         2033  
  1         78  
20 1     1   7 use Getargs::Mixed;
  1         1  
  1         54  
21 1     1   6 use Scalar::Util qw(refaddr);
  1         2  
  1         1006  
22              
23             # Docs {{{1
24              
25             =head1 NAME
26              
27             Data::Hopen::G::GraphBuilder - fluent interface for building graphs
28              
29             =head1 SYNOPSIS
30              
31             A GraphBuilder wraps a L and a current
32             L. It permits building chains of nodes in a
33             fluent way. For example, in an L hopen file:
34              
35             # $Build is a Data::Hopen::G::DAG created by App::hopen
36             use language 'C';
37              
38             my $builder = $Build->C::compile(file => 'foo.c');
39             # Now $builder holds $Build (the DAG) and a node created by
40             # C::compile().
41              
42             =head1 ATTRIBUTES
43              
44             =head2 name
45              
46             An optional name, in case you want to identify your Builder instances.
47              
48             =head2 dag
49              
50             The current L instance, if any.
51              
52             =head2 node
53              
54             The current L instance, if any.
55              
56             =head1 INSTANCE FUNCTIONS
57              
58             =cut
59              
60             # }}}1
61              
62             =head2 add
63              
64             Adds a node to the graph. Returns the node. Note that this B
65             change the builder's current node (L).
66              
67             =cut
68              
69             sub add {
70 0     0 1   my ($self, %args) = getparameters('self', ['node'], @_);
71 0           $self->dag->add($args{node});
72 0           return $args{node};
73             } #add()
74              
75             =head2 default_goal
76              
77             Links the most recent node in the chain to the default goal in the DAG.
78             If the DAG does not have a default goal, adds one called "all".
79              
80             As a side effect, calling this function clears the builder's record of the
81             current node and returns C. The idea is that this function
82             will be used at the end of a chain of calls. Clearing state in this way
83             reduces the chance of unintentionally connecting nodes.
84              
85             =cut
86              
87             sub default_goal {
88 0 0   0 1   my $self = shift or croak 'Need an instance';
89 0 0         croak "Need a node to link to the goal" unless $self->node;
90              
91 0   0       my $goal = $self->dag->default_goal // $self->dag->goal('all');
92 0           $self->dag->add($self->node); # no harm in it - DAG::add() is idempotent
93 0           $self->dag->connect($self->node, $goal);
94              
95 0           $self->node(undef); # Less likely to leak state between goals.
96              
97 0           return undef;
98             # Also, if this is the last thing in an App::hopen hopen file,
99             # whatever it returns gets recorded in MY.hopen.pl. Therefore,
100             # return $self would cause a copy of the whole graph to be dropped into
101             # MY.hopen.pl, which would be a Bad Thing.
102             } #default_goal()
103              
104             =head2 goal
105              
106             Links the most recent node in the chain to the given goal in the DAG.
107             Clears the builder's record of the current node and returns undef.
108              
109             =cut
110              
111             sub goal {
112 0 0   0 1   my $self = shift or croak 'Need an instance';
113 0 0         my $goal_name = shift or croak 'Need a goal name';
114 0 0         croak "Need a node to link to the goal" unless $self->node;
115              
116 0           my $goal = $self->dag->goal($goal_name);
117 0           $self->dag->add($self->node); # no harm in it - DAG::add() is idempotent
118 0           $self->dag->connect($self->node, $goal);
119              
120 0           $self->node(undef); # Less likely to leak state between goals.
121              
122 0           return undef; # undef: See comment in goal()
123             } #goal()
124              
125             =head2 to
126              
127             Connect one node to another, where both are wrapped in Cs.
128             Usage:
129              
130             $builder_1->to($builder_2);
131             # No $builder_1->node has an edge to $builder_2->node
132              
133             Returns C, because chaining would be ambiguous. For example,
134             in the snippet above, would the chain continue from C<$builder_1> or
135             C<$builder_2>?
136              
137             Does not change the state of either GraphBuilder.
138              
139             =cut
140              
141             sub to {
142 0     0 1   my ($self, %args) = parameters('self', [qw(dest)], @_);
143             croak 'Destination is not a ' . __PACKAGE__
144 0 0         unless $args{dest}->DOES(__PACKAGE__);
145             croak 'Cannot connect nodes from different graphs'
146 0 0         if refaddr($self->dag) != refaddr($args{dest}->dag);
147              
148 0           $self->dag->connect($self->node, $args{dest}->node);
149 0           return undef;
150             } #to()
151              
152             =head1 STATIC FUNCTIONS
153              
154             =head2 make_GraphBuilder
155              
156             Given the name of a subroutine, wrap the given subroutine for use in a
157             GraphBuilder chain such as that shown in the L. Usage:
158              
159             sub worker {
160             my $graphbuilder = shift;
161             ...
162             return $node; # Will automatically be linked into the chain
163             }
164              
165             make_GraphBuilder 'worker';
166             # now worker can take a DAG or GraphBuilder, and the
167             # return value will be the GraphBuilder.
168              
169             The C subroutine is called in scalar context.
170              
171             =cut
172              
173             sub _wrapper;
174              
175             sub make_GraphBuilder {
176 0     0 1   my $target = caller;
177 0 0         my $funcname = shift or croak 'Need the name of the sub to wrap'; # yum
178              
179 0           install_modifier $target, 'around', $funcname, \&_wrapper;
180             } #make_GraphBuilder()
181              
182             # The "around" modifier
183             sub _wrapper {
184 0 0   0     my $orig = shift or die 'Need a function to wrap';
185 0 0         croak "Need a parameter" unless @_;
186              
187             # Create the GraphBuilder if we don't have one already.
188 0           my $self = shift;
189             $self = __PACKAGE__->new(dag=>$self)
190 0 0         unless eval { $self->DOES(__PACKAGE__) };
  0            
191             croak "Parameter must be a DAG or Builder"
192 0 0         unless eval { $self->dag->DOES('Data::Hopen::G::DAG') };
  0            
193              
194 0           unshift @_, $self; # Put the builder on the arg list
195              
196             # Call the worker
197 0           my $worker_retval = &{$orig}; # @_ passed to code
  0            
198              
199             # If we got a node, remember it.
200 0 0 0       if(ref $worker_retval && eval { $worker_retval->DOES('Data::Hopen::G::Node') } ) {
  0            
201 0           $self->dag->add($worker_retval); # Link it into the graph
202 0 0         $self->dag->connect($self->node, $worker_retval) if $self->node;
203              
204 0           $self->node($worker_retval); # It's now our current node
205             }
206              
207 0           return $self;
208             }; #_wrapper()
209              
210             1;
211             __END__