Script-based Website Maintenance



m4 extensions

The following macros are very general-purpose. In the webscripts.tgz package, these macros are to be found in the file include/ext.m4.

Counting arguments

Here's a simple macro to count the number of elements in a comma-separated list:

define(`count', ``$#'')dnl

Multiple shifts

Very often, you will need to shift a large number of arguments out of the way before passing the rest onto the next macro. The built-in shift macro shifts just one argument; the macro below shifts the number given by its first argument:

define(`shiftn_pos',`ifelse(eval(`$1<1'),0,
`shiftn_pos(eval(`$1-1'),shift(shift($@)))',
`shift($@)')')dnl
define(`shiftn',`ifelse(eval(`$1>=0'),`0',
`shiftn_pos(eval(count($@)`-1+$1'),shift($@))',
`shiftn_pos($@)')')dnl

So:

shiftn(`2',``a'',``b'',``c'',``d'',``e'',``f'')

…expands to:

`c',`d',`e',`f'

A negative argument shifts the complement of the length, so:

shiftn(`-2',``a'',``b'',``c'',``d'',``e'',``f'')

…expands to:

`e',`f'

Truncating lists

This macro complements shiftn:

define(`keepn_pos',
`ifelse(eval(`$1<1'),0,
``$2'`'ifelse(eval(`$1<2'),0,`,')`'keepn_pos(eval(`$1-1'),
shift(shift($@)))')')dnl
dnl
define(`keepn',`ifelse(eval(`$1>=0'),`0',
`keepn_pos(eval(count($@)`-1+$1'),shift($@))',
`keepn_pos($@)')')dnl

The first argument specifies how many of the remaining arguments should be retained. So:

keepn(`2',``a'',``b'',``c'',``d'',``e'',``f'')

…expands to:

`a',`b'

A negative argument keeps the complement of the length, so:

keepn(`-2',``a'',``b'',``c'',``d'',``e'',``f'')

…expands to:

`a',`b',`c',`d'

Applying string sequences

The macro split splits its fifth argument up using the separator given in its first. It passes each ‘word’, quoted the number of times specified by the third argument, and followed by all arguments after the fifth, to the macro named in the second argument . The resultant expansions are concatenated with the fourth argument as a separator. rsplit is similar, but the separator used to split the string is treated as a regular expression.

define(`x_split',`ifelse(
indir(`$1',`$6',`$2'),`-1',`indir(`$3',`$6',shiftn(`6',$@))',
`indir(`$3',substr(`$6',`0',indir(`$1',`$6',`$2'),
ifelse($4,,``1'',`eval(`$4+1')')),
shiftn(`6',$@))`'$5`'x_split(`$1',`$2',`$3',`$4',`$5',
substr(`$6',eval(indir(`$1',`$6',`$2')`+1'),,`1'),shiftn(`6',$@))')')dnl
dnl
define(`split',`x_split(`index',$@)')dnl
define(`rsplit',`x_split(`regexp',$@)')dnl

Sublists

This macro extracts a sublist from a list:

define(`select',`keepn(`$2',shiftn(`$1',shift(shift($@))))')dnl

The first argument specifies how many of the initial arguments should be discarded. The second argument specifies how many of the remaining arguments should be retained. For example:

select(`2',`3',``a'',``b'',``c'',``d'',``e'',``f'')

…expands to:

`c',`d',`e'

Cored sublists

This macro removes the core of a list, complementing select:

define(`reject',`keepn(`$1',
shift(shift($@))),shiftn(eval(`$1+$2'),shift(shift($@)))')dnl

The first argument specifies how many initial arguments should be retained. The second argument specifies how many of the following arguments should be dropped. For example:

reject(`2',`3',``a'',``b'',``c'',``d'',``e'',``f'')

…expands to:

`a',`b',`f'

Removing quotes

Sometimes you need to strip away a pair of quotes returned by another macro. This macro causes a further evaluation of its argument to do that:

define(`dequote',$*)dnl

If we want to dequote more than once, we use this:

define(`dequoten',`ifelse(eval(`$1<1'),`0',
`dequote(dequoten(eval(`$1-1'),shift($@)))',`shift($@)')')dnl

So:

define(`list',```a'',``b'',``c'',``d'',``e'',``f''')dnl
define(`c',``sea'')dnl
define(`sea',``see'')dnl
dnl
dequoten(`1',list)
dequoten(`2',list)
dequoten(`3',list)

…expands to:

a,b,c,d,e,f
a,b,sea,d,e,f
a,b,see,d,e,f

Requoting

This macro requotes the rest of its arguments the number of times specified by its first:

define(`quoten',`substr($2,`0',,eval(`$1+2'))ifelse(`$#',`0',,
`$#',`1',,
`$#',`2',,
`,quoten(`$1',shift(shift($@)))')')dnl

So:

define(`list',```a'',``b'',``c'',``d'',``e'',``f''')dnl
dnl
quoten(`1',list)
quoten(`2',list)
quoten(`3',list)

…expands to:

``a'',``b'',``c'',``d'',``e'',``f''
```a''',```b''',```c''',```d''',```e''',```f'''
````a'''',````b'''',````c'''',````d'''',````e'''',````f''''

Here is the short version for adding only a single pair of quotes:

define(`quote',`quoten(`1',$@)')dnl

Cherry-picking

The macro selects is a variation of select — its first argument describes the region to be selected:

define(`selects_1',`ifelse(
eval(`-1!='regexp(`$1',`\([+-]?[0-9]+\)\.\.\([+-]?[0-9]+\)')),1,
`regexp(`$1',`\([+-]?[0-9]+\)\.\.\([+-]?[0-9]+\)',
`select(`\1',`\2',shift($@))')',
eval(`-1!='regexp(`$1',`\.\.\([+-]?[0-9]+\)')),1,
`regexp(`$1',`\.\.\([+-]?[0-9]+\)',`keepn(`\1',shift($@))')',
eval(`-1!='regexp(`$1',`\([+-]?[0-9]+\)\.\.')),1,
`regexp(`$1',`\([+-]?[0-9]+\)\.\.',`shiftn(`\1',shift($@))')',
eval(`-1!='regexp(`$1',`\([+-]?[0-9]+\)')),1,
`regexp(`$1',`\([+-]?[0-9]+\)',`select(`\1',1,shift($@))')')')dnl
define(`selects',`split(`,',`selects_1',,`,',$@)')dnl

For example:

selects(`2..3',``a'',``b'',``c'',``d'',``e'',``f'')

…expands to:

`c',`d',`e'

But selects is more powerful than that:

selects(`3..2,-4,1',``a'',``b'',``c'',``d'',``e'',``f'')

…expands to:

`d',`e',`c',`b'

Cherry-picking from many lists

Like selects, the macro mselects accepts a selection string as its first argument. However, each subsequent argument is a list from which we want to select some elements to form a new list. In the selection string, each group of selections is preceded by the number of the list to select from. Selections from different lists are separated by semicolons.

define(`mselects_1',`regexp(`$1',`\([0-9]+\):\(.*\)',
`selects(`\2',dequote(select(`\1',`1',shift($@))))')')dnl
define(`mselects',`split(`;',`mselects_1',,`,',$@)')dnl

For example:

mselects(`1:3..2,-4,1;0:2..4,0',

```A'',``B'',``C'',``D'',``E'',``F'',``G'',``H''',

```a'',``b'',``c'',``d'',``e'',``f''')

…expands to:

`d',`e',`c',`b',`C',`D',`E',`F',`A'

The built-in macro index gives us the position of the first occurance of one string in another. Here's a macro that does the same thing with a list:

define(`search_2',
`ifelse(ifelse(`$2',,`ifelse(`$3',`$4',``1'',``0'')',
`indir(`$2', `$3', `$4')'),`0',
dequote(ifelse(`$#',`4',```-1''',
``search_1(incr($1),reject(`2',`1',shift($@)))'')),
`$1')')dnl
dnl
define(`search_1',
`ifelse(`$#',`0',``-1'',
`$#',`1',``-1'',
`$#',`2',``-1'',
`$#',`3',``-1'',
`search_2($@)')')dnl
dnl
define(`search',`search_1(``0'',$@)')dnl

The second argument of search is the sought string; the arguments after it are searched. The result is the matching argument's index, minus 2. For example:

search(,`d',`a',`b',`c',`d',`e',`f',`g')
search(,`d',`A',`B',`C',`D',`E',`F',`G')

…expands to:

3
-1

The first argument, if not blank, is the name of a macro to be used to match the target string with each one of the listed strings. It should expand to 0 if it fails to match. Another example:

define(`caseless_equals',`ifelse(toupper(`$1'),toupper(`$2'),`1',`0')')dnl
search(`caseless_equals',`d',`a',`b',`c',`d',`e',`f',`g')
search(`caseless_equals',`d',`A',`B',`C',`D',`E',`F',`G')

…expands to:

3
3

Matching words

switchexp generates a regular expression suitable for the built-in regexp to select the words listed in its arguments:

define(`switchexp',``^'switchexp_($@)`$'')dnl
define(`switchexp_',`$1`'ifelse(eval($#`>1'),0,,
``\|'switchexp_(shift($@))')')dnl

…so switchexp(``ab'',``cd'',``ef'') yields ^ab\|cd\|ef$.

There is no attempt to escape special regular-expression characters in the words.

Iterations

foreach reproduces its third argument once for each element of its second argument. A macro named by the first argument is defined to expand to the element each time:

define(`foreach',
`ifelse(dequote(keepn(1,$2)),,,
`pushdef(`$1', substr(keepn(1, $2),0,,
ifelse(`$4',,``1'',`eval(`$4+1')')))`'$3`'popdef(`$1')`
'foreach(`$1',
`shift($2)',shiftn(2,$@))')')dnl

For example:

foreach(`XX',```a'',``b'',``c'',``d'',``e''',``Letter 'XX`
'')dnl

…expands to:

Letter a
Letter b
Letter c
Letter d
Letter e

The fourth argument specifies how many pairs of quotes should be added after expanding the iterator macro (default 0). This is useful if you want to create an argument list suitable for another macro. For example, you might expect that:

foreach(`XX', ```a'',``b'', ``c''', `,`toupper(`XX')'')

…yields:

,toupper(``a''),toupper(``b''),toupper(``c'')

…but instead it yields:

,toupper(`XX'),toupper(`XX'),toupper(`XX')

…because XX was never expanded. If you unquote sufficiently, e.g.:

foreach(`XX', ```a'',``b'', ``c''', `,`toupper('XX`)'')

…you'll get:

,toupper(a),toupper(b),toupper(c)

This is fine, so long as a, b and c, are not macros.

By reinstating two pairs of quotes:

foreach(`XX', ```a'',``b'', ``c''', `,`toupper('XX`)'',`2')

…we get the intended result:

,toupper(``a''),toupper(``b''),toupper(``c'')

You may prefer to use the macro quoten.



Updated: 2007-May-11 16:56 GMT
Contact Steven Simpson

Ĉi tiu paĝo disponeblas ĉi-lingve, laŭ via krozila agordo.