#
# WikiVar  -  A class for doing Wiki style variable processing on text.
# by Alan M. Carroll
# http://network-geographics.com
#
# Version 1.2.4
# 04 Aug 2012
#
# Copyright (c) 2008-2012, Network Geographics, Inc.
# Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.
# -----------------------------------------------------------------------------
use strict;
# -----------------------------------------------------------------------------
package Text::WikiVar::CodeRef;
# This is a specialized class for handling references to subroutines.
# Bad things happen with AUTOLOAD if we try to capture a CODE reference before
# the symbol is defined. As a result, we need to pass in an object that has
# the name and can cache a CODE pointer so that we can lazy eval the CODE
# reference and potentially pull in the supporting package to avoid AUTOLOAD
# entirely.

# Two members, the fully qualified name and the CODE reference
use constant NAME_IDX => 0;
use constant CODE_IDX => 1;

# First argument is fully qualified name.
# Optional second is a CODE pointer. This is useful for closures
sub new {
    bless [ $_[1], $_[2]], $_[0];
}

sub name {
    my ($self, $value) = @_;
    return defined($value) ? ($self->[NAME_IDX] = $value) : $_[0]->[NAME_IDX];
}

# Get a CODE pointer from the CodeRef.
# Returns a CODE object if it found the subroutine, or a string with
# an error condition if not.
sub getCode {
    my ($self) = @_;
    my $status;

    if (not $self->[CODE_IDX]) { # no cached pointer
        no strict 'refs';
        # see if it's there already
        my $name = $self->[NAME_IDX];
        $self->[CODE_IDX] = *{$name}{CODE};
        if (not $self->[CODE_IDX]) {
            # not there, try to bring in the package
            $self->[NAME_IDX] =~ m!(.*)::(?:[^:]+)$!o;
            my $p = $1; # package name
            eval "require $p;";
            if ($@) {
                $status = 'Defun Error: Failed to load package "' . $p
                        . '" for subroutine "' . $name
                        . '" because "' . $@ . '"';
            } else {
                $self->[CODE_IDX] = *{$name}{CODE};
                if (not $self->[CODE_IDX]) {
                    $status = 'Defun Error: Undefined subroutine "'.$name.'"';
                }
            }
        }
    }
    return $status ? $status : $self->[CODE_IDX];
}
# -----------------------------------------------------------------------------
package Text::WikiVar::Arguments;
# Struct for holding the arguments to a WikiVar function.
# It can be used as an array reference to access required arguments, with
# argv[0] being the WikiWord and argv[1]..argv[n] the arguments supplied in
# the text. argc() returns the number of positional arguments.
# The structure can be dereferenced as a hash to access named and keyword
# arguments. Keyword arguments always have a value of '1' if present, undefined
# if not. 
#
# Funky implementation notes
# We use an array as the base implementation type but we can't use that
# directly because we want to override subscripting for clients. The actual
# blessed object is a reference to a reference to the array payload. We can
# then use scalar dereference in methods to get to the array payload,
# bypassing the subscript overload.

# Class array indices
# Note: The array is hand constructed so any changes here require the logic in
# the new method to be updated.
use constant POSITIONAL => 0;    # Positional arguments
use constant NAMED => 1;         # Named and keyword arguments
use constant WIKIVAR => 2;       # WikiVar instance.
use constant ARG_TEXT => 3;      # Literal argument text.
use constant RAW_TEXT => 4;      # Literal invocation text, including delimiters.
use constant REQUIRED_ARGS => 5; # Arguments 1..N only as an array (cache)

use overload '%{}' => sub { return ${$_[0]}->[NAMED]; },
             '@{}' => sub { return ${$_[0]}->[POSITIONAL]; }
             ;

sub new {
    my $class = shift;
    $class = ref($class) || $class; # force to string (name of class)
    my ($wv, $word, $required, $named, $raw_text, $arg_text) = @_;
    my $payload = [ [ $word, @$required ] , $named, $wv, $arg_text, '', $required ];
    my $self = \$payload;
    bless $self, $class;
    return $self;
}

# Required argument value
sub argv {
    my ($self, $n) = @_;
    return $self->[$n];
}

# Number of positional arguments
sub argc { return scalar ${$_[0]}->[POSITIONAL]; }

# Value for named argument.
sub value {
    my ($self, $name) = @_;
    return $self->{$name};
}

# WikiWord invoked.
sub word { return $_[0]->[0]; }

# For each name in the arguments, force that named argument (if present)
# to be an array.
sub force_array {
    my $payload = ${shift()};
    my $args = $payload->[NAMED];
    for my $key (@_) {
        $args->{$key} = [$args->{$key}] if exists $args->{$key} and ref $args->{$key} ne 'ARRAY';
    }
}

# Return a list of the required arguments without the WikiWord.
sub required_args {
    my $payload = ${shift()};
    return $payload->[REQUIRED_ARGS];
}

# Raw (literal) argument text
sub arg_text {
    my $payload = ${shift()};
    return $payload->[ARG_TEXT];    
}

# Get/set raw invocation text.
sub raw_text {
    my $payload = ${shift()};
    $payload->[RAW_TEXT] = shift if @_;
    return $payload->[RAW_TEXT];
}

# Get the WikiVar instance
sub wikivar {
    my $payload = ${shift()};
    return $payload->[WIKIVAR];
}
# -----------------------------------------------------------------------------
package Text::WikiVar;
our $VERSION = '1.2.4';

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

# A bit ugly, but Class::Struct, fields, and Class::Struct::Fields don't do default values and
# private fields.

BEGIN {
    # Field for a WikiVar instance.
    our %_FIELDS = ( DELIMITER => { default => '$' }        # Character that marks WikiVar expressions
                   , KEYWORD_MARK => { default => '' }      # Character that marks a keyword argument to a function.
                   , PRE_ERROR_TEXT => { default => '<!-- WikiVar Error: ' }    # Lead text for error output
                   , POST_ERROR_TEXT => { default => ' -->' }   # Trail text for error output
                   , VALUE_RXP => { private => 1 }            # A value
                   , ARG_RXP => { private => 1}           # An argument to a function
                   , ARG_PARSE_RXP => { private => 1 }     # Pick apart an argument for handling
                   , INVOCATION_RXP => { private => 1 }    # A WikiVar invocation
                   , SCOPE => { default => [] , private => 1 }             # Stack of scopes
                   , TABLE => { default => {} , private => 1 } # Symbol table
                   , FALLBACK => { }                        # Fallback to call when a WikiVar name is not found.
                   );

    # I originally used the Enum package, but that's not installed by default
    # and it only saves me one line of code (I have to walk over the fields
    # anyway, to get the index data correct). Also, Enum uses closures instead
    # of eval, so I am not sure its values get optimized by the compiler.
    no strict "refs";
    my $idx = 0;
    foreach my $key (sort(keys(%_FIELDS))) {
        $_FIELDS{$key}{index} = $idx;
        *{"$key"} = eval "sub () { $idx }"; # use eval to force the constant, avoid a closure
        ++$idx;
    }
    use strict "refs";
}
our %_FIELDS; # Declaration from BEGIN doesn't propagate, so we have to declare again (it's still the same variable)
# Special name used to scope fallback assignments
use constant FALLBACK_NAME => '*Fallback';

## Class constants
# The format for a name, either a function or an argument
our $NAME_RXP = qr/[_[:alpha:]](?:-?[_[:alpha:][:digit:]])*/o;
# Base set of symmetric quote marks accepted
our $QUOTE_MARKS = q('"@`~!#%^&*=+;:/\\?*|$);
# Table of supported assymmetric quotes
our %QUOTE_TABLE = ( '[' => ']' , '(' => ')' , '{' => '}' , '<' => '>' );
# ---------------------------------------------------------------------------
sub new {
    my $class = shift;
    $class = ref($class) || $class; # force to string (name of class)
    my $self = [];
    bless $self, $class;

    # Convert argument keys to upper case.
    my $flip = FALSE;
    my %args = map { $flip = not $flip; $flip ? uc : $_; } @_;

    # Initial values. Use argument if present, otherwise the default if available
    while (my ($key, $value) = each %_FIELDS) {
        my $idx = $value->{index};
        if (not $value->{private} and exists $args{$key}) {
            $self->[$idx] = $args{$key};
        } elsif (exists $value->{default}) {
            $self->[$idx] = $value->{default};
        }
    }

    # Build up the regular expressions we need.
    # We do many of these in two steps because we don't do the string construction
    # in the same line that we compile it to a RE.
    my $marks = $QUOTE_MARKS;
    my $dl = $self->[DELIMITER]; # non-RE base delimiter.
    my $dl_right;
    my $dl_left = quotemeta($dl);
    if (exists $QUOTE_TABLE{$dl}) {
        $dl_right = quotemeta($QUOTE_TABLE{$dl});
    } else {
        $dl_right = $dl_left;
    }
    # Clip delimiter from any quote matching
    $marks =~ s/$dl_left//;
    my %quote_table = %QUOTE_TABLE; # copy so we don't mess up other instances when we clip.
    delete $quote_table{$dl};
    # Clip out the keyword mark if set (only symmetric quotes supported)
    my $kw = $self->[KEYWORD_MARK];
    if ($kw) {
        $kw = quotemeta($kw);
        $marks =~ s/$kw//;
        $kw .= $NAME_RXP;
    } else {
        $kw = ''; # force to null string so $kw is always a string after this
    }
    # RE that matches a value quoted by any of the allowed symmetric quote marks
    my $quoted_value_rxp = join('|', map { my $c = quotemeta($_); $c.'[^'.$c.']*'.$c} split(//,$marks));
    # Generate an RE for the assymmetric quotes
    my @paren_rxp;
    while (my ($left, $right) = each %quote_table) {
        push(@paren_rxp, "[$left][^$left$right]*[$right]");
    }
    my $paren_rxp = join('|', @paren_rxp);
    $paren_rxp = qr/$paren_rxp/;
    # Any quoted value, symmetric or asymmetric
    $self->[VALUE_RXP] = qr<$quoted_value_rxp|$paren_rxp>;
    # An argument to an invocation, syntax checking version
    my $text = "(?:$NAME_RXP\\s*)?" . $self->[VALUE_RXP];
    $text = '(?:' . $text . ')|(?:' . $kw . ')' if $kw;
    $self->[ARG_RXP] = qr<$text>;
    # An argument to an invocation, processing version
    $text = "(?:($NAME_RXP)\\s*)?(" . $self->[VALUE_RXP] . ')';
    $text = '(?:' . $text . ')|(' . $kw . ')' if $kw;
    $self->[ARG_PARSE_RXP] = qr<$text>;
    # If the delimiter is symmetric we want to match double instances and convert them to a single instance
    # as a convenience. We do that by marking the content with a 0,1 marker and checking for it being empty
    # during processing. We do not do this if the delimiter is assymmetric.
    $text = $dl_left . '(?:' . '(' . $NAME_RXP . ')(?:(=' . $self->[VALUE_RXP] . ')|((?:\s*' . $self->[ARG_RXP] . ')*)))';
    $text .= '?' if $dl_left eq $dl_right;
    $text .= $dl_right;
    $self->[INVOCATION_RXP] = qr<$text>;

    return $self;
}

# Now we can create a static instance for convenience.
our $STATIC_INSTANCE = __PACKAGE__->new();

# ---------------------------------------------------------------------------
# Self extraction for class methods.
# Handle three cases:
# - Called directly as a subroutine (Text::WikiVar::method)
# - Called as class method (Text::WikiVar->method)
# - Called from instance ($instance->method)
# An appropriate value for $self is returned, and removed from the argument
# list if present.
sub _self {
    my $self = $_[0][0]; # Get the first argument
    if (not ref $self) { # not a reference, need to fix it up
        shift @{$_[0]} if $self eq __PACKAGE__; # drop only if it's the package
        $self = $STATIC_INSTANCE; # use static instance
    } else {
        shift @{$_[0]}; # was an instance, so drop it
    }
    return $self;
}
# ---------------------------------------------------------------------------
sub assign ($$$) {
    my $self = _self(\@_);
    my ($name, $value) = @_;

    my $table = $self->[TABLE]; # should be hash ref
    my $scope = $self->[SCOPE]; # stack of hash refs
    my $status = '';

    if ($name =~ m/^$NAME_RXP$/) { # verify legal name
        # fault in the definition hash if it's not there.
        if (ref $table ne 'HASH') {
            $table = {};
            $self->[TABLE] = $table;
        }

        # Store the old value so we can unwind this scope later.
        # Don't save the value if it's already there. That means it's
        # already been changed in this scope.
        # Note that if it wasn't defined already then we'll store undef in the
        # scope table. This indicates that we need to delete the value when
        # the scope is exited.
        # Note that scope is a stack with the innermost scope at
        # index 0.
        $scope->[0]->{$name} = $table->{$name} if $scope and not exists $scope->[0]->{$name};

        if (not defined($value)) {
            delete $table->{$name}; # assigning undef deletes assignment
        } else {
            # be backwards compatible for now
            $value = new Text::WikiVar::CodeRef('*',$value) if ref $value eq 'CODE';

            if (! ref $value || $value->isa('Text::WikiVar::CodeRef')) {
                $table->{$name} = $value;
            } else { # not a valid value
                $status = 'Bad value type - "' . ref($value)
                        . '" - in WikiVar assignment to variable "' . $name . '"';
            }
        }
    } else {
        $status = 'Invalid name for function: "' . $name . '"';
    }
    return $status ? ( $self->[PRE_ERROR_TEXT] . $status . $self->[POST_ERROR_TEXT] ) : '';
}

sub fallback {
    my $self = _self(\@_);
    if (scalar @_){
        my $value = shift;
        my $scope = $self->[SCOPE];
        $scope->[0]->{FALLBACK_NAME()} = $self->[FALLBACK] if $scope and not exists $scope->[0]->{FALLBACK_NAME()};
        if (ref $value eq 'CODE') {
            $self->[FALLBACK] = new Text::WikiVar::CodeRef(FALLBACK_NAME,$value);
        } elsif (not ref $value) {
            $self->[FALLBACK] = $value;
        } elsif (value->isa('Text::WikiVar::CodeRef')) {
            $value->name(FALLBACK_NAME);
            $self->[FALLBACK] = $value;
        } else {
            warn "Fallback not set because value was an inappropriate type.\n";
        }
    } else {
        return $self->[FALLBACK];
    }
}

sub _invoke {
    my ($name, $func)  = (shift,shift);
    my $output = '';
    my $errText;
    if (not ref $func) { # simple text, just use it
        $output = $func;
    } elsif (ref $func eq 'CODE') {
        $output = eval { &$func(@_); }; # wrap in eval to trap exceptions
        $errText = 'Error: Invocation of "' . $name
                 . '" failed because "' . $@ . '"'
                 if $@; # failure during invocation
    } elsif ($func->isa('Text::WikiVar::CodeRef')) { # something to call
        my $code = $func->getCode();
        if (ref $code eq 'CODE') { # found the code
            $output = eval { &$code(@_); }; # wrap in eval to trap exceptions
            $errText = 'Error: Invocation of "' . $name
                     . '" as defun "' . $func->name
                     . '" failed because "' . $@ . '"'
                     if $@; # failure during invocation
        } else { # something went wrong, use as error text
            $errText = "Error: CodeRef for '$name' has bad type: " . ref($code);
        }
    } else { # something went wrong, use as error text
        $errText = "Error: WikiVar '$name' has bad type: " . ref($func);
    }
    return ( $errText, $output );
}

# Invoke a macro with arguments.
# Parameters:
#    Macro Name
#    Argument structure
sub invoke {
    my $self = _self(\@_);
    my $name = shift;
    my $output = '';
    my $errText = '';
    my $macro;

    # get our table out of the context
    my $table = $self->[TABLE];

    if (ref $table eq 'HASH' && defined ($macro = $table->{$name})) {
        ($errText, $output) = _invoke($name, $macro, @_);
    } elsif ($self->[FALLBACK]) {
        ($errText, $output) = _invoke($name, $self->[FALLBACK], @_);
    } else {
        $errText = 'Error: WikiVar "' . $name . '" not defined' if not exists $table->{$name};
    }
    $errText = $self->[PRE_ERROR_TEXT] . $errText . $self->[POST_ERROR_TEXT] if $errText;
    return wantarray() ? ($output , $errText) : ($errText ? $errText : $output);
}

# This sets up a new scope which saves old values of macros. These values
# are restored by a LeaveScope call.
sub enter_scope {
    my $self = _self(\@_);
    my $scope = $self->[SCOPE];
    if (ref $scope ne 'ARRAY') {
        $self->[SCOPE] = [ {} ]; # seed original array of single hash
    } else {
        unshift @$scope, {}; # push an empty hash to save changes in scope
    }
}

# Restore saved values for macros.
sub leave_scope {
    my $self = _self(\@_);
    my $scope = $self->[SCOPE];
    if ($scope) { # is there a scope to leave?
        my $table = $self->[TABLE];
        my $values = shift @$scope;
        # For each value in the scope, if it's set restore $table
        # to that otherwise delete the element from table
        foreach my $key (keys %$values) {
            my $v = $values->{$key};
            if ($v) {
                $table->{$key} = $v;
            } else {
                delete $table->{$key};
            }
        }
    }
}

# Takes an argument string and returns an argument object.
sub string_to_args {
    my $self = _self(\@_);
    my ($word, $text, $flags) = @_;
    my @required;
    my %named;
    my $rxp = $self->[ARG_PARSE_RXP];

    while ($text =~ /$rxp/g) {
        my $name = $1; # save these before they get scrozzled
        my $value = $2;
        my $keyword = $3;

        if ($keyword){
            $named{$keyword} = 1;
        } else {
            $value = substr($2, 1, length($2)-2); # strip quotes
            $value = $self->process($value, $flags);
            if ($name) {
                if (exists $named{$name}) {
                    if (ref $named{$name} eq 'ARRAY') { push(@{$named{$name}}, $value); }
                    else { $named{$name} = [ $named{$name} , $value ]; }
                } else {
                    $named{$name} = $value;
                }
            } else {
                push(@required, $value);
            }
        }
    }
    return Text::WikiVar::Arguments->new( $self, $word, \@required , \%named, $text );
}

# Perform text substitution for a single invocation match
sub _process {
    my ($self, $flags, $raw, $name, $assign_text, $arg_text) = @_;
    my $zret = '';

    if (not $name) {
        # Matched empty content -> it's a double delimiter.
        # Generate a single delimiter as the result.
        $zret = $self->[DELIMITER];
    } elsif ($assign_text) {
        my $value = '';
        $value = $self->process(substr($assign_text, 2, length($assign_text) - 3), $flags) if length $assign_text > 2;
        $self->assign($name, $value);
    } else { # if it's not an assignment, it's a call
        $arg_text = '' unless defined($arg_text);
        my $args = $self->string_to_args($name, $arg_text, $flags);
        $args->raw_text($raw);
        $zret = $self->invoke($name, $args);
    }
    return $zret;
}

# Process the text, substituting the results for the invocations.
# Parameters
#    Text
#    Flags
#        scope - create a scope around this transform

sub process {
    my $self = _self(\@_);
    my ($zret, $flags) = @_;
    my $rxp = $self->[INVOCATION_RXP];
    $flags = {} if ref $flags ne 'HASH'; # force hash ref

    my $inScope = $flags->{scope};
    delete $flags->{scope}; # don't pass this down via recursion
    $self->enter_scope() if $inScope;

    $zret =~ s/$rxp/$self->_process($flags, $&, $1, $2, $3)/eg;

    if ($inScope) {
        $self->leave_scope();
        $flags->{scope} = $inScope; # restore original state of flags
    }

    return $zret;
}

sub error_bounding_text {
    my $self = _self(\@_);
    return ($self->[PRE_ERROR_TEXT], $self->[POST_ERROR_TEXT]);
}

# Return the left (leading) delimiter.
sub left_delimiter {
    my $self = _self(\@_);
    return $self->[DELIMITER];    
}
# return the right (trailing) delimiter;
sub right_delimiter {
    my $self = _self(\@_);
    my $delim = $self->[DELIMITER];
    return $QUOTE_TABLE{$delim} || $delim;
}

# Return our version
sub Version {
    return $VERSION;
}

1;
__END__


=pod

=head1 Name

B<WikiVar>

=head1 Synopsis

WikiVar is a text formatter that provides roughly the same functionality as both
Wiki variables and macros. That is, both simple text substitution and code
invocation. Text substitutions (macros) can be defined programmatically or
inline. Code invocations ("functions") can only be defined programmatically but
used inline (thereby providing a mechanism for calling pre-existing Perl
subroutines from text input).

Stylistically WikiVar is based on Wiki variables but extends the syntax to
provide better function invocation capabilities. It supports both positional,
named, and keyword arguments with robust quoting mechanisms.

=head1 Description

=head2 Background

WikiVar was originally intended as a module for an actual Wiki to improve
variable handling, which for that Wiki code was scattered among numerous regular
expressions in many modules, leading to a disturbing lack of consistency.
Instead of that, WikiVar would provide a single place for registering all Wiki
functions and, because it was easy and consistent to add, macros. Bad
interactions between the variables would be minimized because they would all be
processed in one pass, not each in its own separate pass, and much faster for
the same reason. That project was sidelined but I kept WikiVar because
it was useful in other circumstances.

=head2 Overview

WikiVar supports two types of usage, "macros" and "functions".
Stylistically these are very similar, but the implementation / API differ.

Macros are simple text substitution. The invocation of a macro is replaced by
the text for that macro. Macros can be defined inline or via the API. This makes
them handy for predefined terms or local abbrevations.

Functions are calls to Perl subroutines. Arguments (both positional and named)
are passed to the subroutines. The invocation is replaced by the output
of the subroutine. Functions must be defined via the API, they cannot be
assigned inline (only invoked).

=head2 Inline Usage

Inline usage is use of WikiVar by character sequences in the processed text.

=head3 Using a macro inline

The macro "name" is invoked by the character sequence C<$name$>. This set of
characters is replaced by the value of the macro. As a special case, two
adjacent delimiters are converted to a single delimiter. That is, C<$$> is
turned in to C<$>.

=head3 Defining a macro inline

To define the macro "name" to have the value "value", use the text
C<$name=@value@$> where C<@> is a E<delimiter>. The delimiter can be any
printable non-whitespace character that is not a valid macro name character
except C<$>, C< > >, C< ) >, C< ] >, or C< } >. The terminal delimiter is the
same as the initial one, except for C< < >, C< ( >, C< [ >, C < { >. In these
cases the terminal delimiter is C< > >, C< ) >, C< ] >, or C< } > respectively.
The value must not contain any instances of the delimiter. Because of the wide
variety of delimiters that can be used, this is rarely a problem. Even when it
is, there is a work around described after the following examples.

Examples:

=over 4

C<$name="value"$>

C<$name=(value)$>

C<$name=|value|$>

=back

The name must start with a letter or an underscore, and can contain letters, underscores, numbers and dashes. It
may not end with a dash.

Note that no white space is allowed in the syntax. The variable delimiter C<$>
must immediately precede the initial name character and immediately follow the
terminal value delimiter. This avoids problems with the use of the variable
delimiter in normal text.

The actual text of the assignment (everything between the C<$> characters
and those characters as well) is removed completely the output text.

The value itself is evaluated so that any variables in the value are evaluated.
This is intended to allow defining a macro in terms of one or more other macros.
However, it does mean that you can define new macros in the value part of a macro
definition. This nested define must use a different delimiter than the outer define,
limiting the possible recursion depth. However, such use is generally more trouble
than benefit and is mentioned only to warn you to no do it.

There is no provision for escaping characters in the value. This is acceptable
because of the ability to select from a larger number of delimiters. If that's
not sufficient then another macro consisting of just the problematic character can be
defined and used to put the delimiter in to the macro value.

=over 4

C<$dq='"'$> C<$name="A double quote $dq$"$>

=back

=head3 Using a function inline

To invoke the function "func", use the text C<$func$>. This calls the Perl subroutine
associated with the name "func" and replaces the text with the output of the subroutine.

Arguments can be passed. Such arguments are of three types, positional, named,
and keyword. In general, positional arguments are required and named / keyword
arguments are optional, but this is by convention. Each argument value is
delimited in the same way as macro text. Named arguments are indicated by
putting the undelimited name in front of the argument value E<without
intervening whitespace>. Note that the argument name will be separated from the
value by a quote character. Keyword arguments are names with a leading keyword
mark character if keywords have been enabled (keyword arguments are disabled by
default). Named arguments have values, keyword arguments don't, and positional
arguments are just values.

Named and keyword arguments can be in any order, interspersed among the
positional arguments in any fashion, but positional arguments are
passed in the same order as they occur in the text. Named and keyword arguments
are ignored for the purposes of positional arguments, so that the following are
equivalent (presuming ':' has been set as the keyword marking character):

=over 4

C<$func (arg1) 'arg2' <arg3E<gt> name1(value1) name2%value2% :key1 :key2$>

C<$func :key2 {arg1} name2<value2E<gt> "arg2" :key1 name1[value1] (arg3)$>

If multiple named arguments with the same name are found they are grouped
together in a single array value. See C<force_array> for a mechanism to force
the array in all case.

=back

Conventionally the positional arguments are placed first, followed by named
arguments, then keyword arguments. This is simply to reduce confusion and you
can use a different system if that works better for you.

=head2 Programmatic API

WikiVar supports two interfaces, object oriented and functional. These work the
same way except that the functional interface uses a single pre-allocated global
instance while the object oriented styles lets the client create and control
multiple instances. Both of these are useful in different circumstances. The
primary advantage of the OO interface is that the different instances can be set
to have different processing styles (e.g. different variable delimiters) or
different sets of defined macros / functions.

=head3 Text::WikiVar

=head4 Method new

Create a new instance of WikiVar. The argument list is treated as a hash with
the following keys examined.

=over 4

=item DELIMITER

The variable delimiter which must be a single printable
non-alphanumeric/underscore/dash character. Default value is '$'. Setting this
to '%' will make invocations look more like normal Wiki variables. Note that the
value for this will be forbidden for use as a value delimiter (and if set to
something other than '$', then '$' can be used for delimiting values). If the
value is one of the assymmetric delimiters it should be the opening one, the
closing one will be automatically used. The replacement of doubled
delimiters with a single delimiter will not be done for assymmetric delimiters.

=item KEYWORD_MARK

Character mark for keywords. This has the same limitations as
B<DELIMITER> and must not be an assymmetric delimiter. It is removed from the
pool of valid value delimiters. The default is the empty string which disables
keyword arguments. If set, keyword arguments will be processed. Of course, this
and C<DELIMITER> must be different characters.

=item FALLBACK

This is used when text contains what looks like a WikiVar invocation but the
name is not defined. If this is a string, then that string is used as the value
of the variable. If B<FALLBACK> is a C<CODE> reference then it is called as if
the undefined name was a function and B<FALLBACK> the code for that function.
Note that as with C<AUTOLOAD>, this function can change the set of defined
variable names. If an undefined name is use and B<FALLBACK> is not set (the
default) then an error message is generated.

This can be changed later using the C<fallback> method.

The argument object passed in has a C<raw_text> method which can be used to
obtain the original text of the invocation so it is easy to skip the invocation
by replacing the orignal text with itself.

=item PRE_ERROR_TEXT, POST_ERROR_TEXT

If an error occurs, an error message is generated and used as the value of the
variable causing the error. The C<PRE_ERROR_TEXT> and C<POST_ERROR_TEXT> values
are prepended and appended to that message respectively. The defaults are
C<E<lt>!-- WikiVar Error:> and C< --E<gt>> respectively so that if used to
process HTML the errors are findable but not visible.

=back

=head4 Method assign

This defines a macro or a function. The arguments are

=over 4

=item name

The name of the variable. This must consist only of alphanumeric, dash or
underscore characters. The name must start with a letter or an underscore, and
must not end with a dash.

=item value

The value of the variable. This should be one of

=over 4

=item *

A scalar. The result is a macro and the printable version of the value is used as
the substitution text for the macro.

=item *

A reference to a subroutine. The result is a function. When the function is
invoked the subroutine is called with the arguments provided by the inline
invocation as an instance of the C<Text::WikiVar::Arguments> class.

=item *

An instance of C<Text::WikiVar::CodeRef>. This allows a subroutine to be
specified by name. The actual code reference is not accessed until the function
is invoked. This is useful if there are initialization ordering problems and the
module with the subroutine may not be loaded when C<assign> is called. If a
subroutine is defined at the point of assignment, it is generally preferable to
just pass a reference to it instead of using C<CodeRef>.

=item *

C<undef>. This will erase the definition of the variable.

=back

=back

=head4 Method fallback

This has the same semantics as C<assign> except that it stores the variable in a
special slot in the WikiVar instance and invokes it whenever a WikiWord is used
that is not defined. The default behavior in such a case is to generate an
error message as the output text but if the C<fallback> is set then it is called
instead and its return value used.

=head4 Method invoke

This performs the substitution for the variable. The return value is the
substitution string for the variable. The arguments are

=over 4

=item name

The name of the variable.

=item arguments

If I<name> is a not a function then I<arguments> is ignored and may be omitted.
Otherwise the arguments are passed on to the subroutine as is. Since the
function is likely to be expecting a C<Text::WikiVar::Arguments> object you may
want to use the C<string_to_args> method.

=back

=head4 Method string_to_args

Convert an argument I<string> to an instance C<Text::WikiVar::Arguments>. The
string format is as described for inline invocation. The argument list can be
passed directly to a subroutine or passed, along with a name, to C<invoke>. Note
that argument values are processed for further WikiVar substitutions.

=head4 process

This transforms a string, performing all macro substitutions and updating the
context for any new macro definitions. It returns the transformed string. It
takes two arguments.

=over 4

=item text

The text string to transform.

=item flags

A reference to a hash containing processing flags. The currently defined
flags are

=over 4

=item scope

If set to a C<true> value, a scope is entered before processing and
left after processing, isolating any inline definitions in the text.

=back

=back

=head4 Method enter_scope

Creates a new scope for definitions. Any definitions made in this scope can be
reverted with C<leave_scope>. This is handy when processing multiple chunks of
user text so that inline definitions in one chunk do not propagate to other chunks.

=head4 Method leave_scope

Destroys the current definition scope. All definitions (both inline and via C<assign>)
made in the scope are reverted to their state before the scope was entered.

=head3 Text::WikiVar::CodeRef

As noted previously, this class is useful when intialization ordering means that
a subroutine to be used as a function may not be loaded when C<assign> is
called. In that case, a C<CodeRef> wrapper can be used to delay access to the
C<CODE> reference until the function is invoked during text processing.

=head4 Method new

=over 4

=item *

I<name> The fully qualified name of the subroutine.

=back

=head3 Text::WikiVar::Arguments

An instance of this class is passed to the subroutine for a function. It
contains all of the arguments supplied in the text that invoked the function. It
can be treated as an array reference for positional arguments or a hash
reference for named and keyword arguments. If C<$args> is an instance of this
class passed to a function, then

=over 4

=item *

C<$args-E<gt>[0]> is the name of the function.

=item *

C<$args-E<gt>[$n]> is the C<n>th positional argument.

=item *

C<$args-E<gt>{name}> The value for the named or keyword argument C<name>. The value
for a keyword argument is a true value if the keyword was present, and a false
value if not. The value for a named argument is C<undef> if the argument was not
present, the value if it was. The C<exists> keyword can also be used to check if
a named or keyword argument was present.

=back

An instance is also a class with various utility methods.

=over 4

=item *

I<argc> Return the number of positional arguments.

=item *

I<argv> Identical to C<$args-E<gt>[]>. Gotta give my fellow C/C++ programmers some love.

=item *

I<value> The same as C<$args-E<gt>{}> except that the argument is not implicitly
quoted. Useful when the argument name is in a variable and not a literal.

=item *

I<word> The WikiVar variable name invoked. Identical to C<$args-E<gt>[0]> but
a bit clearer in context.

=item *

I<force_array> Force values for named arguments to be C<ARRAY> references. The
arguments to I<force_array> are names that name named arguments. If the value
for one of the names is already an C<ARRAY> reference it is unchanged. If it is
not, it is converted to a reference to an C<ARRAY> of length 1.

=item *

I<required_args> Return a reference to an array of the positional arguments. The
invoking word is not included. Cached so it's very low marginal cost to call
multiple times.

=item *

I<arg_text> Return the raw argument text. This is the string that was processed
to create the arguments.

=item *

I<raw_text> Return the raw invocation text. This is the string which will be
replaced by the function result. If a function returns this value it will have
no net effect on the original text.

For example if the C<FALLBACK> value for a WikiVar instance is set to C<sub { return
$_[0]->raw_text; }> then any WikiVar invocation that doesn't match a defined
name will be effectively ignored.

=item *

I<wikivar> Return the WikiVar instance. When a function is invoked it is in the
context of a specific WikiVar instance, which is accessible via this method.
Using this method and subclasses of WikiVar, additional context can be passed
down to invoked functions.
  
=back

=head4 Method left_delimiter

Returns the character that is the left delimiter for a macro.

=head4 Method right_delimiter

Returns the character that is the right delimiter for a macro.

=head2 Example

Here is some example text illustrating the basic usage.

 $Bob='<a href="mailto:bob@nowhere.com">Bob</a>'$
 $BobHomePage=(<a href="http://nowhere.com/~bob">Home Page</a>)$
 $Bobco="Bob's Organic Software and Vegetables"$

 I first met $Bob$ a about 10 years ago. You can check out his biography at his
 $BobHomePage$. Shortly after I met him he started $Bobco$ to combine his talent
 for software development with his wife's green thumb. $Bobco$ has been a rousing
 success, their "free onion with every download" marketing scheme taking the
 market by storm. I see a good future for $Bob$ and $Bobco$.

After processing, this yields

 I first met <a href="mailto:bob@nowhere.com">Bob</a> a about 10 years ago. You
 can check out his biography at his
 <a href="http://nowhere.com/~bob">Home Page</a>. Shortly after I met him he
 started Bob's Organic Software and Vegetables to combine his talent for software
 development with his wife's green thumb. Bob's Organic Software and Vegetables
 has been a rousing success, their "free onion with every download" marketing
 scheme taking the market by storm. I see a good future for <a
 href="mailto:bob@nowhere.com">Bob</a> and Bob's Organic Software and Vegetables.

=head1 Version History

    0.1: 10 Oct 2003
    1.0: 19 Nov 2004
    1.1 : 16 May 2008
    1.2 : 26 Oct 2008
    1.2.1 : 04 Dec 2008
    1.2.3 : 04 Aug 2012
    1.2.4 : 31 Aug 2012

=head1 Author

    Alan M. Carroll
    https://network-geographics.com

=head1 Additional Credits

The original goal of this project was to extend Textile so that I could do
inline text macros (particularly, predefined ones for links). Originally I had
hacked the Textile source to define the macros, which made maintenance
difficult. WikiVar was started to generalize that. Eventually most of that
functionality was provided by Webiki, but I still use WikiVar for inline macros
and certain Perl based functionality.

While doing the design I started working with Wiki and decided to expand the
functionality enough to handle Wiki variables as well. This was part of an effort
to support Textile formatting in a Wiki. The base logic for handling Wiki
variables was a complete hack, not generalized and scattered about the source
code. WikiVar supports all of the required utility in one place, in an easily
customized, more consistent, and more efficient way.

=head1 Copyright and License

    Copyright (c) 2008-2012 Network Geographics Inc.
    (https://network-geographics.com/)
    All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

*   Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.

*   Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.

*   Neither the name "WikiVar" nor the names of its contributors may
    be used to endorse or promote products derived from this software
    without specific prior written permission.

This software is provided by the copyright holders and contributors "as is"
and any express or implied warranties, including, but not limited to, the
implied warranties of merchantability and fitness for a particular purpose
are disclaimed. In no event shall the copyright owner or contributors be
liable for any direct, indirect, incidental, special, exemplary, or
consequential damages (including, but not limited to, procurement of
substitute goods or services; loss of use, data, or profits; or business
interruption) however caused and on any theory of liability, whether in
contract, strict liability, or tort (including negligence or otherwise)
arising in any way out of the use of this software, even if advised of the
possibility of such damage.

=cut
