MP4H(1)

๐ŸŒธ MP4H(1) โ€” HTML Tools ๐Ÿ’…

ok so bestie, welcome to the mp4h manual, aka Macro Processor for HTML Documents, and it lives in the HTML Tools section of your manual pages. grab a strawberry drink ๐Ÿ“, this one's long but it slays.


โœจ NAME โœจ

mp4h โ€” Macro Processor for HTML Documents ๐ŸŽ€


โœจ VERSION โœจ

this whole doc is describing mp4h version 1.3.1. periodt. ๐Ÿ’…


โœจ INTRODUCTION โœจ

ok so picture this: mp4h is a macro-processor that was literally built for HTML documents. it lets you do powerful programming-construct things, but the syntax is giving very much "familiar to HTML authors" energy, so if you write HTML you're already halfway home. ๐Ÿ โœจ

this software is based on Meta-HTML <URL:http://www.metahtml.org/>, which was written by Brian J. Fox. and yeah, even though both syntaxes look similar, the source code is completely different. fr fr. a subset of Meta-HTML was used as part of a bigger, more complex program called WML (Website Meta Language) <URL:http://www.thewml.org/>, written by Ralf S. Engelschall, and which the author has maintained since January 1999. but for licensing reasons it was hard to hack Meta-HTML, so the move was to just write a whole new macro-processor. iconic, honestly. ๐Ÿ‘‘

and instead of rewriting everything from scratch, the author chose to build on another macro-processor engine: GNU m4 <URL:http://www.gnu.org/software/m4/>, written by Rene Seindal. why? because it's got advantages for days โ€” this software is stable, robust and very well documented. ๐Ÿ’ฏ this version of mp4h is derived from GNU m4 version 1.4n, which is a development version. iykyk.

bestie, real talk: mp4h is not an HTML editor. its one and only goal is giving you an easy way to define your own macros inside HTML documents. there is no plan to add functionality that automagically produces valid HTML documents. so if you wanna clean up your code or validate it, just use a post-processor like tidy <URL:http://www.w3.org/People/Raggett/tidy/>. ๐Ÿงน

โœจ COMMAND LINE OPTIONS โœจ

optional arguments are wrapped in square brackets, okay? and all option synonyms have a similar syntax, so when a long option takes an argument, the short option does too. no cap. ๐Ÿซถ

the syntax call is:

mp4h [options] [filename [filename] ...]

options are described below. if no filename is specified, or if its name is -, then characters are read on standard input. ๐Ÿ“ฅ

๐ŸŒˆ Operation modes

  • --help โ€” display an help message and exit
  • --version โ€” output mp4h version information and exit
  • -E, --fatal-warnings โ€” stop execution after first warning
  • -Q, --quiet, --silent โ€” suppress some warnings for builtins
  • -S, --safety-level="NUMBER" โ€” disable risky functions; 0 means no filtering, 1 disable "execute" and 2 disable this one too plus all filesystem related functions: "file-exists", "real-path", "get-file-properties", "directory-contents" and "include".
โš ๏ธ okay the safety levels are literally the vibe check for risky functions โ€” crank it up if you don't trust the input, bestie.

๐ŸŒธ Preprocessor features

  • -I, --include="DIRECTORY" โ€” search this directory for includes and packages
  • -D, --define="NAME"[=VALUE]"" โ€” set variable NAME to VALUE, or empty
  • -U, --undefine="COMMAND" โ€” delete builtin COMMAND
  • -s, --synclines โ€” generate `#line NO "FILE"' lines

๐Ÿฆ‹ Parser features

  • -c, --caseless="NUMBER" โ€” set case sensitiveness according to the bits of "NUMBER". A null bit means symbol is case sensitive, and bits are defined as followed: 0 for tags, 1 for variables and 2 for entities. Default value is 3, i.e. only entities are case sensitive.
  • -e, --encoding="NAME" โ€” specify document encoding. Valid options are `8bit' (default) or `utf8'.
  • -X, --expansion="NUMBER" โ€” set parser behaviour according to the bits of "NUMBER"

NUMBER is a combination of

  1. 1 do not parse unknown tags
  2. 2 unknown tags are assumed being simple
  3. 4 trailing star in tag name do not make this tag simple
  4. 8 an unmatched end tag closes all previous unmatched begin tags
  5. 16 interpret backslashes as printf
  6. 32 remove trailing slash in tag attributes
  7. 64 do not remove trailing star in tag name
  8. 128 do not remove leading star in tag name
  9. 256 do not add a space before trailing slash in tag attributes
  10. 1024 suppress warnings about bad nested tags
  11. 2048 suppress warnings about missing trailing slash

In version 1.3.1, default value is 3114=2+8+32+1024+2048. ๐Ÿงฎโœจ

๐ŸŽ€ Limits control

  • -H, --hashsize="PRIME" โ€” set symbol lookup hash table size (default 509)
  • -L, -nesting-limit="NUMBER" โ€” change artificial nesting limit (default 250)

๐Ÿ’œ Debugging

  • -d, --debug="FLAGS" โ€” set debug level (no FLAGS implies `aeq')
  • -t, --trace="NAME" โ€” trace NAME when it will be defined
  • -l, --arglength="NUMBER" โ€” restrict macro tracing size
  • -o, --error-output="FILE" โ€” redirect debug and trace output

Flags are any of:

  • t trace for all macro calls, not only debugging-on'ed
  • a show actual arguments
  • e show expansion
  • c show before collect, after collect and after call
  • x add a unique macro call id, useful with c flag
  • f say current input file name
  • l say current input line number
  • p show results of path searches
  • m show results of module operations
  • i show changes in input files
  • V shorthand for all of the above flags

โœจ DESCRIPTION โœจ

ok so mp4h is a macro-processor, which literally means keywords get replaced by other text. this chapter describes all the primitives. ๐Ÿ’… and since mp4h was specially designed for HTML documents, its syntax is very similar to HTML โ€” tags and attributes, the whole thing. but there's one important feature that has no equivalent in HTML: comments until end of line. everything after three colons gets yeeted until the end of the line, like:

;;;  This is a comment

๐Ÿงš Function Macros

note bestie: all the examples in this documentation are processed through mp4h with expansion flags set to zero (there's a description of possible expansion flags at the end of the document), which is why simple tags contain a trailing slash. but mp4h can output plain HTML files with other expansion flags. ๐Ÿ’–

defining new tags is like, the most common task mp4h does. and just like HTML, macro names are case insensitive, unless the "-c" option is used to change this default behaviour. in this documentation, only lowercase letters are used. there are two kinds of tags: simple and complex. a simple tag looks like:

<name [attributes] />

whereas a complex tag looks like:

<name [attributes]>
body
</name>

since version 0.9.1, mp4h knows XHTML syntax too, so your input file may conform to HTML or XHTML syntax. in this manual we adopt the latter, which is why simple tags have a trailing slash in attributes. if you want to produce HTML files with this input file, you may either choose an adequate "--expansion" flag or use a post-processor like tidy <URL:http://www.w3.org/People/Raggett/tidy/>. โœจ

when a simple tag is defined by mp4h, it can be parsed even if the trailing slash is omitted, because mp4h knows that this tag is simple. but it's good practice to always append a trailing slash to simple tags. ๐Ÿ’…

in the macro descriptions below, a slash indicates a simple tag, and a V letter means attributes are read verbatim (without expansion) (see the chapter on macro expansion for further details).


  • define-tag

"name" "[attributes=verbatim]" "[endtag=required]" "[whitespace=delete]"

this function lets you define your own tags. first argument is the command name. replacement text is the function body. ๐Ÿซถ

Source:

<define-tag foo>bar</define-tag>
<foo/>

Output:

bar

even if spaces have usually few incidence on HTML syntax, it is important to note that

<define-tag foo>bar</define-tag>

and

<define-tag foo>
bar
</define-tag>

are not equivalent โ€” the latter form contains two newlines that were not present in the former. ๐Ÿ˜ญ

"whitespace=delete" : Some spaces are suppressed in replacement text, in particular any leading or trailing spaces, and newlines not enclosed within angle brackets.

"endtag=required" : Define a complex tag

Source:

<define-tag foo>bar</define-tag>
<foo/>

Output:

bar

Source:

<define-tag bar endtag=required>;;;
body is: %body</define-tag>
<bar>Here it is</bar>

Output:

body is: Here it is

"attributes=verbatim" : By default attributes are expanded before text is replaced. If this attribute is used, attributes are inserted into replacement text without expansion.

Source:

<define-tag foo>quux</define-tag>
<define-tag bar attributes=verbatim endtag=required>
Body: %Ubody
Attributes: %Uattributes
</define-tag>
<bar txt="<foo/>">Here we go</bar>

Output:

Body: Here we go
Attributes: txt=<foo/>
  • provide-tag

"name" "[attributes=verbatim]" "[endtag=required]" "[whitespace=delete]"

this command is similar to the previous one, except that no operation is performed if this command was already defined. so it's the "only if not already here" bestie. ๐Ÿ™ˆ

  • let "S"

"new=old"

copy a function. this command is useful to save a macro definition before redefining it. ๐Ÿ’พ

Source:

<define-tag foo>one</define-tag>
<let bar=foo />
<define-tag foo>two</define-tag>
<foo/><bar/>

Output:

twoone
  • undef "S"

"name"

delete a command definition. bye bye ๐Ÿ‘‹

Source:

<define-tag foo>one</define-tag>
<undef foo />
<foo/>

Output:

<foo />
  • set-hook

"name" "[position=before|after]" "[action=insert|append|replace]"

add text to a predefined macro. this mechanism allows modifications of existing macros without having to worry about its type, whether it is complex or not. so you can glow up a macro without touching its whole structure. ๐Ÿ’…โœจ

Source:

<let foo=add />
<set-hook foo position=before>
Before</set-hook>
<set-hook foo position=after>
After</set-hook>
<foo 1 2 3 4 />

Output:

Before10
After
  • get-hook "S"

"name" "[position=before|after]"

print current hooks of a macro. ๐Ÿ‘€

Source:

Text inserted with position=before:<get-hook foo position=before />!
Text inserted with position=after:<get-hook foo position=after />!

Output:

Text inserted with position=before:
Before!
Text inserted with position=after:
After!
  • attributes-quote "S"

"%attributes"

like %attributes, except that "attr=value" pairs are printed with double quotes surrounding attribute values, and a leading space is added if some text is printed. ๐ŸŽ€

Source:

<define-tag foo>;;;
%attributes
<img<attributes-quote %attributes />/>
</define-tag>
<foo id="logo" src="logo.gif" name="Logo" alt="Our logo" />
<foo/>

Output:

id=logo src=logo.gif name=Logo alt=Our logo
<img id="logo" src="logo.gif" name="Logo" alt="Our logo"/>

<img/>
  • attributes-extract "S"

"name1" "[,name2[,name3...]]" "%attributes"

extract from %attributes the "attr=value" pairs for names matching any of name1, name2.... ๐Ÿ”

Source:

okay so picking up right where we left off ๐Ÿ’… โ€” here's that attributes-extract example finishing its little moment:

          <define-tag img whitespace=delete>
          <img* <attributes-extract name,src,alt %attributes />  />
          </define-tag>
          <img id="logo" src="logo.gif" name="Logo" alt="Our logo"  />

Output:

          <img src=logo.gif name=Logo alt=Our logo    />

  • attributes-remove "S" โ€” signature is "name1" "[,name2[,name3...]]" "%attributes". It removes from %attributes all the attr=value pairs whose names match any of name1, name2, โ€ฆโ€ฆ. Like a little cleanup crew ๐Ÿงน

Source:

          <define-tag img whitespace=delete>
          <img* <attributes-quote <attributes-remove name,src,alt %attributes />/> />
          </define-tag>
          <img id="logo" src="logo.gif" name="Logo" alt="Our logo"  />

Output:

          <img  id="logo"   />
Note: these two previous functions are special besties, because unlike literally every other macro, their expansion does not form a group. That's necessary so the resulting list of attributes can actually be parsed. no cap.

In those two functions, the names of attributes can be regular expressions ๐Ÿ‘‘ The whole point of these primitives is to help you write macros that accept any kind of attributes without having to declare them all up front. A canonical example is this:

Source:

          <define-tag href whitespace=delete>
          <preserve url name />
          <set-var <attributes-extract url,name %attributes />/>
          <a <attributes-quote <attributes-remove url,name %attributes />/>
             href="<get-var url />"><get-var name /></a>
          <restore  url name />
          </define-tag>
          <href class=web url="http://www.foo.com" name="Welcome"  />

Output:

          <a  class="web"
             href="http://www.foo.com">Welcome</a>

But now say we want to add an image attribute too. So we'd write:

Source:

          <define-tag href whitespace=delete>
          <preserve url name image />
          <set-var <attributes-extract url,name,image %attributes />/>
          <a <attributes-quote <attributes-remove url,name,image %attributes />/>
             href="<get-var url />">
          <if <get-var image />
             <img <attributes-quote <attributes-remove url,name,image %attributes />/>
                src="<get-var image />" alt="<get-var name />" border=0  />
            <get-var name />
          />
          </a>
          <restore  url name image />
          </define-tag>
          <href class=web url="http://www.foo.com" name="Welcome" image="foo.png"/>

Output:

          <a  class="web"
             href="http://www.foo.com"><img  class="web"
                src="foo.png" alt="Welcome" border=0      /></a>

So we need a mechanism to tell mp4h that some attributes refer to specific HTML tags. One solution is to prepend the attribute with the tag name, like this:

Source:

          <define-tag href whitespace=delete>
          <preserve url name image />
          <set-var <attributes-extract url,name,image %attributes />/>
          <a <attributes-quote <attributes-extract a:.* %attributes />/>
             href="<get-var url />">
          <if <get-var image />
             <img <attributes-quote <attributes-extract img:.* %attributes />/>
                src="<get-var image />" alt="<get-var name />"  />
            <get-var name />
          />
          </a>
          <restore  url name image />
          </define-tag>
          <href a:class=web img:id=logo img:border=1
                url="http://www.foo.com" name="Welcome" image="foo.png" />

Output:

          <a  a:class="web"
             href="http://www.foo.com"><img  img:id="logo" img:border="1"
                src="foo.png" alt="Welcome"      /></a>

This example shows that regular expressions can be used inside attribute names, but it's still incomplete, because we want to remove the prefix from the attributes. One solution uses subst-in-string, but there's a more elegant one:

Source:

          <define-tag href whitespace=delete>
          <preserve url name image />
          <set-var <attributes-extract url,name,image %attributes />/>
          <a <attributes-quote <attributes-extract :a:(.*) %attributes />/>
             href="<get-var url />">
          <if <get-var image />
             <img <attributes-quote <attributes-extract :img:(.*) %attributes />/>
                src="<get-var image />" alt="<get-var name />"  />
            <get-var name />
          />
          </a>
          <restore  url name image />
          </define-tag>
          <href :a:class=web :img:id=logo :img:border=1
                url="http://www.foo.com" name="Welcome" image="foo.png" />

Output:

          <a  class="web"
             href="http://www.foo.com"><img  id="logo" border="1"
                src="foo.png" alt="Welcome"      /></a>

When there are subexpressions inside the regular expressions, they get printed instead of the whole expression. Also note that I put a colon before the prefix so they don't get mixed up with XML namespaces. iykyk ๐Ÿ’…


๐ŸŒธ Entities ๐ŸŒธ

Entities are macros in exactly the same way as tags, except they don't take any arguments. Tags are normally used to mark up text, but entities contain text that's already marked up. Also โ€” heads up โ€” unlike tags, entities are case sensitive by default. ๐Ÿ™ˆ

An entity looks like this:

          &entity;
  • define-entity โ€” takes "name". This function is how you define your own entities. The first argument is the entity name, and the replacement text is the function body. Slay ๐ŸŽ€

Source:

          <define-entity foo>bar</define-entity>
          &foo;

Output:

          bar

โœจ Variables โœจ

Variables are a special case of simple tags, because they do not accept attributes. Their whole vibe is different too, since variables contain text whereas macros act like operators. A really nice feature about variables is that you can manipulate them like arrays ๐Ÿฆ‹ For real โ€” variables can be treated as newline-separated lists, which unlocks some powerful manipulation functions, as we'll see below.

  • set-var "S" โ€” takes "name[=value]" "[name[=value]] ...". This command sets variables. ๐Ÿ’–
  • set-var-verbatim "S" "V" โ€” takes "name[=value]" "[name[=value]] ...". Same as above, but the attributes are read verbatim.
  • set-var-x โ€” takes "name=variable-name". This command assigns a variable the value of the body of the command. It's especially useful when the variable values contain newlines and/or quotes. ๐ŸŽ€
Note that the variable can not be indexed with this command. Also note that this command behaves like set-var-verbatim: the body isn't expanded until the variable is shown with get-var.
  • get-var "S" โ€” takes "name" "[name] ...". Shows the variable contents. If a numeric value inside square brackets is appended to a variable name, it's the index of an array. The first index of arrays is 0 by convention. โญ

Source:

          <set-var version="0.10.1" />
          This is version <get-var version />
          <set-var-x name="osversion">Operating system is
          "<include command="uname" />"</set-var-x>
          <get-var osversion />

Output:

          This is version 0.10.1

       Operating system is
          "Linux
          "

Source:

          <set-var foo="0
          1
          2
          3" />
          <get-var foo[2] foo[0] foo />

Output:

          200
          1
          2
          3
  • get-var-once "S" "V" โ€” takes "name" "[name] ...". Same as above, but the attributes are not expanded. ๐Ÿ‘‘

Source:

          <define-tag foo>0.10.1</define-tag>
          <set-var version="<foo/>" />;;;
          Here is version <get-var version />
          <set-var-verbatim version="<foo/>" />;;;
          Here is version <get-var version />
          <set-var-verbatim version="<foo/>" />;;;
          Here is version <get-var-once version />

Output:

          Here is version 0.10.1
          Here is version 0.10.1
          Here is version <foo/>
  • preserve "S" โ€” takes "name" "[name] ...". All variables are global, bestie โ€” there is no variable or macro scope. That's exactly why a stack is used to preserve variables. When this command is invoked, the arguments are the names of variables whose values get put at the top of the stack, and the variables are reset to an empty string. ๐Ÿซถ
  • restore "S" โ€” takes "name" "[name] ...". This is the opposite: the arguments are the names of variables that get set to the value found at the top of the stack, and then the stack is popped down. ๐Ÿ’…
Note: the preserve tag pushes its last argument first, whereas restore first pops its first argument. ๐Ÿ˜ญ

Source:

          <define-tag foo whitespace=delete>
          <preserve src name text />
          <set-var %attributes />
          Inside: src=<get-var src /> name=<get-var name /> text=<get-var text />
          <restore  src name text />
          </define-tag>
          <set-var src=foo.png text="Hello, World!" />
          Before: src=<get-var src /> name=<get-var name /> text=<get-var text />
          <foo src=bar name=quux />
          After: src=<get-var src /> name=<get-var name /> text=<get-var text />

Output:

          Before: src=foo.png name= text=Hello, World!
          Inside: src=bar name=quux text=
          After: src=foo.png name= text=Hello, World!
  • unset-var "S" โ€” takes "name" "[name] ...". Undefines variables. bye bye ๐Ÿ‘‹
  • var-exists "S" โ€” takes "name". Returns "true" when this variable exists.
  • increment "S" โ€” takes "name" "[by=value]". Increments the variable whose name is the first argument. The default increment is one.
    • "by=value" โ€” changes the increment amount.

Source:

          <set-var i=10 />
          <get-var i />
          <increment i /><get-var i />
          <increment i by="-3" /><get-var i />

Output:

          10
          11
          8
  • decrement "S" โ€” takes "name" "[by=value]". Decrements the variable whose name is the first argument. The default decrement is one.
    • "by=value" โ€” changes the decrement amount.

Source:

          <set-var i=10 />
          <get-var i />
          <decrement i /><get-var i />
          <decrement i by="3" /><get-var i />

Output:

          10
          9
          6
  • copy-var "S" โ€” takes "src" "dest". Copies a variable into another one. ๐Ÿฆ‹

Source:

          <set-var i=10 />
          <copy-var i j />
          <get-var j />

Output:

          10
  • defvar "S" โ€” takes "name" "value". If this variable isn't defined, or is defined to an empty string, then it gets set to the second argument. โœจ

Source:

          <unset-var title />
          <defvar title "Title" /><get-var title />
          <defvar title "New title" /><get-var title />

Output:

          Title
          Title
  • symbol-info "S" โ€” takes "name". Shows info on symbols. If it's a variable name, the "STRING" word gets printed, plus the number of lines contained within that variable. ๐Ÿ”

If it's a macro name, one of these messages gets printed: "PRIM COMPLEX", "PRIM TAG", "USER COMPLEX" or "USER TAG".

Source:

          <set-var x="0\n1\n2\n3\n4" />
          <define-tag foo>bar</define-tag>
          <define-tag bar endtag=required>quux</define-tag>
          <symbol-info x />
          <symbol-info symbol-info />
          <symbol-info define-tag />
          <symbol-info foo />
          <symbol-info bar />

Output:

          STRING
          5
          PRIM TAG
          PRIM COMPLEX
          USER TAG
          USER COMPLEX

โœจ String Functions โœจ

okay bestie, this is the fun part โ€” all the little macros that play with text ๐Ÿ’–

  • string-length "S" โ€” feed it a "string" and it tells you how long that string is. that's it. that's the whole vibe. โœจ

Source:

<set-var foo="0
1
2
3" />;;;
<string-length <get-var foo /> />
<set-var foo="0 1 2 3" />;;;
<set-var l=<string-length <get-var foo /> /> />;;;
<get-var l />

Output:

7
7
  • downcase "S" โ€” takes a "string" and makes it all lowercase letters. soft launch energy ๐ŸŽ€

Source:

<downcase "Does it work?" />

Output:

does it work?
  • upcase "S" โ€” takes a "string" and SHOUTS IT. uppercase everything, main character behaviour ๐Ÿ‘‘

Source:

<upcase "Does it work?" />

Output:

DOES IT WORK?
  • capitalize "S" โ€” takes a "string" and turns it into a title, with a capital letter at the beginning of every word. very proper, very demure ๐Ÿ’…

Source:

<capitalize "Does it work?" />

Output:

Does It Work?
  • substring "S" โ€” slices a piece out of a string. the arguments go "string" "[start [end]]": first one is the original string, then start index, then end index. and by convention the first character has a null index (so it's zero-based, iykyk). ๐Ÿ“

Source:

<set-var foo="abcdefghijk" />
<substring <get-var foo /> 4 />
<substring <get-var foo /> 4 6 />

Output:

efghijk
ef
  • string-eq "S" โ€” args are "string1" "string2" "[caseless=true]". it hands you back "true" if the first two arguments are equal. periodt. โœจ

Source:

1:<string-eq "aAbBcC" "aabbcc" />
2:<string-eq "aAbBcC" "aAbBcC" />

Output:

1:
2:true

caseless=true โ€” the comparison is case insensitive.

Source:

1:<string-eq "aAbBcC" "aabbcc" caseless=true />
2:<string-eq "aAbBcC" "aAbBcC" caseless=true />

Output:

1:true
2:true
  • string-neq "S" โ€” args are "string1" "string2" "[caseless=true]". returns "true" if the first two arguments are not equal. the little hater of the string world ๐Ÿ’€

Source:

1:<string-neq "aAbBcC" "aabbcc" />
2:<string-neq "aAbBcC" "aAbBcC" />

Output:

1:true
2:

caseless=true โ€” the comparison is case insensitive.

Source:

1:<string-neq "aAbBcC" "aabbcc" caseless=true />
2:<string-neq "aAbBcC" "aAbBcC" caseless=true />

Output:

1:
2:
  • string-compare "S" โ€” args are "string1" "string2" "[caseless=true]". it compares two strings and returns one of less, greater or equal depending on how they stack up against each other. vibe check central ๐Ÿ”ฅ

Source:

1:<string-compare "aAbBcC" "aabbcc" />
2:<string-compare "aAbBcC" "aAbBcC" />

Output:

1:less
2:equal

caseless=true โ€” the comparison is case insensitive.

Source:

1:<string-compare "aAbBcC" "aabbcc" caseless=true />

Output:

1:equal
  • char-offsets "S" โ€” args are "string" "character" "[caseless=true]". it prints an array with all the indexes where that character shows up in the string. like a little map of where your fave letter is hiding ๐Ÿฆ‹

caseless=true โ€” the comparison is case insensitive.

Source:

1:<char-offsets "abcdAbCdaBcD" a />
2:<char-offsets "abcdAbCdaBcD" a caseless=true />

Output:

1:0
8
2:0
4
8
  • printf "S" โ€” args are "format" "string" "[string ...]". it prints according to a given format. right now only the %s flag character is recognized, and the "$" extension is supported so you can change the order of the arguments. fancyyy ๐ŸŒˆ

Source:

1:<printf "foo %s bar %s" baz 10 />
2:<printf "foo %2$s bar %1$s" baz 10 />

Output:

1:foo baz bar 10
2:foo 10 bar baz

๐ŸŒธ Regular Expressions ๐ŸŒธ

bestie, regex support is provided by the PCRE (Perl Compatible Regular Expressions) library package, which is open source software, copyright by the University of Cambridge. it's genuinely a very nice piece of software, and the latest versions live at ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/. iconic. ๐ŸŽ€

before version 1.0.6, POSIX regular expressions were what got implemented. so for that reason, the following macros recognize two attributes: caseless=true and singleline=true|false. but Perl gives you so much better control over regular expressions with these things called modifiers, which get passed to the new reflags attribute. it may contain one or more modifiers:
  • i โ€” matching is case insensitive
  • m โ€” treat string as multiple lines. when set, a "^" matches any beginning of line, and "$" any end of line. by default, they match begin and end of string.
  • s โ€” treat string as single line. a dot (".") may also match a newline, whereas it does not by default.
  • x โ€” allow formatted regular expression, which means whitespaces, newlines and comments are removed from the regular expression before processing.
Note: the attribute singleline=true is a synonym for the s modifier, whereas singleline=false is a synonym for the m modifier. this behaviour was different up to mp4h 1.0.6, so don't get it twisted ๐Ÿ’€
  • subst-in-string "S" โ€” args are "string" "regexp" "[replacement]" "[caseless=true]" "[singleline=true|false]" "[reflags=[imsx]]". it replaces a regular expression inside a string with a replacement text. glow-up for your text fr fr โœจ

Source:

<set-var foo="abcdefghijk" />
<subst-in-string <get-var foo /> "[c-e]" />
<subst-in-string <get-var foo /> "([c-e])" "\\1 " />

Output:

abfghijk
abc d e fghijk

Source:

<set-var foo="abcdefghijk\nabcdefghijk\nabcdefghijk" />
<subst-in-string <get-var foo /> ".$" "" />
<subst-in-string <get-var foo /> ".$" "" singleline=false />
<subst-in-string <get-var foo /> "
   ([a-c]) | [0-9]
     " ":\\1:" reflags=x />

Output:

abcdefghijk
abcdefghijk
abcdefghij
abcdefghij
abcdefghij
abcdefghij
:a::b::c:defghijk
:a::b::c:defghijk
:a::b::c:defghijk
  • subst-in-var "S" โ€” args are "name" "regexp" "[replacement]" "[caseless=true]" "[singleline=true|false]" "[reflags=[imsx]]". it performs substitutions inside variable content. same energy as above but it lives in your variable ๐Ÿซถ
  • match "S" โ€” args are "string" "regexp" "[caseless=true]" "[singleline=true|false]" "[reflags=[imsx]]" "[action=report|extract|delete|startpos|endpos|length]". this one is the multi-tool and I'm obsessed ๐Ÿ’…
  • action=report โ€” prints "true" if the string contains the regexp.
  • action=extract โ€” prints the expression matching the regexp in the string.
  • action=delete โ€” prints the string without the expression matching the regexp in the string.
  • action=startpos โ€” prints the first char of the expression matching the regexp in the string. if there is no match, it returns "-1".
  • action=endpos โ€” prints the last char of the expression matching the regexp in the string. if there is no match, it returns "-1".
  • action=length โ€” prints the length of the expression matching the regexp in the string.

Source:

1:<match "abcdefghijk" "[c-e]+" />
2:<match "abcdefghijk" "[c-e]+" action=extract />
3:<match "abcdefghijk" "[c-e]+" action=delete />
4:<match "abcdefghijk" "[c-e]+" action=startpos />
5:<match "abcdefghijk" "[c-e]+" action=endpos />
6:<match "abcdefghijk" "[c-e]+" action=length />

Output:

1:true
2:cde
3:abfghijk
4:2
5:5
6:3

๐Ÿฆ‹ Arrays ๐Ÿฆ‹

ngl bestie, with mp4h you can easily deal with string arrays. variables can be treated as a single value or as a newline separated list of strings. so after defining

<set-var digits="0
   1
   2
   3" />

you can view its content, or one of those values:

Source:

<get-var digits />
<get-var digits[2] />

Output:

0
1
2
3
2
  • array-size "S" โ€” takes a "name" and returns the array size, which is the number of lines present in the variable. โœจ

Source:

<array-size digits />

Output:

4
  • array-push "S" โ€” args are "name" "value". it adds a value (or more than one, if that value contains newlines) at the end of an array. ๐Ÿซถ

Source:

<array-push digits "10\n11\n12" />
<get-var digits />

Output:

0
1
2
3
10
11
12
  • array-pop "S" โ€” takes a "name", removes the toplevel value of an array, and returns that string. bye bestie ๐Ÿ‘‹
  • array-topvalue "S" โ€” takes a "name" and prints the last entry of an array. no cap ๐Ÿ’–

Source:

<array-topvalue digits />

Output:

12
  • array-add-unique "S" โ€” args are "name" "value" "[caseless=true]". it adds a value at the end of an array only if that value isn't already chillin' in the variable. gatekeeping, but make it polite ๐ŸŽ€

Source:

<array-add-unique digits 2 />
<get-var digits />

Output:

0
1
2
3
10
11
12

caseless=true โ€” the comparison is case insensitive.

  • array-concat "S" โ€” args are "name1" "[name2] ...". it concatenates all arrays into the first one. squad goals, everyone joins the first array ๐ŸŒˆ

Source:

<set-var foo="foo" />
<set-var bar="bar" />
<array-concat foo bar /><get-var foo />

Output:

foo
bar
  • array-member "S" โ€” args are "name" "value" "[caseless=true]". if the value is contained in the array, it returns its index โ€” otherwise it returns -1. it's giving "do I know you?" ๐Ÿ’€

Source:

<array-member digits 11 />

Output:

5

caseless=true โ€” the comparison is case insensitive.

  • array-shift "S" โ€” args are "name" "offset" "[start=start]". it shifts an array. if the offset is negative, the indexes below 0 are lost. if the offset is positive, the first indexes get filled with empty strings. ๐Ÿฆ‹

Source:

<array-shift digits 2 />
Now: <get-var digits />
<array-shift digits -4 />
And: <get-var digits />

Output:

Now:

0
  1
  2
  3
  10
  11
  12

And: 2
  3
  10
  11
  12

start=start โ€” change the origin of the shifts (default is 0).

Source:

<array-shift digits -2 start=2 /><get-var digits />

Output:

  2
  3
  12
  • sort "S" โ€” args are "name" "[caseless=true]" "[numeric=true]" "[sortorder=reverse]". it sorts the lines of an array in place. the default is to sort lines alphabetically. put it in order, bestie โœจ

Source:

<sort digits /><get-var digits />

Output:

12
2
3

caseless=true โ€” the comparison is case insensitive.

numeric=true โ€” sort lines numerically

Source:

<sort digits numeric=true /><get-var digits />

Output:

  2
  3
  12

sortorder=reverse โ€” reverse the sort order

Source:

<sort digits numeric=true sortorder=reverse />;;;
<get-var digits />

Output:

  12
  3
  2

Numerical operators ๐Ÿ”ข

Okay bestie, these ones do math. That's it, that's the vibe. When every operand is an integer, you get an integer back โ€” the second anything's a float, you get a float. Honestly they explain themselves. No cap.

  • add "S" โ€” adds everything together ๐Ÿ’–

"number1" "number2" "[number3] ..."

  • substract "S" โ€” takes them away from each other (yes, it's spelled substract in here, that's not a typo on my end, that's canon)

"number1" "number2" "[number3] ..."

  • multiply "S" โ€” multiplies โœจ

"number1" "number2" "[number3] ..."

  • divide "S" โ€” divides ๐Ÿ“

"number1" "number2" "[number3] ..."

  • min "S" โ€” smallest one wins ๐Ÿ‘‘

"number1" "number2" "[number3] ..."

  • max "S" โ€” biggest one wins ๐Ÿ’…

"number1" "number2" "[number3] ..."

Source:

<add 1 2 3 4 5 6 />
<add 1 2 3 4 5 6. />

Output:

21
21.000000

See that second one? One little . and boom, float era. ๐Ÿ˜ญ

Source:

<define-tag factorial whitespace=delete>
<ifeq %0 1 1 <multiply %0 "<factorial <substract %0 1 /> />" /> />
</define-tag>
<factorial 6 />

Output:

720

Not me showing you a whole recursive factorial in a manpage ๐Ÿ’€ iconic behaviour fr.

  • modulo "S" โ€” the remainder, the leftovers, the crumbs

"number1" "number2"

Unlike the functions up above, modulo is a strict little diva: it cannot handle more than 2 arguments, and both of those arguments must be integers. No exceptions, no float shenanigans.

Source:

<modulo 345 7 />

Output:

2

Now, these next ones compare two numbers and hand you back "true" when the comparison holds up. But heads up โ€” if one of the arguments isn't a number at all, the comparison is just false. Vibe check failed, moving on. ๐Ÿ™ˆ

  • gt "S" โ€” returns "true" if the first argument is greater than the second.

"number1" "number2"

  • lt "S" โ€” returns "true" if the first argument is lower than the second.

"number1" "number2"

  • eq "S" โ€” returns "true" if the arguments are equal.

"number1" "number2"

  • neq "S" โ€” returns "true" if the arguments are not equal.

"number1" "number2"

Relational operators ๐ŸŒˆ

  • not "S" โ€” returns "true" if the string is empty, otherwise it hands you back an empty string. She's giving reverse psychology. ๐Ÿฆ‹

"string"

  • and "S" โ€” returns the last argument if every single argument is non-empty. All of them have to show up, no ghosting.

"string" "[string] ..."

  • or "S" โ€” returns the first non-empty argument. First one through the door wins. โœจ

"string" "[string] ..."

Flow functions ๐ŸŽ€

  • group "S""V" โ€” smooshes a bunch of statements into one single statement, like a little friend group holding hands ๐Ÿซถ

"expression" "[expression] ..." "[separator=string]"

Some examples of this show up below with the conditional operations, so stay with me.

There's a less obvious but super useful trick with this macro: it preserves newlines when whitespace=delete is in play. That's the whole point, bestie.

Source:

<define-tag text1>
Text on
3 lines without
whitespace=delete
</define-tag>
<define-tag text2 whitespace=delete>
Text on
3 lines with
whitespace=delete
</define-tag>
<define-tag text3 whitespace=delete>
<group "Text on
3 lines with
whitespace=delete" />
</define-tag>
<text1/>
<text2/>
<text3/>

Output:

Text on
3 lines without
whitespace=delete

Text on3 lines withwhitespace=delete
   Text on
   3 lines with
   whitespace=delete

Notice how the newlines get suppressed in text2 and the result is, uhโ€ฆ absolutely not what anyone wanted. ๐Ÿ˜ญ That's the cautionary tale. text1 keeps its newlines, text3 uses group to keep them on purpose, and text2 is just out here suffering.

  • compound โ€” like group, except this tag is complex. ๐Ÿ’…

"expression" "[expression] ..." "[separator=string]"

separator=string By default the arguments are just put aside. This attribute defines a separator that gets inserted between the arguments.

  • disjoin "S" โ€” does the exact opposite job of group: its argument stops being treated as a single object when another command processes it. Breaking up the group chat. ๐Ÿ’”

"expression"

  • noexpand "S""V" โ€” prints its arguments with no expansion whatsoever. They will never ever be expanded unless the expand tag comes along to cancel this noexpand tag. Frozen in time, periodt. ๐ŸงŠ

"command" "[command] ..."

  • expand "S" โ€” cancels the noexpand tag. The un-freezing. ๐Ÿ”ฅ

"command" "[command] ..."

Source:

<subst-in-string "=LT=define-tag foo>bar=LT=/define-tag>" "=LT=" "<" />
<foo/>
<subst-in-string "=LT=define-tag foo>quux=LT=/define-tag>" "=LT="
   "<noexpand "<" />" />
<foo/>

Output:

bar
<define-tag foo>quux</define-tag>
bar
  • if "S""V" โ€” if the string is non-empty, the second argument gets evaluated; otherwise the third one does. โœจ

"string" "then-clause" "[else-clause]"

Source:

<define-tag test whitespace=delete>
<if %0 "yes" "no" />
</define-tag>
<test "string" />
<test "" />

Output:

yes
no
  • ifeq "S""V" โ€” if the first two arguments are identical strings, the third argument is evaluated, otherwise the fourth is.

"string1" "string2" "then-clause" "[else-clause]"

  • ifneq "S""V" โ€” if the first two arguments are not identical strings, the third argument is evaluated, otherwise the fourth is.

"string1" "string2" "then-clause" "[else-clause]"

  • when โ€” when the argument isn't empty, its body gets evaluated. ๐Ÿฆ‹

"string"

  • while "V" โ€” while the condition stays true, the body function keeps getting evaluated. Looping era, no escape. ๐Ÿ‘‘

"cond"

Source:

<set-var i=10 />
<while <gt <get-var i /> 0 />>;;;
  <get-var i /> <decrement i />;;;
</while>

Output:

10 9 8 7 6 5 4 3 2 1
  • foreach โ€” basically the foreach macro from Perl, but make it mp4h. A variable loops over the array values and the function body is evaluated once for each value. ๐ŸŽ€

"variable" "array" "[start=start]" "[end=end]" "[step=pas]"

The first argument is a generic variable name, and the second one is the name of an array.

Source:

<set-var x="1\n2\n3\n4\n5\n6" />
<foreach i x><get-var i /> </foreach>

Output:

1 2 3 4 5 6

start=start Skips the first indexes.

Source:

<set-var x="1\n2\n3\n4\n5\n6" />
<foreach i x start=3><get-var i /> </foreach>

Output:

4 5 6

end=end Stops once the index has reached that value.

Source:

<set-var x="1\n2\n3\n4\n5\n6" />
<foreach i x end=3><get-var i /> </foreach>

Output:

1 2 3

step=step Changes the index increment (default is 1). And if the step is negative, the array gets treated in reverse order โ€” walking backwards through the group chat like a menace. ๐Ÿ’€

Source:

<set-var x="1\n2\n3\n4\n5\n6" />
<foreach i x step=2><get-var i /> </foreach>
<foreach i x step=-2><get-var i /> </foreach>

Output:

1 3 5
6 4 2
  • var-case "S""V" โ€” runs a whole stack of conditions off a single instruction. One tag, many outcomes, main character energy. ๐Ÿ‘‘

"var1=value1 action1" "[var2=value2 action2 ..."

Source:

<set-var i=0 />
<define-tag test>
<var-case
  x=1   <group <increment i /> x<get-var i /> />
  x=2   <group <decrement i /> x<get-var i /> />
  y=1   <group <increment i /> y<get-var i /> />
  y=2   <group <decrement i /> y<get-var i /> />
/>
</define-tag>
<set-var x=1 y=2 /><test/>
<set-var x=0 y=2 /><test/>

Output:

x1y0

y-1
  • break "S" โ€” breaks the innermost while loop. Get me out of here. ๐Ÿƒโ€โ™€๏ธ

Source:

<set-var i=10 />
<while <gt <get-var i /> 0 />>;;;
  <get-var i /> <decrement i />;;;
  <ifeq <get-var i /> 5 <break/> />;;;
</while>

Output:

10 9 8 7 6
  • return "S" โ€” immediately exits the innermost macro, and you can slip a message in too.

"[up=number]" "string"

โš ๏ธ But listen โ€” this macro changes token parsing, so using it may become very hazardous in some situations. Like, actually be careful, bestie. ๐Ÿ™ˆ

up=number This attribute decides how many levels have to be exited. By default only one level is skipped. With a null value, all current macros get exited from. A negative value does the same thing, and additionally stops processing the current file.

  • warning "S" โ€” prints a warning on standard error. Just a lil heads up. ๐Ÿ’œ

"string"

  • exit "S" โ€” immediately exits the program. Bye. โœŒ๏ธ

"[status=rc]" "[message=string]"

message=string Prints a message to the standard error.

status=rc Selects the code returned by the program (-1 by default).

  • at-end-of-file โ€” this one's special: its content is stored and will be expanded after the end of input. A little delayed surprise. ๐ŸŽ

File functions ๐Ÿ“

  • directory-contents "S" โ€” returns a newline separated list of the files inside a given directory. ๐ŸŽ€

"dirname" "[matching=regexp]"

Source:

<directory-contents . matching=".*\\.mp4h$" />

Output:

mp4h.mp4h
  • real-path "S" โ€” resolves all symbolic links, extra ``/'' characters and references to /./ and /../ in the pathname, and expands into the resulting absolute pathname. โœจ

"patname=pathname"

โš ๏ธ Every component of the pathname except the last one has to exist when real-path is called. No cap.

This tag is especially handy when you're comparing whether file or directory names are identical. ๐Ÿ”

Source:

<real-path pathname=<__file__/> />

Output:

/build/mp4h-MsMTo_/mp4h-1.3.1/doc/mp4h.mp4h
  • file-exists "S" โ€” returns "true" if the file exists. Simple, honest, reliable. ๐Ÿซถ

"filename"

  • get-file-properties "S" โ€” returns an array of information about the file. And that info is: size, type, ctime, mtime, atime, owner and group. The whole 411. ๐Ÿ’…

"filename"

Source:

<get-file-properties <__file__/> />

Output:

68604
FILE
1530057894
1530057894
1530057894
root
root
  • include "S" โ€” inserts the contents of a file from the file system into the input stream (if the file attribute is given), or the output from executing a system command into the input stream (if the command attribute is given). ๐Ÿฆ‹

"file=filename | command=command-line" "[alt=action]" "[verbatim=true]"

For backwards compatibility, if neither the file nor the command attribute is given, the first argument is taken as a file to include.

file=filename The given file is read and inserted into the input stream. This attribute cannot be combined with the command attribute.

Files are searched first in the current directory, then in the directories specified on the command-line with the -I option, next in the directories listed in the MP4HLIB environment variable (it used to be MP4HPATH for versions prior to 1.3), and last under the compile-time location (/usr/local/lib/mp4h/1.3.1:/usr/local/share/mp4h by default). That's the full search order, in order, don't skip a step. ๐Ÿ‘‘

command=command-line The given command line is executed on the operating system, and its output is inserted into the input stream. This attribute cannot be combined with the file attribute.

The given command line is executed using the popen(3) standard C library routine. The command is executed using the standard system shell, which on Posix compliant systems is sh(1).

  • alt=action โ€” if the file can't be found, this is the backup plan that gets handled instead. No alt and no file? Then bestie, that's an error, periodt. Heads up tho: this attribute is fully inactive if you also passed command. It just does nothing there. ๐Ÿ™ˆ
  • verbatim=true โ€” the file's content gets dropped in with zero expansion. Basically the same energy as using the m4 undivert macro with a filename as its argument. ๐Ÿ’…

Source:

<include command="uname -s" />

Output:

Linux
  • use "S"
    • "name=package"

Load definitions from a package file. ๐Ÿ“ฆ

  • comment โ€” this tag is literally doing nothing and it ate that. Its whole body just gets thrown away. ๐Ÿ’€
  • set-eol-comment "S"
    • "[string]"

Change comment characters. โœ๏ธ

  • set-quotes "S"
    • "[string string]" "[display=visible]"

By default, everything sitting between an <@[ and a ]@> pair gets read with no parsing at all. Call it with no argument and this macro switches that whole feature off. Call it with two arguments and it redefines the begin and end delimiters. The begin delimiter has to start with a left-angle bracket, and the end delimiter has to end with a right-angle bracket. No negotiating on that. ๐ŸŽ€

  • display=visible

The delimiters also get written into the output. ๐Ÿ‘€


๐ŸŒˆ Diversion functions ๐ŸŒˆ

Diversions are how you temporarily stash output somewhere. At any moment, mp4h's output can get diverted into a temp file, and then later be reinserted โ€” undiverted โ€” back into the output stream. So chic. โœจ

Numbered diversions count from 0 upward, and diversion number 0 is the normal output stream. The number of simultaneous diversions is limited mostly by the memory it takes to describe them, because mp4h tries to keep diversions in memory. But โ€” there's a ceiling on the total memory all diversions can eat together. When that max is about to get blown, a temporary file is opened to receive the contents of the biggest diversion still in memory, which frees that memory up for the other diversions. So theoretically, the number of diversions could end up limited by how many file descriptors you have available. ๐Ÿซ 

  • divert "S"
    • "[ divnum=diversion-number ]"

Output gets diverted with this tag, where diversion-number is the diversion you're sending it to. Leave the divnum attribute out and diversion-number is assumed to be zero. Divert output into a diversion that doesn't exist and it's just... gone. Discarded. Which honestly is a great way to suppress output you don't want. Peep the example below. ๐Ÿ‘‡

When all mp4h input has been processed, every existing diversion is automatically undiverted, in numerical order.

Calling divert several times with the same argument does not overwrite the previous diverted text โ€” it appends to it. ๐Ÿงš

Source:

<divert divnum="-1"/>
This is sent nowhere...
<divert/>
This is output.

Output:

This is sent nowhere...

This is output.
  • undivert "S"
    • "[ divnum=diversion-number ]"

This tag explicitly undiverts diverted text saved in the diversion with the specified number. Don't give it a divnum attribute and all diversions get undiverted, in numerical order. ๐Ÿฆ‹

When diverted text gets undiverted, mp4h does not reread it โ€” it's copied straight to the current output. Which means it is absolutely not an error to undivert into a diversion. Go off. ๐Ÿ’…

Unlike m4, the mp4h undivert tag does not let you pass a file name as an argument. You can do the same thing with the include tag plus the verbatim="true" attribute instead. ๐ŸŽ€

Source:

<divert divnum="1"/>
This text is diverted.
<divert/>
This text is not diverted.
<undivert divnum="1"/>

Output:

This text is diverted.

This text is not diverted.
  • divnum "S"

This tag expands to the number of the current diversion. ๐Ÿ”ข

Source:

Initial <divnum/>
<divert divnum="1"/>
Diversion one: <divnum/>
<divert divnum="2"/>
Diversion two: <divnum/>
<divert/>

Output:

Initial 0

Diversion one: 1

Diversion two: 2

๐Ÿ› Debugging functions ๐Ÿ›

When your constructs get complicated, debugging them can be a whole ordeal. The functions below are genuinely so useful when you're sitting there like "why is this not working." Fair warning tho: these functions are not perfect yet and they need to be improved in future releases. ๐Ÿ™ˆ

  • function-def "S"
    • "name"

Prints the replacement text of a user defined macro. For instance, the macro used to generate all the examples in this documentation is:

Source:

<function-def example />

Output:

<set-var-verbatim verb-body=%ubody /><subst-in-var verb-body "<" "<" />
<subst-in-var verb-body ">" ">" /><subst-in-var verb-body "^\n*" "" /><subst-in-var verb-body "^" "   " reflags=m /><set-var body=%body /><subst-in-var body "<three-colon/>[^;\n]*\n[ \t]*" "" /><subst-in-var body "<three-colon/>$" "" reflags=m /><subst-in-var body "^\n*" "" /><subst-in-var body "^" "   " reflags=m /><group "Source:

<get-var-once verb-body />

Output:

<get-var-once body />

" />
  • debugmode "S"
    • "string"

This command acts like the -d flag, except it can be changed ynamically. (That's the original's spelling, bestie, not mine โ€” I'm keeping it.) โœจ

  • debugfile "S"
    • "filename"

Picks a file where debugging messages get diverted to. If this filename is empty, debugging messages go back to standard error, and if it's set to -, those messages are just discarded. ๐Ÿ—‘๏ธ

Note: there is no way to print these debugging messages into the document being processed. Nope. Not happening. ๐Ÿ’€
  • debugging-on "S"
    • "name" "[name] ..."

Declares these macros as traced, meaning info about them gets printed if the -d flag or the debugmode macro is being used. ๐Ÿ”

  • debugging-off "S"
    • "name" "[name] ..."

These macros are no longer traced. ๐Ÿšซ


โœจ Miscellaneous โœจ

  • __file__ "S"
    • "[name]"

With no argument, this macro prints the current input filename. With an argument, it sets the string that future invocations of this macro will return. ๐Ÿ“„

  • __line__ "S"
    • "[number]"

With no argument, this macro prints the current line number in the input file. With an argument, it sets the number that future invocations of this macro will return. ๐Ÿ”ข

Source:

This is <__file__/>, line <__line__/>.

Output:

This is ./mp4h.mp4h, line 2201.

If you look closely at the source code, you'll notice this number is wrong. Indeed, the number line is the end of the entire block containing this instruction. Not a vibe, but that's how it is. ๐Ÿ˜ญ

  • __version__ "S"

Prints the version of mp4h. ๐Ÿท๏ธ

  • dnl "S"

Discards all characters until a newline is reached. This macro makes sure the following string counts as a comment and does not depend on the value of the comment characters. ๐Ÿšฎ

Source:

<dnl/>This is a comment
foo
<dnl/>This is a comment
bar

Output:

foo
bar
  • date "S"
    • "[epoch]"

Prints local time according to the epoch passed as argument. No argument? Then the current local time gets printed. ๐Ÿ•

  • time

An epoch time specification.

  • format

A format specification as used with the strftime(3) C library routine.

Source:

<date/>
<set-var info=<get-file-properties <__file__/> /> />
<date <get-var info[2] /> />
<date time="<get-var info[2] />" format="%Y-%m-%d %H:%M:%S" />

Output:

Wed Jun 27 00:04:54 2018

Wed Jun 27 00:04:54 2018
2018-06-27 00:04:54
  • timer "S"

Prints the time spent since the last call to this macro. The printed value is the number of clock ticks, so it's dependent on your CPU. โฑ๏ธ

Source:

<timer/>
The number of clock ticks since the beginning of the parsing of
this example by &mp4h; is:
<timer/>

Output:

user 23
sys 23

The number of clock ticks since the beginning of the parsing of
this example by mp4h is:
user 23
sys 23
  • mp4h-l10n "S"
    • "name=value"

Set locale-specific variables. By default, the portable "C" locale is selected. Since locales have different names on different platforms, you have to check your system documentation to find which values suit your system. ๐ŸŒ

  • mp4h-output-radix "S"
    • "number"

Changes the output format of floats by setting how many digits come after the decimal point. Default is to print numbers in the "%6.f" format. ๐Ÿ”ข

Source:

<add 1.2 3.4 />
<mp4h-output-radix 2 />
<add 1.2 3.4 />

Output:

4.600000

4.60

๐Ÿฆ‹ EXTERNAL PACKAGES ๐Ÿฆ‹

It is possible to include external files with the include command. Files get searched in this order, bestie, so pay attention: first in the current directory, then in directories specified on the command line with the -I option, next in directories listed in the MP4HLIB environment variable (fun fact: it used to be MP4HPATH for versions prior to 1.3), and last under the compile-time location, which by default is /usr/local/lib/mp4h/1.3.1:/usr/local/share/mp4h. ๐Ÿ”

Another way to include packages is with the use command. There are two differences between use and include: first, the package name has no suffix; and more importantly, a package cannot be loaded more than once. One and done. ๐Ÿ’…


๐ŸŒธ MACRO EXPANSION ๐ŸŒธ

This part describes the internal mechanism of macro expansion. It's gotta be as precise and exhaustive as possible, so contact me at <URL:mailto:barbier@linuxfr.org> if you have any suggestion. ๐Ÿซถ

๐Ÿ’– Basics ๐Ÿ’–

Let's start with some examples:

Source:

<define-tag foo>
This is a simple tag
</define-tag>
<define-tag bar endtag=required>
This is a complex tag
</define-tag>
<foo/>
<bar>Body function</bar>

Output:

This is a simple tag

This is a complex tag

User defined macros may have attributes, just like HTML tags. To handle these attributes in the replacement text, the following conventions have been adopted (mostly derived from Meta-HTML): ๐ŸŽ€

  • The sequence %name gets replaced by the command name.
  • Attributes are numbered from 0. In the replacement text, %0 gets replaced by the first argument, %1 by the 2nd, etc. Since there's no limitation on the number of arguments, %20 is the 21st argument and not the third followed by the 0 letter. Don't get it twisted. โœจ

Source:

<define-tag href>
<a href="%0">%1</a>
</define-tag>
<href http://www.gimp.org "The Gimp" />

Output:

<a href="http://www.gimp.org">The Gimp</a>
  • The sequence %# prints the number of attributes. ๐Ÿ”ข
  • The sequence %% gets replaced by %, which is useful in nested definitions. ๐Ÿช†

Source:

<define-tag outer>;;;
outer, # attributes: %#
<define-tag inner1>;;;
inner1, # attributes: %#;;;
</define-tag>;;;
<define-tag inner2>;;;
inner2, # attributes: %%#;;;
</define-tag>;;;
<inner1 %attributes and some others />
<inner2 %attributes and some others />
</define-tag>
<outer list attributes />

Output:

...and that's exactly where this page cuts off, bestie โ€” mid-example, mid-vibe, no warning. The rest is coming in the next part. ๐Ÿ˜ญ
            outer, # attributes: 2
            inner1, # attributes: 2
            inner2, # attributes: 5
  • the %attributes sequence gets swapped out for the space-separated list of attributes. slay.

source:

   <define-tag mail1>
   <set-var %attributes />
   <get-var name />
   <get-var mail />
   </define-tag>
   <set-var name="" mail="" />
   <mail1 name="Dr. Foo" mail="hello@foo.com" />

output:

   Dr. Foo
   hello@foo.com
  • %body gets swapped out for the body of a complex macro. iconic behaviour tbh.

source:

   <define-tag mail2 endtag=required whitespace=delete>
   <set-var %attributes />
   <a href="mailto:<get-var mail />">%body</a>
   </define-tag>
   <mail2 mail="hello@foo.com">
   <img src="photo.png" alt="Dr. Foo" border=0    />
   </mail2>

output:

   <a href="mailto:hello@foo.com">
   <img src="photo.png" alt="Dr. Foo" border=0      />
   </a>
  • both of the forms above can take modifiers btw. when you use %Aattributes or %Abody, you get a newline-separated list of attributes printed instead. ๐Ÿ’…

source:

   <define-tag show-attributes whitespace=delete>
   <set-var list="%Aattributes" i=0 />
   <foreach attr list>
   <group "%<get-var i />: <get-var attr />" />
   <increment i />
   </foreach>
   </define-tag>
   <show-attributes name="Dr. Foo" mail="hello@foo.com" />

output:

   %0: name=Dr. Foo%1: mail=hello@foo.com
  • and there's another alternate form, where you swap the "A" for a "U". in that case the text gets replaced but it does not get expanded. ngl this only actually makes sense when the macro was defined with attributes=verbatim, otherwise the attributes get expanded before the replacement even happens.

source:

   <define-tag show1>
   Before expansion: %Uattributes
   After expansion: %attributes
   </define-tag>
   <define-tag show2 attributes=verbatim>
   Before expansion: %Uattributes
   After expansion: %attributes
   </define-tag>
   <define-tag bar>and here %attributes</define-tag>
   <show1 <bar we go /> />
   <show2 <bar we go /> />

output:

   Before expansion: and here we go
   After expansion: and here we go

   Before expansion: <bar we go />
   After expansion: and here we go
  • the "A" and "U" modifiers can be combined. yes bestie, stack them.
note: input expansion is completely different in Meta-HTML versus in mp4h. in Meta-HTML you sometimes have to reach for other constructs like %xbody and %qbody. to make compatibity with Meta-HTML better, those constructs are recognized and interpreted just like %body. another compat feature: for simple tags, %body and %attributes are equivalent. these features exist in the current mp4h version but they might disappear in future releases. so don't get too attached. ๐Ÿ˜ญ

๐ŸŽ€ Attributes ๐ŸŽ€

Attributes are separated by spaces, tabulations or newlines, and every single attribute has to be a valid mp4h entity. so like, with the definitions above, "<bar>" can not be an attribute, because it has to be finished off by "</bar>". but this one is valid:

   <foo <foo/> />

or even:

   <foo <foo name=src url=ici /> />

in both those examples, the "foo" tag has only one argument.

sometimes you gotta group multiple statements into a single one, lowkey necessary. you do that with double quotes, or with the "group" primitive, like:

   <foo "This is the 1st attribute"
              <group and the second /> />
note: unlike HTML, single quotes can not replace double quotes for this purpose. not the vibe, apparently. ๐Ÿ™ˆ

if double quotes show up inside an argument, they gotta be escaped with a backslash "\"".

source:

     <set-var text="Text with double quotes \" inside" />
     <get-var text />

output:

     Text with double quotes " inside

๐Ÿฆ‹ Macro evaluation ๐Ÿฆ‹

Macros are characterized by:

  • name
  • container status (simple or complex)
  • whether attributes are expanded or not
  • function type (primitive or user defined macro)
  • for primitives, the address of the corresponding code in memory, and for user defined macros, the replacement text

Characters get read on input until a left angle bracket shows up. then the macro name gets read. after that, attributes get read โ€” verbatim or not, depending on how this macro was defined. and if the macro is complex, its body is read verbatim. when all that's done, some special sequences in the replacement text get replaced (like %body, %attributes, %0, %1, etc.) and the resulting text gets put on the input stack so it can be rescanned. periodt.

note: by default, attributes are evaluated before any replacement. iykyk.

ok consider this next one, for changing text into typewriter font:

   <define-tag text-tt endtag=required whitespace=delete>
      <tt>%body</tt>
      </define-tag>

ngl this definition has a major drawback:

source:

      <text-tt>This is an <text-tt>example</text-tt></text-tt>

output:

      <tt>This is an <tt>example</tt></tt>

and we would like the inner tags to be removed. ๐Ÿ’€

first idea: use an auxiliary variable to know whether we're still inside such an environment:

   <set-var _text:tt=0 />
      <define-tag text-tt endtag=required whitespace=delete>
      <increment _text:tt />
      <ifeq <get-var _text:tt /> 1 "<tt*>" />
      %body
      <ifeq <get-var _text:tt /> 1 "</tt*>" />
      <decrement _text:tt />
      </define-tag>

(the presence of asterisks in HTML tags is explained in the next section, bestie, stay with me).

source:

      <text-tt>This is an <text-tt>example</text-tt></text-tt>

output:

      <tt>This is an example</tt>

but if we use simple tags, like in the example below, our definition just... doesn't seem to work. ๐Ÿ˜ญ that's because attributes get expanded before they get put into the replacement text.

source:

      <define-tag opt><text-tt>%attributes</text-tt></define-tag>
      <opt "This is an <opt example />" />

output:

      <tt>This is an <tt>example</tt></tt>

so if we wanna prevent this problem, we gotta forbid attributes expansion with:

source:

      <define-tag opt attributes=verbatim>;;;
      <text-tt>%attributes</text-tt>;;;
      </define-tag>
      <opt "This is an <opt example />" />

output:

      <tt>This is an example</tt>

โœจ Expansion flags โœจ

when you wanna embed some server-side scripting language in your pages, you run into some weird problems, like in:

   <a href=<%= $url %>>Hello</a>

the question is: how does mp4h know that this input has some extra delimiters? and the answer is that mp4h should not try to handle some special delimiters, because it literally cannot handle all of them (there's ASP, ePerl, PHP,... and some of them are customizable). so remember: mp4h is a macro-processor, not an XML parser. so we focus on macros, and we format our input file so it can be parsed without any problem. the previous example can be written like:

   <a href="<%= $url %>">Hello</a>

because quotes stop the inner right-angle bracket from closing the "a" tag. ๐Ÿ’…

another common problem: when we need to print only a begin or an end tag alone. for instance it's very desirable to define your own headers and footers with:

   <define-tag header>
      <html*>
      <head>
      ... put here some information ....
      </head>
      <body* bgcolor="#ffffff" text="#000000">
      </define-tag>
      <define-tag footer>
      </body*>
      </html*>
      </define-tag>

asterisks mark these tags as pseudo-simple tags, which means they're complex HTML tags but used as simple tags inside mp4h, because otherwise the tags wouldn't be well nested.

this asterisk is called the ``trailing star'', and it shows up at the end of the tag name.

sometimes HTML tags just aren't parsable, like in this javascript code:

     ...
          document.write('<*img src="foo.gif"');
          if (text) document.write(' alt="'+text+'"');
          document.write('>');
        ...

the ``leading star'' is an asterisk sitting between the left-angle bracket and the tag name, and it stops this tag from being parsed. ๐Ÿซถ

ok so with all that said, we can finally understand what the --expansion flag is for. it controls how expansion is performed by mp4h. it's followed by an integer, which is a bit sum of these values:

  1. do not parse unknown tags. when set, HTML tags are not parsed. when unset, HTML tags are parsed, i.e. that attributes and/or body is collected.
  2. unknown tags are assumed being simple. when set, HTML tags are simple by default. when unset, HTML tags are complex by default, unless their attribute contain a trailing slash or a trailing star appear just after tag name (see below).
  3. trailing star in tag name do not make this tag simple. when set, trailing star in tag name has no special effect. when unset, it causes an HTML tag to be simple.
  4. an unmatched end tag closes all previous unmatched begin tags. when set, all missing end closing tags are automatically inserted. when unset, an unmatched end tag is discarded and interpreted as normal text, so processing goes on until matching and tag is found.
  5. interpret backslashes as printf. when set, backslashes before non special characters are removed. when unset, they are preserved.
  6. remove trailing slash in tag attributes. when set, remove trailing slash in tag attributes on output. when unset, they are preserved.
  7. do not remove trailing star in tag name. when set, trailing star after tag name are preserved on output. when unset, they are removed.
  8. do not remove leading star in tag name. when set, leading star before tag name are preserved on output. when unset, they are removed.
  9. do not add a space before trailing slash in tag attributes by default, a space is inserted before trailing slash in tag attributes. when set, this space is not prepended.

1024. suppress warnings about bad nested tags. when set, warnings about bad nested tags are not displayed. when unset, they are printed on standard error.

2048. suppress warnings about missing trailing slash. when set, warnings about missing trailing slash are not displayed. when unset, they are printed on standard error.

run:

   mp4h -h

to find the default value. the current value matches HTML syntax, and it'll tend toward zero when XHTML syntax becomes more familiar. ๐Ÿ‘‘


๐Ÿ‘‘ AUTHOR ๐Ÿ‘‘

Denis Barbier "<URL:mailto:barbier@linuxfr.org>"

Mp4h has its own homepage "<URL:http://mp4h.tuxfamily.org/>".


๐Ÿ’– THANKS ๐Ÿ’–

Sincere thanks to Brian J. Fox for writing Meta-HTML and Rene Seindal for maintaining this wonderful macro parser called GNU m4. absolute legends, no cap. ๐ŸŒธ


and that's the whole page bestie, you made it to the end ๐ŸŽ‰ this one's from HTML Tools, dated 2018-06-27, and it lives at MP4H(1). go forth and macro-process, queen. ๐Ÿ’…โœจ๐Ÿฆ‹

๐Ÿ“ฆ where this page comes from