image

RubyFrontier Documentation

How a Page Object Gets Rendered

At last we come to the sequence of steps by which a page object is rendered. In other words, you start with some renderable page object and choose RubyFrontier > Publish Page, and this is what happens to that page object. Or, you choose RubyFrontier > Publish Folder or RubyFrontier > Publish Site, and this is what happens to each page object of the site.

This is a lot of information, and it may seem at first like too much to take in. But even if you can’t take it all in, read over this sequence! You are going to want to consult it later, as a guide to the rendering process. Knowing the rendering process is how you know what customizations you can make to that process. Plus, you can use this page for its links to details on various specific topics along the way.

  1. Starting with the folder containing the page object, we walk up the folder hierarchy gathering directive objects to form the page table. Override rules are obeyed: a directive located closer to the page object takes priority over the same directive located further up the hierarchy.

    • Contents of any #tools folders are “folded” to form the "snippets" and "tools" hashes. (The "snippets" hash will be used later to resolve snippets. The "tools" hash will be used later to find outline renderers and methods referred to in macros.)

    • Contents of any #images folders are “folded” to form the "images" hash. (The "images" hash will be used later to find images that you ask to have included in the page.) The same sort of thing is done for the contents of any #stylesheets folders and the contents of any #javascripts folders.

    • Contents of any #glossary.yaml files are read as hashes and the keys of those hashes are “folded” to form the "glossary" hash. (The "glossary" hash will be used later when resolving links.)

    • Contents of any #prefs.yaml files are read as hashes of scalar directives; the keys are “folded” into the top level of the page table. (These scalar directives will be consulted and obeyed at various appropriate points during the rendering process.)

    When we find the #ftpsite.yaml file, we stop walking up hierarchy; we have reached the top of the source folder, and :adrsiteroottable (the top of the source folder) is now known. Obviously, we also know :adrobject (the pathname of the page object we are to render). We have now constructed most (but not all) of the page table.

  2. FirstFilter time. If we found a #filters folder and there is a firstFilter.rb file in it, we call that file’s top-level firstFilter() method with one parameter — the page table. This is your opportunity to modify what happens during this rendering after the page table is formed based on the site but before the page object is loaded.

  3. We now read the page object file itself. We break the file into two pieces: any scalar directives at the start of the file, and everything else.

    • Scalar directives from within the page object override (replace) any same-named directives already stored in the page table. The scalar directives must be located, one per line, right at the start of the page object file, in the form:

          #directivename "some value"
      

      where “#” is the first character on the line, and everything after the name is a valid Ruby expression. (The value is evaluated as a Ruby expression using simple eval, but I can imagine that in the future, RubyFrontier might do something more sophisticated here, such as instance_eval within the PageMaker object that’s rendering the page.)

      If the file is an .rb script file, we accomplish the same thing by calling the script’s runDirectives() method if it has one, passing it a reference to the UserLand::Html::PageMaker object in which rendering is taking place; this method is expected to manipulate page table values as desired (by way of the PageMaker object’s adrPageTable method). At the very least, it should probably define pm.adrPageTable[:title], since it is an error for a renderable to have no title.

    • The remainder of the page object file — i.e., in the case of a .txt file or an .opml file, everything after the last directive at the start of the file, or, in the case of an .rb file, the entire file — is now stored in the page table under the :bodytext key.

    Also, now that we’ve given the page object file a chance to contribute to the pool of scalar directives, we can work out some more page table values. The :folder entry of #ftpsite.yaml tells us :siterootfolder (the folder into which the Web site will be rendered), if we have not acquired it already in some other way. We also determine :fname (the name of the Web page file to be created), :subdirectorypath (the folder path from the source folder down to the page object’s folder), and :f (the full pathname of the Web page file to be created, formed by combining the other three).

  4. The user glossary, if any, is loaded and melded into the page table’s glossary hash.

  5. If the page object file is an .opml file or an .rb file, it is now turned into text. The value we will operate on is the value of the :bodytext key in the page table, and the result of the operation becomes the new value of the :bodytext key in the page table. Here’s what we do:

    • If the page object is an .rb script file, we run the script. In particular, the script is expected to define a top-level method, render(), taking one parameter; that method is called, supplying one parameter — the UserLand::Html::PageMaker object that’s rendering the page. The script is thus in a very powerful position. It can obtain the page table (for example, if the PageMaker object is called pm, the page table is pm.adrPageTable) and can do whatever it wishes. The result of the script becomes the new value of :bodytext.

    • If the page is an .opml file, it consists of an outline expressed as XML; alternatively, it might be a .txt file with the :treatasopml scalar directive set to true, in which case RubyFrontier has translated the text to XML for you, based on indentations. Either way, you must designate and supply an outline renderer to transform it into text. To designate the outline renderer, you must, prior to this moment, have defined the :renderoutlinewith scalar directive; its value must be the string name of the outline renderer class (which, as for any class name, must begin with a capital letter). To supply the outline renderer, you will usually have placed an .rb file in a #tools folder; or, if you want an outline renderer to be available to all your Web sites, you can keep it in the user.rb file. The renderer’s render() method is called, as described here, using macro scoping; the result of this call becomes the new value of :bodytext.

  6. Information about the page object is added to the autoglossary. This is so that other pages in the site (and in other sites) can link easily to this page. In particular, the page object is keyed in the autoglossary under (1) its simple filename (the name of the page object file, minus the extension) and (2) the :title if there is one (and at this point, if this page object is a renderable, there should be one).

  7. If the page object file is not a renderable (that is, it is not a .txt file, an .opml file, or an .rb file), that’s all. The file will simply be copied from its place in the source folder into the corresponding place in the Web site folder, keeping the same filename.

  8. Any snippets are resolved in the :bodytext. Snippets are RubyFrontier’s “boilerplate text” mechanism. A snippet looks like [[this]] — a name in double square brackets. The name is resolved as the name of a .txt file in a #tools folder, and the text of that file is substituted. If no corresponding snippet is found, no error results and the double-bracketed expression remains, double brackets and all.

  9. PageFilter time. If we found a #filters folder and there is a pageFilter.rb file in it, we call that file’s top-level pageFilter() method with one parameter — the page table. This is your chance to modify the page table’s :bodytext entry, which contains the rendered page object, not yet poured into its template, and with macros not yet processed nor glossary expansion yet performed. In my own sites, for example, this is the point at which I would pass a Markdown text page object through Markdown to turn it into HTML.

  10. The template is located, as follows: If there is a :directTemplate directive in force, its value is taken to be the template. Otherwise, if there is no :template scalar directive in force, we expect to have encountered a file #template.txt in the course of constructing the page table, and this file is used. If there is a :template scalar directive in force, then its value is taken to be the simple name of a text file in the #templates directive object folder or in the user templates folder. So, for example, a :template scalar directive value of "mytemplate" would mean a file mytemplate.txt in the #templates folder or in the user templates folder.

  11. Any scalar directives at the start of the template are evaluated and stripped (unless the :directTemplate directive is in force).

  12. The :bodytext is embedded in the template by substituting it for the pseudo-tag <bodytext> or for a tag <p id="bodytext"></p>.

  13. Macros are processed (except in the highly unlikely circumstance that the scalar directive :processmacros is false). A macro is an ERB expression in your page object or template (the two have been combined by the previous step into a single string). This is when calculated content (such as images and automatically generated navigation) is substituted into the Web page.

  14. PostMacroFilter time. If we found a #filters folder and there is a postMacroFilter.rb file in it, we call that file’s top-level postMacroFilter() method with one parameter — the page table. This is your chance to modify the page table’s :postmacrotext entry, which contains the rendered page object, poured into its template, and with macros already processed, but glossary expansion not yet performed. In my own sites, for example, this is the point at which I would pass a kramdown text page object through kramdown to turn it into HTML (if I’m using kramdown instead of Markdown).

  15. Glossary expansion takes place. In RubyFrontier’s version of glossary expansion (quite different from Frontier’s), a glossary item is an <a> tag’s href attribute value. If this value is not (1) an absolute URL (indicated by the presence of colon-slash-slash), or (2) an otherwise complete URL (indicated by a dot within the URL), or (3) a pure local anchor (indicated by a pound-sign at its start), it is considered eligible for glossary expansion.

    The value is first looked up in the autoglossary, as being the identifier of a page within the site; if that succeeds, a relative URL from the page object to the target page is substituted for it.

    If the autoglossary lookup fails, the value is looked up in the normal glossary (which comes from entries in any #glossary.yaml directive objects encountered during page building); if that fails, the user glossary is consulted. This allows you to abbreviate frequently used absolute URLs.

    If an href attribute is considered eligible for glossary expansion, but lookup fails in all of the above locations, an error message is generated, a fake URL is substituted, and rendering of the page object continues.

    An <a> tag, to be a candidate for glossary expansion, can have other attributes, which will be preserved; but the href attribute must be first. In the href attribute, the autoglossary or glossary entry name can be followed by an anchor (e.g. "mypage#myanchor"), which will be preserved: the anchor is stripped, the name is looked up and replaced by the URL, and the anchor is restored.

  16. The page header, if any, is prefixed to the rendered page. The page header is typically all the HTML from the start of the page up to and including the <body> tag, though it doesn’t have to be. There are several ways in which the page header can be called for and the content of the page header can be specified. The default template uses the pageheader() standard macro, which itself calls several other standard macros, which themselves consult various scalar directives, to generate <meta> tags, the <title> tag, javascript file links (copying the appropriate .js files from the #javascripts folder), and stylesheets (copying or embedding the appropriate .css files from the #stylesheets folder). In most cases, that’s probably all you’ll need.

    Since the page header usually involves processing macros, it may be asked why this step is not part of the macro processing which took place earlier. The answer is that, in practice, values in the page header, and specification of its source, often rely upon values that were set by the first round of macro processing.

  17. The page footer, if any, is suffixed to the rendered page. The page footer is typically the closing </body> and </html> tags, and is usually brought into existence through the pagefooter() standard macro in the template.

  18. FinalFilter time. If we found a #filters folder and there is a finalFilter.rb file in it, we call that file’s top-level finalFilter() method with one parameter — the page table. This is your chance to modify the page table’s :renderedtext entry, which contains the rendered page object, completely ready to write out to disk. In my own sites, for example, this is where I do final cleanup or formatting that depends on the entire page HTML being fully formed: it’s the point at which I would run SmartyPants to turn the page’s quotation marks into smart quotes, if I haven’t endowed the page with smart quotes at some earlier point, and in some sites I pass the page through an XSL transform at this point.

The rendering process is now over. The resulting :renderedtext entry from the page table is written out to disk into the Web site folder.

Next: Images and IMG Tags

This documentation prepared by Matt Neuburg, phd = matt at tidbits dot com (http://www.apeth.net/matt/), using RubyFrontier.
Download RubyFrontier from GitHub.