###############################################################################
#
#  WookeeModule.pm
#
#  (C) 2004-2007 Michael Buschbeck <michael@buschbeck.net>
#  $Id$
#
#  Provides a means to embed the output of Perl modules into Wiki pages by
#  adding the following paragraph and character markup:
#
#    {{ ClassName [arguments] }}
#
#  This loads the specified Perl module, instantiates the class of the same
#  name contained in it and calls either its execAsBlock or its execAsChar
#  method, depending on whether the module markup was embedded as paragraph
#  markup or as character markup within a paragraph.
#
#  You should derive the Perl class from the WookeeModule class defined in
#  this module in order to inherit the necessary methods and properties.
#  

use Wookee;


###############################################################################
#
#  class WookeeModule
#
#  Generic superclass for embeddable Wookee modules. You should use this class
#  as the base class for your own embeddable Wookee modules.
#

{
  package WookeeModule;

  use version; our $VERSION = qv('1.103');

  use strict;
  use warnings;

  use constant TRUE  => 1;
  use constant FALSE => 0;

  
  ###########################################################
  #
  #  Static
  #

  use constant propType   => 'inline';  # inline, block
  use constant propOutput => 'raw';     # raw, plain, markup

  
  ###########################################################
  #
  #  new
  #
  #  Instantiates this class or the class of the calling
  #  object. Inheritable.
  #

  sub new
  {
    my $class = shift; $class = ref($class) if ref($class);

    my $self = {};
    return bless($self, $class);
  }


  ###########################################################
  #
  #  exec $arguments, $cgi, $owner
  #
  #  Does whatever this module is supposed to do and returns
  #  text replacing the module markup in the Wiki output.
  #

  sub exec
  {
    my $self      = shift;
    my $arguments = shift;
    my $cgi       = shift;
    my $owner     = shift;

    return undef;
  }


  ###########################################################
  #
  #  execAsBlock $arguments, $cgi, $owner
  #
  #  As before. Called directly by the module markup code if
  #  the module is embedded by paragraph markup. Unless you
  #  override this method, just calls exec.
  #

  sub execAsBlock
  {
    my $self      = shift;
    my $arguments = shift;
    my $cgi       = shift;
    my $owner     = shift;

    return $self->exec($arguments, $cgi, $owner);
  }


  ###########################################################
  #
  #  execAsChar $arguments, $cgi, $owner
  #
  #  As before. Called directly by the module markup code if
  #  the module is embedded by character markup. Unless you
  #  override this method, just calls exec.
  #

  sub execAsChar
  {
    my $self      = shift;
    my $arguments = shift;
    my $cgi       = shift;
    my $owner     = shift;

    return undef
      if $self->propType eq 'block';
    
    return $self->exec($arguments, $cgi, $owner);
  }


  ###########################################################
  #
  #  [static] getFormStart %param
  #
  #  Returns the HTML start tags for a form. Includes hidden
  #  form fields for all vital internal information required
  #  by the Wiki.
  #

  sub getFormStart
  {
    my $class = shift; $class = ref($class) if ref($class);
    my %param = @_;

    $param{method} = 'get'
      unless $param{method};
    $param{action} = $UseModWiki::ScriptName;

    my $result;
    $result  = join('', map(qq[ $_="$param{$_}"], keys(%param)));
    $result  = qq[<form$result>];
    $result .= qq[<input type=hidden name="keywords" value="$UseModWiki::OpenPageName">];

    return $result;
  }

  
  ###########################################################
  #
  #  [static] getFormEnd
  #
  #  Returns the HTML end tag for a form. Added only as a
  #  nice counterpart to the previous function.
  #

  sub getFormEnd
  {
    return qq[</form>];
  }


  ###########################################################
  #
  #  [static] load $module
  #
  #  Loads the given module. Returns an error string if an
  #  error occurred in the attempt to load the module.
  #

  sub load
  {
    my $class  = shift; $class = ref($class) if ref($class);
    my $module = shift;

    return "[Module name contains invalid characters]"
      if $module =~ /[^\w:]/;
    
    eval qq[use $module];
    my $error = $@;
    
    if ($error) {
      $error =~ s[\n.*] []s;
      $error =~ s[\(\@INC contains: .*?\)\s*] [];
      $error =~ s[\.$] [];  # Hint for Vim syntax highlighting: ]]
      
      return "[Unable to load module: $error]";
    }

    return undef;
  }

  
  ###########################################################
  #
  #  [static] run $method, $arguments, $cgi, $owner
  #
  #  Instantiates an object of this class and executes
  #  the method with the given name of it. Returns the
  #  processed function results. Expects that the module
  #  was loaded first.
  #

  sub run
  {
    my $class     = shift; $class = ref($class) if ref($class);
    my $method    = shift;
    my $arguments = shift;
    my $cgi       = shift;
    my $owner     = shift;

    my $object = $class->new();
    my $result;

       if ($method eq 'execAsBlock') { $result = $object->execAsBlock($arguments, $cgi, $owner) }
    elsif ($method eq 'execAsChar')  { $result = $object->execAsChar ($arguments, $cgi, $owner) }
    else { return "[Unsupported method $method requested]" }

    return "[Unable to execute module in this context]" 
      unless defined($result);
    
    return $result
      if $class->propOutput eq 'raw';

    if ($class->propType eq 'block') {
      my $classBlock;
         if ($class->propOutput eq 'plain' ) { $classBlock = 'BlockUnparsedPreformatted' }
      elsif ($class->propOutput eq 'markup') { $classBlock = 'BlockWiki' }
      return $classBlock->new($owner)->parseBlock($result);
    }

    elsif ($class->propType eq 'inline') {
      if ($class->propOutput eq 'plain') {
        return BlockUnparsed->new($owner)->parseBlock($result);
      }

      elsif ($class->propOutput eq 'markup') {
        my $paragraph;
        $paragraph = Paragraph->new($owner);
        $paragraph->addParsed($result);
        return $paragraph->result();
      }
    }
    
    return undef;
  }
}


###############################################################################
#
#  class ParagraphModule
#
#  Implements the Perl module embedding functionality as paragraph markup.
#  Advantages over character markup are that the resulting text can contain
#  HTML block markup without compromising the validity of the resulting HTML.
#

{
  package ParagraphModule;
  use base 'Paragraph';

  use strict;
  use warnings;

  use constant TRUE  => 1;
  use constant FALSE => 0;

  ParagraphModule->register();


  ###########################################################
  #
  #  Static
  #

  use constant propBlockStart  => undef;
  use constant propBlockEnd    => undef;
  use constant propParaStart   => undef;
  use constant propParaEnd     => undef;
  use constant propParaContent => 'BLOCK';
  use constant propNested      => ();
  use constant propChar        => undef;


  ###########################################################
  #
  #  [static] markupParse  $chunk, [$owner]
  #  [static] markupParse \$chunk, [$owner]
  #
  #  Checks whether the given chunk starts with markup for
  #  embeddable modules. If so, loads the module and checks
  #  whether it returns block markup. Recognizes this as
  #  valid embeddable module markup only if that is the case.
  #

  sub markupParse
  {
    my $class = shift;
    my $chunk = shift;
    my $owner = shift;
    
    my $refChunk = ref($chunk) ? $chunk : \$chunk;

    return undef
      unless $$refChunk =~ /^\{\{\s*([\w:]+)/;

    my $module = $1;

    my $error = WookeeModule->load($module);
    return undef
      if $error;

    return undef
      unless $module->propType eq 'block';

    $$refChunk =~ s/\{\{\s*//;
    return $class->new($owner);
  }

  
  ###########################################################
  #
  #  addParsed $chunk
  #
  #  Interprets the given text chunk as a Perl module name
  #  and an optional argument string, loads the module,
  #  executes it and returns its results.
  #

  sub addParsed
  {
    my $self  = shift;
    my $chunk = shift;

    return
      unless length $chunk;

    $chunk =~ s[\s*\}\}\s*$] [];  # Hint for Vim syntax highlighting: ]]

    my ($module, $arguments) = split(/\s+/, $chunk, 2);

    my $result = WookeeModule->load($module);
    $result = $module->run('execAsBlock', $arguments, $UseModWiki::q, $self->{owner})
      unless $result;
      
    $self->addUnparsed($result);
  }
}


###############################################################################
#
#  class CharacterModule
#
#  Implements the Perl module embedding functionality as character markup.
#  Advantages over paragraph markup are that the results can be embedded as
#  inline text in other paragraphs.
#

{
  package CharacterModule;
  use base 'Character';

  use strict;
  use warnings;

  use constant TRUE  => 1;
  use constant FALSE => 0;

  CharacterModule->register();


  ###########################################################
  #
  #  Static
  #

  use constant propNested => 'CharacterModule';

  
  ###########################################################
  #
  #  tokenFind $text
  #
  #  Finds the start or end of module embedding markup in
  #  the given line, and returns its position and length.
  #

  sub tokenFind
  {
    my $self = shift;
    my $text = shift;

    return ()
      unless $text =~ /(\{\{)|(\}\})/;

    return ($-[0], 2)    if $1;
    return ($-[0], 2, 1) if $2;
  }


  ###########################################################
  #
  #  tokenClose $chunk
  #
  #  Interprets the chunk as a module name and its arguments
  #  and executes the module, returning its output.
  #

  sub tokenClose
  {
    my $self  = shift;
    my $chunk = shift;

    pop @{$self->{tokenStack}};

    my ($module, $arguments) = split(/\s+/, $chunk, 2);

    $arguments =~ s/\s*$//
      if defined($arguments);

    my $result = WookeeModule->load($module);
    $result = $module->run('execAsChar', $arguments, $UseModWiki::q, $self)
      unless $result;
    
    return $result;
  }
}


1;

