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.015';
3 23     23   731094 use strict;
  23         148  
  23         763  
4 23     23   126 use warnings;
  23         46  
  23         727  
5 22     22   152 use Carp;
  22         43  
  22         1555  
6 22     22   159 use File::Basename;
  22         43  
  22         2499  
7 22     22   150 use File::Spec;
  22         59  
  22         709  
8 22     22   121 use File::Glob;
  22         45  
  22         10311  
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   143 my ( $class, @dirs ) = @_;
17             return
18             map @$_,
19 496         1716 grep { $_->[0] !~ /^[A-Z]/ } # remove the non-theme subclasses
20 496         9555 map { [ ( fileparse( $_, qr/\.pm$/ ) )[0] => $_ ] }
21 23         77 map { File::Glob::bsd_glob( File::Spec->catfile( $_, qw( Acme MetaSyntactic *.pm ) ) ) } @dirs;
  233         14986  
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   109 my $class = shift;
36              
37 3         17 my @themes = ( grep { $_ eq ':all' } @_ )
38 13 50       55 ? ( 'foo', grep { !/^(?:foo|:all)$/ } keys %META ) # 'foo' is still first
  0         0  
39             : @_;
40              
41 13 100       45 $Theme = $themes[0] if @themes;
42 13         63 $meta = Acme::MetaSyntactic->new( $Theme );
43              
44             # export the metaname() function
45 22     22   174 no strict 'refs';
  22         60  
  22         9919  
46 13         45 my $callpkg = caller;
47 13         33 *{"$callpkg\::metaname"} = \&metaname; # standard theme
  13         83  
48              
49             # load the classes in @themes
50 13         6106 for my $theme( @themes ) {
51 3         245 eval "require Acme::MetaSyntactic::$theme; import Acme::MetaSyntactic::$theme;";
52 3 100       224 croak $@ if $@;
53 2     0   8 *{"$callpkg\::meta$theme"} = sub { $meta->name( $theme, @_ ) };
  2         2061  
  0         0  
54             }
55             }
56              
57             sub new {
58 55     55 1 10625 my ( $class, @args ) = ( @_ );
59 55         106 my $theme;
60 55 100       306 $theme = shift @args if @args % 2;
61 55 100       215 $theme = $Theme unless $theme; # same default everywhere
62              
63             # defer croaking until name() is actually called
64 55         433 bless { theme => $theme, args => { @args }, meta => {} }, $class;
65             }
66              
67             # CLASS METHODS
68             sub add_theme {
69 6     6 1 2071 my $class = shift;
70 6         22 my %themes = @_;
71              
72 6         23 for my $theme ( keys %themes ) {
73 6 100       228 croak "The theme $theme already exists!" if exists $META{$theme};
74 5         10 my @badnames = grep { !/^[a-z_]\w*$/i } @{$themes{$theme}};
  15         59  
  5         17  
75 5 100       113 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         21 our \@List = qw( @{$themes{$theme}} );
84             1;
85             EOC
86 4     3   340 eval $code;
  3     3   25  
  3         7  
  3         97  
  3         1062  
  3         10  
  3         161  
87 4         18 $META{$theme} = 1; # loaded
88              
89             # export the metatheme() function
90 22     22   176 no strict 'refs';
  22         53  
  22         3321  
91 4         14 my $callpkg = caller;
92 4     3   23 *{"$callpkg\::meta$theme"} = sub { $meta->name( $theme, @_ ) };
  4         41  
  3         586  
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 1095 my ($class, $theme ) = @_;
100 20         70 my $data = {};
101              
102 20         65 my $fh;
103 22     22   174 { no strict 'refs'; $fh = *{"$theme\::DATA"}{IO}; }
  22         68  
  22         15309  
  20         57  
  20         49  
  20         125  
104              
105 20         63 my $item;
106             my @items;
107 20         100 $$item = "";
108              
109             {
110 20         45 local $_;
  20         50  
111 20         426 while (<$fh>) {
112 149 100       633 /^#\s*(\w+.*)$/ && do {
113 62         151 push @items, $item;
114 62         105 $item = $data;
115 62         95 my $last;
116 62         438 my @keys = split m!\s+|\s*/\s*!, $1;
117 62   100     511 $last = $item, $item = $item->{$_} ||= {} for @keys;
118 62         223 $item = \( $last->{ $keys[-1] } = "" );
119 62         326 next;
120             };
121 87         190 s/#.*//; # remove end-of-line comments
122 87         545 $$item .= $_;
123             }
124             }
125              
126             # avoid leaving all the DATA handles open
127 20         262 close $fh;
128              
129             # clean up the items
130 20         113 for( @items, $item ) {
131 82         321 $$_ =~ s/\A\s*//;
132 82         500 $$_ =~ s/\s*\z//;
133 82         393 $$_ =~ s/\s+/ /g;
134             }
135 20         135 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 4164 my $self = shift;
144 26         55 my ( $theme, $count );
145              
146 26 100       77 if (@_) {
147 19         48 ( $theme, $count ) = @_;
148 19 100       131 ( $theme, $count ) = ( $self->{theme}, $theme )
149             if $theme =~ /^(?:0|[1-9]\d*)$/;
150             }
151             else {
152 7         37 ( $theme, $count ) = ( $self->{theme}, 1 );
153             }
154              
155 26 100       95 if( ! exists $self->{meta}{$theme} ) {
156 22         93 my ( $Theme, $category ) = split /\//, $theme, 2;
157 22 100       89 if( ! $META{$Theme} ) {
158 12         821 eval "require Acme::MetaSyntactic::$Theme;";
159 12 100       611 croak "Metasyntactic list $Theme does not exist!" if $@;
160 9         32 $META{$theme} = 1; # loaded
161             }
162             $self->{meta}{$theme}
163 19         97 = "Acme::MetaSyntactic::$Theme"->new( %{ $self->{args} },
  19         146  
164             ( category => $category )x!! $category );
165             }
166              
167 23         165 $self->{meta}{$theme}->name( $count );
168             }
169              
170             # other methods
171 9 100   9 1 685 sub themes { wantarray ? ( sort keys %META ) : scalar keys %META }
172 4 100   4 1 39 sub has_theme { $_[1] ? exists $META{$_[1]} : 0 }
173              
174             1;
175              
176             __END__