PPTools/Ppgen/Tutorial/Macros

From DPWiki
< PPTools‎ | Ppgen
Jump to navigation Jump to search

Macros

For basic information on macros, see the description in the ppgen manual. This tutorial will explore some more complex aspects of macros.

Aspects of Macros

Continuation of long parameters

As with other directives and text in ppgen, you can continue a long .pm command by using a \ as the last character on the line:

 .pm ilcc illus-fpc.jpg 382px "This is a long caption that I didn't want to\
 type all on one line so I continued it to a second line."

Resulting code placed in source file at runtime:

 .il fn=illus-fpc.jpg w=382px
 .ca "This is a long caption that I didn't want to type all on one line so I continued it to a second line."

Note that the \ is replaced by a blank. (This behavior was changed in ppgen 3.53ca6 to be more compatible with other ppgen continuation.)

Cooperative macros

ppgen markup can span macros. That is, you can start some markup in one macro, have intervening text, and close the markup in a later macro, and this is actually quite common in practice. Example:

 // Begin Poetry
 .dm onpoem      // macro to set a smaller font size (in HTML) and surround a poem with a .nf b block
 .fs 90%
 .nf b           // we begin the .nf block in this macro
 .dm-
 
 // End Poetry
 .dm offpoem     // macro to end the .nf b block and restore the font size
 .nf-            // we end the .nf block in a different macro
 .fs 100%
 .dm-
 Use the macros:
 .pm onpoem      // begin a poem
 This is line 1
 This is line 2
 Almost done
 And now we're through.
 .pm offpoem     // end the poem

Resulting code placed in source file at runtime:

 .fs 90%
 .nf b
 This is line 1
 This is line 2
 Almost done
 And now we're through.
 .nf-
 .fs 100%

Using macros within a table definition

Though non-Python macros perform simple text substitution, they can still accomplish some interesting tasks. For example, suppose you have a table (.ta directive) with a column that you want to layout a bit differently in the text output than in the HTML. Perhaps, for example, you want to make one column a bit narrower, and hyphenate words in that column for the text version. You could using ".if t" and ".if h" provide two versions of the table, but that would involve copying a lot of text and could lead to errors.

To avoid that, you could use macros, like this:

 // Using continued lines and .pm within the table

 .dm tablemac $1 $2 // define the macro
 .if t              // In the text output we will use parameter $1, which ends with a \
 $1\                //   so the next line (outside the macro) is taken as a continuation
 .if-
 .if h              // In the HTML output we use parameter $2, similarly continued onto the
 $2\                //   next line outside the macro
 .if-
 .dm-
 
 .ta h:9 |h:20 |r:8 |h:21 |r:9 s="TABLE 4"
 =

<al=c>County.|<al=c>Railroad.|<al=c>\

 .pm tablemac "1900 Ap- praisal," "1900 Appraisal,"
 average per acre.|<al=c>Railroad.|<al=c>Actual transfer, average per acre.
 _
 Jackson   |Michigan Central. Air Line                          |$71.36|Michigan Central Air Line. New Line                     |$165.67
 ...
 .ta-

Here the .pm line provides one version of (part of) that cell for the text, with a hyphen and a blank to allow ppgen to wrap the text, and a different version for the HTML where the browser will worry about the wrapping. The only duplicated data is for that one portion of one table cell.

The code processed for the text output will be:

 .ta h:9 |h:20 |r:8 |h:21 |r:9 s="TABLE 4"
 =

<al=c>County.|<al=c>Railroad.|<al=c>1900 Ap- praisal, average per acre.|<al=c>Railroad.|<al=c>Actual transfer, average per acre.

 _
 Jackson   |Michigan Central. Air Line                          |$71.36|Michigan Central Air Line. New Line                     |$165.67
 ...
 .ta-

but the code processed for the HTML output will be:

 .ta h:9 |h:20 |r:8 |h:21 |r:9 s="TABLE 4"
 =

<al=c>County.|<al=c>Railroad.|<al=c>1900 Appraisal, average per acre.|<al=c>Railroad.|<al=c>Actual transfer, average per acre.

 _
 Jackson   |Michigan Central. Air Line                          |$71.36|Michigan Central Air Line. New Line                     |$165.67
 ...
 .ta-

Gory processing details

Most users can safely ignore this, but writing sophisticated macros can be easier if you understand how macro processing works in the context of the other processing that ppgen performs.

In processing the source file, ppgen has two control phases (text, HTML) and within each several phases of examining the source. The important ones for understanding macro processing work the same for both text and HTML, and operate like this:

  • ppgen processes the source and removes comments and code affected by the .ig (ignore) directive.
  • ppgen then processes .if directives. During the text phase ppgen keeps the statements between ".if t" and ".if-" and throws away the statements between ".if h" and ".if-". The reverse happens during the HTML phase. Note that during this processing macros have not been examined or processed, so if there are .if statements within a macro definition the macro will be defined differently for the text phase than for the HTML phase.
  • ppgen then processes macro definitions (.dm through .dm-) and removes them from the source.
  • ppgen then processes .pm directives to "play" the macros and generate source statements to replace the .pm directive. During this time ppgen handles continuation of the .pm directive itself.
  • Later, ppgen processes continuations in general, joining lines together as indicated by \ continuation characters. If a macro generated a line ending with \ it will be joined with the line that follows.
  • Even later, ppgen processes the other directives (e.g., the .ta directive and the statements up through the .ta- in the example above).

Macros in Python

Starting with ppgen version 3.54 you can write macros in the Python programming language if you have a need for processing that is too complex for the simple text substitution that standard macros can perform. Note that macros written in Python can be invoked to handle replacement processing for the .sr directive as well as by .pm or <pm>

Syntax:

 .dm <mac-name> <optional parameters> lang=python
 Python code here
 .dm-

For macros written in Python it will be helpful to think of the variables as being $0, $1, $2, etc. rather than $1, $2, $3. In any case, the parameters specified on the .dm are ignored, and are only there for your reference.

The Python code block is one or more lines of Python statements, with appropriate indentation as required by Python. The macro has access to the paramters specified on the .pm directive that invokes the macro. Any output lines generated by the macro will replace the .pm directive, just as with standard ppgen macros.

All the variables are passed to the macro in a Python dictionary named "var":

  • var[0], var[1], etc. will hold the macro parameters $0, $1, etc. (Note that numbering within the macro starts with 0 as is usual for Python code.)
  • var["type"] (with the quote marks) will contain the string "text" during the text processing pass and "html" during the HTML processing pass.
  • var["style"] will contain either ".pm", "<pm>", or ".sr" depending on how the macro was invoked.
  • var["name"] will contain the defined name of the macro (e.g., "mac-name" or whatever name you used).
  • var["out"] will be an empty list, to which the macro will append the lines of output that it generates.

The macro will have all the standard Python builtin functions available, as well as the "re" module (or "regex", if you have that module installed). The macro will also have two internal ppgen routines available to it:

  • toRoman, which takes an int or a string of digits as input, and which returns the number converted to Roman notation, as a string
  • fromRoman, which takes a string containing the Roman notation of a number, and returns the int representing that Roman number.

The macro will also have available a Python dictionary named savevar, which will initially be empty. Anything the macro saves in that dictionary will be available to Python macros that run later during ppgen processing.

Simple Python Macro Examples

We saw a simple macro above in the "Using macros within a table" section:

 .dm tablemac $1 $2 // define the macro
 .if t
 $1\
 .if-
 .if h
 $2\
 .if-
 .dm-

Writtin in Python, that same macro might be:

 .dm tablemac $0 $1 lang=python
 if var["type"] == "text":
   choice = var[0]
 else:
   choice = var[1]
 var["out"].append(choice)
 .dm-

Of course, an example this simple doesn't need the flexibility of Python. For a slightly more complex case, suppose we didn't want to pass multiple parameters to the macro, but just one. In the table example above we invoked the macro using

 .pm tablemac "1900 Ap- praisal," "1900 Appraisal,"

Now, we will write our Python macro using only one parameter, and will invoke it using

 .pm tablemac2 "1900 Ap%%praisal,"

In addition to inserting a "- " for the text version, we will add "soft hyphens" in the HTML version, to provide some hyphenation hints for the browser.

The macro:

 .dm tablemac2 $0 lang=python
 if var["type"] == "text": # for text pass
    cell = re.sub("%%", "- ", var[0]) # change any %% in the parameter to "- " to allow splitting by ppgen
 
  else: # html pass
    cell = re.sub("%%", "­", var[0]) # change any %% to a soft hyphen to let browser split the word there
 
  var["out"].append(cell + "\\") # pass back changed data followed by a ppgen continuation backslash
 .dm-

The code processed for the text output will be:

 .ta h:9 |h:20 |r:8 |h:21 |r:9 s="TABLE 4"
 =

<al=c>County.|<al=c>Railroad.|<al=c>1900 Ap- praisal, average per acre.|<al=c>Railroad.|<al=c>Actual transfer, average per acre.

 _
 Jackson   |Michigan Central. Air Line                          |$71.36|Michigan Central Air Line. New Line                     |$165.67
 ...
 .ta-

The code processed for the HTML output will be:

 .ta h:9 |h:20 |r:8 |h:21 |r:9 s="TABLE 4"
 =

<al=c>County.|<al=c>Railroad.|<al=c>1900 Ap&shy;praisal, average per acre.|<al=c>Railroad.|<al=c>Actual transfer, average per acre.

 _
 Jackson   |Michigan Central. Air Line                          |$71.36|Michigan Central Air Line. New Line                     |$165.67
 ...
 .ta-

Error Handling in Python Macros

If a Python error occurs while processing your macro ppgen will try to provide information about the error, including the name of the macro, the macro source line which caused the error, and the Python stack trace. You also have the standard Python error handling facilities available (try/except, etc.) if you want to make use of them within your macro.