File Coverage

blib/lib/Hub.pm
Criterion Covered Total %
statement 123 150 82.0
branch 36 58 62.0
condition 12 19 63.1
subroutine 18 24 75.0
pod 8 8 100.0
total 197 259 76.0


line stmt bran cond sub pod time code
1             package Hub;
2 1     1   25942 use strict;
  1         3  
  1         801  
3              
4             our @ISA = qw/Exporter/;
5             our @EXPORT = qw/$Hub/;
6             our @EXPORT_OK = qw/mkinst regns getns trace callback $Hub/;
7             our $VERSION = '4.00043';
8              
9             our %METHODMAP = (); # Maps method names to their implementing package
10             our %OBJECTMAP = (); # Maps object short names to full package name
11             our %KNOTMAP = (); # Maps tie-package short names to their full name
12              
13             # ------------------------------------------------------------------------------
14             # TAG_MAP - Specify virtual tags per directory
15             #
16             # Symbols exported by modules under the specified directory will be added to
17             # each virtual-tag. Virtual tags are the elements of the array.
18             #
19             # Note: by default, each directory name (lower-cased) is a tag, and should not
20             # be listed here. As in, all EXPORT_OK methods in the 'Knots' subdirectory are
21             # exposed with the ':knots' tag.
22             # ------------------------------------------------------------------------------
23              
24             our %TAG_MAP = (
25             'Base' => [ 'standard', ],
26             'Config' => [ 'standard', ],
27             'Data' => [ 'standard', ],
28             'Knots' => [ 'standard', ],
29             'Parse' => [ 'standard', ],
30             'Perl' => [ 'standard', ],
31             'Misc' => [ 'standard', ],
32             );
33              
34             # ------------------------------------------------------------------------------
35             # Gather symbols
36             #
37             # Here we load internal and external modules, adding their exports to our
38             # export arrays.
39             #
40             # External modules (like Carp) are exported for our internal modules'
41             # convienence under the ':lib' tag.
42             #
43             # Internal modules are tagged according to the directory the reside in, and
44             # also any additional tags defined in %TAG_MAP.
45             #
46             # By default, nothing exported from this or any other internal or external
47             # module.
48             # ------------------------------------------------------------------------------
49              
50             map { $METHODMAP{$_} = 'Hub' } @EXPORT_OK;
51             push @EXPORT_OK, _load_external_libs();
52             our %EXPORT_TAGS = (
53             'lib' => [ @EXPORT_OK ],
54             'standard' => [ @EXPORT_OK ],
55             );
56              
57             _load_internal_libs(keys %TAG_MAP);
58              
59             push @EXPORT_OK, keys %METHODMAP;
60              
61             # ------------------------------------------------------------------------------
62             # Runtime variables
63             # ------------------------------------------------------------------------------
64              
65             our $Hub = (); # Hub instance for this thread
66             our $REGISTRY = {}; # The root symbol for all variables
67             $Hub = mkinst('Registry', regns('LIBRARY'));
68             $Hub->bootstrap();
69              
70             # ------------------------------------------------------------------------------
71             # import - Get symbols from this library
72             # This adapter method allows us to look at the requested tags before Exporter
73             # gets ahold of it. We want to dynamically load internal libraries based
74             # on the requested tag. In this way, you can create a new set of modules:
75             #
76             # /path/to/lib/Hub/Mystuff/Peak.pm
77             # /Crescendo.pm
78             #
79             # and use them in a file as:
80             #
81             # use Hub(:mystuff);
82             #
83             # and you get the same facilities as this library itself. Meaning you can
84             # call EXPORT_OK subroutines of Peak.pm and Crescendo.pm as
85             # C or just C.
86             #
87             # Inside Peak.pm and Crescendo.pm, you should:
88             #
89             # use Hub(:lib);
90             #
91             # So you get the standard set of external symbols, like C
92             # cluck, confess, blessed, time, gettimeofday, tv_interval and cwd()>. See
93             # L<_load_external_libs>.
94             #
95             # If you would like Crescendo.pm to use methods from Peak.pm, you should:
96             #
97             # use Hub(:lib :mystuff);
98             #
99             # And then reference those methods as C. This is not a
100             # requirement by any means, but half of the reasons for doing all this in
101             # the first place is to make refactoring simple. If you follow this route
102             # (note you should also be using Hub::mkinst('Peak') to create your objects)
103             # than you can move code around without changing the API.
104             # ------------------------------------------------------------------------------
105              
106             sub import {
107             map {
108 61 100   61   152 if (/^:([\w\d]+)/) {
  228         886  
109 43         117 my $tagname = $1;
110 43 100       93 if ($tagname eq 'all') {
111 1         262 @{$EXPORT_TAGS{'all'}} = keys %METHODMAP;
  1         89  
112 1         20 _load_internal_libs($tagname);
113             }
114 42 100       850 unless (grep /^$tagname$/i, keys %EXPORT_TAGS) {
115 2         9 $EXPORT_TAGS{$tagname} = [];
116             #warn "Tag: $tagname\n";
117 2         20 _load_internal_libs($tagname);
118             #warn "Got internals\n";
119             # if ($tagname eq 'all') {
120             # @{$EXPORT_TAGS{'all'}} = keys %METHODMAP;
121             #warn "OK: ", join(',', @EXPORT_OK), "\n";
122             # } else {
123 2         3 push @{$EXPORT_TAGS{'all'}}, @{$EXPORT_TAGS{$tagname}};
  2         7  
  2         11  
124             # }
125             }
126             }
127             } @_;
128             #warn "Onward then\n";
129 60         38025 goto &Exporter::import;
130             }
131              
132             # ------------------------------------------------------------------------------
133             # _load_external_libs - Load external modules.
134             #
135             # Share minimal list of standard functions which every module in its right mind
136             # would use.
137             # ------------------------------------------------------------------------------
138              
139             sub _load_external_libs {
140 1     1   1523 use UNIVERSAL qw/isa can/;
  1         20  
  1         9  
141 1     1   863 use Exporter qw//;
  1         8  
  1         25  
142 1     1   5 use Carp qw/carp croak cluck confess/;
  1         2  
  1         74  
143 1     1   6 use Scalar::Util qw/blessed/;
  1         2  
  1         116  
144 1     1   1012 use Time::HiRes qw/time gettimeofday tv_interval/;
  1         2177  
  1         5  
145 1     1   278 use Cwd;
  1         2  
  1         94  
146 1     1   1073 use IO::File;
  1         12245  
  1         190  
147 1     1   106708 use File::stat;
  1         98270  
  1         10  
148 1     1   12 return qw/
149             isa
150             can
151             import
152             carp
153             croak
154             cluck
155             confess
156             blessed
157             time
158             gettimeofday
159             tv_interval
160             stat
161             /, @Cwd::EXPORT;
162             }#_load_external_libs
163              
164             # ------------------------------------------------------------------------------
165             # _load_internal_libs - We want to import all EXPORT_OK methods from packages.
166             # _load_internal_libs @list
167             # _load_internal_libs 'all'
168             #
169             # Where each item in @list is the name of a directory beneath 'Hub'.
170             # ------------------------------------------------------------------------------
171              
172             sub _load_internal_libs {
173              
174             # Find all perl modules under the Hub library directory
175 4     4   43 my ($libdir) = $INC{'Hub.pm'} =~ /(.*)\.pm$/;
176 4         12 my @libq = ();
177             #warn ">>\n";
178 4         13 for (@_) {
179 10 100       37 if ($_ eq 'all') {
180             # All directories which we have yet to process
181 1         14 my $h = IO::Handle->new();
182 1 50       80 opendir $h, $libdir or die "$!: $libdir";
183 1 100       17 my @all = grep { !/^(\.+|\.svn|auto|CVS)$/
  13         356  
184             && -d "$libdir/$_" } readdir $h;
185 1         15 closedir $h;
186 1         7 foreach my $dir (@all) {
187             #warn " Should we load $dir?\n";
188 11 100       44 if (!grep {$_ eq $dir} keys %TAG_MAP) {
  100         165  
189             #warn " -yes ($libdir/$dir)\n";
190 3         11 $TAG_MAP{$dir} = [];
191 3         17 push @libq, map { _tagname($_), $_ }
  8         19  
192             _findmodules( "$libdir/$dir", "Hub::$dir" );
193             }
194             }
195             # } elsif ($_ eq 'reload') {
196             # @libq = map { _tagname($_), $_ } _findmodules( $libdir, "Hub" );
197             } else {
198 9         23 my $dir = ucfirst;
199 9   100     35 $TAG_MAP{$dir} ||= [];
200 9         73 push @libq, map { _tagname($_), $_ }
  39         79  
201             _findmodules( "$libdir/$dir", "Hub::$dir" );
202             }
203             }
204             #warn "<<\n";
205              
206             # Load (require) all packages and parse their exported methods.
207 4         11 my @package_names = ();
208 1     1   1088 no strict 'refs';
  1         3  
  1         2260  
209 4         17 while( @libq ) {
210 42         94 my ($tag_names,$pkgname) = (shift @libq, shift @libq);
211 42         81 push @package_names, $pkgname;
212             #warn "$pkgname\n";
213 42         53 my $pkgpath = $pkgname;
214 42         177 $pkgpath =~ s/::/\//g;
215 42         62 $pkgpath .= '.pm';
216 42 100       120 if( $INC{$pkgpath} ) {
217             # commented out to suppress subroutine redefined warnings (added Config dir)
218             # do $pkgpath;
219             } else {
220 40         26532 require $pkgpath;
221             }
222 41         106 my $names = \@{"${pkgname}::EXPORT_OK"};
  41         174  
223 41         102 foreach my $name ( @$names ) {
224 124 100 66     23245 if( $METHODMAP{$name} || grep /^$name$/, @EXPORT_OK ) {
225 2 50       11 next if $pkgname eq $METHODMAP{$name};
226 0         0 warn 'Duplicate name on import: '
227             . "$name defined in '$pkgname' and '$METHODMAP{$name}'";
228 0         0 next;
229             }#if
230             #warn " set: $name\n";
231 122         274 $METHODMAP{$name} = $pkgname;
232 122         209 foreach my $tag_name ( @$tag_names ) {
233 237         297 push @EXPORT_OK, $name;
234             #warn " $tag_name/$name\n";
235 237         244 push @{$EXPORT_TAGS{$tag_name}}, $name;
  237         601  
236             }#for
237             # All exported names in capital characters and underscore
238             # are constants by convention
239 122 100       404 if ($name =~ /^[A-Z_]+$/) {
240 13         18 push @{$EXPORT_TAGS{'const'}}, $name;
  13         28  
241 13         16 push @{$EXPORT_TAGS{'lib'}}, $name;
  13         69  
242             }
243             }
244 41         52 my $import = \&{"${pkgname}::import"};
  41         140  
245 41 100 66     296 &$import( $pkgname, @$names ) if @$names && ref($import) eq 'CODE';
246             }
247              
248             # Find the packages which are classes. This is done outside of the above
249             # loop so that base classes have had a chance to load.
250 3         9 foreach my $pkgname (@package_names) {
251 39 100 66     621 if (UNIVERSAL::can($pkgname, 'new')) {
    100 66        
252 18         57 my ($aka) = $pkgname =~ /.*:(\w+)/;
253 18 50       40 if( $OBJECTMAP{$aka} ) {
254 0         0 die 'Duplicate object package on import: '
255             . "$aka represents '$pkgname' and '$OBJECTMAP{$aka}'";
256             }
257 18         52 $OBJECTMAP{$aka} = $pkgname;
258             } elsif (UNIVERSAL::can($pkgname, 'TIEHASH')
259             || UNIVERSAL::can($pkgname, 'TIEARRAY')
260             || UNIVERSAL::can($pkgname, 'TIESCALAR')) {
261 5         18 my ($aka) = $pkgname =~ /.*:(\w+)/;
262 5 50       13 if ($KNOTMAP{$aka}) {
263 0         0 die 'Duplicate tie package on import: '
264             . "$aka represents '$pkgname' and '$KNOTMAP{$aka}'";
265             }
266 5         11 $KNOTMAP{$aka} = $pkgname;
267             }
268             }
269              
270             }#_load_internal_libs
271              
272             # ------------------------------------------------------------------------------
273             # _findmodules - Recursively get module names
274             # _findmodules $directory, $package_name
275             #
276             # Searches in the sub-directory of this top-level-module for all library files
277             # to represent. $package_name is the package (directory) name which
278             # corresponds to the given $directory.
279             #
280             # Recursive.
281             # ------------------------------------------------------------------------------
282              
283             sub _findmodules {
284              
285             # List directory
286 12     12   25 my ($dir,$pkg) = @_;
287 12         19 my @libs = ();
288 12         88 my $fh = IO::Handle->new();
289 12 50       789 opendir $fh, $dir or die "$!: $dir";
290 12         361 my @all = grep ! /^(\.+|\.svn|auto|CVS)$/, readdir $fh;
291 12         175 closedir $fh;
292              
293             # Extract package names and paths, and exusively process sub-directories
294 12         33 foreach my $name ( @all ) {
295 94 50       2638 if( -d "$dir/$name" ) {
296             #warn " -gather $dir/$name\n";
297 0         0 push @libs, map { $pkg . '::' . $_ }
  0         0  
298             _findmodules( "$dir/$name", $name );
299             } else {
300 94 100       489 $name =~ s/\.pm$// and push @libs, $pkg . '::' . $name;
301             }
302             }
303            
304 12         89 return @libs;
305              
306             }#_findmodules
307              
308             # ------------------------------------------------------------------------------
309             # _tagname - Return which EXPORT_TAGS key to which a module should belong.
310             # _tagname $module_name
311             # ------------------------------------------------------------------------------
312              
313             sub _tagname {
314 47     47   214 my ($dir) = $_[0] =~ /[0-9A-Za-z]+::([0-9A-Za-z]+)::.*/;
315 47 50       118 my @tags = defined $TAG_MAP{$dir} ? @{$TAG_MAP{$dir}} : ();
  47         113  
316             #warn "tags for: $dir: " . join(";",@tags), "\n";
317 47         238 return [ lc($dir), @tags ];
318             }#_tagname
319              
320             # ------------------------------------------------------------------------------
321             # mkinst - Create an instance (object) by its short name.
322             # mkinst $short_name
323             #
324             # See also L.
325             # ------------------------------------------------------------------------------
326             #|test(true) ref(mkinst('Object')) eq 'Hub::Base::Object';
327             #|test(abort) mkinst('DoesNotExist');
328             # ------------------------------------------------------------------------------
329              
330             sub mkinst {
331 24     24 1 47 my $aka = shift;
332 24 50       77 croak "Module not loaded: $aka" unless $OBJECTMAP{$aka};
333 24         28 local $_;
334 24         238 return $OBJECTMAP{$aka}->new( @_ );
335             }#mkinst
336              
337             # ------------------------------------------------------------------------------
338             # knot - Return the implementing package (full name) for the given knot
339             # knot $short_name
340             #
341             # See also L.
342             # ------------------------------------------------------------------------------
343              
344             sub knot {
345 0 0   0 1 0 croak "Module not loaded: $_[0]" unless $KNOTMAP{$_[0]};
346 0         0 return $KNOTMAP{$_[0]};
347             }#knot
348              
349             #-------------------------------------------------------------------------------
350             # callback - Invocation method for persistent applications
351             # callback \&subroutine
352             #
353             # Intended usage:
354             #
355             # #!/usr/bin/perl -w
356             # use strict;
357             # use Hub qw(:standard);
358             # while( my $req = ??? ) {
359             # callback( &main, $req );
360             # }
361             # sub main {
362             # my $req = shift;
363             # # your code here
364             # }
365             #
366             # The callback method wraps your code with the necessary initialization and
367             # destruction code required to isolate this instance (run) from others.
368             #-------------------------------------------------------------------------------
369              
370             sub callback {
371 0     0 1 0 my $instance_key = Hub::bestof($ENV{'WORKING_DIR'}, Hub::getpath($0));
372 0         0 $instance_key .= '/' . Hub::getname($0);
373 0         0 $Hub = getns($instance_key);
374 0 0       0 unless (defined $Hub) {
375 0         0 $Hub = mkinst('Registry');
376 0         0 regns($instance_key, $Hub);
377             }
378 0         0 my $ret = $Hub->run( @_ );
379 0         0 return $ret;
380             }#callback
381              
382             # ------------------------------------------------------------------------------
383             # regns - Register namespace.
384             # regns $name, [\%value]
385             #
386             # I
387             # ------------------------------------------------------------------------------
388              
389             sub regns {
390 2 50   2 1 9 my $ns = shift or return;
391 2   50     16 my $val = shift || {};
392 2   33     12 $REGISTRY->{$ns} ||= $val;
393 2         9 return $REGISTRY->{$ns};
394             }#regns
395              
396             # ------------------------------------------------------------------------------
397             # getns - Get namespace
398             # getns $name, [$address]
399             #
400             # I
401             # ------------------------------------------------------------------------------
402              
403             sub getns {
404 0 0   0 1   my $ns = shift or return;
405 0 0         return hgetv($REGISTRY->{$ns}, @_) if @_;
406 0           return $REGISTRY->{$ns};
407             }#getns
408              
409             # ------------------------------------------------------------------------------
410             # trace - Warn with a stack trace
411             # trace @messages
412             # ------------------------------------------------------------------------------
413              
414             sub trace {
415 0     0 1   warn @_;
416 0           for my $i (0 .. 8) {
417 0           my @caller = caller($i);
418 0 0         last unless @caller;
419 0 0         last if $caller[2] == 0;
420 0           print STDERR "[stack-$i] $caller[0] line $caller[2]\n";
421             }
422             }#trace
423              
424             # ------------------------------------------------------------------------------
425             # about - Return an about message regarding this library
426             # about
427             # ------------------------------------------------------------------------------
428              
429             sub about {
430 0     0 1   return <<_end_print;
431             Hub Library Version $VERSION
432              
433             Redistribution and use in source and binary forms, with or without
434             modification, are permitted provided that the following conditions are met:
435              
436             * Redistributions of source code must retain the above copyright notice,
437             this list of conditions and the following disclaimer.
438              
439             * The origin of this software must not be misrepresented; you must not
440             claim that you wrote the original software. If you use this software in a
441             product, an acknowledgment in the product documentation would be
442             appreciated but is not required.
443              
444             * Altered source versions must be plainly marked as such, and must not be
445             misrepresented as being the original software.
446              
447             * The name of the author may not be used to endorse or promote products
448             derived from this software without specific prior written permission.
449              
450             Copyright (C) 2006-2007 by Livesite Networks, LLC. All rights reserved.
451              
452             Copyright (C) 2000-2005 by Ryan Gies. All rights reserved.
453              
454             _end_print
455             }#about
456              
457             # ------------------------------------------------------------------------------
458             # version - Return the library version number
459             # version
460             # ------------------------------------------------------------------------------
461              
462 0     0 1   sub version { return $VERSION; }#version
463              
464             # ------------------------------------------------------------------------------
465             # END - Finish library wheel.
466             # ------------------------------------------------------------------------------
467              
468             sub END {
469 1 50   1   294 if( Hub::check( '-test=blessed', $Hub ) ) {
470 1         9 $Hub->finish();
471             }#if
472             }#END
473              
474             # ------------------------------------------------------------------------------
475             1;
476              
477             __END__