File Coverage

blib/lib/IRC/Message/Object.pm
Criterion Covered Total %
statement 74 75 98.6
branch 24 34 70.5
condition 1 2 50.0
subroutine 18 18 100.0
pod 6 8 75.0
total 123 137 89.7


line stmt bran cond sub pod time code
1             package IRC::Message::Object;
2             $IRC::Message::Object::VERSION = '0.092002';
3 5     5   2043 use strictures 2;
  5         1146  
  5         152  
4 5     5   629 use Carp;
  5         5  
  5         217  
5              
6 5     5   44 use Scalar::Util 'blessed';
  5         12  
  5         215  
7              
8 5     5   1707 use List::Objects::WithUtils;
  5         2423  
  5         22  
9 5     5   205805 use List::Objects::Types -all;
  5         323253  
  5         65  
10 5     5   17078 use Types::Standard -all;
  5         7  
  5         28  
11              
12 5     5   122629 use POE::Filter::IRCv3;
  5         9180  
  5         308  
13              
14 14     14 1 4225 sub ircmsg { __PACKAGE__->new(@_) }
15             our @EXPORT = our @EXPORT_OK = 'ircmsg';
16              
17              
18 5     5   2267 use Moo; use MooX::TypeTiny;
  5     5   29500  
  5         22  
  5         8208  
  5         652  
  5         23  
19             extends 'Exporter::Tiny';
20              
21             has colonify => (
22             lazy => 1,
23             is => 'ro',
24             predicate => 1,
25             default => sub { 0 },
26             );
27              
28             has command => (
29             required => 1,
30             is => 'ro',
31             );
32              
33              
34             has filter => (
35             is => 'rw',
36             isa => HasMethods[qw/get put/],
37             lazy => 1,
38             predicate => 1,
39             builder => '__build_filter',
40             );
41              
42             sub __build_filter {
43 13 100   13   481 POE::Filter::IRCv3->new(
44             colonify => (
45             defined $_[1] ? $_[1] : $_[0]->colonify
46             ),
47             )
48             }
49              
50              
51             has prefix => (
52             is => 'ro',
53             lazy => 1,
54             predicate => 1,
55             default => sub { '' },
56             );
57              
58             has params => (
59             is => 'ro',
60             lazy => 1,
61             isa => ArrayObj,
62             coerce => 1,
63             predicate => 1,
64             default => sub { array },
65             );
66              
67             has raw_line => (
68             is => 'ro',
69             lazy => 1,
70             predicate => 1,
71             default => sub {
72             my ($self) = @_;
73             my %opts = ( command => $self->command );
74             for (qw/colonify prefix params tags/) {
75             my $pred = "has_".$_;
76             $opts{$_} = $self->$_ if $self->$pred;
77             }
78             $self->filter->put([ \%opts ])->[0]
79             },
80             );
81              
82             has tags => (
83             lazy => 1,
84             is => 'ro',
85             isa => HashObj,
86             coerce => 1,
87             predicate => 'has_tags',
88             default => sub { hash },
89             );
90              
91             sub BUILDARGS {
92 20     20 0 8870 my $class = shift;
93 20 100       72 my %params = @_ > 1 ? @_ : (raw_line => $_[0]) ;
94              
95 20 100       42 if (! defined $params{command}) {
96 12 50       22 if (defined $params{raw_line}) {
97             ## Try to create self from raw_line instead:
98             my $filt = $params{filter} ?
99             $params{filter} : $class->__build_filter(
100 12 50       43 defined $params{colonify} ? $params{colonify} : 0
    50          
101             );
102 12         141 my $refs = $filt->get( [$params{raw_line}] );
103 12 50       727 %params = %{ $refs->[0] } if @$refs;
  12         68  
104             } else {
105 0         0 confess "Bad params; a command or a raw_line must be specified in new()"
106             }
107             }
108              
109 20         308 \%params
110             }
111              
112             sub get_tag {
113 1     1 1 2241 my ($self, $tag) = @_;
114 1 50       6 return unless $self->has_tags;
115             ## A tag might have an undef value ...
116             ## ... see has_tag
117 1         19 $self->tags->{$tag}
118             }
119              
120             sub has_tag {
121 2     2 1 352 my ($self, $tag) = @_;
122 2 50       6 return unless $self->has_tags;
123 2         30 exists $self->tags->{$tag}
124             }
125              
126             sub tags_as_array {
127 6     6 1 202 my ($self) = @_;
128 6 50       13 return array unless $self->has_tags;
129              
130 6         5 my @tag_array;
131 6         90 for ($self->tags->kv->all) {
132 18         114 my ($thistag, $thisval) = @$_;
133 18 100       37 push @tag_array,
134             defined $thisval ? join '=', $thistag, $thisval
135             : $thistag
136             };
137              
138 6         18 array @tag_array
139             }
140              
141             sub tags_as_string {
142 4     4 1 159 my ($self) = @_;
143 4 50       8 return unless $self->has_tags;
144              
145 4         4 my $str;
146 4         64 my $kv = $self->tags->kv;
147              
148             TAG: {
149 4   50     73 my $nxt = $kv->shift || last TAG;
  12         43  
150 12         35 my ($thistag, $thisval) = @$nxt;
151 12 100       26 $str .= $thistag . ( defined $thisval ? '='.$thisval : '' );
152 12 100       15 if ($kv->has_any) {
153 8         26 $str .= ';';
154             redo TAG
155 8         10 }
156             }
157              
158             $str
159 4         36 }
160              
161             sub truncate {
162 2     2 1 199 my ($self) = @_;
163              
164 2         2 my $new;
165 2         26 my $current = $self->raw_line;
166              
167             ## TODO check for CTCP first
168             ## if so, set flag, consider and readd trailing \001 ?
169              
170 2 100       12 if ($self->has_tags) {
171 1         3 my $tagstr = '@' . $self->tags_as_string;
172 1         2 my $trunc = substr $current, (length($tagstr) + 1), 510;
173 1         3 $new = join ' ', $tagstr, $trunc;
174             } else {
175             ## No tags, truncate to 510
176 1 50       9 $new = length $current <= 510 ? $current : substr $current, 0, 510 ;
177             }
178              
179 2         32 (blessed $self)->new(raw_line => $new)
180             }
181              
182             sub TO_JSON {
183 1     1 0 2702 my ($self) = @_;
184             +{
185 1 50       20 command => $self->command,
186             prefix => $self->prefix,
187             params => $self->params,
188             ( $self->has_tags ? (tags => $self->tags) : () ),
189             }
190             }
191              
192             print
193             qq[ fine, be rude like that\n],
194             qq[ SORRY I WAS DISCUSSING THE ABILITY TO],
195             qq[ PUT AN IRCD ON A ROOMBA\n]
196             unless caller; 1;
197              
198             =pod
199              
200             =for Pod::Coverage BUILDARGS TO_JSON has_\w+
201              
202             =head1 NAME
203              
204             IRC::Message::Object - Incoming or outgoing IRC events
205              
206             =head1 SYNOPSIS
207              
208             ## Feed me some parameters:
209             my $event = IRC::Message::Object->new(
210             command => '001',
211             prefix => ':some.server.org',
212             params => [ 'user', 'Welcome to IRC' ],
213             );
214              
215             ## ... or use the 'ircmsg()' shortcut:
216             my $event = ircmsg(
217             command => '001',
218             prefix => ':some.server.org',
219             params => [ 'user', 'Welcome to IRC' ],
220             );
221              
222             ## ... or take a raw IRC line (and parse it):
223             $event = ircmsg(
224             raw_line => ':some.server.org 001 user :Welcome to IRC'
225             );
226              
227             ## ... or feed from POE::Filter::IRCD or POE::Filter::IRCv3:
228             $event = ircmsg( %$ref_from_filter );
229              
230             ## ... retrieve useful bits later (see Methods):
231             my $cmd = $event->command;
232             my $line = $event->raw_line;
233             if ($event->has_tag('monkeys')) {
234             ...
235             }
236              
237             =head1 DESCRIPTION
238              
239             These objects represent incoming or outgoing IRC messages (events); they can
240             be created from either named parameters or a raw IRC line and provide
241             accessors with automatic parsing magic.
242              
243             =head2 Functions
244              
245             =head3 ircmsg
246              
247             Create a new B;
248             shortcut for C<< IRC::Message::Object->new >>.
249              
250             This module uses L, so you can rename the exported constructor
251             if you like:
252              
253             use IRC::Message::Object ircmsg => { -as => 'irc_ev' };
254              
255             =head2 Attributes and Methods
256              
257             =head3 raw_line
258              
259             The raw IRC line. The line is generated via the current
260             L if the message object wasn't constructed with one.
261              
262             predicate: C
263              
264             =head3 command
265              
266             The parsed command received.
267              
268             Note that if the C is set at construction time,
269             no case-folding takes place.
270             However, specifying a C at construction feeds
271             L, which will uppercase commands.
272              
273             =head3 params
274              
275             A L containing the parameters attached to the
276             message.
277              
278             predicate: C
279              
280             =head3 prefix
281              
282             The origin prefix.
283              
284             predicate: C
285              
286             =head3 colonify
287              
288             Passed through to L; see the
289             L documentation for details.
290              
291             Defaults to true.
292              
293             =head3 filter
294              
295             Can be used to change the L used to transform a raw line into a
296             HASH and vice-versa.
297              
298             Defaults to a L instance with C<< colonify => 0 >> set.
299              
300             =head3 get_tag
301              
302             Retrieve a specific IRCv3.2 message tag's value.
303              
304             This only works for tags with a defined value; see L to discover if
305             a tag exists.
306              
307             =head3 has_tag
308              
309             Takes a tag identifier; returns true if the tag exists.
310              
311             This is useful for finding out about tags that have no defined value.
312              
313             =head3 has_tags
314              
315             Returns true if there are tags present.
316              
317             =head3 tags
318              
319             IRCv3.2 message tags, as a L of key-value pairs.
320              
321             =head3 tags_as_array
322              
323             IRCv3.2 message tags, as a L of tags in the
324             form of 'key=value'
325              
326             =head3 tags_as_string
327              
328             IRCv3.2 message tags as a specification-compliant string.
329              
330             =head3 truncate
331              
332             Truncates the raw line to 510 characters, excluding message tags (per the
333             specification), and returns a new L.
334              
335             =head1 AUTHOR
336              
337             Jon Portnoy
338              
339             =cut