== Function Specifiers ==

Both the NIS Server and Schema Compatibility plugins use format
specifiers given in their respective configurations to control how they
massage the contents of entries which are already in the directory
server into entries in either NIS maps or the compatibility area of the
directory.

=== Basics ===

Format specifiers can reference values of attributes in the entry which
is currently being examined like so:
  %{attribute}

The values of multiple attributes can be mixed together like so:
  %{one_attribute}:%{another_attribute}

Like shell variables, a reference to an attribute can specify a default
value to be used in cases where the entry has no value for the specified
attribute:
  %{gecos:-%{cn:-}}

Again, like shell variables, a reference can also specify an alternate
value to be provided when the entry has a value (though this is not
expected to be used as frequently) which should be overridden:
  %{cn:+%{cn},,,}%{cn:-%{gecos}}

Or to use a more concrete (but still simplified) example:
  %{uid}:*:%{uidNumber}:%{gidNumber}:%{gecos:-%{cn:-}}:%{homeDirectory:-/}:%{loginShell:-/bin/sh}

Additional operators include "#", "##", "%", "%%", "/", "//", which
operate in ways similar to their shell counterparts (with one notable
exception: patterns for the "/" operator can not currently be anchored
to the beginning or end of the string).

Strictly speaking, references to attributes return ''lists'' of values,
or produce an evaluation error if no values are found.  This helps
prevent invalid entries from showing up in NIS maps and in the
directory.

=== Functions ===

Additionally, several built-in "function"s are available.  These can be
used to modify data that's been read from the current entry before it's
incorporated into the result, or for importing values from other entries
and combining them with data from the current entry.  Generally,
function invocations look like this:

  %function(ARG[,...])

A function invocation uses a comma-separated list of double-quoted
arguments.  Any arguments which themselves contain a double-quote need
to escape the double-quote using a '\' character.  Naturally, the '\'
character itself also needs to be escaped whenever it appears.  Some
functions can take expressions as arguments, allowing functions to
operate on data after it has been acted on by other functions.

== Implemented Functions ==

=== first ===

  first(''EXPRESSION''[,''DEFAULT''])

Evaluates ''EXPRESSION'', and if one or more values is produced,
provides only the first value.  (Here, ''first'' refers to the first
value in the list of values after they've been sorted.)  If no values
result, then ''DEFAULT'' is evaluated as an expression and its result is
provided.  Nothing is done to ensure that ''DEFAULT'' provides only one
value, however.

=== match ===

  match(''EXPRESSION'',''PATTERN''[,''DEFAULT''])

Selects the single value of ''EXPRESSION'' which matches the globbing
pattern ''PATTERN''.  If no value matches, and a ''DEFAULT'' was
specified, then ''DEFAULT'' is evaluated as an expression and its value
is provided.

Here's an example entry:

  dn: cn=group
  member: bob
  member: dave

And here's how it evaluates out:

  %match("%{member}","b*")        -> bob
  %match("%{member}","d*")        -> dave
  %match("%{member}","e*")        no value
  %match("%{member}","*e*")       -> dave
  %match("%{member}","e*","jim")  -> jim
  %match("%{member}","*","%{cn}") -> group

=== mmatch ===

  mmatch(''EXPRESSION'',''PATTERN''])

Similar to match, except that any number of matching values may be found
and returned.

=== regmatch ===

  regmatch(''EXPRESSION'',''PATTERN''[,''DEFAULT''])

Selects the value of ''EXPRESSION'' which matches the extended regular
expression ''PATTERN''.  If no values match, and a ''DEFAULT'' was
specified, then ''DEFAULT'' is evaluated as an expression and its value
is produced.

Here's an example entry:

  dn: cn=group
  member: bob
  member: dave

And here's how it evaluates out:

  %regmatch("%{member}","^b.*")       -> bob
  %regmatch("%{member}","^d.*")       -> dave
  %regmatch("%{member}","e")          -> dave
  %regmatch("%{member}","^e")         no value
  %regmatch("%{member}","^e.*","jim") -> jim
  %regmatch("%{member}",".*","%{cn}") -> group

=== regmatchi ===

  regmatchi(''EXPRESSION'',''PATTERN''[,''DEFAULT''])

Exactly the same as regmatch, except that pattern matching is performed in a
case-insensitive manner.

=== mregmatch ===

  mregmatch(''EXPRESSION'',''PATTERN''])

Similar to regmatch, except that any number of matching values may be found
and returned.

=== mregmatchi ===

  mregmatchi(''EXPRESSION'',''PATTERN''])

Exactly the same as mregmatch, except that pattern matching is performed in
a case-insensitive manner.

=== regsub ===

  regsub(''EXPRESSION'',''PATTERN'',''TEMPLATE''[,''DEFAULT''])

Selects the value of ''EXPRESSION'' which matches the extended regular
expression ''PATTERN'' and uses ''TEMPLATE'' to build the result.  If no
values match, and a ''DEFAULT'' was specified, then ''DEFAULT'' is
evaluated as an expression and its value is produced, otherwise an error
occurs.

The template is treated as a literal value, but is allowed to
incorporate the n'th substring (one of the first nine) from the matched
value by including the sequence "%n" in the template.

Here's an example entry:

  dn: cn=group
  member: bob
  member: dave

And here's how it evaluates out:

  %regsub("%{member}","o","%0")          -> bob
  %regsub("%{member}","o","%1")          -> 
  %regsub("%{member}","^o","%0")         no value
  %regsub("%{member}","^d(.).*","%1")    -> a
  %regsub("%{member}","^(.*)e","t%1y")   -> tdavy
  %regsub("%{member}","^o","%0","jim")   -> jim
  %regsub("%{member}","^o","%0","%{cn}") -> group

=== regsubi ===

  regsubi(''EXPRESSION'',''PATTERN'',''TEMPLATE''[,''DEFAULT''])

Exactly the same as regsub, except that pattern matching is performed in a
case-insensitive manner.

=== mregsub ===

  mregsub(''EXPRESSION'',''PATTERN'',''TEMPLATE'')

Similar to regsub, except that any number of matching values may be found,
processed, and returned.

=== regsubi ===

  mregsubi(''EXPRESSION'',''PATTERN'',''TEMPLATE'')

Exactly the same as regsubi, except that pattern matching is performed in a
case-insensitive manner.

=== deref ===

  deref(''THISATTRIBUTE'',''THATATTRIBUTE'')

Returns a list of the values of ''THATATTRIBUTE'' for directory entries
named by this entry's ''THISATTRIBUTE''.  Its function is similar in
principle to the indirect CoS functionality provided by the directory
server.

Here are some example entries:

  dn: cn=group
  member: uid=bob
  member: uid=pete

  dn: uid=bob
  uid: bob

  dn: uid=pete
  uid: pete

And here's how various expressions evaluate for ''cn=group'':

  %deref("member","foo") -> no values
  %deref("member","uid") -> (bob,pete)

Because the plugin attempts to track updates, the ''THISATTRIBUTE''
attribute should be indexed and defined with a proper syntax and
equality test in the directory server schema.

=== deref_f ===

  deref_f(''THISATTRIBUTE'',''FILTER'',''THATATTRIBUTE'')

Returns a list of the values of ''THATATTRIBUTE'' for directory entries
named by this entry's ''THISATTRIBUTE'' which match ''FILTER''.  Its function
is similar in principle to the indirect CoS functionality provided by the
directory server.

Here are some example entries:

  dn: cn=group
  member: uid=bob
  member: uid=pete

  dn: uid=bob
  uid: bob

  dn: uid=pete
  uid: pete

And here's how various expressions evaluate for ''cn=group'':

  %deref("member","objectclass=*","foo") -> no values
  %deref("member","objectclass=*","uid") -> (bob,pete)
  %deref("member","uid=pete","uid") -> (pete)

Because the plugin attempts to track updates, the ''THISATTRIBUTE''
attribute should be indexed and defined with a proper syntax and
equality test in the directory server schema.

=== deref_r ===

  deref_r(''ATTRIBUTE''[,''OTHERATTRIBUTE''[...]],''VALUEATTRIBUTE'')

Looks for entries named by this entry's ''ATTRIBUTE'', and then by those
entries' ''ATTRIBUTE'', repeating the search until there are no more
entries to find named by ''ATTRIBUTE'' in the set of entries seen.

Taking that set as a new starting point, searches for entries named by
that set's ''OTHERATTRIBUTE'' values, similarly repeating until a new
complete set of entries is determined.  The process continues to be
repeated for each listed attribute except the last.

When the final set of entries is determined, their ''VALUEATTRIBUTE''
values will be used to construct the result list.

Here are some example entries:

  dn: cn=group
  member: cn=othergroup
  member: uid=bob
  includedgroup: clan=macleod

  dn: cn=othergroup
  member: uid=pete
  uid: bogus

  dn: uid=bob
  uid: bob

  dn: uid=pete
  uid: pete

  dn: clan=macleod
  includedgroup: cn=foundlings

  dn: cn=foundlings
  member: uid=cmacleod
  member: uid=dmacleod

  dn: uid=cmacleod
  uid: cmacleod

  dn: uid=dmacleod
  uid: dmacleod

And here's how various expressions evaluate for ''cn=group'':

  %deref_r("member","foo") -> no values
  %deref_r("member","uid") -> (bogus,bob,pete)

When evaluating the first attribute, the ''member'' attribute of
''cn=group'' produces this set of entries:

    * cn=group (the original entry)
    * cn=othergroup (added because it was named by ''cn=group'')
    * uid=bob (added because it was named by ''cn=group'')
    * uid=pete (added because it was named by ''cn=othergroup'')

The result list is pulled from this set of entries.

Here's another example:

  %deref_r("includedgroup","member","uid") -> (bogus,bob,cmacleod,dmacleod,pete)

When evaluating the first attribute, the ''includedgroup'' attribute
of ''cn=group'' leads to this set of entries:

    * cn=group (the original entry)
    * clan=macleod (named by cn=group)
    * cn=foundlings (named by clan=macleod)

When evaluating the second attribute, the ''member'' attribute values
for the previous set of entries produces this set of entries:

    * cn=othergroup (named by cn=group)
    * uid=bob (named by cn=group)
    * uid=cmacleod (named by cn=foundlings)
    * uid=dmacleod (named by cn=foundlings)
    * uid=pete (named by cn=othergroup)

The result list is pulled from this set of entries.

Because the plugin attempts to track updates, every attribute used here
(except for ''VALUEATTRIBUTE'') should be indexed and defined with a
proper syntax and equality test in the directory server schema.

=== deref_rf ===

  deref_r(''ATTRIBUTE'',''FILTER''[,''OTHERATTRIBUTE'',''OTHERFILTER''[,...]],''VALUEATTRIBUTE'')

Looks for entries named by this entry's ''ATTRIBUTE'' which match the
''FILTER'', and then by those entries' ''ATTRIBUTE'' which also match
the ''FILTER'', repeating the search until there are no more entries to
find named by ''ATTRIBUTE'' in the set of entries seen.

Taking that set as a new starting point, searches for entries named by
that set's ''OTHERATTRIBUTE'' values which match ''OTHERFILTER'',
similarly repeating until a new complete set of entries is determined.
The process continues to be repeated for each listed attribute except
the last.

When the final set of entries is determined, their ''VALUEATTRIBUTE''
values will be used to construct the result list.

Here are some example entries:

  dn: cn=group
  objectclass: group
  member: cn=othergroup
  member: uid=bob
  includedgroup: clan=macleod

  dn: cn=othergroup
  objectclass: group
  member: uid=pete
  uid: bogus

  dn: uid=bob
  objectclass: user
  uid: bob

  dn: uid=pete
  objectclass: user
  uid: pete

  dn: clan=macleod
  objectclass: group
  includedgroup: cn=foundlings

  dn: cn=foundlings
  objectclass: group
  member: uid=cmacleod
  member: uid=dmacleod

  dn: uid=cmacleod
  objectclass: user
  uid: cmacleod

  dn: uid=dmacleod
  objectclass: user
  uid: dmacleod

And here's how various expressions evaluate for ''cn=group'':

  %deref_rf("member","objectclass=*","foo") -> no values
  %deref_rf("member","objectclass=user","uid") -> (bob)

When evaluating the first attribute, the ''member'' attribute of
''cn=group'' against filter 'objectclass=user', produces this set of
entries:

    * uid=bob (added because it was named by ''cn=group'' and matches
               the filter)

The result list is pulled from this set of entries.

Here's another example:

  %deref_rf("includedgroup","objectclass=group","member","objectclass=user","uid") -> no values

When evaluating the first attribute, the ''includedgroup'' attribute
of ''cn=group'' leads to this set of ''objectclass=group'' entries:

    * clan=macleod (named by cn=group)

When evaluating the second attribute, the ''member'' attribute values
for the previous set of entries produces no ''objectclass=user'' entries.

The result list is, therefore, empty.

Because the plugin attempts to track updates, every attribute used here
(except for ''VALUEATTRIBUTE'') should be indexed and defined with a
proper syntax and equality test in the directory server schema.  This
function can also be invoked as ''deref_fr''.

=== referred ===

  referred(''SET'',''THATATTRIBUTE'',''THATOTHERATTRIBUTE'')

Creates a separated list of the values of ''THATOTHERATTRIBUTE'' stored
in directory entries which have entries in the current group in the
named ''SET'' and which also have this entry's name as a value for
''THATATTRIBUTE''.

Here are some example entries:

  dn: cn=group

  dn: uid=bob
  memberof: cn=group
  uid: bob

  dn: uid=pete
  memberof: cn=group
  uid: pete

And here's how various expressions evaluate for ''cn=group'', if
''uid=bob'' and ''uid=pete'' are both part of a set of entries named
''SET'':

  %referred("SET","memberof","foo") -> no values
  %referred("SET","memberof","uid") -> (bob,pete)

Because the plugin performs searches internally, ''THATATTRIBUTE''
should be indexed and defined with a proper syntax and equality test in
the directory server schema.

=== referred_r ===

  referred_r(''SET'',''ATTRIBUTE''[,''OTHERSET'',''OTHERATTRIBUTE''[,...],''VALUEATTRIBUTE'')

Searches for entries in the current group in both the current set and
''SET'' which refer to this entry using ''ATTRIBUTE'', then searches for
entries which refer to any of the just-found entries, repeating the
process until a complete set of referring entries is formed.

Then, searches for entries in ''SET'' and ''OTHERSET'' which refer to
entries in the just-found set, repeating the process until a new
complete set is formed.

The value of ''VALUEATTRIBUTE'' for every entry encountered along the
way will be returned.

Here are some example entries:

  dn: cn=group

  dn: cn=othergroup
  memberOf: cn=group

  dn: uid=bob
  uid: bob
  memberOf: cn=group

  dn: uid=pete
  uid: pete
  memberOf: cn=othergroup

And here's how various expressions evaluate for ''cn=group'', if all of
the entries with ''uid'' values are in a set named ''people'':

  %referred("people","memberof","foo") -> no values
  %referred("people","memberof","uid") -> (bob,pete)

Because the plugin performs searches internally, every attribute used
here (except for ''VALUEATTRIBUTE'') should be indexed and defined with
a proper syntax and equality test in the directory server schema.

=== merge ===

  merge(''SEPARATOR'',''EXPRESSION''[,...])

Evaluates and then creates a single value using multiple expressions
which may evaluate to either single values or lists.  Any expressions
which cannot be evaluated are ignored.

Here are some example entries:

  dn: cn=group
  membername: jim
  member: uid=bob
  member: uid=pete

  dn: uid=bob
  uid: bob

  dn: uid=pete
  uid: pete

And here's how an example expression evaluates for ''cn=group'':

  %merge(":","%{membername}","%deref(\"member\",\"uid\")") -> jim:bob:pete
  %merge(":","%{madeup}") -> (empty string)

=== collect ===

  collect(''EXPRESSION''[,...])

Evaluates each ''EXPRESSION'' in turn, creating one large list by
appending to it all of the values which are produced by evaluating every
expression.  Any expressions which cannot be evaluated are ignored.

Here's an example entry:

  dn: cn=group
  cn: group
  membername: jim
  member: uid=bob
  member: uid=pete

And here's how some example expressions evaluate for ''cn=group'':

  %collect("%{bogus}","%{member}","%{membername}") -> (uid=bob,uid=pete,jim)

=== link ===

  link(''EXPRESSION'',''PAD''[,''SEPARATOR'',''EXPRESSION2'',''PAD2''[,...])

Evaluates each ''EXPRESSION'' and ''PAD'' in turn to produce a list of
values.  If the lists produced by each of the expressions are not of the
same length, then each ''EXPRESSION'''s corresponding ''PAD'' value is
appended to each list to pad them out until they are all of equal length
(i.e., the length of the longest list).

Then, one list of values is produced by using the first value from each
list (separated by the corresponding SEPARATOR), then using the second
values from each list, continuing until all lists have been exhausted.

Here's an example entry:

  dn: cn=group
  cn: group
  membername: jim
  member: uid=bob
  member: uid=pete

And here's how an example expression evaluates for ''cn=group'':

  %link("%{member}","?","/","%{membername}","?") -> (uid=bob/jim,uid=pete/?)

=== ifeq ===

  ifeq(''ATTRIBUTE'',''EXPRESSION'',''MATCH-EXPRESSION'',''NONMATCH-EXPRESSION'')

Evaluates ''EXPRESSION'', and if the entry's ''ATTRIBUTE'' attribute
matches the value provided by the expression (as determined by the
server's matching rules for the attribute), evaluates and returns
''MATCH-EXPRESSION'', otherwise ''NONMATCH-EXPRESSION'' will be
evaluated and returned.

Here's an example entry:

  dn: cn=group
  cn: group
  membername: jim
  member: uid=bob
  member: uid=pete

And here's how an example expression evaluates for ''cn=group'':

  %ifeq("member","jim","","%{membername}") -> (jim)

=== default ===

  default(''EXPRESSION1'',''EXPRESSION2''[,...])

Evaluates ''EXPRESSION1'', returning its values if any are produced.  If no
result is produced, evaluates ''EXPRESSION2'', returning its values if any are
produced.  Keeps trying successive expressions until it gets results or runs
out of expressions.

Here's an example entry:

  dn: cn=group
  cn: group
  membername: jim
  member: uid=bob
  member: uid=pete

And here's how an example expression evaluates for ''cn=group'':

  %default("%{member}","jim") -> (uid=bob,uid=pete)
  %default("%{membername}","bob") -> (jim)
  %default("%{nosuchvalue}","bob") -> (bob)

=== sort ===

  sort(''EXPRESSION'')

Evaluates ''EXPRESSION'', returning its values if any are produced, sorted by
binary value.  This is more useful for ensuring consistency than prettiness,
as the sorting is not case-aware and doesn't recognize numbers.

