File Coverage

blib/lib/Acme/MetaSyntactic.pm
Criterion Covered Total %
statement 117 119 98.3
branch 29 30 96.6
condition 2 2 100.0
subroutine 21 22 95.4
pod 7 7 100.0
total 176 180 97.7


line stmt bran cond sub pod time code
1             package Acme::MetaSyntactic;
2             $Acme::MetaSyntactic::VERSION = '1.013';
3 23     23   153569 use strict;
  23         39  
  23         652  
4 23     23   92 use warnings;
  23         30  
  23         750  
5 22     22   82 use Carp;
  22         23  
  22         1551  
6 22     22   101 use File::Basename;
  22         30  
  22         1549  
7 22     22   90 use File::Spec;
  22         34  
  22         540  
8 22     22   84 use File::Glob;
  22         29  
  22         7869  
9              
10             # some class data
11             our $Theme = 'foo'; # default theme
12             our %META;
13              
14             # private class method
15             sub _find_themes {
16 23     23   110 my ( $class, @dirs ) = @_;
17             return
18             map @$_,
19 496         1451 grep { $_->[0] !~ /^[A-Z]/ } # remove the non-theme subclasses
20 496         8201 map { [ ( fileparse( $_, qr/\.pm$/ ) )[0] => $_ ] }
21 23         50 map { File::Glob::bsd_glob( File::Spec->catfile( $_, qw( Acme MetaSyntactic *.pm ) ) ) } @dirs;
  233         13545  
22             }
23              
24             # fetch the list of standard themes
25             $META{$_} = 0 for keys %{ { __PACKAGE__->_find_themes(@INC) } };
26              
27             # the functions actually hide an instance
28             my $meta = Acme::MetaSyntactic->new( $Theme );
29              
30             # END OF INITIALISATION
31              
32             # support for use Acme::MetaSyntactic 'foo'
33             # that automatically loads the required classes
34             sub import {
35 13     13   62 my $class = shift;
36              
37 3         17 my @themes = ( grep { $_ eq ':all' } @_ )
38 13 50       45 ? ( 'foo', grep { !/^(?:foo|:all)$/ } keys %META ) # 'foo' is still first
  0         0  
39             : @_;
40              
41 13 100       50 $Theme = $themes[0] if @themes;
42 13         28 $meta = Acme::MetaSyntactic->new( $Theme );
43              
44             # export the metaname() function
45 22     22   113 no strict 'refs';
  22         37  
  22         8392  
46 13         113 my $callpkg = caller;
47 13         19 *{"$callpkg\::metaname"} = \&metaname; # standard theme
  13         65  
48              
49             # load the classes in @themes
50 13         4335 for my $theme( @themes ) {
51 3         238 eval "require Acme::MetaSyntactic::$theme; import Acme::MetaSyntactic::$theme;";
52 3 100       157 croak $@ if $@;
53 2     0   8 *{"$callpkg\::meta$theme"} = sub { $meta->name( $theme, @_ ) };
  2         1341  
  0         0  
54             }
55             }
56              
57             sub new {
58 55     55 1 8616 my ( $class, @args ) = ( @_ );
59 55         61 my $theme;
60 55 100       199 $theme = shift @args if @args % 2;
61 55 100       139 $theme = $Theme unless $theme; # same default everywhere
62              
63             # defer croaking until name() is actually called
64 55         270 bless { theme => $theme, args => { @args }, meta => {} }, $class;
65             }
66              
67             # CLASS METHODS
68             sub add_theme {
69 6     6 1 1655 my $class = shift;
70 6         21 my %themes = @_;
71              
72 6         22 for my $theme ( keys %themes ) {
73 6 100       181 croak "The theme $theme already exists!" if exists $META{$theme};
74 5         9 my @badnames = grep { !/^[a-z_]\w*$/i } @{$themes{$theme}};
  15         89  
  5         17  
75 5 100       115 croak "Invalid names (@badnames) for theme $theme"
76             if @badnames;
77              
78 4         17 my $code = << "EOC";
79             package Acme::MetaSyntactic::$theme;
80             use strict;
81             use Acme::MetaSyntactic::List;
82             our \@ISA = qw( Acme::MetaSyntactic::List );
83 4         17 our \@List = qw( @{$themes{$theme}} );
84             1;
85             EOC
86 4     3   442 eval $code;
  3     3   23  
  3         4  
  3         88  
  3         1201  
  3         8  
  3         166  
87 4         15 $META{$theme} = 1; # loaded
88              
89             # export the metatheme() function
90 22     22   112 no strict 'refs';
  22         28  
  22         2708  
91 4         10 my $callpkg = caller;
92 4     3   20 *{"$callpkg\::meta$theme"} = sub { $meta->name( $theme, @_ ) };
  4         42  
  3         451  
93             }
94             }
95              
96             # load the content of __DATA__ into a structure
97             # this class method is used by the other Acme::MetaSyntactic classes
98             sub load_data {
99 20     20 1 220 my ($class, $theme ) = @_;
100 20         45 my $data = {};
101              
102 20         32 my $fh;
103 22     22   92 { no strict 'refs'; $fh = *{"$theme\::DATA"}{IO}; }
  22         26  
  22         13473  
  20         32  
  20         24  
  20         118  
104              
105 20         45 my $item;
106             my @items;
107 20         44 $$item = "";
108              
109             {
110 20         23 local $_;
  20         33  
111 20         319 while (<$fh>) {
112 149 100       522 /^#\s*(\w+.*)$/ && do {
113 62         109 push @items, $item;
114 62         65 $item = $data;
115 62         59 my $last;
116 62         365 my @keys = split m!\s+|\s*/\s*!, $1;
117 62   100     498 $last = $item, $item = $item->{$_} ||= {} for @keys;
118 62         129 $item = \( $last->{ $keys[-1] } = "" );
119 62         300 next;
120             };
121 87         118 s/#.*//; # remove end-of-line comments
122 87         433 $$item .= $_;
123             }
124             }
125              
126             # avoid leaving all the DATA handles open
127 20         180 close $fh;
128              
129             # clean up the items
130 20         73 for( @items, $item ) {
131 82         235 $$_ =~ s/\A\s*//;
132 82         485 $$_ =~ s/\s*\z//;
133 82         549 $$_ =~ s/\s+/ /g;
134             }
135 20         116 return $data;
136             }
137              
138             # main function
139 2     2 1 904 sub metaname { $meta->name( @_ ) };
140              
141             # corresponding method
142             sub name {
143 26     26 1 4067 my $self = shift;
144 26         30 my ( $theme, $count );
145              
146 26 100       357 if (@_) {
147 19         36 ( $theme, $count ) = @_;
148 19 100       117 ( $theme, $count ) = ( $self->{theme}, $theme )
149             if $theme =~ /^(?:0|[1-9]\d*)$/;
150             }
151             else {
152 7         35 ( $theme, $count ) = ( $self->{theme}, 1 );
153             }
154              
155 26 100       83 if( ! exists $self->{meta}{$theme} ) {
156 22         91 my ( $Theme, $category ) = split /\//, $theme, 2;
157 22 100       112 if( ! $META{$Theme} ) {
158 11         966 eval "require Acme::MetaSyntactic::$Theme;";
159 11 100       522 croak "Metasyntactic list $Theme does not exist!" if $@;
160 8         26 $META{$theme} = 1; # loaded
161             }
162             $self->{meta}{$theme}
163 19         45 = "Acme::MetaSyntactic::$Theme"->new( %{ $self->{args} },
  19         143  
164             ( category => $category )x!! $category );
165             }
166              
167 23         117 $self->{meta}{$theme}->name( $count );
168             }
169              
170             # other methods
171 9 100   9 1 774 sub themes { wantarray ? ( sort keys %META ) : scalar keys %META }
172 4 100   4 1 31 sub has_theme { $_[1] ? exists $META{$_[1]} : 0 }
173              
174             1;
175              
176             __END__