File Coverage

blib/lib/IRC/Toolkit/ISupport.pm
Criterion Covered Total %
statement 111 116 95.6
branch 23 32 71.8
condition 4 8 50.0
subroutine 34 35 97.1
pod 1 1 100.0
total 173 192 90.1


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