scripting / scripting-5Markdown ↓

Pure scripts

Pure scripts run where speed matters: once per pipeline row, thousands of times in one schedule optimisation. They have the whole language and its libraries but no ctx, and they cannot load other scripts, so everything a Pure script needs arrives in its data. Where CoCoCo knows the shape of that data, such as a pipeline's columns or a product template's components, reading a field that doesn't exist is an error when the script is checked.

Data pipeline stages

A pipeline stage runs once for every row. The row is input. Return the row to keep, changed as you like, or nil to drop it.

-- A pipeline stage runs once for every row, so it is Pure: the language only,
-- with no ctx. The row is `input`. Return the row to keep, changed as you like,
-- or nil to drop it.
if input.status == "cancelled" then return nil end

local net = input.gross / (1 + input.taxRate)

return {
  orderId = input.orderId,
  net = math.floor(net * 100 + 0.5) / 100,
  region = string.upper(input.region),
}

Report preparation

A report's preparation script runs before its outputs are generated. input holds the report's parameters, context and datasets. Change the datasets and return input; the parameters stay as they were passed. The Reports manual shows how a report declares its preparation script.

-- A report's preparation script runs before its outputs are generated. `input`
-- holds the report's parameters, context and datasets. Change the datasets and
-- return `input`; the parameters stay as they were passed.
local totals = {}
local customers = {}
for _, row in ipairs(input.datasets.orders) do
  if totals[row.customer] == nil then
    totals[row.customer] = 0
    table.insert(customers, row.customer)
  end
  totals[row.customer] = totals[row.customer] + row.amount
end

local summary = {}
for _, customer in ipairs(customers) do
  table.insert(summary, { customer = customer, total = totals[customer] })
end

input.datasets.summary = summary
return input

Scheduler penalty functions

A penalty function scores each candidate schedule the scheduler considers; the scheduler prefers the lowest score. It defines exports.calculatePenalty(schedule), which receives the schedule's jobs (each with its id, isLate, priority and lateness) and operations (each with its id, jobId, machine, startTime, endTime and setupTime), and returns a number. It runs very often, so its limits are tight. If it fails or doesn't return a number, the schedule scores 0.

-- The scheduler calls exports.calculatePenalty for every candidate schedule it
-- considers, thousands of times in one optimisation, and prefers the lowest
-- score. So the function is Pure and has a tight time limit.
exports.calculatePenalty = function(schedule)
  local penalty = 0

  -- A late job costs ten points per unit of lateness, more for high priority.
  for _, job in ipairs(schedule.jobs) do
    if job.isLate then
      penalty = penalty + job.lateness * 10 * (1 + job.priority)
    end
  end

  -- Every unit of setup time costs a point.
  for _, operation in ipairs(schedule.operations) do
    penalty = penalty + operation.setupTime
  end

  return penalty
end

Product template resolvers

A product template's resolver adjusts the job a template creates to the options chosen for it. input.config holds those options, input.components the template's components and input.finishing its finishing flags. It returns a patch: fields to change on a component, named by its localId; components to leave out; and finishing flags to set or clear. Returning nil changes nothing.

-- A product template's resolver adjusts the job a template creates to the
-- options chosen for it. `input.config` holds those choices,
-- `input.components` the template's components and `input.finishing` its
-- finishing flags. Return a patch: fields to change on a component (by its
-- localId), components to leave out, and finishing flags to set or clear.
-- Returning nil changes nothing.
local patch = { components = {}, exclude = {}, finishing = {} }

if type(input.config.pages) == "number" then
  patch.components.body = { totalPages = input.config.pages }
end
if input.config.cover == "none" then
  table.insert(patch.exclude, "cover")
end
if input.config.finish == "gloss" then
  patch.finishing.LAMINATING = true
end

return patch

JDF templates

A JDF template is a stored script that another script loads with ctx.jdf.loadTemplate(name), and a JMF template one it loads with ctx.jmf.loadTemplate(name). The template runs on its own, with no data and no ctx, and the value it returns is what the loading script receives. Return data, such as the settings a ticket needs: functions don't carry over.

-- A JDF template is a stored script that another script loads with
-- ctx.jdf.loadTemplate(name). It runs on its own, with no data and no ctx,
-- and the value it returns is what the loading script receives. Return
-- data, such as the settings a ticket needs; functions don't carry over.
return {
  binding = "SaddleStitch",
  sheetsPerSignature = 4,
  colours = { front = "CMYK", back = "CMYK" },
}

Published platform manual · revision 3ac3c77f87f3