This short guide explains the main report concepts by using illustrative examples scripts. The API reference is available here.
Language
pepper's report scripts are written in Lua. There's extensive online information about the programming language, including a detailed introduction and a reference manual.
Script layout
Report scripts should encapsulate all of their code, except
from module imports, in functions. The function which
will be used to run the report is mandatory and is called
run
. A single argument will be passed to it - the current report
context. Meta-data
like the report title, description or accepted options will be queried
by calling describe()
.
A skeleton script might look light the following:
-- Meta-data
function describe()
local r = {}
r.title = "Skeleton"
r.description = "Just a skeleton"
return r
endf
-- Entry point with report context "self"
function run(self)
-- Nothing here
end
Using repository information
Report scripts can use the their context to access the current environment
The repository
object provides numerous functions to access the source code repository
selected by the user. The following code will print the repository's type,
its location and the current head revision.
function run(self)
local repository = self:repository()
print(repository:type() .. " repository at " .. repository:url())
print("HEAD is " .. repository:head())
end
Examining a revision range
A revision range can be accessed using a revision
iterator
obtained by calling the repository's
iterator()
function. The iterator's
revisions()
function can be used as a standard Lua
iterator as shown in the code below. Furthermore, a
map()
function
is provided that maps a callback function to every revision.
The following code will gather information about the number of commits and the number of authors on a branch.
-- Entry point
function run(self)
local repository = self:repository()
local branch = repository:default_branch()
local authors = {} -- Dictionary to mark authors
local num_authors = 0
local num_commits = 0
-- Initialize iterator and loop over all revisions
local iterator = repository:iterator(branch)
for revision in iterator:revisions() do
-- Check if the author has already been counted
if authors[revision:author()] == nil then
num_authors = num_authors + 1
authors[revision:author()] = true
end
num_commits = num_commits + 1
end
-- Print result
print("Commits on branch " .. branch .. ":")
print(num_commits .. " commits from " .. num_authors .. " author(s)")
end
Here,
default_branch()
will return the branch that is currently checked out for
local repositories. The equivalent revision iteration
using map()
would look like the following, using a
closure containing the
previous the loop body. As opposed to iteration by using
revisions()
,
progress information will be written to the console.
-- Initialize iterator and loop over all revisions
local iterator = repository:iterator(branch)
iterator:map(function (revision)
-- Check if the author has already been counted
if authors[revision:author()] == nil then
num_authors = num_authors + 1
authors[revision:author()] = true
end
num_commits = num_commits + 1
end)
Technical information regarding revision iterators
Internally, revision iterators are designed in a asynchronous manner. The actual revisions on the branch and range selected don't need to be known at the time of construction since fetching this information from the repository may be time-consuming. Thus, the revision iterator will probably block during iteration when waiting for further revision identifiers from the backend. Currently, this kind of background log fetching is implemented for the Subversion backend only as it is the the only repository backend that operates on remote repositories.
Once a bunch of revision identifiers is known to be part of an iteration, meta-data and diffstats can be prefetched by the respective backend implementation in background threads. Most of the backends are using this kind of prefetching. Currently, the only exception is the Mercurial backend.
Option handling
Users can specify report options at the command line after defining the path to the report script. Option handling in scripts consists of two parts:
- Declaration of available options in the table returned by
describe()
. These options will be presented to the user if help is requested at the command line. - Retrieval of option values using
report:getopt()
. Default option values can be specified, too.
Here's a short script that prints the head revision of a given branch for illustrative purposes:
function describe()
local r = {}
r.title = "Branch HEAD"
r.options = {{"-bARG, --branch=ARG", "Select branch"}}
return r
end
function run(self)
local repository = self:repository()
local branch = self:getopt("b,branch", repository:default_branch())
print("Branch " .. branch .. " is at " .. repository:head(branch))
end
Graphical reports
Reports can use the built-in GNUPlot interface to produce graphical output. Furthermore, the pepper.plotutils Lua module provides support for common command-line options and plot setup.
The following report plots a histogram of commits by week days.
-- Include the plotutils module
require "pepper.plotutils"
-- Meta-data
function describe()
local r = {}
r.title = "Commits by Week Days"
-- Add common plotting options
pepper.plotutils.add_plot_options(r)
return r
end
-- This function will be called for every revision
function callback(revision)
-- Determine week day of commit
local day = os.date("*t", revision:date())["wday"]
-- Update commit count
commits[day] = commits[day] + 1
end
-- Entry point
function run(self)
local repository = self:repository()
local days = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}
-- Global table of commit counts with an entry for each day
commits = {0, 0, 0, 0, 0, 0, 0}
-- Gather data by iterating over the default branch
local branch = repository:default_branch()
repository:iterator(branch):map(callback)
-- Create a new plot with a title including the current branch
local plot = pepper.gnuplot:new()
plot:set_title("Commits by Week Days (on " .. branch .. ")")
-- This will setup the plot output using to the command-line
-- options added in describe()
pepper.plotutils.setup_output(plot)
-- Make sure the Y axis starts at zero
plot:cmd("set yrange [0:*]")
-- Run the plot
plot:plot_histogram(days, commits)
end
This time, the iterator's map()
function has been used with a
corresponding callback function. The commits
variable is global, so
it can be accessed from within the callback function. However, we could
have used a closure as well and kept the variable local.
If you pass --help
as a main option to the program when
you run this report, common plot options like output file
name and image size will be shown. Those have been added by calling
add_plot_options()
.
setup_output(plot)
makes sure that these options are actually applied
to the plot.