Using Python

SectionWhy Python
Starting with the initial PageStream 4.1 Linux release, Python was added to PageStream's bag of tricks. The impetus was to provide the same scripting environment for all platforms to make user scripts the same on any platform PageStream supports. Previously the Amiga version had used ARexx and the Macintosh version AppleScript. While this was friendly for the platform it made quite a bit of work adding a new scripting engine for each platform and with Windows and Linux being added there had to be a better way.

There are other cross-platform scripting languages -- the selection of Python was pragmatic and development driven: support was added in a single day. Since then the scripting interface has been modified to allow cleaner Python code. Though PageStream's scripting interface may not implemented in a Python-object fashion it works quite well.

SectionUnderstanding The Documentation?
Strictly speaking the scripting interface works with methods exposed to PageStream's internal macro language, although some commands make no sense in that context (for example, those that use variables -- a feature not present in the macro language). The macro language and ARexx share vary similar semantics, but Python just looks different.

SectionUnderstanding The Documentation
The command names and even the templates are essentially the same. Where the documentation says NEWCHAPTER use the camel-cased NewChapter. If the command takes arguments, they are included in the parenthesis as quoted strings. Take care to include keywords as well, again quoted. So

Python:

newchapter 'Part Three' 7

becomes

Python:

NewChapter("PartThree", 7)

NoteCamelCasing is not actually required, but it does help readibility. PageStream actually lowercases all commands, keywords and variable names that are passed to it.

SectionUsing Python
So much for issuing commands, but what about getting values back from them? All PageStream commands return a list. At a minimum there are two keys in the list, .result and .error. If the command issued included stem variables then those will be included as well. A return list might look something like:

Python:

{'.error': 1, '.result': 0, 'stem': {'width': 8.5, 'sides': 'DOUBLE', 'orientation': 'PORTRAIT', 'height': 11.0}}

The common keys and their meanings are as follows:

.error This is 1 if there is no error, otherwise it is zero
.result This is the result code for the command; the meaning depends on the command.
.errno This key is present if there is an error and is the number for the error. For example, CurrentWindowPath errno 1001 means that there is no window to have a current window path.
.errstr This key is present if there is an error and is the text string for the error. For example, CurrentMasterPage errstr 'No Master Page' means that there is no master page for the current document.

To issue PageStream commands you must import them first. The library is, unsurprisingly, called PageStream.

Python:

from PageStream import *

Because PageStream implements Python by embedding the interpreter in its own executable instead of providing a standalone library you can't import PageStream except from within PageStream. This makes development and testing a bit awkward.

One way of dealing with this on Linux is to redirect PageStream's output into a log file. Alter the script that starts PageStream so that it includes the following:

Code:

echo "----" >> ~/PgS.log
echo `date` >> ~/PgS.log
$PGSPATH/PageStream5 >> ~/PgS.log 2>&1

This creates a log file in your home directory named PgS.log (or appends to it if a file by that name already exists). Marking PageStream's startup and putting in a date/time stamp helps make the log file more readable. The end result is a trace of PageStream's output, both stdout and stderr. However, the file only gets written to when the output buffer gets flushed. The only certain way of flushing the buffer is to close PageStream, but often a script that creates a non-critical error will work as well.

Because the shells provided by Microsoft don't show the output from PageStream on Windows it is recommended to use a replacement shell like msys instead.

SectionObtaining Python

The main Python site is http://www.python.org -- follow the download link. There are source archives as well as a Windows installer.

SectionResource Files

The layout of a window or dialog box is determined by a resource file. Such files have the .rsc extension and, being plain text, can be edited with any text editor.(2) If characters outside the standard 7-bit ASCII are needed make sure to use an editor that will save the file with UTF-8 encoding.

NoteAll considerations of white space are solely for readability. PageStream itself does not care about white space.

A resource file consists of string table, requester and icon definitions. String tables, string items, icons, requesters and the objects in a requester are each identified by a number. In the case of string tables, icons and requesters the number must be unique in the file for objects of that type. Other objects must be unique in their respective container, irrespective of type.

SectionString Tables

The purpose of a string table is to provide for localization. Ideally, all text associated with a resource is placed in a resource file string table rather than in code. This is true even for scripts. It becomes substantially more difficult to localize text if this is not done. By convention string tables are placed first in a resource file. This allows ready location of all strings for translation.

String tables consist of the string table declaration followed by a series of string item declarations. The string table ends when the next string table starts, a requester is declared, or the file ends. A string table that is associated with a requester will usually be assigned a unique ID one after the requesters. For example, if the requester is ID 200 then its string table would be 201.

Code:

STRINGTABLE #201
STRINGITEM #1 :"Some text for requester 200"
STRINGITEM #2 :"Some more text"
STRINGITEM #3 :"And even more text"

SectionRequesters

Requesters provide resizeable and scalable graphical user interface objects. The layout rules to get the desired result can be a little tricky -- if a requester does not behave as expected when resized take a careful look at how you specify its layout. The template for a requester declaration is as follows:

REQUESTER #<ID> [W#<Width> H#<Height>] [L:"<Title>"] [:"path/to/help.html"]

Containers
There are three basic containers for controlling layout: boxes, grids and button groups. The end of a container is marked with the END keyword and the contents of a container are indented. As containers are meta information belonging to a requester and not themselves objects they do not have IDs.

Boxes can be either horizontal or vertical, the keyword being HBOX and VBOX, respectively. Whether or not there is white space padding round the container is controlled by the MARGIN | NOMARGIN keywords.

Grids provide a flexible grid of cells with the top left corner having coordinates of 0,0. Objects contained by a grid specify their position in it by putting the cell boundaries after the object's ID using the following template. L#<left> R#<right> T#<top> B#<bottom>

Code:

REQUESTER #101 L:"Sample Requester"
VBOX
GRID NOMARGIN
END
BGROUP
END
END

Basic Objects
There are four basic objects: string boxes, check boxes, buttons and popup menus. As each is an object it must have an ID that is unique to the requester. The template is <ObjectType> #<ID> [<Grid Coordinates>] L:"<Label>". The string box has an additional parameter W#<Width> that can be used to set the maximum string length to be input.

Code:

STRING #10 L#1 R#3 T#0 B#1 L:"Input text"
CHECKBOX #11 L#0 R#1 T#1 B#2 L:"Check me"
BUTTON #12 L#2 R#3 T#1 B#2 L:"Click me"
POPUP #13 L#1 R#3 T#2 B#3 L:"Select one"

A button can have an optional keyword to specify that it is a special button. The keyword ISOK causes the "OK" icon (a green checkmark) to be added to the button. ISCANCEL causes the "Cancel" icon (a red X) to be added to the button.

[note=sidebar]Keyboard shortcuts can be set for any object that has a label by putting an underscore before the shortcut character. For example, STRING #10 L:"Input _text" would set the letter 't' as the shortcut for string box number 10.

Code:

BUTTON #9 ISCANCEL L:"Cancel"
BUTTON #1 ISOK L:"Accept"

[/note]

Popup Menus
A popup is empty until given some content. A list can be defined in the resource file or added by the program. To define a list in the resource file indent from the POPUP declaration to show that the list is contained by the popup menu. A list declaration is a container the end of which is marked by the ENDLIST keyword. Everything in the list declaration should be a list item. The template for a list item is LISTITEM #<ID> :"<Text in the popup>". The ID for a list item only needs to be unique in that list and is the value associated with the entry.

Code:

LIST
LISTITEM #1 :"Apples"
LISTITEM #2 :"Oranges"
LISTITEM #3 :"Pears"
ENDLIST

Other Objects
There are a variety of additional objects available as well.

Help Button
HELP #<ID> LABEL
To put a help button on the requester you must specify a unique ID. If the button is clicked it will attempt to open a web browser and load the URL specified by the requester for its help file. The URL is always relative to the PageStream help file directory.

Code:

HELP #999 LABEL

Display Text
DISPLAY #<ID> [Grid coordinates] [H#<Height>] [RECESSED] [CENTER] [L:"<Label>"] S:"<Text to be displayed>"
This object puts text in the requester. It can be framed, inset, etc.
Code:

DISPLAY #10 L#0 R#4 T#0 B#1 H#4 S:"Some longer text that is displayed in the requester without having a scroller or any border"

Toggle
TOGGLE #<ID> I#<number>

Mini Menu
MINIMENU #<ID>
This is the kind of menu found in the Edit Palette for selecting the type of corner used on a box. The options displayed are either added to the mini menu programmatically or via a list container.

Code:

MINIMENU #30 L#2 R#3 T#0 B#1
LIST
LISTITEM #1 :Load
LISTITEM #2 :Save
ENDLIST


List View
LISTVIEW #<ID> [H:<Height>] [L:"<Label>"]
Provides a scrolling view port on a list. The list can be declared in the resource file or added programmatically.

Slider
SLIDER #<ID> M#<Maximum Value> [L:"<Label>"]
This is used to put a slider that represents a value from zero to the specified maximum.

SectionIcons
An icon consists of a 1-bit mask and RGB color data. Because an icon is an object it must have a unique identifier. By convention it is numbered following the requester it is associated with. For example, an icon for requester 100 might have ID 110. The mask is declared using the template ICONMASK <width> <height> and the color data is declared using ICONPART <bits per pixel> <width> <height>. In both cases the actual data is supplied four bytes per line.

Code:

ICON #100
ICONMASK 16 16
0000
7FFE
7FFE
7FFE
7FFE
7FFE
7FFE
7FFE
7FFE
7FFE
7FFE
7FFE
7FFE
7FFE
7FFE
0000
ICONPART 1 16 16
0000
7FFE
4202
4202
7FFE
4202
56AA
4202
6B56
4202
56AA
4202
6B56
4202
7FFE
0000


SectionUsing Requesters in Python
For a script to have anything more than a simple pre-set GUI (such as those provide by AlertRequester) it must use a resource file. The resource file contains the definition of one or more requesters. To use a requester the resource file containing it must be opened for use with the OpenResource command. Before using the resource it is important to verify that it was successfully opened. The unique identifier for the resource is in the .result value. This will be 0 if the resource file could not be opened.

Python:

rc = OpenResource("RequesterExample")
if rc['.result'] == 0:
AlertRequester(AlertType_Error, "RequesterExample.rsc missing", "OK")
else:
# the code using the requester

NoteThe resource file must be in the same directory as the script and the extension is not specified: PageStream automatically adds '.rsc' to the name.

Any values that need to be read from the string tables in the resource file can now be loaded with the GetResourceAdd Page command. Requester definitions can also be loaded with the CreateRequester command.

Code:

reqH = CreateRequester(rscH, 100)['.result']

Any lists that will be set programmatically can now be done so. A special case of this is getting the list of available languages from PageStream with the CreateLanguageEngineList command. Although that command can be used at any time it is usually used to set a list in the requester. Note the use of SetControlAttr to set the currently selected language to a specific value.

Code:

lelistH = CreateLanguageEngineList()['.result']
SetControlAttr(reqH, 11, ControlAttr_List, lelistH)
SetControlAttr(reqH, 11, ControlAttr_String, "American")

In general, set any control in the requester as desired to complete setup. When everything is set use DoRequester to make the requester visible to the user. The DoRequester() command is a blocking command. Script execution stops until the requester is closed, the return value being the ID of button that was clicked.

Code:

buttonid = DoRequester(reqH)
if buttonid == 1:
# do stuff
elif buttonid == 2:
# do other stuff
else:
# do something else

To get information out of the requester, such as text typed into a string, use the GetControlAttr command.


1. Amiga Python does not work for Python scripting as presented here (PageStream uses an embedded interpreter that requires an external library -- this is not currently supported by Amiga Python).

2. Although you can technically edit the rsc files that come with PageStream it is not recommended as an invalid rsc file will result in undefined behavior.

 

Using Python by Tim Doty  Chapter  url:PGScmd/Cpython
  created:2006-03-31 14:53:35   last updated:2006-08-08 16:49:35
  Copyright © 1985-2017 GrasshopperLLC. All Rights Reserved.

User Contributed Comments For Using Python
Tim Doty wrote...2006-04-25 04:38:41

From my scripting notes:

================================================================================

Problem: calling sys.exit() exits the script AND PageStream so how do you exit a script?

Solution: there are several ways to do this, but the best ways revolve around declaring a main() function. The main() function itself is called by a simple construct that makes it safe to import the script file as a module:

Code:

def main():
... code 1 ...

if condition1:
return 0

... code 2 ...

if condition2:
raise MyExit, "exit"

... code 3 ...

if __name__ == "__main__":
try:
main()
except MyExit:
# Allow the exit.
pass

Tim Doty wrote...2006-04-25 05:02:54

Again from my notes:

Global variables
Any variable in the implied main() function of a program can be read by any function declared in that file, but you can't set it without first declaring (in the function) that the variable is global. Failure to do so leads to the obtuse error:

UnboundLocalError: local variable 'my_variable' referenced before assignment

So, there are three ways that file-scope variables can be used.

1. Read Only
If all the function does is read the value of the variable just do so and it works.

2. Global
If a function needs to set the value of a global variable that function must declare the variable as global, e.g.:

Code:


global my_variable

3. Pass value
Of course you can always pass by value as a parameter to the function. This differs from the first option in that you can change the value of the variable, though of course the change is local only.

Tim Doty wrote...2006-04-25 05:05:11

This is really a Python issue, but it is an interesting problem that I had some difficulty finding a good answer for (the solution is obvious for those familiar with Python, but just coding for PgS doesn't make that so).

Calling a function by composed string
Most of the documentation out there says you can't call a function via a composed string. This is an incorrect supposition based on a simple error. If you naively do the following:

Code:


>>> def my_func():
>>> print "Hello World"
>>>
>>> my_string = "my_func"
>>> my_string()
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: 'str' object is not callable

However, all local functions and variables are returned in a list by the locals() function and all global functions and variables are returned in a list by the globals() function. To call a function with a composed string name all you have to do is the following:

Code:


>>> locals()[my_string]()

Henry G Belot wrote...2006-04-25 12:25:29

This would be a good place to tell people where to get Python. There is a reference further down in Tim's comments to the main Python site. As I remember, Python for the Amiga is on a different site and I'm not sure there's a link at the Python site.

A line or two about the pros and cons of using either of the scripting languages as opposed to the internal macro capability would also be appropriate. I would also add an explicit caveat that the documentation will not teach someone either language.

HB

Deron Kazmaier wrote...2006-09-14 09:25:14

New in 5.0.2.11:
+ When executing external python scripts, PageStream now adds the script path to the sys.path list so included scripts can be stored in the same folder as the executed python script.

Tim Doty wrote...2007-07-05 05:50:29

SectionTranslating Resource Files
If you've gone to all the trouble of creating a resource file for your script you may want to take advantage of the localization facilities provided by PageStream. Here is how you do it.
First, you will want to generate a translatable language file. Although you can write one by hand it is easier to have PageStream do this for you.
1. Close PageStream
2. Open the PageStream prefs file in a text editor and save
3. Add a line that says "rsrcexport true" (without the quotes)
4. Copy the script and all support files to the PageStream directory
5. Start PageStream
6. Execute the script
7. Close PageStream
8. Remove the "rsrcexport true" line from the PageStream prefs file and save
9. Remove the script and support files from the PageStream directory (to avoid clutter)
10. The generated translatable resource file will be located in PageStream's Locale subdirectory with the same name as your regular resource file with "_en_US" (or whatever language code for the language you have set in PageStream's preferences) before the ".rsc" extension
11. Copy the generated translatable resource file from PageStream's Locale subdirectory to the script's directory

And there you have it: a translatable resource file. Keep this one as your base and copy it with appropriate name changes for each language to support.
That was easy, now comes the hard part: translation. It is best to have native speakers of the language in question do the translation. Provide each with a copy of the relevant language resource file (change the language string at the top of the file). Simply include each translated resource file in your script's directory and PageStream will automatically use the apppropriate one for the PageStream preferences language setting. A missing resource file simply results in no translation being done.

SectionTranslated Resource File Format
Great, but you are curious about the format used by the translation files? Basically its a streamlined version of the regular resource file. Here's how it goes:
First it starts with a language identifier. In practice this is optional and serves to identify the file based on content rather than name. It can be simply the language identifier ("nl", "en_US", etc.) or it can start with the tag "LANGUAGE " followed by the identifier
String table entries look almost the same as a regular resource file. The only change is below each STRINGITEM there is a #DEFAULT entry with the text from the original resource file. This is to provide a reference for the translator. Only the actual STRINGITEM text needs to be translated. For example:
Code:
STRINGITEM #1 :Annuleren
#DEFAULT :Cancel

Each requester entry in the original resource file starts with a REQUESTER declaration. This becomes:
Code:
STRING R#100 :Requester title
#DEFAULT :Requester Title

The 'R' is for requester. Menus are the same, except they use 'M' for menu.
The translation resource file really couldn't care what the elements that make up a requester are so it doesn't preserve that information and they simply become STRING items with an appropriate number to identify them. Instead of the 'R' for resource first level elements are identified by 'A', second level elements by 'B', and so on. For example, a popup list is a first level element and so would be identified as 'A' while the list items inside it are second level elements and would be identified as 'B'.
Code:
STRING A#81 Very Happyay format
#DEFAULT Very Happyay format
STRING B#1 :Number Only
#DEFAULT :Number Only

Finally, string hints such as tool tips are also preserved. If the original were:
Code:
POPUP #81 L#4 R#5 T#0 B#1 L:"Day format" :Select how the day's date will be formatted
LIST
LISTITEM #1 :"Number Only"

Then the translatable resource file would like like:
Code:
STRING A#81 Very Happyay format
#DEFAULT Very Happyay format

STRING HINT :Select how the day's date will be formatted
#DEFAULT :Select how the day's date will be formatted

STRING B#1 :Number Only
#DEFAULT :Number Only


And there you have it.

Tim Doty wrote...2007-07-05 14:32:39

One difficulty with developing PageStream scripts is that you have to fore go the Python interactive shell that lets you try things out so easily. Or do you?

Many things can still be worked out in the interactive shell, but if your code depends on PageStream to do anything then you are stuck. Or are you?

It depends on just how dependent your code is. Ideally your code will be modular. Along with other benefits if a module doesn't need to make any PageStream function calls simply don't import PageStream. This allows you to import the module into the Python command shell for testing and debugging.

And even if some parts of the module must have the PageStream functions you can still test in the Python command shell if you import PageStream like this:

Python:

try:
from PageStream import *
except ImportError:
print "WARNING: PageStream dependant functions will not work"

Another option is to try the Console script from http://www.morfarpottery.com/PageStream/scripts.php

Tim Doty wrote...2008-03-22 14:21:57

Further options for an element in a resource file: alignment

For example a DISPLAY element can have the alignment specified as LEFT | CENTER | RIGHT and the flag RAISED | RECESSED is used to make it appear raised or recessed.

Additional elements include ARROW. Arrow types include LEFTRIGHT, UPDOWN, RUPDOWN, ANGLE. In each case a pair of arrows are shown with the indicated presentation (left/right or up/down). The ANGLE type is for the rotating arrows as are used to show rotation. I'm not sure what difference there is between UPDOWN and RUPDOWN -- presumably one increases with UP while the other decreases.

Tim Doty wrote...2010-01-07 10:51:29

This probably shouldn't be buried in a comment, but as it does not appear to be documented on this site:

If you have Python installed and PageStream is not recognizing that fact (the "Play External Script" menu item is ghosted) then you most likely have to manually specify the version of Python to use. This isn't a pretty way to do it, but it does work to get around a limitation of how Python libraries have to be opened by the application.

This can also come in handy if you have multiple versions of Python installed on your system and you need to force PageStream to use a particular version.

What you have to do is add a line to your prefs file. On linux this is ~/.PageStream5/PageStream5.prefs. On OS X the preferences file will be under ~/Library/Application Support/PageStream5. On Windows it will be in your user profile -- off hand I don't know the path. The line is as follows:

PYTHONLIBRARY <python library/dll> <API version number>

For example, on linux this might be:

PYTHONLIBRARY libpython2.4.so 1012

or

PYTHONLIBRARY libpython2.6.so 1013

Note: make sure you include the correct library. E.g., /usr/lib/libpython2.6.so.1 would be libpython2.6.so.1 -- in particular the library filename may be longer than indicated above.

T.J. Zweers wrote...2010-01-07 14:46:00

Tim said:
On Windows it will be in your user profile.

This where it will be: C:\Documents and Settings\AccountName\Application Data\PageStream5 (at least on Windows XP).

AccountName is the name you use to log in if you have more than one account.
If you have one account it will be probably in: C:\Documents and Settings\Default User\Application Data\PageStream5
(The question here is that it might also be called: All Users instead of Default User)

For Windows the prefs file is called: PageStream5.ini.
For others it could be named: PageStream5.prefs.

T.J. Zweers wrote...2010-03-28 07:18:16

Adding this:
On Windows 7, and probably Windows Vista, it is found at:
C:\Users\AccountName\AppData\Roaming\PageStream5

'Users' can be a different name in your language (Gebruikers in Dutch), but if you click (left mouse button) in the Windows Explorer bar, it is English.

User Contributed Comments For Using Python