File Coverage

blib/lib/Class/LOP.pm
Criterion Covered Total %
statement 126 269 46.8
branch 15 64 23.4
condition 1 12 8.3
subroutine 32 51 62.7
pod 17 22 77.2
total 191 418 45.6


line stmt bran cond sub pod time code
1             package Class::LOP;
2              
3             =head1 NAME
4              
5             Class::LOP - The Lightweight Object Protocol
6              
7             =head1 DESCRIPTION
8              
9             Just like L is built from L. You can build your own using this module. It is a little different
10             from L though, because it doesn't use a meta class, it has less features, but it's a lot faster.
11             If you need a lightweight object protocol, this may be the tool for you.
12             Using this module you could build an extremely quick OOP framework that could be used from a CLI or as a standard
13             module.
14              
15             =head1 SYNOPSIS
16              
17             package Goosey;
18              
19             use Class::LOP;
20              
21             sub import {
22              
23             my $caller = caller();
24             # Methods can be chained for simplicity and easy tracking.
25             # Below, we'll create the 'new' constructor, enable warnings and strict, and also
26             # bestow the accessors feature, so our module can create them
27             Class::LOP->init($caller)
28             ->create_constructor
29             ->warnings_strict
30             ->have_accessors('has');
31              
32             # import multiple methods into the specified class
33             Class::LOP->init('Goosey')->import_methods($caller, qw/
34             extends
35             after
36             before
37             /);
38             }
39              
40             # Add a few hook modifiers
41             # This code sure looks a lot cleaner than writing it yourself ;-)
42             sub after {
43             my ($name, $code) = @_;
44              
45             Class::LOP->init(caller())->add_hook(
46             type => 'after',
47             name => $name,
48             method => $code,
49             );
50             }
51              
52             # Extending a class is similar to 'use base'
53             # You may have also seen this from Moose
54             # ->extend_class() makes it really easy for you
55             sub extends {
56             my (@classes) = @_;
57             Class::LOP->init(caller())
58             ->extend_class(@classes);
59             }
60              
61             # MyClass.pm
62             package MyClass;
63              
64             use Goosey; # enables warnings/strict
65             extends 'Some::Module::To::Subclass';
66              
67             has 'name' => ( is => 'rw', default => 'Foo' );
68              
69             after 'name' => sub {
70             print "This code block runs after the original!\n";
71             };
72              
73             Wow, that all looks familiar.. but we wrote it all in a fairly small amount of code. Class::LOP takes care of the
74             dirty work for you, so you can just worry about getting the features in your module that you want.
75              
76             =cut
77              
78 4     4   28 use warnings;
  4         10  
  4         164  
79 4     4   24 use strict;
  4         8  
  4         151  
80 4     4   4462 use mro;
  4         3533  
  4         30  
81              
82             our $VERSION = '0.003';
83              
84             sub new {
85 0     0 1 0 my ($self, $class) = @_;
86 0 0       0 if (!$class) {
87 0         0 warn "No class specified";
88 0         0 return 0;
89             }
90              
91             {
92 4     4   368 no strict 'refs';
  4         10  
  4         1357  
  0         0  
93 0 0       0 if (! scalar %{ "${class}::" }) {
  0         0  
94 0         0 *{"${class}::new"} = sub {
95 0     0   0 return bless {}, $class;
96 0         0 };
97             }
98             }
99              
100 0         0 return bless {
101             _name => $class,
102             _attributes => [],
103             },
104             __PACKAGE__;
105             }
106              
107             sub init {
108 7     7 1 17 my ($self, $class) = @_;
109 7 50       25 if (!$class) {
110 0         0 warn "No class specified";
111 0         0 return 0;
112             }
113              
114 7         51 return bless {
115             _name => $class,
116             },
117             __PACKAGE__;
118             }
119              
120             sub name {
121 5     5 0 11 my $self = shift;
122 5         180 return $self->{_name};
123             }
124              
125             sub warnings_strict {
126 4     4 1 8 my $self = shift;
127 4         78 warnings->import();
128 4         73 strict->import();
129 4         22 return $self;
130             }
131              
132             sub getscope {
133 0     0 1 0 my ($self) = @_;
134 0         0 return scalar caller(1);
135             }
136              
137             sub class_exists {
138 1     1 1 2 my ($self, $class) = @_;
139 1 50       4 $class = $self->{_name} if !$class;
140             {
141 4     4   24 no strict 'refs';
  4         9  
  4         378  
  1         2  
142 1         1 return scalar %{ "${class}::" };
  1         12  
143             }
144             }
145              
146             sub list_methods {
147 0     0 1 0 my $self = shift;
148 0         0 my $class = $self->{_name};
149 0         0 my @methods = ();
150             {
151 4     4   20 no strict 'refs';
  4         6  
  4         1040  
  0         0  
152 0         0 foreach my $method (keys %{"${class}::"}) {
  0         0  
153 0 0       0 push @methods, $method
154             if substr($method, -2, 2) ne '::';
155             }
156             }
157              
158 0         0 return @methods;
159             }
160              
161             sub method_exists {
162 1     1 1 3 my ($self, $class, $method) = @_;
163 1 50       5 if (!$method) {
164 0         0 $method = $class;
165 0         0 $class = $self->{_name};
166             }
167 1         16 return $class->can($method);
168             }
169              
170             sub subclasses {
171 0     0 1 0 my $self = shift;
172 0         0 my @list = ();
173 0         0 my $class = $self->{_name};
174 0         0 @list = @{ $class->mro::get_isarev() };
  0         0  
175              
176 0 0       0 return scalar(@list) > 0 ? @list : 0;
177             }
178              
179             sub superclasses {
180 0     0 1 0 my $self = shift;
181 0         0 my $class = $self->{_name};
182             {
183 4     4   22 no strict 'refs';
  4         10  
  4         392  
  0         0  
184 0         0 return @{ "${class}::ISA" };
  0         0  
185             }
186             }
187              
188             sub import_methods {
189 5     5 1 38 my ($self, $class, @methods) = @_;
190 5         24 my $caller = $self->name();
191 4         1128 localscope: {
192 4     4   23 no strict 'refs';
  4         6  
  5         11  
193 5 50       6 if (! scalar(%{ "${class}::" })) {
  5         37  
194 0         0 warn "Class ${class} does not exist";
195 0         0 return 0;
196             }
197             else {
198 5         15 for my $method (@methods) {
199 67 50       577 *{"${class}::${method}"} = *{"${caller}::${method}"}
  67         304  
  67         158  
200             if $caller->can($method);
201             }
202             }
203             }
204              
205 5         124 return $self;
206             }
207              
208             sub extend_class {
209 0     0 1 0 my ($self, @mothers) = @_;
210              
211 0         0 my $class = $self->{_name};
212 0         0 foreach my $mother (@mothers) {
213             # if class is unknown to us, import it (FIXME)
214 0 0       0 unless (grep { $_ eq $mother } @{$self->{'classes'}}) {
  0         0  
  0         0  
215 0         0 eval "use $mother";
216 0 0       0 warn "Could not extend $mother: $@"
217             if $@;
218            
219 0         0 $mother->import;
220             }
221 0         0 push @{$self->{'classes'}}, $class;
  0         0  
222             }
223              
224             {
225 4     4   24 no strict 'refs';
  4         7  
  4         536  
  0         0  
226 0         0 @{"${class}::ISA"} = @mothers;
  0         0  
227             }
228              
229 0         0 return $self;
230             }
231              
232             sub have_accessors {
233 1     1 1 2 my ($self, $name) = @_;
234 1         3 my $class = $self->{_name};
235 1 50       4 if ($self->class_exists($class)) {
236             {
237 4     4   69 no strict 'refs';
  4         13  
  4         144  
  1         1  
238 4     4   21 no warnings 'redefine';
  4         7  
  4         2201  
239 1         5 *{"${class}::${name}"} = sub {
240 1     1   12 my ($acc, %args) = @_;
241 1         3 my $default = delete $args{default};
242 1         3 my $type = delete $args{is};
243 1 50 33     8 if ($type && $type eq 'ro') {
244 0         0 *{"${class}::${acc}"} = sub {
245 0 0   0   0 if (@_ > 1) {
246 0 0       0 if ($default) {
247 0 0       0 if (! exists $_[0]->{"$acc\_$_[0]\_default_used"}) {
248 0         0 $self->_add_attribute($_[0], $acc, $_[1]);
249 0         0 $_[0]->{$acc} = $_[1];
250 0         0 $_[0]->{"$acc\_$_[0]\_default_used"} = 1;
251 0         0 return $_[1];
252             }
253             }
254              
255 0         0 warn "Can't modify a read-only accessor (${acc})";
256 0         0 return 0;
257             }
258              
259 0         0 return $_[0]->{$acc};
260 0         0 };
261             }
262             else {
263 1         8 *{"${class}::${acc}"} = sub {
264 4 100   4   19 if (@_ > 1) {
265 2         12 $self->_add_attribute($_[0], $acc, $_[1]);
266 2         8 $_[0]->{$acc} = $_[1];
267             }
268              
269 4         19 return $_[0]->{$acc};
270 1         4 };
271             }
272              
273 1 50       4 if ($default) {
274 1         2 my $fullpkg = "${class}::${acc}";
275 1         3 $class->$acc($default);
276             }
277 1         5 };
278             }
279              
280 1         5 return $self;
281             }
282             else {
283 0         0 warn "Can't create accessors in class '$class', because it doesn't exist";
284 0         0 return 0;
285             }
286             }
287              
288             sub create_constructor {
289 1     1 1 3 my ($self, @args) = @_;
290 1         3 my $caller = $self->{_name};
291 1 50       14 if (! $caller->can('new')) {
292 4         826 doconstructor: {
293 4     4   32 no strict 'refs';
  4         8  
  1         2  
294 1         4 *{"${caller}::new"} = sub {
295 1     1   1060 my ($cself, @cargs) = @_;
296 1         4 bless {}, $cself;
297 1         10 shift;
298 1 50       12 if ($cself->can('__init')) {
299 0         0 $cself->__init(@_);
300             }
301            
302 1         3 return $cself;
303 1         7 };
304             }
305            
306 1         6 return $self;
307             }
308             }
309              
310             sub create_class {
311 0     0 0 0 my ($self, $class) = @_;
312 0         0 my $caller = $self->{_name};
313 0 0       0 if ($self->class_exists($caller)) {
314 0         0 warn "Can't create class '$class'. Already exists";
315 0         0 return 0;
316             }
317             else {
318             {
319 4     4   23 no strict 'refs';
  4         8  
  4         579  
  0         0  
320 0         0 *{"${class}::new"} = sub {
321 0     0   0 return bless {}, $class;
322 0         0 };
323             }
324             }
325              
326 0         0 return 1;
327             }
328              
329             sub create_method {
330 0     0 1 0 my ($self, $name, $code) = @_;
331 0         0 my $class = $self->{_name};
332 0 0       0 if ($self->class_exists($class)) {
333             {
334 4     4   22 no strict 'refs';
  4         7  
  4         753  
  0         0  
335 0 0       0 if ($self->method_exists($class, $name)) {
336 0         0 warn "Method $name already exists in $class. Did you mean to use override_method()?";
337 0         0 return 0;
338             }
339            
340 0         0 *{"${class}::${name}"} = $code;
  0         0  
341             }
342             }
343             else {
344 0         0 warn "Can't create ${name} in ${class}, because ${class} does not exist";
345 0         0 return 0;
346             }
347              
348 0         0 return $self;
349             }
350              
351             sub override_method {
352 1     1 1 3 my ($self, $name, $method) = @_;
353 1         3 my $class = $self->{_name};
354             {
355 4     4   23 no warnings 'redefine';
  4         6  
  4         161  
  1         1  
356 4     4   28 no strict 'refs';
  4         16  
  4         1277  
357 1 50       8 if (! $self->method_exists($class, $name)) {
358 0         0 warn "Cant't find '$name' in class $class - override_method()";
359 0         0 return 0;
360             }
361            
362 1         2 *{"${class}::${name}"} = $method;
  1         9  
363             }
364             }
365              
366             sub last_errors {
367 0     0 0 0 my $self = shift;
368 0         0 my $errors = $self->{errors};
369 0         0 $self->{errors} = [];
370 0         0 return $errors;
371             }
372              
373             sub add_hook {
374 0     0 1 0 my ($self, %args) = @_;
375 0         0 my $caller = $self->{_name};
376 0         0 my ($type, $class, $method, $code) = (
377             $args{'type'},
378             $self->{_name},
379             $args{'name'},
380             $args{'method'}
381             );
382              
383 0 0       0 if ($self->class_exists($caller)) {
384 0 0 0     0 if ($type && $class && $method && $code) {
      0        
      0        
385 0 0       0 if (! $self->method_exists($class, $method)) {
386 0         0 warn "Can't add hook because class $class does not have method $method";
387 0         0 return 0;
388             }
389              
390 0         0 my $fullpkg = "${class}::${method}";
391 0         0 my $old_code = \&{$fullpkg};
  0         0  
392 0         0 my $new_code;
393              
394 4         139 addhook: {
395 4     4   25 no strict 'refs';
  4         8  
  0         0  
396 4     4   20 no warnings 'redefine';
  4         6  
  4         1893  
397 0         0 for ($type) {
398 0 0       0 if (/after/) {
    0          
    0          
399 0         0 *{"${fullpkg}"} = sub {
400 0     0   0 $old_code->(@_);
401 0         0 $code->(@_);
402 0         0 };
403             }
404             elsif (/before/) {
405 0         0 *{"${fullpkg}"} = sub {
406 0     0   0 $code->(@_);
407 0         0 $old_code->(@_);
408 0         0 };
409             }
410             elsif (/around/) {
411 0         0 *{"${fullpkg}"} = sub {
412 0     0   0 $code->($old_code, @_);
413 0         0 };
414             }
415             else {
416 0         0 warn "Unknown hook type: $type";
417 0         0 return 0;
418             }
419             }
420             }
421 0         0 return $self;
422             }
423             else {
424 0         0 warn "Hook expecting type, class, method, and code";
425 0         0 return 0;
426             }
427             }
428             else {
429 0         0 warn "Can't add hook becase class '$class' does not exist";
430 0         0 return 0;
431             }
432             }
433              
434             sub clone_object {
435 0     0 1 0 my $self = shift;
436 0         0 my $class = $self->{_name};
437 0 0       0 if (! ref($class)) {
438 0         0 warn "clone_object() expects a reference\n";
439 0         0 return 0;
440             }
441 0         0 bless { %{ $class } }, ref $class;
  0         0  
442             }
443              
444             sub delete_method {
445 0     0 0 0 my ($self, $name) = @_;
446 0         0 my $class = $self->{_name};
447             {
448 4     4   25 no strict 'refs';
  4         11  
  4         905  
  0         0  
449             #$class = \%{"$class\::"};
450 0         0 delete $class::{$name};
451             }
452             }
453              
454             sub get_attributes {
455 0     0 0 0 my $self = shift;
456 0         0 my $class = $self->{_name};
457 0         0 return $self->{_attributes}->{$class};
458             }
459              
460             sub _add_attribute {
461 2     2   5 my ($self, $class, $attr, $value) = @_;
462 2 100       15 if ($self->{_attributes}->{$class}) {
463 1         4 $self->{_attributes}->{$class}->{$attr} = $value;
464             }
465             else {
466 1         3 $self->{_attributes}->{$class} = {
467             $attr => $value,
468             };
469             }
470             }
471             =head1 METHODS
472              
473             =head2 init
474              
475             Initialises a class. This won't create a new one, but will set the current class as the one specified, if it
476             exists.
477             You can then chain other methods onto this, or save it into a variable for repeated use.
478              
479             Class::LOP->init('SomeClass');
480              
481             =head2 new
482              
483             Initialises a class, but will also create a new one should it not exist. If you're wanting to initialise a class
484             you know exists, you're probably better off using C, as it involves less work.
485              
486             Class::LOP->new('MyNewClass')
487             ->create_method('foo', sub { print "foo!\n" });
488              
489             my $class = MyNewClass->new();
490             $class->foo(); # prints foo!
491              
492             Using C then chaining C onto it, we were able to create a class and a method on-the-fly.
493              
494             =head2 warnings_strict
495              
496             Enables C and C pragmas in Class::LOP modules
497              
498             $class->warnings_strict();
499              
500             =head2 getscope
501              
502             Basically just a C. Use this in your modules to return the class name
503              
504             my $caller = $class->getscope();
505              
506             =head2 class_exists
507              
508             Checks to make sure the class has been imported
509              
510             use Some::Module;
511              
512             if ($class->class_exists()) {
513             print "It's there!\n";
514             }
515              
516             =head2 method_exists
517              
518             Detects if a specific method in a class exists
519              
520             if ($class->method_exists($method_name)) { .. }
521              
522             =head2 subclasses
523              
524             Returns an list of subclassed modules
525              
526             my @subclass_mods = $class->subclasses();
527             for (@subclass_mods) {
528             print "$_\n";
529             }
530              
531             =head2 superclasses
532              
533             Returns a list of superclass (base) modules
534              
535             my @superclass_mods = $class->superclasses();
536             for (@superclass_mods) {
537             print "$_\n";
538             }
539              
540             =head2 import_methods
541              
542             Injects existing methods from the scoped module to a specified class
543              
544             $class->import_methods($destination_class, qw/this that and this/);
545              
546             Optionally, C can return errors if certain methods don't exist. You can read these
547             errors with C. This is only experimental at the moment.
548              
549             =head2 extend_class
550              
551             Pretty much the same as C. The first parameter is the subclass, and the following array
552             will be its "mothers".
553              
554             my @mommys = qw(This::Class That::Class);
555             $class->extend_class(@mommys)
556              
557             =head2 have_accessors
558              
559             Adds Moose-style accessors to a class. First parameter is the class, second will be the name of the method to
560             create accessors.
561              
562             # Goosey.pm
563             $class->have_accessors('acc');
564              
565             # test.pl
566             use Goosey;
567              
568             acc 'x' => ( is => 'rw', default => 7 );
569              
570             Currently the only two options is C and C.
571              
572             =head2 create_constructor
573              
574             Simply adds the C method to your class. I'm wondering whether this should be done automatically? The
575             aim of this module is to give the author as much freedom as possible, so I chose not to.
576              
577             $class->create_constructor;
578              
579             =head2 create_method
580              
581             Adds a new method to an existing class.
582              
583             $class->create_method('greet', sub {
584             my $self = shift;
585             print "Hello, World from " . ref($self) . "\n";
586             });
587              
588             MooClass->greet();
589              
590             =head2 add_hook
591              
592             Adds hook modifiers to your class. It won't import them all - only use what you need :-)
593              
594             $class->add_hook(
595             type => 'after',
596             method => $name,
597             code => $code,
598             );
599              
600             The types are C, C, and C.
601              
602             =head2 list_methods
603              
604             Returns a list of all the methods within an initialised class. It will filter out classes
605              
606             my @methods = Class::LOP->init('SomeClass')->list_methods();
607              
608             =head2 clone_object
609              
610             Takes an object and spits out a clone of it. This means mangling the original will have no side-effects to the cloned one
611             I know L has its own C method, but still, it's a good example.
612              
613             my $dt = DateTime->now;
614             my $dt2 = Class::LOP->init($dt)->clone_object;
615              
616             print $dt->add(days => 5)->dmy() . "\n";
617             print $dt2->dmy() . "\n";
618              
619             Simply changing C<$dt2 = $dt> would mean both results would have the same date when we printed them, but because we cloned the object, they are separate.
620              
621             =head2 override_method
622              
623             Unlike C, this method will let you replace the existing one, thereby overriding it.
624              
625             sub greet { print "Hello\n"; }
626            
627             Class::LOP->init('ClassName')->override_method('greet', sub { print "Sup\n" });
628              
629             greet(); # prints Sup
630              
631             =head1 AUTHOR
632              
633             Brad Haywood
634              
635             =head1 LICENSE
636              
637             This library is free software. You can redistribute it and/or modify
638             it under the same terms as Perl itself.
639              
640             =cut
641              
642             1;