logo
 

Ruby WEB Site Support Programs

    1. Introduction
    2. Ruby
      1. Ruby Methods
      2. Ruby Classes
      3. Ruby Objects
    3. Templates
    4. A little Ruby
      1. Strings
      2. Regular Expressions
    5. First Template Processor
      1. mkpage1.rb
      2. presentation1.tpl
      3. hello.src
      4. Execution
      5. Output: hello.html
      6. Summary
    6. Second Template Processor
      1. mkpage2.rb
      2. Explanation
    7. Third Template Processor
      1. mkpage3.rb
      2. Explanation of mkpage3.rb
        1. presentation2.tpl
        2. style.tpl
        3. Output
      3. An Enhancement
        1. Modification Date
    8. Fourth Sample Template Engine
      1. New Tags
        1. pix.src
        2. command execution
    9. Download



 Introduction



I maintain the 912 site using a set of scripts written in an object oriented scripting language called Ruby. There are now several English books on Ruby, one of which is on-line.
  1. Programming Ruby by David Thomas and Andrew Hunt. Addison Wesley. 2000. ISBN 0-201-71089-7.
  2. Ruby in a Nutshell by Yukihiro Matsumoto. O'Reilly. 2002. ISBN 0-596-00214-9.
  3. The Ruby Way by Hal Fulton. Sams. 2002.


I use Ruby with Linux, but it also run on Windows. There even is a version that uses a standard Window installer that you can find here.

Of course it also runs on Macs.

This tutorial describes the basic logic of a program similar to the one used in maintaining the 912 WEB site. It serves as a brief introduction to the Ruby language using WEB page creation as the sample application.

At the bottom of this page is a link to download all the source and scripts discussed in this tutorial and an enhanced version that will process this tutorial.

 Ruby



Ruby is similar to Perl and Python. All three of these are computationally complete programming languages that are compiled on the fly and executed immediately.

Here is a Ruby program to print "Hello World" on the console.

puts "Hello World"


The program can be created as a one line text file named 'hello1.rb'.

In a console window this program can be executed like this:

$ ruby hello1.rb
Hello World
$


 Ruby Methods



Here is a Ruby method:

def function_name(parameters)
  1. statements end


The parameters are optional. Here is 'hello2.rb':

def hello
puts "Hello World"
end

hello()


 Ruby Classes



Ruby is extended by making classes. Our hello program can be extended by putting the interesting statements into a class.

  1. hello3.rb

    class Hello def initialize puts "Hello World" end end

    Hello.new


Comments start with '#' and continue to the end of the line.

We have defined a class named 'Hello'

We have created an instance of the class by calling 'new' as in 'Hello.new' which creates a Hello object.

The initialization of the object always calls the 'initialize' method of a class. This method is normally used to initialize the class by creating default values for variables. The 'initialize' method is not required if there is nothing for it to do.

 Ruby Objects



We can create an object from a class definition and execute methods against that object.

  1. hello4.rb

    class Hello def message puts "Hello World" end end

    h = Hello.new h.message


And parameters can be passed to methods

  • hello5.rb

    class Hello def message( msg ) puts msg end end

    h = Hello.new h.message("Hello World")


  •  Templates



    Now lets get back to using Ruby to help us make and maintain WEB pages.

    The philosophy used is to separate content from presentation. In this context, presentation is HTML and content is words and pictures. Templates are used to define the HTML.

    Let's do a quick example. Here is "Hello World" in HTML.

    <html>
    <head>
    <title>Hello World</title>
    </head>
    <body>
    <h1>Hello World</h1>

    I am telling the world "Hello"!

    </body> </html>


    Almost lost in the middle of this bit HTML is our actual content.

    <h1>Hello World</h1>

    I am telling the world "Hello"!


    And by the way, most WEB pages have a lot more standard boiler plate surrounding the content than this little snippet.

    If our site has many pages (the 912 site has 502 pages on Jan 2, 2002), then how can we possibly maintain it in a consistent way?

    By separating presentation and content. By using templates.

    Here is a simple division.

    Imagine dividing up our files into two pieces:
    1. hello.src - a content file. There will be many of these to make a normal WEB site, one for each page.
    2. presentation.tpl - The template file.


    Here is hello.src. We have stripped all the presentation layer from what we want to display and are left with a command and some text.

    [title Hello World]
    I am telling the world "Hello"!
    


    presentation1.tpl:

    <html>
    <head>
    <title>_TITLE_</title>
    </head>
    <body>
    <h1>_TITLE_</h1>
    _TEXT_
    </body>
    </html>
    


    The template has two special variables:
    1. _TITLE_ - change all instances of this to 'Hello World'
    2. _TEXT_ - change this to 'I am telling the world "Hello"!'


    We want to write a Ruby program that will produce HTML from the source file by analyzing our content file to find the TITLE and the TEXT and substituting that into the template.

    $ruby mkhtml.rb hello.src > hello.html
    


    Our next step is write some Ruby code that will combine the content and template files.

     A little Ruby



    Before giving the answer, lets look at strings and regular expressions a moment by using some examples.

     Strings



    A string is a built-in class in Ruby.

    Here is string1.rb:

    a = 'I am telling the world "Hello"!'
    puts a
    puts a.length
    puts "There are #{a.length} characters in the string \'#{a}\'"
    


    There are many methods available for strings.

     Regular Expressions



    A regular expression uses arcane rules for dividing strings into pieces based on patterns. Patterns in Ruby have a special notation such as this (regex1.rb):

    p_title = /\[title (.*)\]/
    a = "[ltitle Hello World]
    I am telling the world \"Hello\"!"
    if a =~ p_title
    puts "pre  =" + $`
    puts "match=" + $1
    puts "post =" + $'
    end
    


    The brackets have to be escaped using backslashes because brackets have special meaning in regular expressions.

    Which produces:

    $ ruby regex1.rb
    pre  =
    match=Hello World
    post =
    I am telling the world "Hello"!
    $
    


    The statement a =~ p_title produces 'true' if the pattern is matched.

    'pre =' is empty because there are no characters before the pattern.

    'match=' shows the information inside of the parenthesis on the pattern.

    'post =' shows the <cr> following the match, then the rest of the string.

    You can also see how to write a simple if statement.

     First Template Processor



     mkpage1.rb



    tpl = File.open('presentation1.tpl').read
    dat = File.open(ARGV[0]).read
    title = ''
    dat.gsub!(/\[title (.*)\]/) {title = $1; ''}
    tpl.gsub!('_TITLE_',title)
    tpl.sub!('_TEXT_',dat)
    puts tpl
    


     presentation1.tpl



    <html>
    <head>
    <title>_TITLE_</title>
    </head>
    <body>
    <h1>_TITLE_</h1>
    _TEXT_
    </body>
    </html>
    


     hello.src



    [title Hello World]
    I am telling the world "Hello"!
    


     Execution



    Here is the command to do the work:

    $ ruby mkpage1.rb hello.src > hello.html
    


     Output: hello.html



    <html>
    <head>
    <title>Hello World</title>
    </head>
    <body>
    <h1>Hello World</h1>

    I am telling the world "Hello"!

    </body> </html>


     Summary

    Congratulations, you have made a template process that splits content and presentation.

    Of course it is not very pretty, abstract, flexible, extensible or robust. It usually produces incorrect output and we have not explained it. But we have done a lot conceptually in only 7 lines of Ruby code!

     Second Template Processor



    Let's start our second version by solving some of problems of the first by making the program a little more abstract and extensible.

     mkpage2.rb



    class Mkpage
    def initialize(fn)
    @dat = load(fn)
    @tpl = load('presentation1.tpl')
    @title = ''
    end

    def load(fn) begin File.open(fn).read rescue puts "#{fn} not found" exit! end end

    def show_page @dat.gsub!(/\[title (.*)\]/) { @title = $1; '' } @tpl.gsub!('_TITLE_',@title) @tpl.gsub!('_TEXT_',@dat) puts @tpl end end

    if __FILE__ == $0 Mkpage.new(ARGV[0]).show_page() end


     Explanation



    Well, we have added 20 lines of code with only a bit of functionality change. Specifically:
    • abstraction - Our program is now defined within a class and we have some functions. The object can be created directly or could be used by another program. This is assisted by the if __FILE__ == $0 statements at the bottom of the listing. This is a common idiom found in Ruby programs and is often used to embed test code for individual classes. __FILE__ is the name of the file. $0 is the name of the program that was called. When they match, the statement is evaluated as true.
    • error handling - the begin ... rescue in the load method handles an exception if there is an error in opening and reading the file. The rescue statements are not executed unless there is a problem found in reading the file. Exception handling is a powerful mechanism that allows easy handling of errors without complex control statements.
    • variable scoping - We've added '@' in front of the names of those variables that need to have visibility outside of the methods. This designation provides object instance scoping.


    The meat of the program is in the show_page() method. Let's look at one of the string functions: String#gsub!. There are several forms.

    s.gsub(x,y)
    s.gsub(x) {...}
    s.gsub!(x,y)
    s.gsub!(x) {...}
    


    All strings matching x are replaced with y. If a block is specified, matched strings are replaced with the result of the block. A block is the statements within the braces following the call. The form with the ! change the string in place, otherwise a new string is returned with the substitutions made in it.

    @dat.gsub!(/\[title (.*)\]/) { @title = $1; '' }
    


    @dat is our content file. The function looks for all instances of the [title (.*)]. (.*) means any character following the space and before ]. The regular expression processor assigns these characters to a numbered variable based on the number of expressions within parenthesis. In this case we have one such set and the variable assigned is $1. The block is executed each time the pattern is matched (presumably once). The object instance variable @title is assigned the value. The last statement in the block is '', an empty string. This replaces the [title ...] tag.

    We use the String#gsub often in our processing of the content and template files for HTML creation.

     Third Template Processor



    We next make two new modifications that extend our program in several ways:
    • Support for multiple include files using a <include "..."> tag.
    • Support for extended substitutions using a hash array.


     mkpage3.rb



    class Mkpage
    def initialize(fn)
    @dat = load(fn)
    @tpl = includes(load('presentation2.tpl'))
    @title = ''
    end

    $p_include = /<include "(.*)">/ def includes(content) while content =~ $p_include content = $` + load($1) + $' end return content end

    def load(fn) begin File.open(fn).read rescue puts "#{fn} not found" exit! end end

    def tplparam { '_TITLE_' => @title, '_TEXT_' => @dat, } end

    def expand_template tplparam.each_key { |key| @tpl.gsub!( key, tplparam.fetch(key)) } end

    def show_page @dat.gsub!(/\[title (.*)\]/) { @title = $1; '' } expand_template() puts @tpl end end

    if __FILE__ == $0 Mkpage.new(ARGV[0]).show_page() end


     Explanation of mkpage3.rb



    Thanks to the includes(content) method, we can have nested template files. I use these extensively on my web sites.

     presentation2.tpl



    <html>
    <head>
    <title>_TITLE_</title>
    <include "style.tpl">
    </head>
    <body>
    <h1>_TITLE_</h1>
    _TEXT_
    </body>
    </html>
    


     style.tpl



    <style type=text/css>
    h1 { color : red; text-align : center }
    h2,h3,h4,h5,h6 { color : red; font-family:sans-serif }
    </style>
    


     Output



    $ ruby mkpage3.rb hello.src
    <html>
    <head>
    <title>Hello World</title>
    <style type=text/css>
    h1 { color : red; text-align : center }
    h2,h3,h4,h5,h6 { color : red; font-family:sans-serif }
    </style>

    </head> <body> <h1>Hello World</h1>

    I am telling the world "Hello"!

    </body> </html> $


     An Enhancement

    The other change is to add the following code to our program:

    def tplparam
    {
    '_TITLE_' => @title,
    '_TEXT_'  => @dat,
    }
    end

    def expand_template tplparam.each_key { |key| @tpl.gsub!( key, tplparam.fetch(key)) } end


    This routine uses a hash data structure which has the form { kkk => val }, or key value pairs. The name of the hash is tplparam. Hashes are another Ruby class that is rich with functionality. The expand_template routine iterates through the hash using Hash#each_key, passing the key to a block. The substitution uses the Hash#fetch method to retrieve the value associated with the key.

     Modification Date

    The hash can be easily expanded by adding another key value pair to tplparam. For example, I look up the modification date on the content file and create an HTML string that I can append to the end of the page by adding the following code:

    In the initialize() method add:

    @date = File.mtime(fn).strftime("%a, %d-%b-%y %H:%M:%S %Z")
    


    In the tplparam definition add:

    '_DATE_' => @date,
    


    And finally add _DATE_ following _TEXT_ in the template file.

    Thus, by adding three lines to two files, we can cause all our pages to display the page modification date and time.

     Fourth Sample Template Engine



    Now we need to solve a problem with the content processing; browsers require a <p> tag to start a new paragraph. I think an empty line should start a new paragraph.

    It is time to split the content processing out of the show_page() routine.

    At the same time we can add support for adding pictures to the WEB site. I like pictures and have created a number of mechanisms to support their display.

    Here is the new and modified code that makes up mkpage4.rb

    $p_title = /\[title (.*)\]/
    $p_blank = /^\s*$/
    $p_pix = /\[pix (.*)\]/
    def expand_content
    @dat.gsub!($p_title) { @title = $1; '' }
    @dat.gsub!($p_pix)   { "<img src='/images/#{$1}'>" }
    @dat.gsub!($p_blank, "<p>")
    end

    def show_page expand_content() expand_template() puts @tpl end


     New Tags



    We have added support for two new tags.
    • Blank lines are translated to <p>
    • Picture insertion support using [pix ...].


     pix.src



    [title Pix Example]
    This is a multi-paragraph example:
    [pix L1-11.jpg]
    This picture is an example of joy.

    And this line should be a second paragraph.


     command execution



    $ ruby mkpage4.rb pix.src
    <html>
    <head>
    <title>Pix Example</title>
    <style type=text/css>
    h1 { color : red; text-align : center }
    h2,h3,h4,h5,h6 { color : red; font-family:sans-serif }
    </style>

    </head> <body> <h1>Pix Example</h1> <p> This is a multi-paragraph example: <img src='/images/L1-11.jpg'> This picture is an example of joy. <p> And this line should be a second paragraph.

    </body> </html> $


     Download



    The examples in this tutorial are here including this tutorial in source and html form using an even fancier version of mkpage.rb. It includes expansions for block quotes, lists, HTTP tags, headers and table of contents.
    Last modified: Sun, 06 Jan 2002

     Links

    Site Details. Disclaimer. Comments? Questions? Dave Hillman
    Content attribted to others remains their property. Otherwise the text and images are licensed under a Creative Commons License.
    Creative Commons License Valid XHTML 1.0 Transitional Valid CSS!