The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package IkiWiki::Plugin::syntax;

use warnings;
use strict;
use Carp;
use utf8;
use Data::Dumper;

use IkiWiki 2.00;

use IkiWiki::Plugin::syntax::gettext;   # fake gettext for the IkiWiki old version
use IkiWiki::Plugin::syntax::Simple;    # the last option
use IkiWiki::Plugin::syntax::X;

our $VERSION    =   '0.25';
our $_syntax    =   undef;

my  %engine_parameters = (
    'Simple'    =>  undef,
    'Kate'      =>  undef,
    'Vim'       =>  undef,
);
my  $use_template   =   0;

# Module implementation here
sub import { #{{{
    ### Define the hooks into ikiwiki ...
    hook(type => 'checkconfig', id => 'syntax', call => \&checkconfig);
	hook(type => "preprocess", id => "syntax", call => \&preprocess);
} # }}}

sub checkconfig {
    
    ### select an engine from the global ikiwiki configuration
    #
    my  $engine = $IkiWiki::config{syntax_engine} || q(Simple);

    ### Read global parameters ...
    $use_template = $IkiWiki::config{syntax_use_template} || 0;

    # search for special parameters (unused)
    foreach my $engine_name (keys %engine_parameters) {
        my  $key    =  "syntax_" . lc($engine_name);
        if (exists $IkiWiki::config{$key}) {
            $engine_parameters{$engine_name} = $config{$key};
        }
    }

    _change_engine( $engine, %{ $engine_parameters{$engine} } );

    return;            
}

sub _change_engine {
    my  ($engine_name, %engine_params) = @_;

    # close the old engine 
    if (defined $_syntax) {
        undef $_syntax;
    }

    ## create a reusable object
    $_syntax = _new_engine( $engine_name );

    if ($_syntax) {
        $_syntax->logging( sprintf 'using the engine %s', $engine_name );
    }
    else {
        error 'could not create a IkiWiki::Plugin::syntax object';
    }
    
    return $_syntax;
}

sub _new_engine {
    my  $name       =   shift;
    my  $engine     =   sprintf 'IkiWiki::Plugin::syntax::%s', $name;
    my  $object     =   undef;

    eval "use ${engine}";

    if (not @_) {
        if (not $object = $engine->new( )) {
            $object = IkiWiki::Plugin::syntax::Simple->new();
        }
    }

    return $object;
}

sub preprocess (@) { #{{{
    my %params = (
        language        =>  undef,      ### plugin parameters
        description     =>  undef,      #
        text            =>  undef,      #
        file            =>  undef,      #
        linenumbers     =>  0,          #
        formatcomments  =>  0,          ###
        force_subpage   =>  0,          ###  Ikiwiki parameters
        page            =>  undef,      #
        destpage        =>  undef,      ###
        @_
    );

    #   engine change ? 
    if (defined( $params{engine} )) {
        _change_engine( $params{engine} );
    }

    #   check parameters
    eval {
        _clean_up_parameters( \%params );
    };

    if ($@) {
        if (my $ex = Syntax::X::Parameters::None->caught()) {
            # show the plugin info
            return _info_response();
        } else {
            return $_syntax->fail_response( $ex );
        }
    }

    $_syntax->logging( sprintf 'set language to %s', $params{language} );

    ### getting syntax highlight in html format ...  
    eval {
        $_syntax->syntax_highlight( %params );
        };

    if (my $ex = $@) {
        return $_syntax->fail_response( $ex );
    }        

    ### decode to utf-8 ...
    # utf8::decode( $syntax_html );

    ###     build the final text with a template named "syntax" or joining html
    #       blocks
    return _build_output( \%params, $_syntax->output() );
} # }}}

sub _build_output {
    my  $params_ref =   shift;
    my  $html_text  =   shift;
    my  %params     =   (
            language    =>  $_syntax->language(),
            title       =>  $params_ref->{title},
            description =>  $params_ref->{description},
            text        =>  $html_text,
            url         =>  defined $params_ref->{file} 
                            ? IkiWiki::urlto( $params_ref->{file}, 
                                              $params_ref->{page} )
                            : undef,
        );

    if ($use_template) {
        # take a template reference             
        my $tmpl = template('syntax.tmpl', default_escape => 0);
        
        # set substitutions variables            
        $tmpl->param( %params );

        # and process ..
        return $tmpl->output(); 
    }
    else {
        return _manual_output( %params );
    }
}

sub _manual_output {
    my  %params     =   @_;
    my  @html       =   ();

    if (defined $params{title}) {
        push(@html, $_syntax->css('title', $params{title}));
    }

    if (defined $params{url}) {
        push(@html, $params{url});
    }

    if (defined $params{text}) {
        push(@html, $_syntax->css('syntax', $params{text} ) );
    }

    if (defined $params{description}) {
        push(@html, $_syntax->css('description', $params{description}) );
    }

    return join("\n", @html);
}

sub _clean_up_parameters {
    my  $params_ref     =   shift;

    # check for the object availability	        
    if (not $_syntax) {
        Syntax::X->throw( 'syntax plugin not available' );
    }
    else {
        $_syntax->reset_engine();
    }

    ## save a selected group of parameters in the engine object
    $_syntax->page( $params_ref->{page} );
    if ($params_ref->{linenumbers}) {
        $_syntax->linenumbers( 1 );
        $_syntax->first_line_number( $params_ref->{linenumbers} );
    }
    if ($params_ref->{bars}) {
        $_syntax->bars( $params_ref->{bars} );
    }

    #   if defined a external source file ...
    if (defined($params_ref->{file})) {
        ### load file content: $params_ref->{file}
        $_syntax->source( readfile(srcfile($params_ref->{file})) );

        ### add depend from file ...
        add_depends($params_ref->{page}, $params_ref->{file});
    }
    elsif (defined($params_ref->{text})) {
        $_syntax->source( $params_ref->{text} );
    }
    else {
        Syntax::X::Parameters::None->throw( gettext(q(missing text or file parameters)) );
    }

    ## check the source language
    if (not $_syntax->detect_language( 
                    proposed => $params_ref->{language},
                    filename => $params_ref->{file},
                    content  => $_syntax->source() )) {
        Syntax::X::Parameters::Wrong->throw( 
            gettext( q(could not determine or manage the source language) ));
    }
    else {
        # save the language
        $params_ref->{language} = $_syntax->language();
    }

    return;
}

sub _info_response {
    my  %info       =   $_syntax->plugin_info();
    my $tmpl        =   template('syntax_info.tmpl');

    $tmpl->param( %info );

    return $tmpl->output();
}

1; 

__END__

=head1 NAME

IkiWiki::Plugin::syntax - Add syntax highlighting to ikiwiki

=head1 SYNOPSIS

In any source page include the following:

    This is the example code 

    [[syntax language=perl text="""
    #!/usr/bin/perl
    
    print "Hello, world\n";
    """]]

    and this is my bash profile (using file type autodetection )

    [[syntax file="software/examples/mybash_profile" 
        description="My profile" ]]

In order to facilitate the life to the administrator the plugin could create a
html table with information about the engine capabilities. 

Use the directive C<syntax> without any parameters as is:

    This is the syntax engine chart in this site:

    [[syntax ]]

=head1 DESCRIPTION

This plugin adds syntax highlight capabilities to Ikiwiki using third party
modules if they are installed. 

Those modules can be:

=over

=item * L<Syntax::Highlight::Engine::Kate>

Uses the Syntax::Highlight::Engine::Kate package, a port to Perl of the
syntax highlight engine of the Kate text editor.

Copyright (c) 2006 by Hans Jeuken, all rights reserved.

=item * L<Text::VimColor>

This plugin uses the Text::VimColor module and the vim editor.

Copyright 2002-2006, Geoff Richards.

=item * L<IkiWiki::Plugin::syntax::Simple>

This is the default engine. It's a passtrough engine with line numering capability.

=back

and they can be selected at runtime with the C<syntax_engine> parameter. In
case of fail loading the module the plugin switch to use the simple engine.

The module register a preprocessor directive named B<syntax>.

=head2 Parameters

The syntax directive has the following parameters:

=over

=item language (optional)

Name of the source language for select the correct plugin. If not defined the
module will try to determine the appropiated value.

=item description (optional)

Text description for the html link 

=item text

Source text for syntax highlighting. Mandatory if not exists the file
parameter.

=item file

Ikiwiki page name as source text for syntax highlighting. The final html
includes a link to it for direct download.

=item linenumbers

Enable the line numbers in the final html.

=item bars

Enable the bars feature. The final html text will be label with css tags on the
odd lines.

=item force_subpage

Parameter for inline funcion to the source page

=back

=head2 CSS

The package uses the following list of css tags:

=over

=item

=back

=head1 METHODS/SUBROUTINES

=head2 checkconfig( )

This method is called by IkiWiki main program and the plugin uses it for load
global configuration values and initialize his internals.

=head2 preprocess( )

This method is called when the ikiwiki parser found a C<syntax> directive.
Without parameters the method show information about the external syntax
parser.

=head1 CONFIGURATION AND ENVIRONMENT

IkiWiki::Plugin::syntax uses the following global parameters:

=over

=item syntax_engine (optional)

Set to a keyword for select the engine to use.

=over

=item Kate

Uses the L<Syntax::Highlight::Engine::Kate> as backend.

=item Vim

Uses the L<Text::VimColor> as backend.

=item Simple

Uses the L<IkiWiki::Plugin::syntax::Simple> as backend.

=back

If this parameter is omitted or the external module fails, the plugin switch to
use the Simple engine.

=item syntax_Kate (optional)

Parameters to configure the engine (not implemented yet).

=item syntax_Vim (optional)

Parameters to configure the engine (not implemented yet).

=item syntax_Simple (optional)

Parameters to configure the engine (not implemented yet).

=back

=head1 DEPENDENCIES

The module needs the following perl packages:

=over

=item L<Module::Build::IkiWiki>

Extension to L<Module::Build> for build and install ikiwiki plugins.

=item L<Class::Accessor::Fast>

=item L<Test::More>

=item L<Exception::Class>

=item L<HTML::Entities>

=item L<HTML::Template>

=item L<URI::Escape>

=back

And it recommends:

=over

=item L<Syntax::Highlight::Engine::Kate>

=item L<Text::VimColor>

=back

=head1 BUGS AND LIMITATIONS

Please, see the included file BUGS for a complete list, and report any bugs or
feature requests to the author.

=head1 FEATURE REQUESTS 

=over 

=item Operate on filenames as wikilinks because the current system works as
pagespecs, and the plugin only operates on a unique page.

Suggested by Steven Black.

=back

=head1 AUTHOR

"Víctor Moral"  C<< victor@taquiones.net >>

=head1 CONTRIBUTORS

=over

=item "Steven Black" C<< yam655@gmail.com >> 

=item "Manoj Srivastava"

=back

=head1 LICENCE AND COPYRIGHT

Copyright (c) 2008, "Víctor Moral".

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.