File Coverage

blib/lib/Bot/BasicBot/Pluggable/Module/Crontab.pm
Criterion Covered Total %
statement 18 57 31.5
branch 0 24 0.0
condition 0 12 0.0
subroutine 6 10 60.0
pod 1 3 33.3
total 25 106 23.5


line stmt bran cond sub pod time code
1             package Bot::BasicBot::Pluggable::Module::Crontab;
2              
3 4     4   57880 use warnings;
  4         6  
  4         119  
4 4     4   15 use strict;
  4         4  
  4         155  
5              
6             our $VERSION = '0.01';
7            
8             #----------------------------------------------------------------------------
9              
10             #############################################################################
11             #Library Modules #
12             #############################################################################
13              
14 4     4   14 use base qw(Bot::BasicBot::Pluggable::Module);
  4         12  
  4         1144  
15              
16 4     4   2600 use DateTime;
  4         358822  
  4         133  
17 4     4   1332 use IO::File;
  4         20731  
  4         552  
18 4     4   1781 use Time::Crontab;
  4         8744  
  4         1698  
19              
20             #############################################################################
21             #Variables
22             #############################################################################
23              
24             my @crontab;
25             my $load_time = 0;
26              
27             #----------------------------------------------------------------------------
28              
29             #############################################################################
30             # Public Methods #
31             #############################################################################
32              
33             sub init {
34 0     0 0   my $self = shift;
35              
36 0           my $file = $self->store->get( 'crontab', 'file' );
37 0 0         unless($file) {
38 0           $file = $0;
39 0           $file =~ s/\.pl$/.cron/;
40             }
41              
42 0           $self->store->set( 'crontab', 'file', $file );
43             }
44            
45             sub help {
46 0     0 0   return "Posts messages to specific channels based on a crontab.";
47             }
48            
49             sub tick {
50 0     0 1   my $self = shift;
51              
52 0           $self->_load_cron();
53              
54 0           for my $cron (@crontab) {
55 0 0         next unless($cron->{tab}->match(time));
56             next unless(
57 0 0 0       $cron->{weekno} eq '*'
      0        
      0        
      0        
58             || ($cron->{modulus} && $cron->{result} == (DateTime->now->week_number % $cron->{modulus})
59             || ($cron->{weekno} =~ /^\d+$/ && DateTime->now->week_number == $cron->{weekno})
60             ));
61              
62 0           $self->say(
63             channel => $cron->{channel},
64             body => $cron->{message}
65             );
66             }
67              
68 0           return 60 - DateTime->now->second; # ensure we are running each minute
69             }
70              
71             #############################################################################
72             # Private Methods #
73             #############################################################################
74              
75             sub _load_cron {
76 0     0     my $self = shift;
77              
78 0 0         my $fn = $self->store->get( 'crontab', 'file' ) or return 0;
79 0 0         return 0 unless(-r $fn); # file must be readable
80              
81 0           my $mod = (stat($fn))[9];
82 0 0         return 1 if($mod <= $load_time); # don't reload if not modified
83              
84 0           @crontab = ();
85              
86 0 0         my $fh = IO::File->new($fn,'r') or die "Cannot load file [$fn]: $!\n";
87 0           while(<$fh>) {
88 0           s/\s+$//;
89 0           my $line = $_;
90 0 0         next unless($line);
91              
92 0 0         next if($line =~ /^#/); # ignore comment lines
93 0 0         next if($line =~ /^$/); # ignore blank lines
94              
95 0           my @fields = split(' ',$line,8);
96 0           my $crontab = join(' ',(@fields)[0..4]);
97 0           my $tab;
98 0           eval { $tab = Time::Crontab->new($crontab) };
  0            
99 0 0         next if($@);
100              
101 0           my ($modulus,$result);
102 0 0         ($modulus,$result) = split('/',$fields[5],2) if($fields[5] =~ m!^\d+/\d+!);
103              
104 0           push @crontab, {
105             tab => $tab,
106             weekno => $fields[5],
107             modulus => $modulus,
108             result => $result,
109             channel => $fields[6],
110             message => $fields[7]
111             };
112              
113 0           print "added $crontab $fields[5] = $fields[6] - $fields[7]\n";
114             }
115              
116 0           $fh->close;
117 0           $load_time = $mod;
118             }
119            
120            
121             1;
122            
123             __END__
124            
125             #----------------------------------------------------------------------------
126              
127             =head1 NAME
128            
129             Bot::BasicBot::Pluggable::Module::Crontab - Provides a crontab-like message service for IRC channels
130            
131             =head1 DESCRIPTION
132              
133             This module does not respond to user messages, public or private. It is purely
134             for posting messages to notminated channels as a specifed time.
135              
136             A crontab like file is used to load instruction sets, which are then acted on
137             at the designated time.
138              
139             Examples are:
140              
141             +-- minute
142             | +-- hour
143             | | +-- day of the month
144             | | | +-- month (1= January, ...)
145             | | | | +-- day of the week (0 = sunday ....)
146             | | | | | +-- week of the year (2/0 = even weeks, 2/1 = odd weeks)
147             v v v v v v
148             * * * * * * #dev Minute Check!
149             0 * * * * * #dev Hour Check!
150             */10 * * * * * #dev 10 minute Check!
151             0 9 * * 1 2/0 #dev Review every even week
152             0 9 * * 1 3/1 #dev Review every third Week
153              
154             As per normal crontabs, the first 5 fields allow for ranges, steps as well as strict values.
155             The 6th field is the week of the year field. In working with Scrum teams running 2 week sprints,
156             knowing when it was an odd week or even week, meant we knew whether we had a regular stand-up
157             or sprint planning meeting.
158              
159             By default this field can be attributed to every week using the traditional '*' symbol. However,
160             to determine when to run, a two part value is used, separated by a '/' symbol. The first part
161             designates the modulus value, and the second part the result it must match. For example, to
162             trigger fornightly on week 1, 3, 5, etc, this would be '2/1'. To trigger every second week of a
163             3 week sprint, this would be '3/1'.
164              
165             The 7th field determines the channel to post to. Note that the bot cannot post to all channels.
166             However, may be a feature added in a future release.
167              
168             The final free form field is the message. The complete line will be sent, with any line
169             continuation markers ignored. Line continuation markers may be a feature in the future.
170              
171             =head1 SYNOPSIS
172              
173             my $bot = Bot::BasicBot::Pluggable->new(
174             ... # various settings
175             };
176              
177             $bot->store->set( 'crontab', 'file', '/path/to/mycrontab.file' },
178             $bot->load('Crontab');
179              
180             =head1 METHODS
181            
182             =over 4
183            
184             =item tick()
185            
186             Loads the crontab file, if not previously done so, and checks all entries to see whether the
187             timing stated matches the current time. If so the message is sent to the appropriate channel,
188             otherwise the entry is ignored. The process repeats every 60 seconds.
189              
190             Note that a change to the crontab file, will force a reload of the file on the next invocation.
191             As such, note that there may be a delay of up to 2 minutes before you see the next updated
192             entry actioned.
193              
194             =back
195            
196             =head1 VARS
197            
198             =over 4
199            
200             =item crontab
201            
202             Path to the crontab file.
203            
204             The crontab file is assumed to be either based on the calling script, or a designated crontab
205             file. If based on the calling script, if your script was mybot.pl, the crontab file would
206             default to mybot.cron.
207              
208             If you wish to designate another filename or path, you may do this via the variable storage
209             when the bot is initiated. For example:
210              
211             my $bot = Bot::BasicBot::Pluggable->new(
212             ... # various settings
213             };
214              
215             $bot->store->set( 'crontab', 'file', '/path/to/mycrontab.file' },
216            
217             =back
218            
219             =head1 AUTHOR
220              
221             Barbie, <barbie@cpan.org>
222             for Miss Barbell Productions <http://www.missbarbell.co.uk>.
223              
224             =head1 COPYRIGHT AND LICENSE
225              
226             Copyright (C) 2015 Barbie for Miss Barbell Productions
227              
228             This distribution is free software; you can redistribute it and/or
229             modify it under the Artistic License v2.
230              
231             =cut