graphein: make (gMLP Weaving)

1. make

1.1. Quick Summary

BUG: Removing all pdfs on "make veryclean" removes the three hand-generated PDFs in the artifice/measurement/data directory. Should I make an exception for these in the makefile, add their automatic regeneration to a makefile for that directory (probably, yes), or simply ignore it for now (for now, yes). As a workaround hack, the PDFs are stored in SVN. They may be recovered if removed by "reverting" the local change (removal) to the original version, so for example: svn revert shop-size-table.pdf

My goal is to have a single makefile which automatically detects all source files to be processed in its directory, and which is automatically copied into all subdirectories without change. I'm tired of editing makefiles.

To accomplish this, the makefile uses GNU make "static pattern rules" with multiple targets (themselves defined through the use of functions in variables). A combination of bash and Awk scripts is used to identify dependencies which cannot be detected simply via filenames (for example, XML SYSTEM entities in TEI texts).

1.2. Defining Variables using Functions

The static pattern rules (for which see below) need lists of multiple targets. To automate this (vs. hardcoding the targets, which won't suffice for my goals), I need to be able to create variables which contain these multiple targets. For example, if I need to create files foo.html, bar.html, and baz.html, I need a variable which contains " foo.html bar.html baz.html". Moreover, if I want to generate, say, PDFs, then I'll need another variable with " foo.pdf bar.pdf baz.pdf".

In general, all of these variables have a set of common filename bases (" foo", " bar", and " baz"), to which are added distinguishing suffixes. All of these come from files of a single type: TEI encoded source documents ( foo.pdf, bar.pdf, and baz.pdf). To get this list, first I use the make wildcard function to get a list of all *.tei files in the current directory. Then I use the make basename function to extract from this list only the filename bases. Thus:

 
ROOTS=$(basename $(wildcard *.tei)) 

Having obtained a list of the necessary filename bases, I use the make patsubst function to create variables for each desired target suffix. As the info make GNU make documentation says, the patsubst function takes a list given in its third argument (" $(ROOTS)" here), matches each of them with the pattern in the first argument (here just " %", a very simple pattern which matches the whole of each of the ROOTS), and replaces it with the replacement specified in the second argument. Thus, for each filename base in ROOTS, this process takes the entire filename base and appends to it an appropriate suffix ( .lint, .html, .fo, .pdf):

 
LINTS=$(patsubst %,%.lint,$(ROOTS)) 
HTMLS=$(patsubst %,%.html,$(ROOTS)) 
FOS=$(patsubst %,%.fo,$(ROOTS)) 
PDFS=$(patsubst %,%.pdf,$(ROOTS)) 

1.3. Static Pattern Rules

Now I have a set of variables each of which contains a list of filename targets that I wish to make (e.g., HTMLS is " foo.html bar.html baz.html", PDFS is " foo.pdf bar.pdf baz.pdf", and so forth). The next step is to specify that I do in fact want to make them. There are three variations here: make all of them, make only one type (HTML, PDF, etc.), or make only one individual target file. As it turns out, setting things up to make everything also gives (or can give) the other two variations "for free."

First, So first I set up a default target (" all") which specifies the large-scale dependent targets (" lint html pdfs"). Then for each of these large-scale dependent targets I specify as dependencies the variables containing my lists of targets to actually build:

 
all: lint html pdfs subdir 

lint: $(LINTS) 

html: $(HTMLS) 

pdf: $(PDFS) 

(Ignore the " subdir dependency for now.)

Then for each of these, I create a GNU make "static pattern rule" to do the building. For example, for the HTML targets:

 
$(HTMLS): %.html : %.tei $(STATIC_FILES) $(LINKING_IMAGES) dependencies.dep images-scaled.dep 

GNU make static pattern rules are subtle and powerful. I recommend that you read the info make documentation for a better understanding of them. Briefly, the leftmost part (" $(HTMLS)") expands to a list of targets potentially to be made (" foo.html bar.html baz.html"). So in execution the rule above becomes, effectively:

 
foo.html bar.html baz.html: %.html : %.tei graphein-tohtml.xsl dependent-entities.dep 
static-files 

For each of these targets, GNU make applies the static pattern rule separately. What it does, for each, is to match the pattern in the middle (" %.html", the "TARGET-PATTERN" in the info make docs) against the target (say, " foo.html"). It then applies the patterns on the right side (" %.tei ...", the "PREREQ-PATTERNS" of the info make docs) to create the list of prerequisites (dependencies). So the rule aboves says that " foo.html" depends on " tei.html" (matching up the patterns) and also on whatever else happens to be on the right (" graphein-tohtml.xsl dependent-entities static-files"; that is, any other dependencies).

So the result is the " foo.html" will be (re)made only if it is older than " foo.tei" or any of the other dependencies (here, the file " graphein-tohtml.xsl", or the further GNU make target " dependent-entities", or the further GNU make target " static-files", for more on which, see below).

This will happen for each HTML file (each filename listed in " $(HTMLS)") independently of every other HTML file. So long as I obey the conventions for filename suffixes, I never have to code the name of any source (TEI) or object (HTML, PDF, etc.) file manually in the makefile.

Pretty cool.

If instead of saying just " make " or " make all " on the command line (thus making everything) I say " make lint " or " make html " or " make pdf " then only the "mid-level" target specified gets made (target " html", or " pdf", or ...). I find this to be particularly useful when I'm adding some feature to, say, the PDF generation and yet simultaneously (where "simultaneously" often means "in the same few months," given how slowly all of this goes) am writing some new textual material that I'd like to see (at least) in HTML (or would like to lint).

If Instead I specify a particular target file explicitly (e.g., " make foo.html "), then make finds that target in the left side of the static pattern rule in which it appears and makes only it. This might be useful, for example, if I'm modifying two texts simultaneously where the modifications to one break the existing encoding (and thus it shouldn't be remade) while the modifications to the other should be remade for linting or viewing.

1.4. XML SYSTEM Entity Dependencies

The make program detects file dependencies by examining filenames and dates. Not all dependencies are uniquely encoded in this fashion, however. For example, markup languages defined in XML, such as the TEI, can contain references to XML "entities" which are external text sequences (and in particular, to "SYSTEM" entities which are (usually, though not strictly necessarily) files on the local filesystem).

For example, the source file literate-programming.tei in this graphein documentation contains a SYSTEM entity reference (an "include", basically) to an entire sub-section contained in makefile-tangle.tei-entity, as well as to a single gMLP code chunk contained in target-dependencies.tei-entity.

These SYSTEM entities are not standalone TEI documents and so can't themselves be remade separately by the same processing used for the .tei files. They must be processed as a part of the TEI document(s) in which they are referenced, and changes in them must trigger the remaking of these TEI documents.

Giving them different suffixes is necessary to distinguish them from the "starting point" TEI documents (I use " .tei-entity"), but does not suffice to trigger a remake of those TEI documents when the SYSTEM entities change. What I need is a way to associate each top level TEI file and its dependent SYSTEM entities.

From the GNU make point of view, the way to do this is to specify the dependencies explicitly: for each source file (e.g., foo.tei), specify the files which are its dependencies. Thus:

 
foo.tei: foodep1.tei-entity foodep2.tei-entity 

While this is straightforward, it is manual. The trick is to automate it. This takes three steps: an Awk script to extract the entity relationships, a bash script to orchestrate the Awk script's use, and a GNU make dependency and target to trigger this bash script.

The Awk script, generate-dependent-entities.awk, is simplistic. The TEI (carrying over an XML structural feature) requires that SYSTEM entities be defined so that a name within the document (e.g., " quick-summary") is associated with its corresponding system-dependent name (e.g., a filename such as " quick-summary.tei-entity"). The script assumes that this XML SYSTEM entity reference is on a single line. E.g.:

 
<!ENTITY quick-summary SYSTEM "quick-summary.tei-entity"> 

The script just identifies those lines and prints the filenames (all on one line). It makes the assumption that each SYSTEM entity definition will be entirely on a single line; note that XML and the TEI do not require this: it is a convention of the use of the TEI in graphein.

<<generate-dependencies.awk>>= 
{ 
if ($1 == "<!ENTITY" && $3 == "SYSTEM") { 
printf ("%s ",substr(substr($4,2),1,length(substr($4,2))-2)) 
} 
} 

The bash script, generate-dependent-entities.sh, runs the Awk script against each .tei file, constructing and echoing (to stdout) make dependency declarations for each. Each dependency declaration needs to touch its target, if a prerequisite is newer, in order for that target in turn to trigger further actions for which it is a prerequisite. To handle directories which have no TEI content, the entire script is enclosed in an if statement which tests for the existence of TEI files.

<<non-image-dependencies>>= 
ls *.tei > /dev/null 2> /dev/null 
if [ $? -eq 0 ]; then 
for f in `ls *.tei`; do 
fbaselength=`expr index $f "."` 
fbaselength=`expr $fbaselength - 1` 
fbase=`expr substr $f 1 $fbaselength` 
echo -n "`basename $fbase`.tei: " 
awk -f generate-dependent-entities.awk $f; 
echo 
echo " touch `basename $fbase`.tei " 
done 
fi 

<<generate-dependencies.sh>>= 
<< 
non-image-dependencies>> 
cd images-mastered-for-scaling; 

<< 
generate-image-dependencies>> 
<< 
generate-images>> 
cd .. 

To use this information, the makefile must do three things. First, it must set up a dependency which triggers the running of this bash script. So for each static pattern rule, the dependency list includes a target which is the filename of the file which will contain these dependencies. E.g.:

<<target-dependencies>>= 
$(HTMLS): %.html : %.tei $(STATIC_FILES) $(LINKING_IMAGES) dependencies.dep images-scaled.dep 

Second, it must run the bash script whenever this file is older than any of the *.tei-entity files (on which, of course, it depends):

 
dependent-entities.dep: *.tei-entity 
./generate-dependent-entities.sh > dependent-entities.dep 

Finally, it must include the file:

 
include dependent-entities.dep 

Of course, when make first runs, and in some cases when it runs to make various "cleanup" targets, this file may not exist. Fortunately, make handles this situation well. The info make documentation says:

"If an included makefile cannot be found [...] a warning message is generated, but it is not an immediately fatal error; processing of the makefile containing the `include' continues. [after which] `make' will try to remake any [included makefile fragments] that are out of date or don't exist. [...] Only after it has tried to find a way to remake a makefile and failed, will `make' diagnose the missing makefile as a fatal error.

All of this is a little convoluted, but so long as I follow two conventions (put each TEI SYSTEM entity declaration on a single line, and name the entity files with the " .tei-entity" suffix) it is entirely automated.

1.5. Fictitious Targets (.lint)

After the convolutions of generating entity dependencies (above), this section is refreshingly simple.

GNU make is designed to maintain files with regular suffixes. Thus it turns .tei files into .html files (for example). The timestamps of the generated .html files relative to the timestamps of the corresponding .tei source files indicate whether the .html files need to be regenerated.

This doesn't work for activities which generate no files, such as running a "lint" program to do syntax checking. To accomodate this, I simply generate an (empty) file with a " .lint" suffix each time the xmllint program runs successfully:

<<make-lint>>= 
$(LINTS): %.lint : %.tei 
rm -f temp1.linthack temp2-linthack 
xmllint --nonet --xinclude $*.tei > temp1-linthack 
awk -f xmllint-hack.awk temp1-linthack > temp2-linthack 
xmllint --nonet --noout --valid temp2-linthack 
rm -f temp1-linthack temp2-linthack 
if [ $$? -eq 0 ]; then touch $*.lint; fi 

1.6. Recursion

1.6.1. Recursive make for Ordinary Targets

I handle the recursive invocation of make for "ordinary" targets in the subdirs target (itself a dependency of the all target, as shown earlier. I handle recursive invocation for the various "cleanup" targets ( clean, very, and mrproper) separately, as discussed in the next section.

The general idea in both situations is the same, however, and involves bash shell scripting. The basic idea is first to get the list of immediate subdirectories by invoking a bash shell to issue a find command:

 
SUBDIRS:= $(shell find . -mindepth 1 -maxdepth 1 -type d) 

Then this list is used in a bash script in the subdir target. In that script, I filter out several subdirectories with conventionalized names (of my own invention; there's nothing standard about this) which should not be traversed recursively. Then for each subdirectory to be traversed recursively, I enter the directory, then copy an appropriate makefile into the subdirectory (but only if no newer makefile exists in the subdirectory's new-static-files sub-subdirectory; this allows the introduction of a new makefile at any point in the recursive traversal), then invoke make , and finally (explicitly; this is important) leave the directory.

In implementing this, I use one "trick." The checking of subdirectories is something that will occur again, three times, in the clean, veryclean, and mrproper targets (discussed later). I therefore abstract it using the call feature of GNU make :

 
if-subdirectory-is-traversable = \ 
if [ $(1) != "./images-mastered-for-scaling" ] && \ 
[ $(1) != "./image-development" ] && \ 
[ $(1) != "./new-static-files" ] && \ 
[ $(1) != "./notes" ] && \ 
[ $(1) != "./research" ] 

This is really a "macro" expansion, not a function call. It is used, here, in the subdir target.

 
subdirs: 
@for subdirectory in $(SUBDIRS); do \ 
$(call if-subdirectory-is-traversable,$$subdirectory); then \ 
cd $$subdirectory; \ 
$(call pull-static-file,makefile); \ 
$(MAKE); \ 
cd ..; \ 
fi; \ 
done 

(The "@" at the start of the bash script simply suppresses its echoing. This unclutters the output, often making the hierarchy traversal clearer.

1.6.2. Recursive make for Cleanup Targets

The clean target requires a bit of care. Ordinary cleaning involves getting rid of the transient and output files produced by the document transformation process. This is straightforward. The only complexity involves recursion. I handle this using the same if-subdirectory-is-traversable call (a macro, really) described earlier under "Recursive make."

 
clean: 
rm -f *.lint 
rm -f *.html 
rm -f *.fo 
rm -f *.aux 
rm -f *.out 
rm -f *.log 
rm -f *.pdf 
rm -f dependent-entities.dep 
for subdirectory in $(SUBDIRS); do \ 
$(call if-subdirectory-is-traversable,$$subdirectory); then \ 
cd $$subdirectory; \ 
$(call pull-static-file,makefile); \ 
$(MAKE) clean; \ 
cd .. ; \ 
fi; \ 
done 

(Note the cd .. command; it is important.)

It would also be nice to be able to remove the static files propagated through the hierarchy (with the exception of the makefile itself). The only real trick is to avoid removing the source for new static files. This is easy to if I put their source in the conventionally named new-static-files subdirectory. I'll call this target veryclean. It forces a dependency on clean, of course.

 
veryclean: clean 
rm -f generate-dependent-entities.sh 
rm -f generate-dependent-entities.awk 
rm -f main.css 
rm -f common.css 
rm -f link-image-home.png 
rm -f link-image-category.png 
rm -f link-image-up.png 
for subdirectory in $(SUBDIRS); do \ 
$(call if-subdirectory-is-traversable,$$subdirectory); then \ 
cd $$subdirectory; \ 
$(MAKE) veryclean; \ 
cd ..; \ 
fi; \ 
done 

Finally, in some situations it might be nice to be able to return things to the as-distributed state by removing the makefile itself (but not its source in new-static-files, of course!) Rebuilding after this would require manually copying the makefile into the working directory out of the new-static-files subdirectory. In honor of Linus Torvalds, I'll call this "back to the distribution state" target mrproper (read the Linux[R] kernel build documentation for an explanation).

Unfortunately, a "naive" implementation such as this won't work:

 
mrproper: veryclean 
rm -f makefile 
for subdirectory in $(SUBDIRS); do \ 
$(call if-subdirectory-is-traversable,$$subdirectory); then \ 
cd $$subdirectory; \ 
$(MAKE) mrproper; \ 
cd ..; \ 
fi; \ 
done 

The problem is that for the mrproper target to make sense, veryclean must have been done first (either in this make invocation, as a dependency, or in a separate make invocation.

There may be some way to finesse this, but I adoped the brute-force approach of defining a separate, simple, recursive makefile-mrproper to do nothing but remove makefile files (and makefile-mrproper files, of course).

 
SUBDIRS:= $(shell find . -mindepth 1 -maxdepth 1 -type d) 

# this is really a macro, not a function call 
if-subdirectory-is-traversable = \ 
if [ $(1) != "./icons" ] && \ 
[ $(1) != "./image-development" ] && \ 
[ $(1) != "./new-static-files" ] && \ 
[ $(1) != "./notes" ] && \ 
[ $(1) != "./research" ] 

all: 
for subdirectory in $(SUBDIRS); do \ 
$(call if-subdirectory-is-traversable,$$subdirectory); then \ 
cd $$subdirectory; \ 
cp -f ../makefile-mrproper .; \ 
$(MAKE) --file makefile-mrproper; \ 
cd ..; \ 
fi; \ 
done 
rm -f makefile 
rm -f makefile-mrproper 


To use this:

 
make veryclean 
cp new-static-files/makefile-mrproper . 
make --file makefile-mrproper 

Whew. Cleaning up always seems to take too long. There is indeed naught in this world but trouble and dirt.

1.6.3. Propagating "Static" Files

Several types of files tend not to change from one level of the writing hierarchy to another, and so should be maintained in one location and propagated (down). However, sometimes some of these do change, so there must be a mechanism for picking up the new versions (and propagating them). I'll call these types of files "static," though they do sometimes change.

1.6.3.1. Types of Static Content

1.6.3.1.1. Icons for Standard Linking

One type of static file is that of the icons used to create standard links through the writing hierarchy. There are four of these:

where "n" indicates the Scale Factor (0 to 4).

In each case, these icon images as actually used are JPGs generated automatically during the make process from PNG masters (see the earlier chapter on "Scaling" and in particular its section on "Resolution Issues and Proposed Resolution" for a further discussion). However, sometimes these JPGs are generated directly from master PNGs in the present directory's ./images-mastered-for-scaling subdirectory, while at other times they are obtained from (scaled, generated) JPGs in related directories (e.g., the "up" link icon comes feom the parent directory; the "home" link icon comes from the parent directory but ultimately, in a chain of propagation, from the home directory itself).

Here is a discussion of each of the four types:

1.6.3.1.1.1. Icon for Linking: Home

The master link-home-sfN.png should appear only in the ./images-mastered-for-scaling subdirectory of the "home" (top level) directory itself. From this master, make will generate the scaled JPGs, placing them in the home directory.

Each writing subdirectory will "pull" its copies of link-home-sfN.jpg from its parent directory. In this way, a top-level make will propagate this icon throughout the hierarchy.

The actual link to "home" might plausibly be of two types: an absolute link to a URL, or a relative link up. The advantage of the first is that it re-anchors a copied website at the distribution URL. The advantage of the second is that it does not. Since I plan to use an explicit "Presented originally by ..." link in the content of my documents, I'll choose the second. (It is also automatable via make , while an absolute URL would have to be specified explicitly.)

The only exception to the use of the "home" icon occurs in the index.tei document of the home directory itself. Since it is the "home" location, it has no need to link to "home."

To implement the propagation of this icon, first put it in a variable and make it a dependency:

 
LINKING_IMAGES=link-home-sf0.jpg link-category-sf0.jpg link-up-sf0.jpg 

[...] 
$(HTMLS): %.html : %.tei $(STATIC_FILES) $(LINKING_IMAGES) dependencies.dep images-scaled.dep 

Then write a rule to copy it from the parent directory, if it is newer there than here. The only complication is ensuring that this is not done if in the home directory. I suppose you could do this checking either by putting the conditional in make or in the commands it executes. I chose the former:

 
ifeq ($(shell [ -e ../link-home-sf0.jpg ] && echo 1),1) 
link-home-sf0.jpg: ../link-home-sf0.jpg 
cp -f ../link-home-sf0.jpg link-home-sf0.jpg 
cp -f ../link-home-sf1.jpg link-home-sf1.jpg 
cp -f ../link-home-sf2.jpg link-home-sf2.jpg 
else 
link-home-sf0.jpg: 
endif 

1.6.3.1.1.2. Icon for Linking: Category

Understanding the "category" and "topic" links involves the intended overall structure of the writing hierarchy.

Each writing directory contains one or more TEI-encoded documents which ought to be more or less about the same "topic." One of these documents is distinguished as the main entry point into the directory by virtue of its name: index.tei

Arbitrary subtrees of writing directories in the hierarchy may be thought of as belonging to "categories."

For example, in my "CircuitousRoot" writing hierarchy one of the categories is my (blacksmithing) Forge. This category represents all of my writing concerning my blacksmithing. It contains individual directories, subdirectories, subsubdirectories and so forth, each of which will (presumably) addess a single "topic" within the general category of my blacksmithing writing.

The "category" link therefore gets set up when a category's subtree starts. At the top level, the "home" category is the same as the overall home. Thus in the top level ./images-mastered-for-scaling subdirectory the new-link-category-sfN.png scaling master will most sensibly just be a symbolic link to link-home-sfN.png. The "Forge" category appears in a writing subdirectory ( forge) I intend a new category to start here, so I put a new scaling master for new-link-category-sfN.png in forge/images-mastered-for-scaling The various writing directories within the Forge category won't themselves have new scaling masters, unless at some lower level in the hierarchy I wish explicitly to set up a new (sub)category within the Forge category.

In the make process, each directory either obtains or creates its scaled "category" icons from its parent directory. If the parent directory's (../)images-mastered-for-scaling subdirectory has a new master, then it is setting up a new category (of which we are part) and the scaled icons are generated from it. Otherwise, the parent it itself part of some higher category, so we just pull ( cp ) down its generated scaled PNG "category" icons.

Note that this implies that the scaled link-category-sfN.jpg icons of a directory which creates a new category are not generated from its new category masters but instead are pulled from its parent directory. This makes sense: if you're setting up a new category, your children directories will be a part of this new category, but you yourself are still a part of your parents' category.

There is some question in my mind, however, as to whether this should apply just to all of the document files in such a directory, or just the "index" (main) file. For now, I'll implement things such that it applies only to the "index" file. All other document files in a directory which starts a new category will point to this "index" file (and thus for such a directory the "category" and "topic" links are the same).

The link up the hierarchy to the present "category" is a relative link. It can be computed during make by traversing the hierarchy up until the most recent link-category-sfN.png master is found.

The only trick in the generation of these "category" icons is in the file naming. When a new "category" image master is introduced, it should be named new-link-category-sfN.png in the ./images-mastered-for-scaling. The regular scaling process will then automatically generate one or more new-link-category-sfN.jpg scaled icons from it. Because of the prefix " new-" in their filenames, these will not interfere with the actual link-category-sfN.jpg icons in the present directory. They will, however, be available to be copied into child directories.

The process of "pulling" the "category" icons into child directories is simple, therefore. If new-link-category-sfN.jpg files exist in the parent, use them. Otherwise, use the parents' link-category-sfN.jpg files. (Exception: in the home directory, do nothing.)

 
LINKING_IMAGES=link-home-sf0.jpg link-category-sf0.jpg link-up-sf0.jpg 

[...] 
$(HTMLS): %.html : %.tei $(STATIC_FILES) $(LINKING_IMAGES) dependencies.dep images-scaled.dep 

 
ifeq ($(shell [ -e ../new-link-category-sf0.jpg ] && echo 1),1) 
link-category-sf0.jpg: ../new-link-category-sf0.jpg 
cp -f ../new-link-category-sf0.jpg link-category-sf0.jpg 
cp -f ../new-link-category-sf1.jpg link-category-sf1.jpg 
cp -f ../new-link-category-sf2.jpg link-category-sf2.jpg 
else 
ifeq ($(shell [ -e ../link-category-sf0.jpg ] && echo 1),1) 
link-category-sf0.jpg: ../link-category-sf0.jpg 
cp -f ../link-category-sf0.jpg link-category-sf0.jpg 
cp -f ../link-category-sf1.jpg link-category-sf1.jpg 
cp -f ../link-category-sf2.jpg link-category-sf2.jpg 
else 
link-category-sf0.jpg: 
touch link-category-sf0.jpg 
touch link-category-sf1.jpg 
touch link-category-sf2.jpg 
endif 
endif 

1.6.3.1.1.3. Icon for Linking: Topic

The "topic" links are simpler. The link itself is just to the (generated version of the) index.tei file in the current directory. Each directory (each topic) should in principle have its own icon. In practice this can be anything, including a symbolic link to some other icon (which might be confusing, of course), but in principle it should be new for this topic. So there will always be a link-topic-sfN.png scaling master in the ./images-mastered-for-scaling subdirectory.

Aside on linking "down" or "into": Typically, some document in each directory (usually, but not necessarily its index.tei) will contain links to its subdirectories (again, usually but not necessarily to (the generated versions of) their index.tei document). Typically (but again not necessarily) this link will also employ an icon. While this icon can in principle be anything, in practice it makes sense to use (a symbolic link to) the "topic" icon of the subdirectory (its topic is characteristic of it, after all). If so used, these symbolic links will be in the ./images-mastered-for-scaling subdirectory, and the make process will generate scaled JPG copies of the subdirectory's "topic" icon. (This is extra work, but cleaner than making assumptions about what might or might not be used for this linking icon or how it might be named.) The SF specified in the link should match its target. For example:

 
circuitousroot 
images-mastered-for-scaling 
link-to-my-forge-writing-sf3.png 
-> ../forge/images-mastered-for-scaling/link-topic-sf4.png 
forge 
images-mastered-for-scaling 
link-topic-sf4.png -> ../image-development/picture-of-anvil-sf4.png 
image-development 
picture-of-anvil-sf4.png 
picture-of-anvil-original.png 

Propagating the topic links requires no special effort. As they're always mastered in the current directory (in its images-mastered-for-scaling subdirectory, that is), they're generated as a part of the regular image scaling process.

1.6.3.1.1.4. Icon for Linking: Up

The link up is also relatively easy. There will never be a link-up-sfN.png scaling master in any ./images-mastered-for-scaling subdirectory. Instead, the "up" linking icons will be copied (not linked, but copied, so as to make the current directory as generated more independent) from the scaled link-topic-sfN.jpg icons of the parent directory. After all, the link "up" is a link to the parent topic.

The propagation of the "up" images is similar to that of the "home" images. First put it in a variable and make it a dependency:

 
LINKING_IMAGES=link-home-sf0.jpg link-category-sf0.jpg link-up-sf0.jpg 

[...] 
$(HTMLS): %.html : %.tei $(STATIC_FILES) $(LINKING_IMAGES) dependencies.dep images-scaled.dep 

Then conditionally make it.

 
ifeq ($(shell [ -e ../link-topic-sf0.jpg ] && echo 1),1) 
link-up-sf0.jpg: ../link-topic-sf0.jpg 
cp -f ../link-topic-sf0.jpg link-up-sf0.jpg 
cp -f ../link-topic-sf1.jpg link-up-sf1.jpg 
cp -f ../link-topic-sf2.jpg link-up-sf2.jpg 
else 
link-up-sf0.jpg: 
touch link-up-sf0.jpg 
touch link-up-sf1.jpg 
touch link-up-sf2.jpg 
endif 

Note that it is necessary to touch a fake link-up-sf0.jpg file in the home directory. Unlike the "home" image, for example, it will never exist (it isn't scaled from a local master), but if something with its name doesn't exist, make will have an unsatisfied dependency.

1.6.3.1.1.5. Using the Icons in Various Locations

Although there are four standard linking icons available at all times, not all documents use all four. The two basic distinctions are between the "home" directory and all others, and in each directory between the "index" document ( index.html) and other documents. I'll give each of the four possible resulting situations a number, so that I can calculate it with a script find-location.sh and compute with it (this number) in the XSL. The location numbers are:

0 - for documents not in the home directory and not the "index" document in their directory. These documents use all four standard links.

1 - for "index" documents not in the home directory. These documents use three of the standard links (all of them save the "topic" link, since this document is itself the target of the "topic" link).

2 - for documents in the home directory other than the "index" document. These documents use only one of the standard links, the "home" link. There is no "up" link from the home directory, and there is no special sense in the home directory in which their is a special topic or category (other than the fact of being here, which is conveyed by the "home" link). I may in the future change my mind on this an add a "topic" or "category" link if only for consistency of pattern, but for now I omit it. Fortunately, this could be done simply by changing the XSL,; it would require no changes to the TEI of the document encoding.

3 - for the "index" document in the home directory itself (the "home page"). This document uses none of the standard links (it is the home already, there isn't any special category or topic, and there is no "up" from here.)

The use in the makefile of the script which determines the current "location" (which is also a sort of a type) is straightforward. It is executed and its results are passed as a parameter into the XSL processor invocation (it must also be handled as an ordinary static file):

<<make-htmls>>= 
$(HTMLS): %.html : %.tei $(STATIC_FILES) $(LINKING_IMAGES) dependencies.dep images-scaled.dep 
(for resolution in 0; do \ 
java \ 
-Djavax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl 
\ 
-Djavax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp.SAXParserFactoryImpl 
\ 
-Dorg.apache.xerces.xni.parser.XMLParserConfiguration=org.apache.xerces.parsers.XIncludeParserConfiguration 
\ 
-classpath "/usr/share/java/Xerces-j-2.9.0/xerces-2_9_0/xml-apis.jar:/usr/share/java/Xerces-j-2.9.0/xerces-2_9_0/xercesImpl.jar:/usr/share/java/xml-commons-resolver-1.1.jar:/usr/share/java/saxon-8.8/saxon8.jar:/etc/java/resolver" 
\ 
net.sf.saxon.Transform \ 
-x org.apache.xml.resolver.tools.ResolvingXMLReader \ 
-y org.apache.xml.resolver.tools.ResolvingXMLReader \ 
-r org.apache.xml.resolver.tools.CatalogResolver \ 
-u \ 
$*.tei graphein-tohtml.xsl \ 
own-basename=$* \ 
css-basename=main \ 
scale-factor=$$resolution \ 
location=`./find-location.sh $*` \ 
depth-in-hierarchy=`./current-depth-in-hierarchy.sh` \ 
depth-in-category=`./current-depth-in-category.sh` \ 
public-or-private=$(P) > $*.html-tmp; \ 
awk -f ./insert-img-dimensions.awk $*.html-tmp > $*-$$resolution.html; \ 
rm -f $*.html-tmp; \ 
if [ $$resolution -eq 0 ]; then \ 
rm -f $*.html; \ 
ln -s $*-0.html $*.html; \ 
fi; \ 
done ) 

FOR REFERENCE, HERE'S HOW TO INVOKE Xerces 2.6.2 and SAXON 6.5.3:

 
STATIC_FILES=makefile makefile-mrproper \ 
generate-dependencies.awk generate-dependencies.sh \ 
graphein-tohtml.xsl graphein-tofo.xsl \ 
main.css common.css \ 
find-location.sh \ 
current-depth-in-hierarchy.sh current-depth-in-category.sh \ 
insert-img-dimensions.awk \ 
make-subdirectory.sh \ 
gpl gfdl 

[...] 

$(HTMLS): %.html : %.tei $(STATIC_FILES) $(LINKING_IMAGES) dependencies.dep images-scaled.dep 
java \ 
-classpath "/usr/share/java/xerces-j2.jar:/usr/share/saxon-6.5.3/saxon.jar:/usr/share/java/xml-commons-resolver-1.1.jar:/etc/java/resolver" 
\ 
com.icl.saxon.StyleSheet \ 
-x org.apache.xml.resolver.tools.ResolvingXMLReader \ 
-y org.apache.xml.resolver.tools.ResolvingXMLReader \ 
-r org.apache.xml.resolver.tools.CatalogResolver \ 
-u \ 
$*.tei graphein-tohtml.xsl \ 
own-basename=$* \ 
css-basename=main \ 
scale-factor=0 \ 
location=`./find-location.sh $*` \ 
depth-in-hierarchy=`./current-depth-in-hierarchy.sh` \ 
depth-in-category=`./current-depth-in-category.sh` > $*.html 

[...] 
ifeq ($(shell [ -f new-static-files/find-location.sh ] && echo 1),1) 
find-location.sh: new-static-files/find-location.sh 
$(call pull-static-file,find-location.sh) 
chmod +x find-location.sh 
endif 
ifeq ($(shell [ -f ../find-location.sh ] && echo 1),1) 
find-location.sh: ../find-location.sh 
$(call pull-static-file,find-location.sh) 
chmod +x find-location.sh 
endif 

Its use in the XSL is also straightforward. For example, in the XSLT for XHTML conversion:

 
<!-- ========== --> 
<!-- Parameters --> 
<!-- ========== --> 
<xsl:param name="own-basename" /> 
<xsl:param name="css-basename" /> 
<xsl:param name="scale-factor" /> 
<xsl:param name="location" /> 
<xsl:param name="depth-in-hierarchy" /> 
<xsl:param name="depth-in-category" /> 

[...] 

<xsl:if test="$location < 3"> 
<a href="../index.html"> 
<img> 
<xsl:attribute name="src">link-home-sf<xsl:value-of select="$scale-factor" />.jpg</xsl:attribute></img> 
Home</a> 
</xsl:if> 

The only complication comes in the oddity of XSL expressions, where a literal "<" character would be interpreted as markup so its entity version is used. I don't know my XSL(T) well enough to know if the entity version is used directly or translated at some point to "<"; it doesn't matter. (Ironically, in coding the present document I needed to use the "lt" entity version to represent the literal character.)

The find-location.sh script itself is trivial:

 
# find out if we're in the home directory by looking for something 
# in our parent which can only exist in ourself or lower 
if [ ! -d ../new-static-files ]; then 
if [ $1 == "index" ]; then 
echo 3 
else 
echo 2 
fi 
else 
if [ $1 == "index" ]; then 
echo 1 
else 
echo 0 
fi 
fi 

1.6.3.1.2. Scripts, Makefiles, Etc.

All other "static" content goes in " ./new-static-files". These files are:

1.6.3.2. Propagation

The trick is to pull the propagated file from the next-highest level of the hierarchy rather than to push it down to the next-lowest level (that way multiple subdirectories can each specify their own new static content independently of each other).

Thus, each static file can come from one of two places: the new-static-files subdirectory of the present directory, or the present directory's parent directory. It is possible (likely, even) that for most directories the static file will not exist in new-static-files. Moreover, the static file cannot, perforce, exist in the parent of the top-level directory. Getting make to handle two alternative prerequisites, either of which may not exist, is nontrivial.

The first step is straightforward enough: establish a variable which lists all of the static files.

 
STATIC_FILES=makefile makefile-mrproper \ 
generate-dependencies.awk generate-dependencies.sh \ 
graphein-tohtml.xsl graphein-tofo.xsl \ 
main.css common.css \ 
find-location.sh \ 
current-depth-in-hierarchy.sh current-depth-in-category.sh \ 
insert-img-dimensions.awk \ 
xmllint-hack.awk \ 
make-subdirectory.sh 

This variable may be used as a prerequisite for various targets, e.g.:

 
$(LINTS): %.lint : %.tei $(STATIC_FILES) dependent-entities.dep 

Each static file then needs to become a target so that it can be built. Unfortunately, the following doesn't work (shown for makefile):

 
makefile: new-static-files/makefile ../makefile 
actions here 

new-static-files/makefile: 

../makefile: 

The problem is that if either new-static-files/makefile or ../makefile doesn't exist, make fails. (N.B., identidying them as " .PHONY" doesn't seem to work.)

My solution, which seems crude but works, is to use the conditional facility of make to include only targets which exist. Thus:

 
ifeq ($(shell [ -f new-static-files/makefile ] && echo 1),1) 
makefile: new-static-files/makefile 
$(call pull-static-file,makefile) 
endif 
ifeq ($(shell [ -f ../makefile ] && echo 1),1) 
makefile: ../makefile 
$(call pull-static-file,makefile) 
endif 

At least one or the other target will exist, so this method always creates a make rule. However, it is also possible that both exist. If so, then the version in new-static-files has priority. Rather than figuring out how to encode this to make , I simply use a make call function (macro, really), which re-checks for their existence and selects the right one.

 
pull-static-file = \ 
if [ -f ./new-static-files/$(1) ]; then \ 
if [ ./new-static-files/$(1) -nt $(1) ]; then \ 
cp -f ./new-static-files/$(1) .; \ 
chmod 444 $(1); \ 
fi; \ 
else \ 
if [ ! -f $(1) ] || [ ../$(1) -nt $(1) ]; then \ 
cp -f ../$(1) .; \ 
chmod 444 $(1); \ 
fi; \ 
fi 

1.7. Image Dimension Fixup

I can't figure out how to extract the dimensions of an image (width and height in pixels) into XSL(T). Neither can I figure out how to run a shell command from within XSL(T) (from which I could get the dimensions). So I'll adopt an awkward workaround: run an Awk script after XSL(T) transformation which inserts the XHTML width and height attributes into the file.

It is invoked simply enough:

 
$(HTMLS): %.html : %.tei $(STATIC_FILES) $(LINKING_IMAGES) dependencies.dep images-scaled.dep 
[Saxon invocation]> $*.html-tmp 
awk -f ./insert-img-dimensions.awk $*.html-tmp > $*.html 
rm -f $*.html-tmp 

The script is itself a "static" file, as described earlier, and is maintained in the same place and propagated in the same way as the other static files.

The script isn't even that complicated:

 
{ 
field = 1 
while (field <= NF) { 
printf "%s ", $field 
if (substr($field,0,4) == "src=") { 
command_for_width = "identify " substr($field,6,match($field,"g\"")-5) " | awk '{print 
substr($3,1,match($3,\"x\")-1)}'" 
command_for_width | getline width 
close(command_for_width) 

command_for_height = "identify " substr($field,6,match($field,"g\"")-5) " | awk '{print 
substr($3,match($3,\"x\")+1)}'" 
command_for_height | getline height 
close(command_for_height) 

printf "width=\"%s\" height=\"%s\" ", width, height 
} 
field++ 
} 
printf "\n" 
} 

There are only two tricks.

One is an assumption: that the "src=..." field does not also include the "/>". Perhaps the easiest way to do this in the XSL is to generate an "alt" attribute after the "src" attribute.

The other is the use of a shell command from within the Awk script (which itself in turn invokes another Awk script, as well as the ImageMagick identify command. Look in the Awk documentation for the piping of commands into getline , with a variable afterwards, for more detailed explanations.