package CodeCounter::Standards;

use version; $VERSION = qv('0.0.1');

use warnings;
use strict;
use Carp;

use base qw( Exporter );

use XML::Simple;
use Data::Dumper;

our @EXPORT = qw( load_standards );

sub load_standards{
    my ($file) = @_;    
    my $xml_doc = XMLin( $file, ForceArray => 1 );
    return evaluate_standards($xml_doc->{standard});
}

sub evaluate_standards{
    my ($standards) = @_;
    my $std = {};
    # iterate through all elements at this level
    foreach my $standard (values %{ $standards }){
        my $rules = create_rules($standard->{rules}->[0]->{rule},
                                 $standard->{"complex-rules"}->[0]->{"complex-rule"});
        
    
        foreach my $ext (@{ $standard->{extentions}->[0]->{extention} }){
            $std->{ $ext->{value} } = $rules;
        }
    }
    return $std;
}

sub create_rules{
    my($rules, $complex) = @_;
    my %rule_structure;
   
    foreach my $rule (keys %{$rules}){
        $rule_structure{$rule} = build_rule($rules->{$rule}->{value}, $rules->{$rule}->{op}); 
    }
    
    foreach my $rule (keys %{ $complex }) {
        $rule_structure{$rule} = build_complex_rule($complex->{$rule}->{"start-value"},
                                                    $complex->{$rule}->{"stop-value" },
                                                    $complex->{$rule}->{"op"         },)
                                                    ;
    }
    
    return \%rule_structure;
}

sub build_rule{
    my($value, $op) = @_;
    
    my $apply = sub{
        my ($line) = @_;
        if ($line =~ /$value/){
            return $op;
        }
        return "pass";
    };
    return $apply;
}

sub build_complex_rule {
    my($start, $stop, $op) = @_;
    my $found = 0;
    
    my $apply = sub{
        my ($line) = @_;
        
        if($found){
            if ($line =~ /$stop/){
                $found = 0;
                return $op;
            }
            return $op;
        }
        else{
            if ($line =~ /$start/){
                $found = 1;
                return $op;
            }
            return 'pass';
        }
    };
    
    return $apply;
}

1; # Magic true value required at end of module

__END__

=head1 NAME

CodeCounter::Standards - Loads and provides counting standards for CodeCounter

=head1 VERSION

This document describes CodeCounter::Standards version 0.0.1

=head1 SYNOPSIS

    use CodeCounter::Standards; # exports load_standards
    $standards = load_standards('t/standards.xml');
    
    # figure out the files extention
    $file =~ /\.(.+)$/;
    $ext = $1;
    
    # pull the rules for that extention (i.e., Perl rules for .pm files)    
    $rules = $standards->{$ext};
    
    # open up some source file and import the lines into
    # an array
    open $in, "<", $file;
    my @lines = <$in>;
    
    # iterate through the source line by line
    foreach my $line (@lines){
        my $result;
        
        # get rid of whitespace that may interfere    
        $line =~ s/^\s+//;
        $line =~ s/\s+$//;
        
        # apply the rules in order, unless a stop or fail is encounterd    
        for my $rule (keys %{$rules}){ 
            $result = $rules->{$rule}->($line);
            last if $result ne "pass";
        }
        $count++ if $result eq 'pass';
        last if $result eq 'stop'; # stop if stop is encountered.
    }
  
=head1 DESCRIPTION

    This module is designed to pull code counting standards from a 
    formatted XML file and apply pull the rules as needed base on
    file extention.  A dispatch table is returned to the caller so
    the caller can call the rules directly.  Each rule is presented
    as a key value pair matching the rules name in the XML document.

=head1 INTERFACE 

=head2 hash ref load_standards(file_name_and_path)
    This function takes the filename path to the standards XML document,
    reads it, and returns a hash reference.  The hash is keyed on file
    extentions as set forth by code counting standards, where each extention
    references another hash references of rule names and rule subroutines.
    
=head2 $standard->{$ext}->{$rule_name}->($line)
    Each rule name references a subroutine that will validate an input line
    with either a pass, fail, or stop.  Pass means that the line passes the
    rule, fail means it didn't, and stop means that the line has some sort of
    syntactix end of file marker, like '__END__' or '__DATA__'

=head2 hash ref evaluate_standards (XML::Simple structure)

    This method operates like an interpreter, reading in the XML standards document
    and building the extention to rules mapping.

=head2 hash ref create_rules (XML::Simple rules_structure)

    This function takes the XML::Simple rules structure and builds
    a hash reference that maps the rule names defined in the XML
    document and the actual rules implemented as annonymous subroutines.

=head2 sub ref build_rule(pattern_value, operation_type)

    This function returns a subroutine that implements the pattern matching
    logic and is capable of returning a pass value or the operation if the pattern
    matches.

=head2 sub ref build_complex_rule(pattern_start_value, pattern_stop_value, operation_type)

    This function returns a subroutine that implements the pattern matching
    logic and is capable of returning a pass value or the operation if the pattern
    matches.  It returns a the operation_type until after the stop value is passed.
    
=head1 DIAGNOSTICS

    The only errors thus far are pass through by XML::Simple.

=over

=item C<< File not found >>

    You've mistyped the file name or directory path.

=item C<< Other Syntax Errors >>

    If you XML is not well formed you'll be presented with one or more XML syntax errors.
    See XML::Simple and XML::Parser for details.

=back

=head1 CONFIGURATION AND ENVIRONMENT

    CodeCounter::Standards requires a standards XML document.
    A standard for Perl is provided in the /conf directory
    of the CodeCounter distrubution.  Modify this one to meet your
    needs or create you're own.  The file can exist anywhere that
    the program can read from and you can describe.
    
    The format goes like this:
        <standards>
            <!-- name your standard -->
            <standard name="Perl-Java">
                <extentions>
                    <!-- provide any extentions you may support -->
                    <extention value="pm" />
                    <extention value="pl" />
                    <extention value="t"  />
                </extentions>
                <!-- specify and complex, or multi-line, rules you may need -->
                <complex-rules>
                     <complex-rule name="multi-line-comment"   <!-- name as always -->
                                  start-value="^\/\*.+"        <!-- the value for the line that starts it -->
                                  stop-value="\*\/"            <!-- the value for the line the turns it off -->
                                  op="fail" />                 <!-- the operation that is sent while the rule is in effect -->
                </complex-rules>
                <rules>
                    <!-- document any rules you may support, re's are encouraged -->
                    <!-- don't forget a name and the operation if the result matches -->
                    <rule name="whitespace"     value="^$"         op="fail" />
                    <rule name="lone-bracket"   value="^}$"        op="fail" />
                    <rule name="line-comment"   value="^#"         op="fail" />
                    <rule name="_data_"         value="^__DATA__$" op="stop" />
                    <rule name="_end_"          value="^__END__$"  op="stop" />
                </rules>
             </standard>
        [snip...]

=head1 DEPENDENCIES

XML::Simple

=head1 INCOMPATIBILITIES

None reported.

=head1 BUGS AND LIMITATIONS

No known limitations at this time.

Please report any bugs or feature requests to
C<bug-codecounter-standards@rt.cpan.org>, or through the web interface at
L<http://rt.cpan.org>.


=head1 AUTHOR

John Fraser  C<< <raptnor@yahoo.com> >>


=head1 LICENCE AND COPYRIGHT

Copyright (c) 2005, John Fraser C<< <raptnor@yahoo.com> >>. All rights reserved.

This module is free software; you can redistribute it and/or
modify it under the same terms as Perl itself. See L<perlartistic>.


=head1 DISCLAIMER OF WARRANTY

BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
NECESSARY SERVICING, REPAIR, OR CORRECTION.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
