# # 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 => '' } # 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 =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. 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. 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' name1(value1) name2%value2% :key1 :key2$> C<$func :key2 {arg1} name2 "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 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 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 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 is a C reference then it is called as if the undefined name was a function and B the code for that function. Note that as with C, this function can change the set of defined variable names. If an undefined name is use and B is not set (the default) then an error message is generated. This can be changed later using the C method. The argument object passed in has a C 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 and C values are prepended and appended to that message respectively. The defaults are C!-- WikiVar Error:> and C< --E> 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 class. =item * An instance of C. 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 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. =item * C. This will erase the definition of the variable. =back =back =head4 Method fallback This has the same semantics as C 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 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 is a not a function then I 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 object you may want to use the C method. =back =head4 Method string_to_args Convert an argument I to an instance C. 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. 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 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. 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) 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 is called. In that case, a C wrapper can be used to delay access to the C reference until the function is invoked during text processing. =head4 Method new =over 4 =item * I 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[0]> is the name of the function. =item * C<$args-E[$n]> is the Cth positional argument. =item * C<$args-E{name}> The value for the named or keyword argument C. 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 if the argument was not present, the value if it was. The C 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 Return the number of positional arguments. =item * I Identical to C<$args-E[]>. Gotta give my fellow C/C++ programmers some love. =item * I The same as C<$args-E{}> except that the argument is not implicitly quoted. Useful when the argument name is in a variable and not a literal. =item * I The WikiVar variable name invoked. Identical to C<$args-E[0]> but a bit clearer in context. =item * I Force values for named arguments to be C references. The arguments to I are names that name named arguments. If the value for one of the names is already an C reference it is unchanged. If it is not, it is converted to a reference to an C of length 1. =item * I 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 Return the raw argument text. This is the string that was processed to create the arguments. =item * I 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 value for a WikiVar instance is set to Craw_text; }> then any WikiVar invocation that doesn't match a defined name will be effectively ignored. =item * I 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='Bob'$ $BobHomePage=(Home Page)$ $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 Bob a about 10 years ago. You can check out his biography at his Home Page. 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 Bob 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