The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package POE::Component::IRC::Plugin::CSS::PropertyInfo;

use warnings;
use strict;

our $VERSION = '2.001002'; # VERSION

use base 'POE::Component::IRC::Plugin::BaseWrap';
use POE qw(Component::IRC::Plugin::CSS::PropertyInfo::Data);

my %Properties
= POE::Component::IRC::Plugin::CSS::PropertyInfo::Data->_make_property_data;

my %Value_Types
= POE::Component::IRC::Plugin::CSS::PropertyInfo::Data->_make_vt_data;

my %Make_Output_For_Command = (
    initial     => \&_command_initial,
    values      => \&_command_values,
    inherited   => \&_command_inherited,
    percentages => \&_command_percentages,
    applies_to  => \&_command_applies_to,
    media       => \&_command_media,
    value_type  => \&_command_value_type,
);

sub _make_default_args {
    return (
        trigger          => qr/^css\s+(?=\S+\s+\S+)/i,
        command_triggers => {
            exists      => qr/^ e (?:xist s?)?                    \s+/xi,
            initial     => qr/^ i (?:nitial)?                     \s+/xi,
            values      => qr/^ v (?:alue s?)?                    \s+/xi,
            inherited   => qr/^ in (?:herit (?:ed)? )?            \s+/xi,
            percentages => qr/^ p (?:ercent (?:age s? )? )?       \s+/xi,
            applies_to  => qr/^ a (?: ppl (?:y|ies))? \s* (?:to)? \s+/xi,
            media       => qr/^ m (?: edia)? \s* (?:type)?        \s+/xi,
            value_type  => qr/^ v (?: alue )? \s* t (?: ypes?)?  \s+/xi,
        },
        response_event   => 'irc_css_property_info',
    );
}

sub _do_response {
    my ( $self, $in_ref ) = @_;

    my $response_message = $self->_make_response_message( $in_ref );

    $in_ref->{out} = $response_message;
    $self->{irc}->_send_event(
        $self->{response_event} => $in_ref,
    );

    if ( $self->{auto} ) {
        my $response_type = $in_ref->{type} eq 'public'
                        ? 'privmsg'
                        : $in_ref->{type};

        my $where = $in_ref->{type} eq 'public'
                ? $in_ref->{channel}
                : (split /!/, $in_ref->{who})[0];

        $poe_kernel->post( $self->{irc} =>
                $response_type =>
                $where =>
                $response_message
        );
    }

    undef;
}

sub _make_response_message {
    my ( $self, $in_ref ) = @_;

    my $in = $in_ref->{what};

    my $trigs_ref = $self->{command_triggers};

    for my $command ( sort keys %$trigs_ref ) {
        my $trigger = $trigs_ref->{ $command };
        if ( $in =~ s/$trigger// ) {
            $in =~ s/^\s+|\s+$//g;
            $in = lc $in;

            if ( $command ne 'value_type' and !exists $Properties{$in} ) {
                return "Property '$in' does not seem to exist";
            }
            if ( $command eq 'exists' ) {
                return "Yes, property '$in' does exist";
            }

            return $Make_Output_For_Command{ $command }->( $in );
        }
    }

    return 'Invalid command in CSS Property Info plugin';
}

sub _command_initial {
    my $in = shift;
    return "Initial value for '$in' is $Properties{ $in }{initial}";
}

sub _command_values {
    my $in = shift;
    return "Property '$in' accepts: $Properties{ $in }{values}";
}

sub _command_inherited {
    my $in = shift;
    return $Properties{ $in }{inherited} eq 'yes'
            ? "Yes, '$in' is inherited"
            : "No, '$in' is not inherited";
}

sub _command_percentages {
    my $in = shift;
    return $Properties{ $in }{percentages} eq 'N/A'
            ? "Percetages do not apply to '$in'"
            : "Percentages for '$in' refer to "
                . $Properties{ $in }{percentages};
}

sub _command_applies_to {
    my $in = shift;
    return "Property '$in' applies to: $Properties{ $in }{applies_to}";
}

sub _command_media {
    my $in = shift;
    return "Property '$in' belongs to $Properties{ $in }{media} "
                . "media type(s)";
}

sub _command_value_type {
    my $in = shift;
    return exists $Value_Types{ $in }
            ? "Value type '$in' is described on $Value_Types{$in}"
            : "I am not aware of value type '$in'";
}

1;
__END__

=encoding utf8

=for stopwords bot privmsg regexen usermask usermasks

=head1 NAME

POE::Component::IRC::Plugin::CSS::PropertyInfo - lookup CSS property information from IRC

=head1 SYNOPSIS

    use strict;
    use warnings;

    use POE qw(Component::IRC  Component::IRC::Plugin::CSS::PropertyInfo);

    my $irc = POE::Component::IRC->spawn(
        nick        => 'CSSInfoBot',
        server      => 'irc.freenode.net',
        port        => 6667,
        ircname     => 'CSS Property Info bot',
    );

    POE::Session->create(
        package_states => [
            main => [ qw(_start irc_001) ],
        ],
    );

    $poe_kernel->run;

    sub _start {
        $irc->yield( register => 'all' );

        $irc->plugin_add(
            'CSSInfo' =>
                POE::Component::IRC::Plugin::CSS::PropertyInfo->new
        );

        $irc->yield( connect => {} );
    }

    sub irc_001 {
        $irc->yield( join => '#zofbot' );
    }


    <Zoffix> CSSInfoBot, css exists foo
    <CSSInfoBot> Property 'foo' does not seem to exist
    <Zoffix> CSSInfoBot, css exists float
    <CSSInfoBot> Yes, property 'float' does exist

    <Zoffix> CSSInfoBot, css initial bar
    <CSSInfoBot> Property 'bar' does not seem to exist
    <Zoffix> CSSInfoBot, css initial float
    <CSSInfoBot> Initial value for 'float' is none

    <Zoffix> CSSInfoBot, css values position
    <CSSInfoBot> Property 'position' accepts: static | relative | absolute | fixed | inherit

    <Zoffix> CSSInfoBot, css inherited color
    <CSSInfoBot> Yes, 'color' is inherited
    <Zoffix> CSSInfoBot, css inherited display
    <CSSInfoBot> No, 'display' is not inherited

    <Zoffix> CSSInfoBot, css percentages width
    <CSSInfoBot> Percentages for 'width' refer to refer to width of containing block
    <Zoffix> CSSInfoBot, css percentages display
    <CSSInfoBot> Percetages do not apply to 'display'

    <Zoffix> CSSInfoBot, css applies to display
    <CSSInfoBot> Property 'display' applies to: all elements
    <Zoffix> CSSInfoBot, css applies to width
    <CSSInfoBot> Property 'width' applies to: all elements but non-replaced inline elements, table rows, and row groups

    <Zoffix> CSSInfoBot, css media color
    <CSSInfoBot> Property 'color' belongs to visual media type(s)
    <Zoffix> CSSInfoBot, css media azimut
    <CSSInfoBot> Property 'azimut' belongs to aural media type(s)

    <Zoffix> CSSInfoBot, css value type margin-width
    <CSSInfoBot> Value type 'margin-width' is described on http://www.w3.org/TR/CSS21/box.html#value-def-margin-width
    <Zoffix> CSSInfoBot, css value type counter
    <CSSInfoBot> Value type 'counter' is described on http://www.w3.org/TR/CSS21/syndata.html#value-def-counter

=head1 DESCRIPTION

This module is a L<POE::Component::IRC> plugin which uses
L<POE::Component::IRC::Plugin> for its base. It provides means to lookup
information pertaining to CSS properties (see log snippet in 'SYNOPSIS'
above)

It accepts input from public channel events, C</notice> messages as well
as C</msg> (private messages); although that can be configured at will.

=head1 CONSTRUCTOR

=head2 new

    # plain and simple
    $irc->plugin_add(
        'CSSPropertyInfo' =>
            POE::Component::IRC::Plugin::CSS::PropertyInfo->new
    );

    # juicy flavor
    $irc->plugin_add(
        'CSSPropertyInfo' =>
      POE::Component::IRC::Plugin::CSS::PropertyInfo->new(
        auto             => 1,
        banned           => [ qr/aol\.com$/i ],
        root             => [ qr/mah.net$/i ],
        addressed        => 1,
        trigger          => qr/^css\s+(?=\S+\s+\S+)/i,
        command_triggers => {
            exists      => qr/^ e (?:xist s?)?                    \s+/xi,
            initial     => qr/^ i (?:nitial)?                     \s+/xi,
            values      => qr/^ v (?:alue s?)?                    \s+/xi,
            inherited   => qr/^ in (?:herit (?:ed)? )?            \s+/xi,
            percentages => qr/^ p (?:ercent (?:age s? )? )?       \s+/xi,
            applies_to  => qr/^ a (?: ppl (?:y|ies))? \s* (?:to)? \s+/xi,
            media       => qr/^ m (?: edia)? \s* (?:type)?        \s+/xi,
            value_type  => qr/^ v (?: alue )? \s* t (?: ypes?)?  \s+/xi,
        },
        response_event   => 'irc_css_property_info',
        listen_for_input => [ qw(public notice privmsg) ],
        eat              => 1,
        debug            => 0,
      )
    );

The C<new()> method constructs and returns a new
C<POE::Component::IRC::Plugin::CSS::PropertyInfo> object suitable to be
fed to L<POE::Component::IRC>'s C<plugin_add> method. The constructor
takes a few arguments, but I<all of them are optional>. The possible
arguments/values are as follows:

=head3 auto

    ->new( auto => 0 );

B<Optional>. Takes either true or false values, specifies whether or not
the plugin should auto respond to requests. When the C<auto>
argument is set to a true value plugin will respond to the requesting
person with the results automatically. When the C<auto> argument
is set to a false value plugin will not respond and you will have to
listen to the events emitted by the plugin to retrieve the results (see
EMITTED EVENTS section and C<response_event> argument for details).
B<Defaults to:> C<1>.

=head3 response_event

    ->new( response_event => 'event_name_to_receive_results' );

B<Optional>. Takes a scalar string specifying the name of the event
to emit when the results of the request are ready. See EMITTED EVENTS
section for more information. B<Defaults to:> C<irc_css_property_info>

=head3 banned

    ->new( banned => [ qr/aol\.com$/i ] );

B<Optional>. Takes an arrayref of regexes as a value. If the usermask
of the person (or thing) making the request matches any of
the regexes listed in the C<banned> arrayref, plugin will ignore the
request. B<Defaults to:> C<[]> (no bans are set).

=head3 root

    ->new( root => [ qr/\Qjust.me.and.my.friend.net\E$/i ] );

B<Optional>. As opposed to C<banned> argument, the C<root> argument
B<allows> access only to people whose usermasks match B<any> of
the regexen you specify in the arrayref the argument takes as a value.
B<By default:> it is not specified. B<Note:> as opposed to C<banned>
specifying an empty arrayref to C<root> argument will restrict
access to everyone.

=head3 trigger

    ->new( trigger => qr/^css\s+(?=\S+\s+\S+)/i );

B<Optional>. Takes a regex as an argument. Messages matching this
regex will be considered as requests. See also
B<addressed> option below which is enabled by default. B<Note:> the
trigger will be B<removed> from the message, therefore make sure your
trigger doesn't match the actual data that needs to be processed including
sub triggers which are set by C<command_triggers> argument (see below).
B<Defaults to:> C<qr/^css\s+(?=\S+\s+\S+)/i>

=head3 command_triggers

    command_triggers => {
        exists      => qr/^ e (?:xist s?)?                    \s+/xi,
        initial     => qr/^ i (?:nitial)?                     \s+/xi,
        values      => qr/^ v (?:alue s?)?                    \s+/xi,
        inherited   => qr/^ in (?:herit (?:ed)? )?            \s+/xi,
        percentages => qr/^ p (?:ercent (?:age s? )? )?       \s+/xi,
        applies_to  => qr/^ a (?: ppl (?:y|ies))? \s* (?:to)? \s+/xi,
        media       => qr/^ m (?: edia)? \s* (?:type)?        \s+/xi,
        value_type  => qr/^ v (?: alue )? \s* t (?: ypes?)?  \s+/xi,
    },

B<Optional>. After the C<trigger> (see above) is matched and B<removed>
a match for a particular "command" will be made. As the case is with
C<trigger> the C<command_triggers> will be B<removed> from the request
string before proceeding thus make sure they don't match the data needed
for the request. That data will be a name of the CSS property for all
the commands except for C<value_type> command for which the data are
CSS value types listed below. The C<command_triggers>
argument takes a hashref with keys being command names and values
being regexes. The B<default> settings are presented in the snippet above.
The commands (keys of the C<command_triggers> hashref) represent the
following commands:

=head4 exists

    exists      => qr/^ e (?:xist s?)?                    \s+/xi,

    <Zoffix> CSSInfoBot, css exists foo
    <CSSInfoBot> Property 'foo' does not seem to exist
    <Zoffix> CSSInfoBot, css exists float
    <CSSInfoBot> Yes, property 'float' does exist

The C<exists> command checks whether or not CSS property exists.

=head4 initial

    initial     => qr/^ i (?:nitial)?                     \s+/xi,

    <Zoffix> CSSInfoBot, css initial bar
    <CSSInfoBot> Property 'bar' does not seem to exist
    <Zoffix> CSSInfoBot, css initial float
    <CSSInfoBot> Initial value for 'float' is none

The C<initial> command lists property's initial values.

=head4 values

    values      => qr/^ v (?:alue s?)?                    \s+/xi,

    <Zoffix> CSSInfoBot, css values position
    <CSSInfoBot> Property 'position' accepts: static | relative | absolute | fixed | inherit

The C<values> command lists valid values accepted by CSS property. Those
will be either literal values or "value types". The link describing certain
value type can be obtained by inquiring the plugin's C<value_type> command
(see below).

=head4 inherited

    inherited   => qr/^ in (?:herit (?:ed)? )?            \s+/xi,

    <Zoffix> CSSInfoBot, css inherited color
    <CSSInfoBot> Yes, 'color' is inherited
    <Zoffix> CSSInfoBot, css inherited display
    <CSSInfoBot> No, 'display' is not inherited

The C<inherited> command tells one whether or not a certain CSS property's
values are inherited or not.

=head4 percentages

    percentages => qr/^ p (?:ercent (?:age s? )? )?       \s+/xi,

    <Zoffix> CSSInfoBot, css percentages width
    <CSSInfoBot> Percentages for 'width' refer to refer to width of containing block
    <Zoffix> CSSInfoBot, css percentages display
    <CSSInfoBot> Percetages do not apply to 'display'

The C<percentages> command tells one to what do the percentage values
for the property refer to.

=head4 applies_to

    applies_to  => qr/^ a (?: ppl (?:y|ies))? \s* (?:to)? \s+/xi,

    <Zoffix> CSSInfoBot, css applies to display
    <CSSInfoBot> Property 'display' applies to: all elements
    <Zoffix> CSSInfoBot, css applies to width
    <CSSInfoBot> Property 'width' applies to: all elements but non-replaced inline elements, table rows, and row groups

The C<applies_to> command tells one to which elements the specified property
applies.

=head4 media

    media       => qr/^ m (?: edia)? \s* (?:type)?        \s+/xi,

    <Zoffix> CSSInfoBot, css media color
    <CSSInfoBot> Property 'color' belongs to visual media type(s)
    <Zoffix> CSSInfoBot, css media azimut
    <CSSInfoBot> Property 'azimut' belongs to aural media type(s)

The C<media> command tells one to which media type a certain property
belongs.

=head4 value_type

    value_type  => qr/^ v (?: alue )? \s* t (?: ypes?)?  \s+/xi,

    <Zoffix> CSSInfoBot, css value type margin-width
    <CSSInfoBot> Value type 'margin-width' is described on http://www.w3.org/TR/CSS21/box.html#value-def-margin-width
    <Zoffix> CSSInfoBot, css value type counter
    <CSSInfoBot> Value type 'counter' is described on http://www.w3.org/TR/CSS21/syndata.html#value-def-counter

Lastly, the C<value_type> command. It takes "value types" as an argument
as opposed to CSS properties and simply returns a URI pointing to the
documentation describing the value type. Possible value types are these:

    margin-width
    absolute-size
    number
    time
    string
    border-width
    border-style
    frequency
    identifier
    color
    integer
    specific-voice
    relative-size
    generic-voice
    padding-width
    angle
    percentage
    family-name
    uri
    length
    generic-family
    shape
    counter

=head3 addressed

    ->new( addressed => 1 );

B<Optional>. Takes either true or false values. When set to a true value
all the public messages must be I<addressed to the bot>. In other words,
if your bot's nickname is C<Nick> and your trigger is
C<qr/^trig\s+/>
you would make the request by saying C<Nick, trig a float>.
When addressed mode is turned on, the bot's nickname, including any
whitespace and common punctuation character will be removed before
matching the C<trigger> (see above). When C<addressed> argument it set
to a false value, public messages will only have to match C<trigger> regex
in order to make a request. Note: this argument has no effect on
C</notice> and C</msg> requests. B<Defaults to:> C<1>

=head3 listen_for_input

    ->new( listen_for_input => [ qw(public  notice  privmsg) ] );

B<Optional>. Takes an arrayref as a value which can contain any of the
three elements, namely C<public>, C<notice> and C<privmsg> which indicate
which kind of input plugin should respond to. When the arrayref contains
C<public> element, plugin will respond to requests sent from messages
in public channels (see C<addressed> argument above for specifics). When
the arrayref contains C<notice> element plugin will respond to
requests sent to it via C</notice> messages. When the arrayref contains
C<privmsg> element, the plugin will respond to requests sent
to it via C</msg> (private messages). You can specify any of these. In
other words, setting C<( listen_for_input => [ qr(notice privmsg) ] )>
will enable functionality only via C</notice> and C</msg> messages.
B<Defaults to:> C<[ qw(public  notice  privmsg) ]>

=head3 eat

    ->new( eat => 0 );

B<Optional>. If set to a false value plugin will return a
C<PCI_EAT_NONE> after
responding. If eat is set to a true value, plugin will return a
C<PCI_EAT_ALL> after responding. See L<POE::Component::IRC::Plugin>
documentation for more information if you are interested. B<Defaults to>:
C<1>

=head3 debug

    ->new( debug => 1 );

B<Optional>. Takes either a true or false value. When C<debug> argument
is set to a true value some debugging information will be printed out.
When C<debug> argument is set to a false value no debug info will be
printed. B<Defaults to:> C<0>.

=head1 EMITTED EVENTS

=head2 response_event

    $VAR1 = {
        'out' => 'Property \'float\' applies to: all, but see http://www.w3.org/TR/CSS21/visuren.html#dis-pos-flo',
        'who' => 'Zoffix!n=Zoffix@unaffiliated/zoffix',
        'what' => 'a float',
        'type' => 'public',
        'channel' => '#zofbot',
        'message' => 'CSSInfoBot_, css a float'
    };

The event handler set up to handle the event, name of which you've
specified in the C<response_event> argument to the constructor
(it defaults to C<irc_css_property_info>) will receive input
every time request is completed. The input will come in a form of a
hashref in C<$_[ARG0]>. The keys/values of that hashref are as follows:

=head3 out

    { 'out' => 'Property \'float\' applies to: all, but see http://www.w3.org/TR/CSS21/visuren.html#dis-pos-flo', }

The C<out> key will contain the "information message", this will be
the response string containing the response to the particular command
and this will be what will be sent to IRC if C<auto> argument to constructor
is set to a true value.

=head3 what

    { 'what' => 'a float' }

The C<what> key will contain the command and the data associated with it.
In other words what the user requested after the C<trigger> was stripped
off, in the sample above the command is C<applies_to> and the property
is C<float>.

=head3 who

    { 'who' => 'Zoffix!n=Zoffix@unaffiliated/zoffix' }

The C<who> key will contain the usermask of the user who sent the request.

=head3 type

    { 'type' => 'public' }

The C<type> key will contain the "type" of the message sent by the
requester. The possible values are: C<public>, C<notice> and C<privmsg>
indicating that request was requested in public channel, via C</notice>
and via C</msg> (private message) respectively.

=head3 channel

    { 'channel' => '#zofbot' }

The C<channel> key will contain the name of the channel from which the
request
came from. This will only make sense when C<type> key (see above) contains
C<public>.

=head3 message

    { 'message' => 'CSSInfoBot_, css a float' }

The C<message> key will contain the message which the user has
sent to request.

=head1 REPOSITORY

Fork this module on GitHub:
L<https://github.com/zoffixznet/POE-Component-IRC-PluginBundle-WebDevelopment>

=head1 BUGS

To report bugs or request features, please use
L<https://github.com/zoffixznet/POE-Component-IRC-PluginBundle-WebDevelopment/issues>

If you can't access GitHub, you can email your request
to C<bug-POE-Component-IRC-PluginBundle-WebDevelopment at rt.cpan.org>

=head1 AUTHOR

Zoffix Znet <zoffix at cpan.org>
(L<http://zoffix.com/>, L<http://haslayout.net/>)

=head1 LICENSE

You can use and distribute this module under the same terms as Perl itself.
See the C<LICENSE> file included in this distribution for complete
details.

=cut