File Coverage

blib/lib/IRC/Toolkit/ISupport.pm
Criterion Covered Total %
statement 114 120 95.0
branch 23 32 71.8
condition 4 8 50.0
subroutine 34 35 97.1
pod 1 1 100.0
total 176 196 89.8


line stmt bran cond sub pod time code
1             package IRC::Toolkit::ISupport;
2             $IRC::Toolkit::ISupport::VERSION = '0.091001';
3 2     2   15887 use strictures 2;
  2         1193  
  2         95  
4              
5 2     2   545 use Carp 'confess';
  2         4  
  2         136  
6 2     2   12 use Scalar::Util 'blessed';
  2         2  
  2         135  
7              
8 2     2   503 use List::Objects::WithUtils;
  2         733  
  2         21  
9              
10 2     2   68636 use IRC::Message::Object 'ircmsg';
  2         5  
  2         19  
11              
12              
13 2     2   486 use parent 'Exporter::Tiny';
  2         4  
  2         15  
14             our @EXPORT = 'parse_isupport';
15              
16              
17             my $parse_simple_flags = sub {
18             my ($val) = @_;
19             +{ map {; $_ => 1 } split '', ( defined $val ? $val : '' ) }
20             };
21              
22             my $parse = +{
23              
24             chanlimit => sub {
25             my ($val) = @_;
26             my $ref = {};
27             for my $chunk (split /,/, $val) {
28             my ($prefixed, $num) = split /:/, $chunk;
29             for my $pfx (split '', $prefixed) {
30             $ref->{$pfx} = $num
31             }
32             }
33             $ref
34             },
35              
36             chanmodes => sub {
37             my ($val) = @_;
38             my ($list, $always, $whenset, $bool) = split /,/, $val;
39             +{
40             list => array( split '', ( defined $list ? $list : '' ) ),
41             always => array( split '', ( defined $always ? $always : '' ) ),
42             whenset => array( split '', ( defined $whenset ? $whenset : '' ) ),
43             bool => array( split '', ( defined $bool ? $bool : '' ) ),
44             }
45             },
46              
47             chantypes => $parse_simple_flags,
48              
49             elist => $parse_simple_flags,
50              
51             extban => sub {
52             my ($val) = @_;
53             my ($prefix, $flags) = split /,/, $val;
54             +{
55             prefix => $prefix,
56             flags => array( split '', ( defined $flags ? $flags : '' ) ),
57             }
58             },
59              
60             maxlist => sub {
61             my ($val) = @_;
62             my $ref = {};
63             for my $chunk (split /,/, $val) {
64             my ($modes, $num) = split /:/, $chunk;
65             my @splitm = split '', $modes;
66             for my $mode (@splitm) {
67             $ref->{$mode} = $num
68             }
69             }
70             $ref
71             },
72              
73             prefix => sub {
74             my ($val) = @_;
75             my ($modes, $prefixes) = $val =~ /\(([^)]+)\)(.+)/;
76             return +{} unless $modes and $prefixes;
77              
78             my @modes = split '', $modes;
79             my @pfxs = split '', $prefixes;
80             unless (@modes == @pfxs) {
81             warn "modes/prefixes do not appear to match: $modes $prefixes";
82             return +{}
83             }
84              
85             my $ref = +{};
86             for my $mode (@modes) {
87             $ref->{$mode} = shift @pfxs
88             }
89             $ref
90             },
91              
92             statusmsg => $parse_simple_flags,
93              
94             targmax => sub {
95             my ($val) = @_;
96             my $ref = +{};
97             TARGTYPE: for my $chunk (split /,/, $val) {
98             my ($type, $lim) = split /:/, $chunk, 2;
99             next TARGTYPE unless defined $lim;
100             $ref->{ lc $type } = $lim;
101             }
102             $ref
103             },
104              
105             };
106              
107             sub _isupport_hash {
108 4     4   6 my ($obj) = @_;
109 4         5 my %cur;
110 4         69 confess "No object passed or no params to process"
111 4 50 33     11 unless defined $obj and @{ $obj->params };
112             ## First arg should be the target.
113             ## Last is 'are supported by ...'
114 4         74 my %split = map {;
115 46         74 my ($key, $val) = split /=/, $_, 2;
116 46 100       107 ( lc($key), (defined $val ? $val : '0 but true') )
117 4         717 } @{ $obj->params }[1 .. ($#{ $obj->params } - 1) ];
  4         64  
118              
119 4 50       16 unless (keys %split) {
120 0         0 warn "Appear to have been passed valid IRC, but not an ISUPPORT string";
121             return +{}
122 0         0 }
123              
124 4         12 for my $param (keys %split) {
125 46 100 66     194 if (defined $parse->{$param} && defined $split{$param}) {
126 18         39 $cur{$param} = $parse->{$param}->($split{$param})
127             } else {
128 28         37 $cur{$param} = $split{$param}
129             }
130             }
131              
132 4         51 \%cur
133             }
134              
135             sub _isupport_hash_to_obj {
136 2     2   3 my ($isupport_hash) = @_;
137 2         14 IRC::Toolkit::ISupport::Obj->__new($isupport_hash)
138             }
139              
140             sub parse_isupport {
141 2     2 1 147 my @items = map {;
142 4 100       1002 blessed $_ ? $_ : ircmsg(raw_line => $_)
143             } @_;
144              
145 2 50       110 confess
146             'Expected a list of raw IRC lines or IRC::Message::Object instances'
147             unless @items;
148              
149 2         4 my %cur;
150 2         5 ITEM: for my $item (@items) {
151 4 50       18 if ($item->isa('IRC::Message::Object')) {
152 4         10 my $piece = _isupport_hash($item);
153 4         28 @cur{keys %$piece} = values %$piece;
154             next ITEM
155 4         18 } else {
156 0         0 confess "expected an IRC::Message::Object but got $item"
157             }
158             }
159              
160 2         7 _isupport_hash_to_obj(\%cur);
161             }
162              
163              
164             { package
165             IRC::Toolkit::_ISchanmodes;
166 2     2   2906 use Carp 'confess';
  2         5  
  2         206  
167 2     2   12 use strictures 2;
  2         15  
  2         103  
168             sub new {
169 1     1   4 my ($cls, %self) = @_;
170 1         24 bless +{%self}, $cls
171             }
172              
173 3     3   15 sub list { $_[0]->{list} }
174 3     3   18 sub always { $_[0]->{always} }
175 3     3   10 sub whenset { $_[0]->{whenset} }
176 2     2   14 sub bool { $_[0]->{bool} }
177              
178             sub as_string {
179 1     1   2 my ($self) = @_;
180 1         4 join ',', map {; join '', @$_ }
  4         25  
181             $self->list,
182             $self->always,
183             $self->whenset,
184             $self->bool
185             }
186             }
187              
188             { package
189             IRC::Toolkit::_ISextban;
190 2     2   901 use Carp 'confess';
  2         3  
  2         122  
191 2     2   14 use strictures 2;
  2         10  
  2         88  
192             sub new {
193 1     1   5 my ($cls, %self) = @_;
194 1         12 bless +{%self}, $cls
195             }
196              
197 2     2   8 sub prefix { $_[0]->{prefix} }
198 3     3   18 sub flags { $_[0]->{flags} }
199              
200             sub as_string {
201 1     1   2 my ($self) = @_;
202 1         3 join ',', $self->prefix, join '', @{ $self->flags }
  1         3  
203             }
204             }
205              
206             { package
207             IRC::Toolkit::ISupport::Obj;
208              
209 2     2   678 use Carp 'confess';
  2         6  
  2         109  
210 2     2   10 use strictures 2;
  2         8  
  2         75  
211 2     2   405 use Scalar::Util 'blessed';
  2         4  
  2         128  
212              
213 2     2   11 { no strict 'refs';
  2         6  
  2         1494  
214             ## We have parsers for these that generate HASHes:
215             for my $acc (qw/
216             chanlimit
217             chantypes
218             elist
219             maxlist
220             prefix
221             statusmsg
222             targmax
223             / ) {
224             *{ __PACKAGE__ .'::'. $acc } = sub {
225 21     21   1064 my ($ins, $val) = @_;
226 21 100 50     87 return ($ins->{$acc} || +{}) unless defined $val;
227 15         89 $ins->{$acc}->{$val}
228             };
229             }
230             }
231              
232             sub __new {
233 2     2   3 my ($cls, $self) = @_;
234 2 50       13 confess "Expected a HASH from _isupport_hash"
235             unless ref $self eq 'HASH';
236 2         23 bless $self, $cls
237             }
238              
239             ## These are special:
240             sub chanmodes {
241 8     8   15 my ($self) = @_;
242 8 50       23 return unless $self->{chanmodes};
243 8 100       32 unless (blessed $self->{chanmodes}) {
244 1         10 return $self->{chanmodes} =
245 1         2 IRC::Toolkit::_ISchanmodes->new(%{$self->{chanmodes}})
246             }
247 7         32 $self->{chanmodes}
248             }
249              
250             sub extban {
251 5     5   9 my ($self) = @_;
252 5 50       14 return unless $self->{extban};
253 5 100       18 unless (blessed $self->{extban}) {
254 1         10 return $self->{extban} =
255 1         2 IRC::Toolkit::_ISextban->new(%{$self->{extban}})
256             }
257 4         13 $self->{extban}
258             }
259              
260             ## Everything else is bool / int / str we can't parse:
261             our $AUTOLOAD;
262             sub AUTOLOAD {
263 13     13   25 my ($self) = @_;
264 13         43 my $method = (split /::/, $AUTOLOAD)[-1];
265 13         58 $self->{$method}
266             }
267              
268             sub can {
269 2     2   42 my ($self, $method) = @_;
270 2 50       18 if (my $sub = $self->SUPER::can($method)) {
271 0         0 return $sub
272             }
273 2 100       13 return unless exists $self->{$method};
274             sub {
275 1     1   327 my ($this) = @_;
276 1 50       8 if (my $sub = $this->SUPER::can($method)) {
277 0         0 goto $sub
278             }
279 1         2 $AUTOLOAD = $method; goto &AUTOLOAD
  1         4  
280             }
281 1         6 }
282              
283 0     0     sub DESTROY {}
284              
285             }
286              
287              
288             print
289             qq[ "BREAKING: NH MAN HEARS ABOUT CLIMATE CHANGE, ],
290             qq[CLEARS FIVE HUNDRED ACRES FOR COCA PLANTATION"\n]
291             unless caller;
292             1;
293              
294             =pod
295              
296             =head1 NAME
297              
298             IRC::Toolkit::ISupport - IRC ISUPPORT parser
299              
300             =head1 SYNOPSIS
301              
302             use IRC::Toolkit::ISupport;
303             my $isupport = parse_isupport(@raw_lines);
304              
305             ## Get the MODES= value
306             my $maxmodes = $isupport->modes;
307              
308             ## Get the PREFIX= char for mode 'o'
309             my $prefix_for_o = $isupport->prefix('o');
310              
311             ## Find out if we have WHOX support
312             if ( $isupport->whox ) {
313             ...
314             }
315              
316             ## ... etc ...
317              
318             =head1 DESCRIPTION
319              
320             An ISUPPORT (IRC numeric 005) parser that accepts either raw IRC lines or
321             L instances and produces struct-like objects with some
322             special magic for parsing known ISUPPORT types.
323              
324             See L
325              
326             =head2 parse_isupport
327              
328             Takes a list of raw IRC lines or L instances and
329             produces ISupport objects.
330              
331             Keys not listed here will return their raw value (or '0 but true' for boolean
332             values).
333              
334             The following known keys are parsed to provide a nicer interface:
335              
336             =head3 chanlimit
337              
338             If passed a channel prefix character, returns the CHANLIMIT= value for that
339             prefix.
340              
341             Without any arguments, returns a HASH mapping channel prefixes to their
342             respective CHANLIMIT= value.
343              
344             =head3 chanmodes
345              
346             The four mode sets described by a compliant CHANMODES= declaration are list
347             modes, modes that always take a parameter, modes that take a parameter only
348             when they are set, and boolean-type 'flag' modes, respectively:
349              
350             CHANMODES=LIST,ALWAYS,WHENSET,BOOL
351              
352             You can retrieve L ARRAY-type objects
353             containing lists of modes belonging to each set:
354              
355             my @listmodes = @{ $isupport->chanmodes->list };
356              
357             my @always = $isupport->chanmodes->always->all;
358              
359             my $whenset = $isupport->chanmodes->whenset;
360             my $boolean = $isupport->chanmodes->bool;
361              
362             Or retrieve the full string representation via B:
363              
364             my $chanmodes = $isupport->chanmodes->as_string;
365              
366             =head3 chantypes
367              
368             Without any arguments, returns a HASH whose keys are the allowable channel
369             prefixes.
370              
371             If given a channel prefix, returns boolean true if the channel prefix is
372             allowed per CHANTYPES.
373              
374             =head3 elist
375              
376             Without any arguments, returns a HASH whose keys are the supported ELIST
377             tokens.
378              
379             With a token specified, returns boolean true if the token is enabled.
380              
381             =head3 extban
382              
383             Returns an object with the following methods:
384              
385             B returns the extended ban prefix character.
386              
387             B returns the supported extended ban flags as an
388             L of flags:
389              
390             if ($isupp->extban->flags->grep(sub { $_[0] eq 'a' })->has_any) {
391             ...
392             }
393              
394             B returns the string representation of the EXTBAN= declaration.
395              
396             =head3 maxlist
397              
398             Without any arguments, returns a HASH mapping list-type modes (see
399             L) to their respective numeric limit.
400              
401             If given a list-type mode, returns the limit for that list.
402              
403             =head3 prefix
404              
405             Without any arguments, returns a HASH mapping status modes to their respective
406             prefixes.
407              
408             If given a status modes, returns the prefix belonging to that mode.
409              
410             =head3 statusmsg
411              
412             Without any arguments, returns a HASH whose keys are the valid message target
413             status prefixes.
414              
415             If given a status prefix, returns boolean true if the prefix is listed in
416             STATUSMSG.
417              
418             =head3 targmax
419              
420             Given a target type (as of this writing charybdis specifies
421             'names', 'list', 'kick', 'whois', 'privmsg', 'notice', 'accept', 'monitor'),
422             returns the TARGMAX definition for that type, if present.
423              
424             Returns undef if the specified TARGMAX key is nonexistant or has no limit
425             defined.
426              
427             =head1 AUTHOR
428              
429             Jon Portnoy
430              
431             =cut