File Coverage

blib/lib/Sirc/Util.pm
Criterion Covered Total %
statement 106 293 36.1
branch 17 128 13.2
condition 6 36 16.6
subroutine 29 52 55.7
pod 26 28 92.8
total 184 537 34.2


line stmt bran cond sub pod time code
1             # $Id: Util.pm,v 1.15 2001-07-27 09:06:13-04 roderick Exp $
2             #
3             # Copyright (c) 1997-2000 Roderick Schertler. All rights reserved.
4             # This program is free software; you can redistribute it and/or modify
5             # it under the same terms as Perl itself.
6              
7 1     1   5 use strict;
  1         2  
  1         51  
8              
9             package Sirc::Util;
10              
11             =head1 NAME
12              
13             Sirc::Util - Utility sirc functions
14              
15             =head1 SYNOPSIS
16              
17             # sirc functions
18             use Sirc::Util ':sirc';
19             # overrides:
20             addhelp $cmd, $usage_line, $rest;
21             timer $delay, $code_string_or_ref, [$reference];
22              
23             # user messages
24             arg_count_error undef, $want, [@arg]; # or 1st arg $name
25             tell_error $msg;
26             tell_question $msg;
27             xtell $msg;
28              
29             # miscellaneous
30             $pattern = ban_pattern $nick, $user, $host;
31             $boolean = by_server [$who, $user, $host];
32             eval_this $code, [@arg];
33             eval_verbose $name, code$, [@arg];
34             $boolean = have_ops $channel;
35             $boolean = have_ops_q $channel;
36             $boolean = ieq $a, $b;
37             $re = mask_to_re $mask;
38             $unused_timer = newtimer;
39             optional_channel or return;
40             $boolean = plausible_channel $channel;
41             $boolean = plausible_nick $nick;
42             $arg = xgetarg;
43             $restricted = xrestrict;
44              
45             # /settables
46             settable name, $var_ref, $setter_ref;
47             settable_boolean $name, $var_ref, [$validate_ref];
48             settable_int $name, $var_ref, [$validate_ref];
49             settable_str $name, $var_ref, [$validate_ref];
50              
51             # hooks
52             add_hook_type $name;
53             add_hook $name, $code;
54             run_hook $name, [@arg];
55              
56             =head1 DESCRIPTION
57              
58             This module provides a bunch of utility functions for B.
59              
60             It also allows you to import from it all of the standard sirc API
61             functions, so that you can more simply write your script as a module.
62              
63             Nothing is exported by default.
64              
65             =cut
66              
67 1     1   5 use vars qw($VERSION @ISA @EXPORT_OK %EXPORT_TAGS %Cmd $Debug %Hook);
  1         8  
  1         106  
68              
69 1     1   5 use Exporter ();
  1         1  
  1         42  
70              
71             # Supply dummy definitions for testing.
72             BEGIN {
73 1 50 33 1   706 eval q{
  1     1   3  
  14     14   26  
  21     21   56  
  1     1   3  
  1     1   38  
74             sub main::addhelp { }
75             sub main::addhook { }
76             sub main::addset { }
77             sub main::docommand { }
78             sub main::tell { print @_, "\n" }
79             } unless $::version || $::version;
80             }
81              
82             # I need %EXPORT_TAGS in a BEGIN to get the list of symbols to import
83             # from main, so just set all the globals at compile time.
84              
85             BEGIN {
86             # This first line is for MakeMaker, it extracts the version for the
87             # whole distribution from here.
88 1     1   2 $VERSION = '0.12';
89 1         8 $VERSION .= '-l' if 0;
90 1 50 33     7 $::add_ons .= "+libsirc $VERSION"
91             if !defined $::add_ons || $::add_ons !~ /\blibsirc\b/;
92              
93             # This is the real version for this file.
94 1         1 $VERSION = do{my@r=q$Revision: 1.15 $=~/\d+/g;sprintf '%d.'.'%03d'x$#r,@r};
  1         7  
  1         10  
95 1 50       6 $VERSION .= '-l' if q$Locker: $ =~ /: \S/;
96              
97 1         15 @ISA = qw(Exporter);
98 1         5 @EXPORT_OK = qw(
99             arg_count_error tell_error tell_question xtell
100              
101             ban_pattern by_server eval_this eval_verbose have_ops
102             have_ops_q ieq mask_to_re newtimer optional_channel
103             plausible_channel plausible_nick xgetarg
104             xrestrict
105              
106             settable settable_boolean settable_int settable_str
107              
108             add_hook add_hook_type run_hook
109              
110             );
111              
112             =head1 STANDARD SIRC FUNCTIONS
113              
114             You can import the standard SIRC API functions individually or, using
115             the tag B<:sirc>, as a group. The available functions are:
116              
117             =over
118              
119             =item
120              
121             accept addcmd addhelp addhook addset connect deltimer describe docommand
122             doset dosplat dostatus eq getarg getuserline getuserpass listen load me
123             msg newfh notice print remhook remsel resolve say sl tell timer userhost
124             yetonearg
125              
126             =back
127              
128             Some of these are actually enhanced versions of the routines that B
129             provides, see below for information about them.
130              
131             =cut
132              
133 1         7 %EXPORT_TAGS = (
134             'sirc' => [qw(accept addcmd addhelp addhook addset
135             connect deltimer describe docommand doset dosplat
136             dostatus eq getarg getuserline getuserpass listen
137             load me msg newfh notice print remhook remsel
138             resolve say sl tell timer userhost yetonearg)],
139             );
140 1         61 Exporter::export_ok_tags;
141              
142 1         37 $Debug = 0;
143             }
144              
145             my $Old_w;
146 1     1   3 BEGIN { $Old_w = $^W; $^W = 1 }
  1         24  
147              
148             # Import sirc's functions.
149             BEGIN {
150 1     1   6 no strict 'refs';
  1         2  
  1         135  
151 1     1   2 for my $fn (grep { $_ !~ /^(addcmd|addhelp|timer|userhost)$/ }
  32         68  
  1         3  
152             @{ $EXPORT_TAGS{'sirc'} }) {
153 28         31 *$fn = \&{ "main::$fn" };
  28         194  
154             }
155             }
156              
157 1     1   6998 use subs qw(tell_error xtell);
  1         31  
  1         5  
158              
159             sub debug {
160 6 50   6 0 21 xtell "debug " . join '', @_
161             if $Debug;
162             }
163              
164             #------------------------------------------------------------------------------
165              
166             =head1 STANDARD MESSAGE FORMS
167              
168             These functions provide for a few standard message forms which are shown
169             to the user via main::tell().
170              
171             =over
172              
173             =item B I, I, [I...]
174              
175             This prints an error appropriate to an incorrect number of arguments.
176             I is the name to report as having been invoked incorrectly. If
177             it's C (which is the usual case) it's set to the caller's
178             function name. I is how many arguments were desired and the
179             remaining I arguments are the arguments which were actually
180             received.
181              
182             =cut
183              
184             sub arg_count_error {
185 0     0 1 0 my ($fn, $want, @got) = @_;
186 0 0       0 $fn = (caller 1)[3] if !defined $fn;
187 0         0 tell_error "Wrong number of args to $fn, wanted $want got "
188             . @got . ' (' . join(', ', @got) . ')';
189             }
190              
191             =item B I
192              
193             This formats I as an error message and passes it to main::tell.
194             It's appropriate for errors caused by the system or an invalid invocation
195             of your code.
196              
197             =cut
198              
199             #';
200              
201             sub tell_error {
202 1 50   1   6 unless (@_ == 1) {
203 0         0 arg_count_error undef, 1, @_;
204 0         0 return;
205             }
206 1         29 main::tell("*\cbE\cb* $_[0]");
207             }
208              
209             =item B I
210              
211             This formats I as an error message for something the user did
212             wrong. The message is passed to main::tell.
213              
214             =cut
215              
216             sub tell_question {
217 0 0   0 1 0 unless (@_ == 1) {
218 0         0 arg_count_error undef, 1, @_;
219 0         0 return;
220             }
221 0         0 main::tell("*\cb?\cb* $_[0]");
222             }
223              
224             =item B I
225              
226             This is just C.
227              
228             =cut
229              
230             sub xtell {
231 0     0   0 my $s = shift;
232 0         0 main::tell("*** $s");
233             }
234              
235             =back
236              
237             =cut
238              
239             #------------------------------------------------------------------------------
240              
241             =head1 MISCELLANEOUS FUNCTIONS
242              
243             These are some functions which don't fall nicely into groups like those
244             following do.
245              
246             =over
247              
248             =item B I
249              
250             This is an enhanced version of B's addcmd(). It lets you define
251             commands whose names contain non-alphanumeric characters.
252              
253             =cut
254              
255             sub addcmd {
256 6 50   6 1 19 @_ == 1 || arg_count_error undef, '1', @_;
257 6         8 my ($cmd) = @_;
258              
259 6         11 (my $qcmd = $cmd) =~ s/(['\\])/\\$1/g;
260 6         11 my $ucmd = uc $cmd;
261 6         20 $::cmds{$ucmd} = "\&{'cmd_$qcmd'}();";
262 6         21 debug "command $cmd => $::cmds{$ucmd}";
263             }
264              
265             =item B I, I
266              
267             =item B I, I, I
268              
269             This is an enhanced version of B's addhelp(). It arranges for the
270             new command to appear in the master help list.
271              
272             Additionally, there's a new 3-arg syntax. When called with 2 args it
273             uses the regular addhelp() command. I hate the way this makes you
274             hardcode the standard form for help info, though, so I added the second
275             form. This form takes the usage info which appears after the command
276             as its first arg, and the bulk of the help as its 3rd arg.
277              
278             =cut
279              
280             { my (%seen_cmd, %seen_set);
281             sub addhelp {
282 1 50 33 1 1 19 @_ == 2 || @_ == 3 || arg_count_error undef, '2 or 3', @_;
283 1         2 my $cmd = shift @_;
284 1 50       8 my $text = @_ == 1 ? shift : ("Usage: \cB\U$cmd\E\cB " . join "\n", @_);
285              
286 1         2 my ($rseen, $seen_tag, $targ, $intro);
287 1 50       10 if ($cmd =~ /^set (.*)/) {
288 0         0 $rseen = \%seen_set;
289 0         0 $seen_tag = uc $1;
290 0         0 $targ = '@set';
291 0         0 $intro = "List of non-builtin SET variables:";
292             }
293             else {
294 1         3 $rseen = \%seen_cmd;
295 1         2 $seen_tag = uc $cmd;
296 1         2 $targ = '@main';
297 1         2 $intro = "List of non-builtin commands with help:";
298             }
299              
300 1 50 33     6 if (@::help && !$rseen->{$seen_tag}++) {
301             # The help info is stored as an array of lines, then they're
302             # scanned when you use /help! Entries are introduced with
303             # "@name".
304              
305 0         0 my $state = 0;
306 0         0 my $i = -1;
307 0         0 my $first = undef;
308 0         0 my $len = 0;
309              
310 0         0 for (@::help) {
311 0         0 $i++;
312 0 0       0 if ($state == 0) {
    0          
    0          
313 0 0       0 $state = 1 if $_ eq $targ;
314             }
315             elsif ($state == 1) {
316 0 0       0 if ($_ eq $intro) {
    0          
317 0         0 $first = $i;
318 0         0 $len = 1;
319 0         0 $state = 2;
320             }
321             elsif (/^@/) {
322 0         0 $first = $i;
323 0         0 $len = 0;
324 0         0 last;
325             }
326             }
327             elsif ($state == 2) {
328 0 0       0 if (/^@/) {
329 0         0 last;
330             }
331             else {
332 0         0 $len++;
333             }
334             }
335             }
336              
337 0 0       0 if (defined $first) {
338             # I found the help entry, $first and $len are the splice()
339             # indicators which for the part I've added to it.
340 0         0 local $_;
341 0         0 my @labels = sort keys %$rseen;
342 0         0 my $l = 0; # max label length
343 0         0 for (@labels) {
344 0 0       0 $l = length if length > $l;
345             }
346 0         0 $l += 2; # spaces between
347 0         0 my $w = 80 - 4; # XXX terminal width less wrap margin
348 0         0 my @out = ($intro, '');
349 0         0 while (@labels) {
350 0         0 my $this = sprintf "%-${l}s", shift @labels;
351 0 0       0 if (length($out[$#out]) + length($this) > $w) {
352 0         0 push @out, '';
353             }
354 0         0 $out[$#out] .= $this;
355             }
356 0 0       0 if ($out[$#out] eq '') {
357 0         0 pop @out;
358             }
359 0         0 splice @::help, $first, $len, @out;
360             }
361             }
362              
363 1         29 return main::addhelp $cmd, $text;
364             } }
365              
366             =item B I, I, I
367              
368             This returns a pattern suitable for banning the given nick, user and host.
369              
370             The current implementation is this: Any nick is always matched. If the
371             user has a ~ at the start (that is, it didn't come from identd) all user
372             names are matched, else just the one given matches. If the host is an
373             IP address, it bans a class C sized chunk of IP space, otherwise
374             part of it is wildcarded (how much depends on how many parts it has).
375              
376             For example:
377              
378             qw(Nick user 1.2.3.4) *!user@1.2.3.*
379             qw(Nick ~user 1.2.3.4) *!*@1.2.3.*
380             qw(Nick user host.foo.com) *!user@*.foo.com
381             qw(Nick ~user host.foo.com) *!*@*.foo.com
382             qw(Nick user foo.com) *!user@*foo.com
383             qw(Nick ~user foo.com) *!*@*foo.com
384              
385             =cut
386              
387             sub ban_pattern {
388 0     0 1 0 debug "ban_pattern @_";
389 0 0       0 unless (@_ == 3) {
390 0         0 arg_count_error undef, 1, @_;
391 0         0 return;
392             }
393 0         0 my ($n, $u, $h) = @_;
394              
395 0         0 $n = '*';
396 0         0 $u =~ s/^~.*/*/;
397             # 1.2.3.4 => 1.2.3.*
398 0 0       0 if ($h =~ /^(\d+\.\d+\.\d+)\.\d+$/) {
    0          
    0          
399 0         0 $h = "$1.*";
400             }
401             # foo.bar.baz => *.bar.baz
402             elsif ($h =~ /^[^.]+\.(.+\..+)$/) {
403 0         0 $h = "*.$1";
404             }
405             # foo.bar => *foo.bar
406             elsif ($h =~ /^[^.]+\.[^.]+$/) {
407 0         0 $h = "*$h";
408             }
409 0         0 return "$n!$u\@$h";
410             }
411              
412             =item by_server [I, I, I]
413              
414             If the given I, I, I corresponds to a server rather
415             than a user, return the server name, else return undef. If these aren't
416             specified the global $::who, $::user, and $::host are used, which is
417             what you usually want anyway.
418              
419             =cut
420              
421             sub by_server {
422 0 0 0 0 1 0 unless (@_ == 0 || @_ == 3) {
423 0         0 arg_count_error undef, '0 or 3', @_;
424 0         0 return;
425             }
426 0 0       0 my ($n, $u, $h) = @_ ? @_ : ($::who, $::user, $::host);
427              
428 0 0       0 return $u eq '' ? $n : undef;
429             }
430              
431             =item B I, [I...]
432              
433             This Bs I with I as arguments. The I can be
434             either a code reference or a string. In either case the Is will be
435             available in @_. The return value is whatever the I returns.
436             $@ will be set if an exception was raised.
437              
438             =cut
439              
440             #';
441              
442             sub eval_this {
443 0     0 1 0 debug "eval_this @_";
444 0 0       0 unless (@_ >= 1) {
445 0         0 arg_count_error undef, '1 or more', @_;
446 0         0 return;
447             }
448 0         0 my $code = shift;
449              
450             package main;
451 1     1   1774 no strict;
  1         2  
  1         996  
452 0 0       0 return ref $code ? eval { $code->(@_) } : eval $code;
  0         0  
453             }
454              
455             =item B I, I, [I...]
456              
457             This is like B except that if an exception is raised it is
458             passed along to B (with a message indicating it's from
459             B).
460              
461             =cut
462              
463             #';
464              
465             sub eval_verbose {
466 0 0   0 1 0 unless (@_ >= 2) {
467 0         0 arg_count_error undef, '2 or more', @_;
468 0         0 return;
469             }
470 0         0 my ($what, $code, @arg) = @_;
471              
472 0         0 eval_this $code, @arg;
473 0 0       0 if ($@) {
474 0         0 chomp $@;
475 0         0 tell_error "Error running code for $what: $@";
476 0         0 return 0;
477             }
478 0         0 return 1;
479             }
480              
481             =item B I
482              
483             This function returns true if you have ops on the specified channel. If
484             you don\'t have ops it prints an error message and returns false.
485              
486             =cut
487              
488             sub have_ops {
489 0 0   0 1 0 unless (@_ == 1) {
490 0         0 arg_count_error undef, 1, @_;
491 0         0 return;
492             }
493 0         0 my ($c) = @_;
494              
495 0 0       0 if (!$::haveops{lc $c}) {
496 0         0 tell_question "You don't have ops on $c";
497 0         0 return 0;
498             }
499 0         0 return 1;
500             }
501              
502             =item B I
503              
504             This is like B except that no message is printed, it just
505             returns true or false depending on whether you have ops on the specified
506             channel.
507              
508             =cut
509              
510             sub have_ops_q {
511 0 0   0 1 0 unless (@_ == 1) {
512 0         0 arg_count_error undef, 1, @_;
513 0         0 return;
514             }
515 0         0 my ($c) = @_;
516              
517 0         0 return $::haveops{lc $c};
518             }
519              
520             =item B $a, $b
521              
522             This sub returns true if its two args are eq, ignoring case.
523              
524             =cut
525              
526             sub ieq {
527 0 0   0 1 0 unless (@_ == 2) {
528 0         0 arg_count_error undef, 2, @_;
529 0         0 return;
530             }
531 0         0 return lc($_[0]) eq lc($_[1]);
532             }
533              
534             =item B I
535              
536             Convert the given "mask" (an IRC-style glob pattern) to a regular
537             expression. The only special characters in IRC masks are C<*> and
538             C (there's no way to escape one of these). The returned pattern
539             always matches case insensitively and is anchored at the front and
540             back (as IRC does it).
541              
542             =cut
543              
544             sub mask_to_re {
545 6 50   6 1 150 unless (@_ == 1) {
546 0         0 arg_count_error undef, 1, @_;
547 0         0 return;
548             }
549 6         8 my ($s) = @_;
550              
551 6         13 $s = quotemeta $s;
552 6         13 $s =~ s/\\\*/.*/g;
553 6         10 $s =~ s/\\\?/./g;
554 6         20 return "(?is)^$s\$";
555             }
556              
557             =item B
558              
559             This sub examines $::args to see if the first word in it looks like a
560             channel. If it doesn't then $::talkchannel is inserted there. If there
561             was no channel present and you're not on a channel then an error message
562             is printed and false is returned, otherwise true is returned.
563              
564             Here's a replacement for /names which runs /names for your current
565             channel if you don't provide any args.
566              
567             sub main::cmd_names {
568             optional_channel or return;
569             docommand "/names $::args";
570             }
571             addcmd 'names';
572              
573             =cut
574              
575             sub optional_channel {
576 0 0   0 1 0 unless (@_ == 0) {
577 0         0 arg_count_error undef, 0, @_;
578 0         0 $::args = "#invalid-optional_channel-invocation $::args";
579 0         0 return;
580             }
581 0         0 my $ret = 1;
582 0 0       0 if ($::args !~ /^[\#&]/) {
583 0 0       0 if (!$::talkchannel) {
584 0         0 tell_question "Not on a channel";
585 0         0 $ret = 0;
586             }
587 0   0     0 $::args = ($::talkchannel || '#not-on-a-channel') . " $::args";
588             }
589 0         0 return $ret;
590             }
591              
592             =item B
593              
594             Return an unused timer number.
595              
596             =cut
597              
598             sub newtimer {
599 0 0   0 1 0 unless (@_ == 0) {
600 0         0 arg_count_error undef, 1, @_;
601 0         0 return;
602             }
603              
604 0         0 while (1) {
605 0         0 my $n = 1 + int rand 2**31;
606 0 0       0 return $n unless grep { $_ == $n } @::trefs;
  0         0  
607             }
608             }
609              
610             =item B I
611              
612             This returns true if I is syntactically valid as a channel
613             name.
614              
615             =cut
616              
617             sub plausible_channel {
618 0 0   0 1 0 unless (@_ == 1) {
619 0         0 arg_count_error undef, 1, @_;
620 0         0 return;
621             }
622 0         0 my ($c) = @_;
623 0         0 return $c =~ /^[\#&][^ \a\0\012\015,]+$/;
624             }
625              
626             =item B I
627              
628             This returns true if I is syntactically valid as a nick name.
629             Originally I used the RFC 1459 definition here, but that turns out to be
630             no longer valid. I don't know what definition modern IRC servers are
631             using. This sub allows characters in the range [!-~].
632              
633             =cut
634              
635             #';
636              
637             sub plausible_nick {
638 0 0   0 1 0 unless (@_ == 1) {
639 0         0 arg_count_error undef, 1, @_;
640 0         0 return;
641             }
642 0         0 my ($n) = @_;
643             #return $n =~ /^[a-z][a-z0-9\-\[\]\\\`^{}]*$/i;
644 0         0 return $n =~ /^[!-~]+$/;
645             }
646              
647             =item B @args
648              
649             This is an enhanced version of B's timer(). It allows you to use
650             a code reference as the code arg.
651              
652             =cut
653              
654             #';
655              
656             my $timer_name = 'timersub000';
657              
658             sub timer {
659 0     0 1 0 my @arg = @_;
660              
661 0 0 0     0 if (@arg > 1 && ref $arg[1]) {
662             # The strategy here is to give a name to the code reference
663             # and then call it via that name. After calling it the glob
664             # containing the name is deleted to free memory. (You can't
665             # just undef the &sub because that would leave the glob and CV
666             # in existance.)
667 1     1   6 no strict 'refs';
  1         1  
  1         330  
668 0         0 $timer_name++;
669 0         0 my $pkg = __PACKAGE__;
670 0         0 *{ "${pkg}::$timer_name" } = $arg[1];
  0         0  
671 0         0 $arg[1] = qq{${pkg}::$timer_name(); delete \$${pkg}::{"$timer_name"}};
672             }
673 0         0 return main::timer(@arg);
674             }
675              
676             # Hack: Chantrack overrides userhost, so I have to call through here.
677             # If I assign to *userhost at compile time I'll retain a reference to
678             # the original sub.
679              
680             sub userhost {
681 0     0 0 0 goto &main::userhost;
682             }
683              
684             =item B
685              
686             This is like main::getarg, but it returns the new argument (in addition
687             to setting $::newarg).
688              
689             =cut
690              
691             sub xgetarg {
692 0     0 1 0 getarg;
693 0         0 return $::newarg;
694             }
695              
696             =item B
697              
698             This just returns $::restrict.
699              
700             =cut
701              
702             sub xrestrict {
703 0     0 1 0 return $::restrict;
704             }
705              
706             =back
707              
708             =cut
709              
710             #------------------------------------------------------------------------------
711              
712             =head1 /SET COMMANDS
713              
714             These commands provide a simplified interface to adding /set variables.
715              
716             =over
717              
718             =item B I, I, I
719              
720             This sub adds a user-settable option. I is its name, I
721             is a reference to the place it will be stored, and I is a
722             reference to code to validate and save new values. The code will be
723             called as C<$rsetter->($rvar, $name, $value)>. $name will be in upper
724             case. The code needs to set both $$rvar and $::set{$name}. (The values
725             in %set are user-visible.)
726              
727             =cut
728              
729             sub settable {
730 21     21 1 28 my ($name, $rvar, $rsetter) = @_;
731 21         34 my $subname = "main::set_$name";
732 21         35 my $uname = uc $name;
733             my $closure = sub {
734 0     0   0 my $val = shift;
735 0         0 $rsetter->($rvar, $uname, $val);
736 21         67 };
737             {
738 1     1   6 no strict 'refs';
  1         2  
  1         1101  
  21         26  
739 21         125 *$subname = $closure;
740             }
741             # XXX 2nd arg is ignored
742 21         575 addset $name, $name;
743             }
744              
745             =item B I, I, [I]
746              
747             This adds a /settable boolean called I. I is a reference
748             to the scalar which will store the value.
749              
750             I, if provided, will be called to validate a new value is
751             legal. It will receive both the I and the new value (as a boolean,
752             not as the user typed it) as arguments. It should return a boolean to
753             indicate whether the value is okay.
754              
755             =cut
756              
757             sub settable_boolean {
758 9     9 1 17 my ($name, $rvar, $rvalidate) = @_;
759             my $closure = sub {
760 0     0   0 my ($rvar, $name, $val) = @_;
761 0         0 my $new = $$rvar;
762 0         0 my $lval = lc $val;
763 0 0       0 if ($lval eq 'on') {
    0          
    0          
    0          
764 0         0 $new = 1;
765             }
766             elsif ($lval eq 'off') {
767 0         0 $new = 0;
768             }
769             elsif ($lval eq 'toggle') {
770 0         0 $new = !$new;
771             }
772             elsif ($lval eq 'nil') {
773             # do nothing, for initial set
774             }
775             else {
776 0         0 tell_question "Invalid value `$val' for $name";
777 0         0 return;
778             }
779 0 0 0     0 if ($rvalidate && !$rvalidate->($name, $new)) {
780 0         0 tell_question "Invalid value `$val' for $name";
781 0         0 return;
782             }
783 0         0 $$rvar = $new;
784 0 0       0 $::set{$name} = $$rvar ? 'on' : 'off';
785 9         48 };
786 9         20 settable $name, $rvar, $closure;
787 9 100       52 $::set{uc $name} = $$rvar ? 'on' : 'off';
788             }
789              
790             =item B I, I, [I]
791              
792             This function adds a /settable integer called I. I is a
793             reference to the scalar which will store the value.
794              
795             I, if provided, will be called to validate a new
796             value is legal. It will receive both the I and the new value as
797             arguments. Before it is called the new value will have been vetted for
798             number-hood. It should return a boolean to indicate whether the value
799             is okay.
800              
801             =cut
802              
803             sub settable_int {
804 10     10 1 17 my ($name, $rvar, $rvalidate) = @_;
805             my $closure = sub {
806 0     0   0 my ($rvar, $name, $val) = @_;
807 0 0 0     0 if (!defined $val) {
    0 0        
808 0         0 tell_question "Can't set $name to undefined value";
809             }
810             elsif ($val !~ /^-?\d+$/
811             || ($rvalidate && !$rvalidate->($name, $val))) {
812 0         0 tell_question "Invalid value `$val' for $name";
813             }
814             else {
815 0         0 $$rvar = $::set{$name} = $val;
816             }
817 10         55 };
818 10         21 settable $name, $rvar, $closure;
819 10   50     24 $$rvar ||= 0; # must be defined for /set to work
820 10         37 $::set{uc $name} = $$rvar;
821             }
822              
823             =item B I, I, [I]
824              
825             This function adds a /settable called I. I is a reference
826             to the scalar which will store the value.
827              
828             I, if provided, will be called to validate the a new
829             value is legal. It will receive both the I and the new value as
830             arguments. It should return a boolean to indicate whether the value is
831             okay.
832              
833             =cut
834              
835             sub settable_str {
836 2     2 1 4 my ($name, $rvar, $rvalidate) = @_;
837             my $closure = sub {
838 0     0   0 my ($rvar, $name, $val) = @_;
839 0 0 0     0 if (!defined $val) {
    0          
840 0         0 tell_question "Can't set $name to undefined value";
841             }
842             elsif ($rvalidate && !$rvalidate->($name, $val)) {
843 0         0 tell_question "Invalid value `$val' for $name";
844             }
845             else {
846 0         0 $$rvar = $::set{$name} = $val;
847             }
848 2         8 };
849 2         6 settable $name, $rvar, $closure;
850 2   50     6 $$rvar ||= ''; # must be defined for /set to work
851 2         7 $::set{uc $name} = $$rvar;
852             }
853              
854             =back
855              
856             =cut
857              
858             #------------------------------------------------------------------------------
859              
860             #=head1 CHAINED COMMANDS
861             #
862             #=over
863             #
864             #=cut
865             #
866             #sub chain_cmd_runner {
867             # my $type = shift;
868             # for my $code (@{ $Cmd{$type} }) {
869             # if (ref $code) {
870             # eval { &$code };
871             # }
872             # else {
873             # eval $code;
874             # }
875             # die if $@;
876             # }
877             #}
878             #
879             #sub chain_cmd {
880             # my ($type, $new) = @_;
881             # $type = lc $type;
882             # my $old = $main::cmds{$type};
883             # my $cmd = "chain_cmd_runner '$type'";
884             # if ($old && $old ne $cmd) {
885             # push @{ $Cmd{$type} }, $old;
886             # $main::cmds{$type} = $cmd;
887             # }
888             # push @{ $Cmd{$type} }, $new;
889             #}
890             #
891             #=back
892             #
893             #=cut
894              
895             #------------------------------------------------------------------------------
896              
897             =head1 HOOKS
898              
899             Sirc::Util provides functionality for creating, adding code to and
900             running hooks.
901              
902             =over
903              
904             =item B I
905              
906             This creates a new hook called I.
907              
908             =cut
909              
910             sub add_hook_type {
911 6 50   6 1 15 unless (@_ == 1) {
912 0         0 arg_count_error undef, 1, @_;
913 0         0 return;
914             }
915 6         7 my ($hook) = @_;
916              
917 6 50       14 if (exists $Hook{$hook}) {
918 0         0 tell_error "add_hook_type: Hook $hook already exists";
919 0         0 return;
920             }
921 6         16 $Hook{$hook} = [];
922             }
923              
924              
925             =item B I, I
926              
927             Add I to the I hook. The I must already have been
928             created with add_hook_type(). The I can be either a string or a
929             code reference.
930              
931             =cut
932              
933             sub add_hook {
934 4 50   4 1 19 unless (@_ == 2) {
935 0         0 arg_count_error undef, 2, @_;
936 0         0 return;
937             }
938 4         7 my ($hook, $code) = @_;
939              
940 4 50       13 if (!exists $Hook{$hook}) {
941 0         0 tell_error "add_hook: Invalid hook `$hook'";
942 0         0 return;
943             }
944 4         5 push @{ $Hook{$hook} }, $code;
  4         14  
945             }
946              
947             =item B I, [I...]
948              
949             Run the I hook, passing the Is to each hook member via @_.
950              
951             =cut
952              
953             sub run_hook {
954 0 0   0 1   unless (@_ >= 1) {
955 0           arg_count_error undef, '1 or more', @_;
956 0           return;
957             }
958 0           my ($hook, @arg) = @_;
959              
960 0 0         if (!exists $Hook{$hook}) {
961 0           tell_error "run_hook: Invalid hook `$hook'";
962 0           return;
963             }
964 0           for my $code (@{ $Hook{$hook} }) {
  0            
965 0           eval_verbose "$hook hook", $code, @arg;
966             }
967             }
968              
969             =back
970              
971             =cut
972              
973             #------------------------------------------------------------------------------
974              
975 1     1   36 BEGIN { $^W = $Old_w }
976              
977             1;
978              
979             =head1 AVAILABILITY
980              
981             Check CPAN or http://www.argon.org/~roderick/ for the latest version.
982              
983             =head1 AUTHOR
984              
985             Roderick Schertler >
986              
987             =head1 SEE ALSO
988              
989             sirc(1), perl(1), Sirc::Chantrack(3pm).
990              
991             =cut