File Coverage

blib/lib/App/TemplateCMD.pm
Criterion Covered Total %
statement 45 192 23.4
branch 0 70 0.0
condition 0 16 0.0
subroutine 15 26 57.6
pod 9 9 100.0
total 69 313 22.0


line stmt bran cond sub pod time code
1             package App::TemplateCMD;
2              
3             # Created on: 2008-03-26 13:47:07
4             # Create by: ivanw
5             # $Id$
6             # $Revision$, $HeadURL$, $Date$
7             # $Revision$, $Source$, $Date$
8              
9 2     2   57526 use strict;
  2         4  
  2         48  
10 2     2   8 use warnings;
  2         3  
  2         41  
11 2     2   709 use version;
  2         3076  
  2         9  
12 2     2   126 use Carp;
  2         3  
  2         103  
13 2     2   982 use Data::Dumper qw/Dumper/;
  2         11267  
  2         121  
14 2     2   824 use English qw/ -no_match_vars /;
  2         4387  
  2         13  
15 2     2   690 use File::Find;
  2         4  
  2         96  
16 2     2   1177 use Getopt::Long;
  2         19020  
  2         12  
17 2     2   1070 use YAML qw/Dump LoadFile/;
  2         11093  
  2         99  
18 2     2   980 use Readonly;
  2         6639  
  2         91  
19 2     2   940 use Template;
  2         31405  
  2         54  
20 2     2   14 use Template::Provider;
  2         4  
  2         36  
21 2     2   903 use File::ShareDir qw/dist_dir/;
  2         43401  
  2         101  
22 2     2   1052 use JSON qw/decode_json/;
  2         15175  
  2         12  
23 2     2   287 use base qw/Exporter/;
  2         4  
  2         3401  
24              
25             our $VERSION = version->new('0.6.12');
26             our @EXPORT_OK = qw//;
27             our %EXPORT_TAGS = ();
28              
29             # Set the default name for the configuration file
30             # Note when this appears in the home dir a dot '.' is prepended
31             Readonly my $CONFIG_NAME => 'template-cmd.yml';
32              
33             sub new {
34 0     0 1   my $caller = shift;
35 0 0         my $class = ref $caller ? ref $caller : $caller;
36 0           my %param = @_;
37 0           my $self = \%param;
38              
39 0           bless $self, $class;
40              
41             # Find the available commands
42 0           $self->{cmds} = { map {lc $_ => $_} $self->get_modules('App::TemplateCMD::Command') };
  0            
43              
44             # read the configuration files
45 0           $self->config();
46              
47 0           return $self;
48             }
49              
50             sub get_modules {
51 0     0 1   my ($self, $base) = @_;
52 0           $base =~ s{::}{/}gxms;
53              
54 0           my %modules;
55              
56 0           for my $dir (grep {-d $_} map { "$_/$base/" } @INC) {
  0            
  0            
57             find(
58             sub {
59 0     0     my ($name) = $File::Find::name =~ m{^ $dir ( [\w/]+ ) .pm $}xms;
60 0 0         return if !$name;
61              
62 0           $modules{$name}++;
63             },
64 0           $dir
65             );
66             }
67              
68 0           return keys %modules;
69             }
70              
71             sub process {
72              
73 0     0 1   my ($self, @argv) = @_;
74              
75 0           my $cmd = shift @argv;
76              
77 0 0 0       if ( !$cmd || !grep { $_ eq $cmd } keys %{$self->{'cmds'}} ) {
  0            
  0            
78 0 0         if ($cmd) {
79 0           $self->unknown_cmd($cmd);
80             }
81              
82 0           unshift @argv, $cmd;
83 0           $cmd = 'help';
84             }
85              
86 0           my $module = $self->load_cmd($cmd);
87 0           my %default = $module->default($self);
88 0           my @args = (
89             'out|o=s',
90             'args|a=s%',
91             'verbose|v!',
92             'path|p=s',
93             $module->args($self),
94             );
95              
96             {
97 0           local @ARGV = @argv;
  0            
98 0           Getopt::Long::Configure('bundling');
99 0 0         GetOptions( \%default, @args ) or $module = 'App::TemplateCMD::Command::Help';
100 0           $default{files} = [ @ARGV ];
101             }
102              
103 0           my $conf = $self->add_args(\%default);
104 0           my $out;
105              
106 0           my $path = $conf->{path};
107 0 0         if ( $default{path} ) {
108 0           $path = "$default{path}:$path";
109             }
110 0           $path =~ s(~/)($ENV{HOME}/)gxms;
111              
112             $self->{providers} = [
113 0           Template::Provider->new({ INCLUDE_PATH => $path }),
114             ];
115              
116             $self->{template} = Template->new({
117             LOAD_TEMPLATES => $self->{providers},
118 0           EVAL_PERL => 1,
119             });
120              
121 0 0         if ( $default{'out'} ) {
122 0 0         open $out, '>', $default{out} or die "Could not open the output file '$default{out}': $OS_ERROR\n";
123             }
124             else {
125 0           $out = *STDOUT;
126             }
127              
128 0           print {$out} $module->process($self, %default);
  0            
129              
130 0           return;
131             }
132              
133             sub add_args {
134 0     0 1   my ($self, $default) = @_;
135 0           my @files;
136              
137 0   0       my $args = $default->{args} || {};
138 0   0       my $files = $default->{files} || [];
139              
140             # add any args not prefixed by -a[rgs]
141 0           for my $file (@{$files}) {
  0            
142 0 0         if ($file =~ /=/ ) {
143              
144             # merge the argument on to the args hash
145 0           my ($arg, $value) = split /=/, $file, 2;
146 0   0       $default->{args}->{$arg} = eval { decode_json($value) } || $value;
147             }
148             else {
149              
150             # store the "real" file
151 0           push @files, $file;
152             }
153             }
154              
155 0           for my $value (values %{$args}) {
  0            
156 0 0         $value = $value =~ /^( q[wqr]?(\W) ) .* ( \2 )$/xms? [ eval($value) ] ## no critic
    0          
    0          
    0          
157             : $value =~ /^( \{ ) .* ( \} )$/xms? eval($value) ## no critic
158             : $value =~ /^( \[ ) .* ( \] )$/xms? eval($value) ## no critic
159             : $value =~ /^( , )(.*) $/xms? [ split /,/xms, $2 ]
160             : $value;
161             }
162              
163             # replace the files with the list with out args
164 0           $default->{files} = \@files;
165              
166             # merge the args with the config and save
167 0           return $self->{config} = $self->conf_join($self->config(), $args);
168             }
169              
170             sub config {
171              
172 0     0 1   my ($self, %option) = @_;
173              
174 0 0         return $self->{'config'} if $self->{'config'};
175              
176             my $conf = {
177             path => '~/template-cmd:~/.template-cmd/:~/.template-cmd-local:/usr/local/template-cmd/src/:' . dist_dir('App-TemplateCMD'),
178             aliases => {
179             ls => 'list',
180             des => 'describe',
181             },
182             contact => {
183             fullname => $ENV{USER},
184             name => $ENV{USER},
185 0 0         email => "$ENV{USER}@" . ($ENV{HOST} ? $ENV{HOST} : 'localhost'),
186             address => '123 Timbuc Too',
187             },
188             company => {
189             name => '',
190             address => '',
191             },
192             };
193              
194 0           $self->{configs} = [];
195 0           $self->{config_default} = "$ENV{HOME}/.$CONFIG_NAME";
196              
197 0 0         if ( -f "/etc/$CONFIG_NAME" ) {
198 0           my $second = LoadFile("/etc/$CONFIG_NAME");
199 0           $conf = $self->conf_join($conf, $second);
200 0           push @{$self->{configs}}, "/etc/$CONFIG_NAME";
  0            
201             }
202 0 0 0       if ( $option{'conf'} && -f $option{'conf'} ) {
    0          
203 0           my $second = LoadFile($option{'conf'});
204 0           $conf = $self->conf_join($conf, $second);
205 0           push @{$self->{configs}}, $option{'conf'};
  0            
206             }
207             elsif ( -f "$ENV{HOME}/.$CONFIG_NAME" ) {
208 0           my $second = LoadFile("$ENV{HOME}/.$CONFIG_NAME");
209 0           $conf = $self->conf_join($conf, $second);
210 0           push @{$self->{configs}}, "$ENV{HOME}/.$CONFIG_NAME";
  0            
211             }
212 0           $conf = $self->conf_join($conf, \%option);
213              
214             # set up some internal config options
215 0 0         if ($ENV{'TEMPLATE_CMD_PATH'}) {
216 0           $conf->{'path'} .= $ENV{'TEMPLATE_CMD_PATH'};
217             }
218              
219             # set up the aliases
220 0 0         if ($conf->{'aliases'}) {
221 0           for my $alias (keys %{ $conf->{aliases} }) {
  0            
222 0           $self->{'cmds'}{$alias} = ucfirst $conf->{aliases}{$alias};
223             }
224             }
225              
226             # set up temporial variables (Note that these are always the values to
227             # use and over ride what ever is set in the configuration files)
228 0           my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime;
229 0           $mon++;
230 0           $year += 1900;
231              
232 0 0         $conf->{date} = "$year-" . ( $mon < 10 ? '0' : '' ) . "$mon-" . ( $mday < 10 ? '0' : '' ) . $mday;
    0          
233 0 0         $conf->{time} = ( $hour < 10 ? '0' : '' ) . "$hour:" . ( $min < 10 ? '0' : '' ) . "$min:" . ( $sec < 10 ? '0' : '' ) . $sec;
    0          
    0          
234 0           $conf->{year} = $year;
235 0           $conf->{user} = $ENV{USER};
236 0           $conf->{path} =~ s/~/$ENV{HOME}/gxms;
237 0           $conf->{path} =~ s{/:}{:}gxms;
238              
239             # return and cache the configuration item
240 0           return $self->{'config'} = $conf;
241             }
242              
243             sub conf_join {
244              
245 0     0 1   my ($self, $conf1, $conf2, $t) = @_;
246 0           my %conf = %{$conf1};
  0            
247 0 0         warn '-'x10, Dumper $conf1, $conf2 if $t;
248              
249 0           for my $key ( keys %{$conf2} ) {
  0            
250 0 0 0       if ( ref $conf2->{$key} eq 'HASH' && ref $conf{$key} eq 'HASH' ) {
251 0 0         warn 'merging' if $t;
252 0           $conf{$key} = $self->conf_join($conf{$key}, $conf2->{$key});
253             }
254             else {
255 0 0         warn "replacing: $key" if $t;
256 0           $conf{$key} = $conf2->{$key};
257             }
258             }
259              
260 0           return \%conf;
261             }
262              
263             sub load_cmd {
264              
265 0     0 1   my ($self, $cmd) = @_;
266              
267 0 0         if (!$cmd) {
268 0           carp 'No command passed!';
269 0           return;
270             }
271              
272             # check if we have already loaded the command module
273 0 0         if ( $self->{'loaded'}{$cmd} ) {
274 0           return $self->{'loaded'}{$cmd};
275             }
276              
277 0 0         if (!$self->{cmds}{$cmd}) {
278 0           $self->unknown_cmd($cmd);
279             }
280              
281             # construct the command module's file name and require that
282 0           my $file = "App/TemplateCMD/Command/$self->{cmds}{$cmd}.pm";
283 0           my $module = "App::TemplateCMD::Command::$self->{cmds}{$cmd}";
284 0           eval { require $file };
  0            
285              
286             # check if there were any errors
287 0 0         if ($EVAL_ERROR) {
288 0           die "Could not load the command $cmd: $EVAL_ERROR\n$file\n$module\n";
289             }
290              
291             # return success
292 0           return $self->{'loaded'}{$cmd} = $module;
293             }
294              
295             sub list_templates {
296 0     0 1   my ($self) = @_;
297              
298 0           my $path = $self->config->{path};
299 0           my @path = grep {-d $_} split /:/, $path;
  0            
300              
301 0           my @files;
302              
303 0           for my $dir (@path) {
304 0 0         next if !-d $dir;
305 0           $dir =~ s{/$}{}xms;
306              
307             find(
308             sub {
309 0 0   0     return if -d $_;
310 0           my $file = $File::Find::name;
311 0           $file =~ s{^$dir/}{}xms;
312 0           push @files, { path => $dir, file => $file };
313             },
314 0           $dir
315             );
316             }
317              
318 0           return @files;
319             }
320              
321             sub unknown_cmd {
322              
323 0     0 1   my ($self, $cmd) = @_;
324              
325 0           my $program = $0;
326 0           $program =~ s{^.*/}{}xms;
327              
328 0           die <<"DIE";
329             There is no command named $cmd
330             For help on commands try '$program help'
331             DIE
332             }
333             1;
334              
335             __END__