KielceRB
is a highly customizable templating engine for generating assignments, syllabi, web pages and other course documents. It loads a hierarchy of key-value pairs from files at various file system levels. These values can then be inserted into documents using Ruby's ERB templating engine. KielceRB
simplifies the maintenance of course documents by moving data that changes regularly into external data files where they can be easily identified and updated. By loading data from various file system levels, it is easy to share values among all documents for a particular course and/or semester.
KielceRB
also provides methods for including one document inside another allowing users to easily share common content among several pages (navigation bars, contact information, assignment headers, etc.).
KielceRB
is a Ruby Gem. To install, simply run gem install kielce
. (You can also download the code from GitHub and run directly from the repo.)
Data files are Ruby files that return a hash containing key-value pairs. The names of these files must mach the pattern kielce_data*.rb
.
{
semester: 'Fall',
year: '2020',
credit_hours: 12
}
Values from these hashes can be inserted into documents using Ruby's ERB syntax:
<html>
<body>
<h1>Computer Science 1 <%= $d.semester %> <%= $d.year %>
...
Notice that the hash returned by the data file is converted to an object for which each key in the hash is now a method (hence the method-call syntax above as opposed to "square-bracket" syntax used by a plain Hash). This object is placed in the global variable $d
.
The simplest use case is:
- Create a data file named
kielce_data.rb
- Create an erb file using the data (let's call it
my_file.html.erb
) - Run
kielce my_file.html.erb > my_file.html
The values in the data files can be any type of Ruby object (including nested Hashes).
{
course: {
name: 'Computer Science I',
code: 'CIS 162',
exam_dates: {
midterm: Date.new(2020, 10, 22),
final: Date.new(2020, 12, 14)
}
},
semester: {
term: 'Fall',
year: '2020'
},
general: {
office_hours: ['Monday 10:00 - 11:00', 'Thursday 2:00 - 3:00']
}
}
Nested hashes are converted into objects just as the root hash is. Thus, the syntax for accessing the term
value is
<%= $d.semester.term %>`
Objects that are not hashes are simply returned. Thus, one could use the following to display the final exam date:
<code><%= $d.course.exam_dates.final.strftime("%A, %-d %B %Y")></code>
Note: Only directly nested hashes are converted to objects.
{
# Note: KielceRB will *not* convert the hashes in the array to objects.
books: [{
author: 'Homer',
name: 'Odyssey'
},
{
author: 'Chaucer',
name: 'Canterbury Tales'
}]
}
The values in the data file may be functions (i.e., Ruby Lambdas):
{
semester: {
term: 'Fall',
year: '2020',
full_name: -> () {"#{term} #{year}"}
}
}
Referencing the key calls the function:
<%= $d.semester.full_name %>
Notice in the example above that other keys in the same hash may be referenced without qualification. Keys in other objects can be referenced using the root
method:
{
course: {
name: 'Computer Science I',
full_name: () -> {"#{name} #{root.semester.term} #{root.semester.year}"}
},
semester: {
term: 'Fall',
year: '2020'
}
}
Functions may take parameters:
{
greeting: -> (name) { "Greetings, #{name}. How are you today?"}
}
Parameters are passed in the usual way:
<%= $d.greeting('Bob') %>
Parameter names will shadow (i.e., hide) keys in the local data object. It is, of course, best to avoid parameter names that are identical to data object keys. If this can't be avoided, you can reference the data object through self
:
{
city: 'London',
my_func: -> (city) {"parameter: #{city} data value: #{self.city}"}
}
Similarly, avoid using root
as a variable name. If that can't be avoided, you can reference the object hierarchy through self.root
.
KielceRB
searches for multiple data files. Specifically, it begins in the directory containing the source file and searches each ancestor directory for files matching the pattern kielce_data*rb
. The data in these files are merged into a single object hierarchy.
-
If a key is used in multiple files, the value defined deepest in the hierarchy (i.e., closest to the template file) takes precedence. Thus, you can place default values at the top of the hierarchy (e.g., in a root directory) and override those values as you get "closer" to the specific template file. (See the
SampleSite
directory for an example.) -
If the same key appears in multiple files in the same directory (i.e., at the same level of the hierarchy), there is no guarantee which value will be used. (Therefore, avoid including the same key in multiple files unless those files are in different directories.)
-
Data files must have a name that matches this pattern:
kielce_data*.rb
. The specific value matched by*
doesn't matter. I tend to have multiple data files open at once (usually because I'm working on multiple courses at once) and found it helpful for each data file to have a different name.
You can use the render
or render_relative
method to include one file inside another. For example, the main page for both courses in the included SampleSite
example display the professor's contact information by including the external file Common/contactInfo.html.erb
.
The template cs101_index.html.erb
includes the contact info file directly:
<%= $k.render_relative('../Common/contactInfo.html.erb')%>`
However, this approach is somewhat fragile because if either template file is moved, the path will need to be updated. A better approach is to put the filename in a variable. (See cs202_index.html.erb
.) That way, if the contact info file ever moves, we need only update the variable.
The render
method interprets the file name relative to the current working directory. render_relative
interprets the filename relative to the current file (cs101_index.html.erb
in the case above). I have found that render
works best when given an absolute path name. Notice that in kielce_data_site.rb
, the common_dir
value uses Ruby's Kernel#__dir__
method to dynamically generate an absolute path name for use with render
. Generating the path name dynamically means that we can move entire web site directory (e.g., SampleSite
) without breaking any of the render
calls.
The render
and render_relative
methods optionally take a second parameter that is a hash of key/value pairs. For example
<%= $k.render('fileToInclude.html.erb', { x: 'Hi', y: 'Mom'} %>
Important: When rendering an included template, variables are resolved with respect to the outermost file (i.e., the one listed on the kielce
command line). In the SampleSite
example, both CS101/cs101_index.html.erb
and CS202/cs202_index.html.erb
include Common/contactInfo.html.erb
. contactInfo.html.erb
references the key short_name
. Notice that is the the value of course.short_name
provided by the data file in CS101/kielce_data_cs101.rb
or CS202/kielce_data_cs202.rb
that appears in the output, not use the value of course.short_name
given in Common/kielce_data_common.rb
. If you want to use key/value pairs specific to the included template, you can
- Put the data directly in the erb file as a local variable
- Put the data in a
kielce_data*.rb
file that is in a common ancestor of both the "outer" and "inner" file. (In theSampleSite
example, we could put such data in theSampleSite
directory because it is the root of both the includedcontactInfo.html.erb
and the outer course pages.)
Coming Soon
Kielce::Kielce
is the main class. The script creates a singleton object of this type that is placed in the global variable $k
. Kielce
provides the following public methods:
This method generates an anchor tag with the given URL and link text. For example,
$k.link('https://www.gatech.edu', "Go, Jackets!")
returns
<a href='https://www.gatech.edu'>Go, Jackets!</a>
If link_text
is nil
, then the URL is used as the link text and the link text is rendered in a fixed-width font. For example,
$k.link('https://www.gvsu.edu')
returns
<a href='https://www.gvsu.edu'><code>https://www.gvsu.edu</code></a>
Setting code
to true
renders the link text in a fixed-width font (as shown in the example above). The default is to print the link text in a normal font.
The string passed to classes
is the value for the class
attribute on the anchor tag.
$k.link('https://www.gatech.edu', "Go, Jackets!", classes: 'important')
will generate
<a href='https://www.gatech.edu' class='important'>Go, Jackets!</a>
KielceRB
adds a link
method to the String
class to make it easier to generate links. This link
method simply calls Kielce#link
. For example, to generate a link to https://www.gvsu.edu
and have the URL as the link text, you can simply add this to the erb document:
<%= "https://www.gatech.edu".link %>
Similarly, if you want to also specify the link text you can do this:
<%= "https://www.gatech.edu".link('Go, Jackets!') %>
The render
and render_relative
methods includes one .erb file inside another. See the "Nesting Files" section above.
- Don't use
root
,inspect
, ormethod_missing
as key names. - Don't use a key name in multiple files in the same directory. (Using the same key name in different directories is fine --- and, in fact, potentially useful for setting default values.)
- Avoid having function parameters with the same name as key values.
- Keys must be symbols. (They can't be strings, numbers, etc.)
- When loading and rendering, the same context object is used by default. That means that instance variables and methods set in one
kielce_data
file are available in other files as well as when rendering erb. However, a new Ruby binding is requested during each method call, so local variables set during one load/render are not available for subsequent loads/renders