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.014';
3 23     23   524563 use strict;
  23         149  
  23         576  
4 23     23   111 use warnings;
  23         41  
  23         529  
5 22     22   122 use Carp;
  22         48  
  22         1106  
6 22     22   119 use File::Basename;
  22         43  
  22         1373  
7 22     22   117 use File::Spec;
  22         39  
  22         448  
8 22     22   96 use File::Glob;
  22         35  
  22         6672  
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   118 my ( $class, @dirs ) = @_;
17             return
18             map @$_,
19 496         1429 grep { $_->[0] !~ /^[A-Z]/ } # remove the non-theme subclasses
20 496         8169 map { [ ( fileparse( $_, qr/\.pm$/ ) )[0] => $_ ] }
21 23         70 map { File::Glob::bsd_glob( File::Spec->catfile( $_, qw( Acme MetaSyntactic *.pm ) ) ) } @dirs;
  233         8850  
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   87 my $class = shift;
36              
37 3         18 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       41 $Theme = $themes[0] if @themes;
42 13         55 $meta = Acme::MetaSyntactic->new( $Theme );
43              
44             # export the metaname() function
45 22     22   142 no strict 'refs';
  22         47  
  22         7285  
46 13         38 my $callpkg = caller;
47 13         31 *{"$callpkg\::metaname"} = \&metaname; # standard theme
  13         67  
48              
49             # load the classes in @themes
50 13         4528 for my $theme( @themes ) {
51 3         259 eval "require Acme::MetaSyntactic::$theme; import Acme::MetaSyntactic::$theme;";
52 3 100       157 croak $@ if $@;
53 2     0   13 *{"$callpkg\::meta$theme"} = sub { $meta->name( $theme, @_ ) };
  2         2386  
  0         0  
54             }
55             }
56              
57             sub new {
58 55     55 1 8205 my ( $class, @args ) = ( @_ );
59 55         96 my $theme;
60 55 100       200 $theme = shift @args if @args % 2;
61 55 100       199 $theme = $Theme unless $theme; # same default everywhere
62              
63             # defer croaking until name() is actually called
64 55         369 bless { theme => $theme, args => { @args }, meta => {} }, $class;
65             }
66              
67             # CLASS METHODS
68             sub add_theme {
69 6     6 1 1526 my $class = shift;
70 6         18 my %themes = @_;
71              
72 6         20 for my $theme ( keys %themes ) {
73 6 100       197 croak "The theme $theme already exists!" if exists $META{$theme};
74 5         8 my @badnames = grep { !/^[a-z_]\w*$/i } @{$themes{$theme}};
  15         61  
  5         13  
75 5 100       93 croak "Invalid names (@badnames) for theme $theme"
76             if @badnames;
77              
78 4         15 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         18 our \@List = qw( @{$themes{$theme}} );
84             1;
85             EOC
86 4     3   267 eval $code;
  3     3   18  
  3         6  
  3         74  
  3         540  
  3         5  
  3         119  
87 4         13 $META{$theme} = 1; # loaded
88              
89             # export the metatheme() function
90 22     22   138 no strict 'refs';
  22         39  
  22         2464  
91 4         10 my $callpkg = caller;
92 4     3   17 *{"$callpkg\::meta$theme"} = sub { $meta->name( $theme, @_ ) };
  4         29  
  3         568  
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 832 my ($class, $theme ) = @_;
100 20         48 my $data = {};
101              
102 20         39 my $fh;
103 22     22   131 { no strict 'refs'; $fh = *{"$theme\::DATA"}{IO}; }
  22         50  
  22         11297  
  20         40  
  20         37  
  20         103  
104              
105 20         49 my $item;
106             my @items;
107 20         51 $$item = "";
108              
109             {
110 20         35 local $_;
  20         41  
111 20         319 while (<$fh>) {
112 149 100       534 /^#\s*(\w+.*)$/ && do {
113 62         130 push @items, $item;
114 62         93 $item = $data;
115 62         76 my $last;
116 62         362 my @keys = split m!\s+|\s*/\s*!, $1;
117 62   100     448 $last = $item, $item = $item->{$_} ||= {} for @keys;
118 62         209 $item = \( $last->{ $keys[-1] } = "" );
119 62         265 next;
120             };
121 87         153 s/#.*//; # remove end-of-line comments
122 87         381 $$item .= $_;
123             }
124             }
125              
126             # avoid leaving all the DATA handles open
127 20         137 close $fh;
128              
129             # clean up the items
130 20         68 for( @items, $item ) {
131 82         273 $$_ =~ s/\A\s*//;
132 82         453 $$_ =~ s/\s*\z//;
133 82         369 $$_ =~ s/\s+/ /g;
134             }
135 20         116 return $data;
136             }
137              
138             # main function
139 2     2 1 1763 sub metaname { $meta->name( @_ ) };
140              
141             # corresponding method
142             sub name {
143 26     26 1 2906 my $self = shift;
144 26         47 my ( $theme, $count );
145              
146 26 100       64 if (@_) {
147 19         43 ( $theme, $count ) = @_;
148 19 100       101 ( $theme, $count ) = ( $self->{theme}, $theme )
149             if $theme =~ /^(?:0|[1-9]\d*)$/;
150             }
151             else {
152 7         32 ( $theme, $count ) = ( $self->{theme}, 1 );
153             }
154              
155 26 100       79 if( ! exists $self->{meta}{$theme} ) {
156 22         76 my ( $Theme, $category ) = split /\//, $theme, 2;
157 22 100       63 if( ! $META{$Theme} ) {
158 12         674 eval "require Acme::MetaSyntactic::$Theme;";
159 12 100       455 croak "Metasyntactic list $Theme does not exist!" if $@;
160 9         22 $META{$theme} = 1; # loaded
161             }
162             $self->{meta}{$theme}
163 19         63 = "Acme::MetaSyntactic::$Theme"->new( %{ $self->{args} },
  19         114  
164             ( category => $category )x!! $category );
165             }
166              
167 23         93 $self->{meta}{$theme}->name( $count );
168             }
169              
170             # other methods
171 9 100   9 1 527 sub themes { wantarray ? ( sort keys %META ) : scalar keys %META }
172 4 100   4 1 29 sub has_theme { $_[1] ? exists $META{$_[1]} : 0 }
173              
174             1;
175              
176             __END__