File Coverage

blib/lib/IRC/Message/Object.pm
Criterion Covered Total %
statement 71 72 98.6
branch 22 32 68.7
condition 1 2 50.0
subroutine 17 17 100.0
pod 6 8 75.0
total 117 131 89.3


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