File Coverage

blib/lib/Bot/BasicBot/Pluggable/Module/Crontab.pm
Criterion Covered Total %
statement 90 90 100.0
branch 11 42 26.1
condition 4 24 16.6
subroutine 30 30 100.0
pod 4 4 100.0
total 139 190 73.1


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