diff --git a/classes/base.lua b/classes/base.lua
index 6f061ef6e..c51df53e9 100644
--- a/classes/base.lua
+++ b/classes/base.lua
@@ -1,19 +1,19 @@
-local base = pl.class()
-base.type = "class"
-base._name = "base"
-
-base._initialized = false
-base.deferredLegacyInit = {}
-base.deferredInit = {}
-base.pageTemplate = { frames = {}, firstContentFrame = nil }
-base.defaultFrameset = {}
-base.firstContentFrame = "page"
-base.options = setmetatable({}, {
+local class = pl.class()
+class.type = "class"
+class._name = "base"
+
+class._initialized = false
+class.deferredLegacyInit = {}
+class.deferredInit = {}
+class.pageTemplate = { frames = {}, firstContentFrame = nil }
+class.defaultFrameset = {}
+class.firstContentFrame = "page"
+class.options = setmetatable({}, {
     _opts = {},
     __newindex = function (self, key, value)
       local opts = getmetatable(self)._opts
       if type(opts[key]) == "function" then
-        opts[key](base, value)
+        opts[key](class, value)
       elseif type(value) == "function" then
         opts[key] = value
       elseif type(key) == "number" then
@@ -27,7 +27,7 @@ base.options = setmetatable({}, {
       if type(key) == "number" then return nil end
       local opt = getmetatable(self)._opts[key]
       if type(opt) == "function" then
-        return opt(base)
+        return opt(class)
       elseif opt then
         return opt
       else
@@ -35,15 +35,15 @@ base.options = setmetatable({}, {
       end
     end
   })
-base.hooks = {
+class.hooks = {
   newpage = {},
   endpage = {},
   finish = {},
 }
 
-base.packages = {}
+class.packages = {}
 
-function base:_init (options)
+function class:_init (options)
   if self == options then options = {} end
   self:declareOptions()
   self:registerRawHandlers()
@@ -63,10 +63,9 @@ function base:_init (options)
         end
       end)
     end)
-  return self
 end
 
-function base:_post_init ()
+function class:_post_init ()
   self._initialized = true
   for i, func in ipairs(self.deferredInit) do
     func(self)
@@ -74,7 +73,7 @@ function base:_post_init ()
   end
 end
 
-function base:setOptions (options)
+function class:setOptions (options)
   options = options or {}
   options.papersize = options.papersize or "a4"
   for option, value in pairs(options) do
@@ -82,11 +81,11 @@ function base:setOptions (options)
   end
 end
 
-function base:declareOption (option, setter)
+function class:declareOption (option, setter)
   self.options[option] = setter
 end
 
-function base:declareOptions ()
+function class:declareOptions ()
   self:declareOption("class", function (_, name)
     if name then
       if self._legacy then
@@ -114,7 +113,7 @@ function base:declareOptions ()
   end)
 end
 
-function base.declareSettings (_)
+function class.declareSettings (_)
   SILE.settings:declare({
     parameter = "current.parindent",
     type = "glue or nil",
@@ -135,17 +134,20 @@ function base.declareSettings (_)
   })
 end
 
-function base:loadPackage (packname, args)
-  local pack = require("packages." .. packname)
+function class:loadPackage (packname, options)
+  local pack = require(("packages.%s"):format(packname))
   if pack.type == "package" then -- new package
-    self.packages[pack._name] = pack(self, args)
-  else -- legacay package
-    self:initPackage(pack, args)
+    if not self._initialized then
+      SILE.scratch.half_initialized_class = self
+    end
+    self.packages[pack._name] = pack(options)
+  else -- legacy package
+    self:initPackage(pack, options)
   end
 end
 
-function base:initPackage (pack, args)
-  SU.deprecated("class:initPackage(args)", "package(class, args)", "0.14.0", "0.16.0", [[
+function class:initPackage (pack, options)
+  SU.deprecated("class:initPackage(options)", "package(options)", "0.14.0", "0.16.0", [[
   This package appears to be a legacy format package. It returns a table
   an expects SILE to guess a bit about what to do. New packages inherit
   from the base class and have a constructor function (_init) that
@@ -162,26 +164,26 @@ function base:initPackage (pack, args)
       pack.registerCommands(self)
     end
     if type(pack.init) == "function" then
-      self:registerPostinit(pack.init, args)
+      self:registerPostinit(pack.init, options)
     end
   end
 end
 
-function base:registerLegacyPostinit (func, args)
-  if self._initialized then return func(self, args) end
+function class:registerLegacyPostinit (func, options)
+  if self._initialized then return func(self, options) end
   table.insert(self.deferredLegacyInit, function (_)
-      func(self, args)
+      func(self, options)
     end)
 end
 
-function base:registerPostinit (func, args)
-  if self._initialized then return func(self, args) end
+function class:registerPostinit (func, options)
+  if self._initialized then return func(self, options) end
   table.insert(self.deferredInit, function (_)
-      func(self, args)
+      func(self, options)
     end)
 end
 
-function base:registerHook (category, func)
+function class:registerHook (category, func)
   for _, func_ in ipairs(self.hooks[category]) do
     if func_ == func then
       return SU.warn("Attempted to set the same function hook twice, probably unintended, skipping.")
@@ -190,14 +192,14 @@ function base:registerHook (category, func)
   table.insert(self.hooks[category], func)
 end
 
-function base:runHooks (category, args)
+function class:runHooks (category, options)
   for _, func in ipairs(self.hooks[category]) do
-    SU.debug("classhooks", "Running hook from " .. category, args and "with args " .. #args)
-    func(self, args)
+    SU.debug("classhooks", "Running hook from " .. category, options and "with options " .. #options)
+    func(self, options)
   end
 end
 
-function base.registerCommand (_, name, func, help, pack)
+function class.registerCommand (_, name, func, help, pack)
   SILE.Commands[name] = func
   if not pack then
     local where = debug.getinfo(2).source
@@ -210,11 +212,11 @@ function base.registerCommand (_, name, func, help, pack)
   }
 end
 
-function base.registerRawHandler (_, format, callback)
+function class.registerRawHandler (_, format, callback)
   SILE.rawHandlers[format] = callback
 end
 
-function base:registerRawHandlers ()
+function class:registerRawHandlers ()
 
   self:registerRawHandler("text", function (_, content)
     SILE.settings:temporarily(function()
@@ -226,7 +228,16 @@ function base:registerRawHandlers ()
 
 end
 
-function base:registerCommands ()
+local function packOptions (options)
+  local relevant = pl.tablex.copy(options)
+  relevant.src = nil
+  relevant.format = nil
+  relevant.module = nil
+  relevant.require = nil
+  return relevant
+end
+
+function class:registerCommands ()
 
   local function replaceProcessBy(replacement, tree)
     if type(tree) ~= "table" then return tree end
@@ -298,46 +309,78 @@ function base:registerCommands ()
   end, "Within a macro definition, processes the contents of the macro body.")
 
   self:registerCommand("script", function (options, content)
-    if options.src then
-      SILE.require(options.src)
+    local packopts = packOptions(options)
+    if SU.hasContent(content) then
+      return SILE.processString(content[1], options.format or "lua", nil, packopts)
+    elseif options.src then
+      return SILE.require(options.src)
     else
-      SILE.processString(content[1], options.format or "lua")
+      SU.error("\\script function requires inline content or a src file path")
+      return SILE.processString(content[1], options.format or "lua", nil, packopts)
     end
   end, "Runs lua code. The code may be supplied either inline or using src=...")
 
   self:registerCommand("include", function (options, content)
-    if options.src then
-      SILE.processFile(options.src, options.format)
+    local packopts = packOptions(options)
+    if SU.hasContent(content) then
+      return SILE.processString(content[1], options.format, nil, packopts)
+    elseif options.src then
+      return SILE.processFile(options.src, options.format, packopts)
     else
-      SILE.processString(content[1], options.format)
+      SU.error("\\include function requires inline content or a src file path")
     end
   end, "Includes a content file for processing.")
 
   self:registerCommand("lua", function (options, content)
-    if options.module then
-      SILE.require(options.module)
+    local packopts = packOptions(options)
+    if SU.hasContent(content) then
+      return SILE.processString(content[1], "lua", nil, packopts)
     elseif options.src then
-      SILE.processFile(options.src, "lua")
+      return SILE.processFile(options.src, "lua", packopts)
+    elseif options.require then
+      local module = SU.required(options, "require", "lua")
+      return require(module)
     else
-      SILE.processString(content[1], "lua")
+      SU.error("\\lua function requires inline content or a src file path or a require module name")
     end
-  end, "Run Lua code. The code may be supplied either inline or using src=...")
+  end, "Run Lua code. The code may be supplied either inline, using require=... for a Lua module, or using src=... for a file path")
 
   self:registerCommand("sil", function (options, content)
-    if options.src then
-      SILE.processFile(options.src, "sil")
+    local packopts = packOptions(options)
+    if SU.hasContent(content) then
+      return SILE.processString(content[1], "sil")
+    elseif options.src then
+      return SILE.processFile(options.src, "sil", packopts)
     else
-      SILE.processString(content[1], "sil")
+      SU.error("\\sil function requires inline content or a src file path")
     end
   end, "Process sil content. The content may be supplied either inline or using src=...")
 
   self:registerCommand("xml", function (options, content)
-    if options.src then
-      SILE.processFile(options.src, "xml")
+    local packopts = packOptions(options)
+    if SU.hasContent(content) then
+      return SILE.processString(content[1], "xml", nil, packopts)
+    elseif options.src then
+      return SILE.processFile(options.src, "xml", packopts)
+    else
+      SU.error("\\xml function requires inline content or a src file path")
+    end
+  end, "Process xml content. The content may be supplied either inline or using src=...")
+
+  self:registerCommand("use", function (options, content)
+    local packopts = packOptions(options)
+    if content[1] and string.len(content[1]) > 0 then
+      SILE.processString(content[1], "lua", nil, packopts)
     else
-      SILE.processString(content[1], "xml")
+      if options.src then
+        SU.warn("Use of 'src' with \\use is discouraged because some of it's path handling\n  will eventually be deprecated. Use 'module' instead when possible.")
+        SILE.processFile(options.src, "lua", packopts)
+      else
+        local module = SU.required(options, "module", "use")
+        SILE.use(module, packopts)
+      end
     end
-  end, "Run xml content. The content may be supplied either inline or using src=...")
+  end, "Load and initialize a SILE module (can be a package, a shaper, a typesetter, or whatever). Use module=... to specif what to load or include module code inline.")
 
   self:registerCommand("raw", function (options, content)
     local rawtype = SU.required(options, "type", "raw")
@@ -417,7 +460,7 @@ function base:registerCommands ()
 
 end
 
-function base:initialFrame ()
+function class:initialFrame ()
   SILE.documentState.thisPageTemplate = pl.tablex.deepcopy(self.pageTemplate)
   SILE.frames = { page = SILE.frames.page }
   for k, v in pairs(SILE.documentState.thisPageTemplate.frames) do
@@ -430,7 +473,7 @@ function base:initialFrame ()
   return SILE.documentState.thisPageTemplate.firstContentFrame
 end
 
-function base:declareFrame (id, spec)
+function class:declareFrame (id, spec)
   spec.id = id
   if spec.solve then
     self.pageTemplate.frames[id] = spec
@@ -448,14 +491,14 @@ function base:declareFrame (id, spec)
   -- })
 end
 
-function base:declareFrames (specs)
+function class:declareFrames (specs)
   if specs then
     for k, v in pairs(specs) do self:declareFrame(k, v) end
   end
 end
 
 -- WARNING: not called as class method
-function base.newPar (typesetter)
+function class.newPar (typesetter)
   typesetter:pushGlue(SILE.settings:get("current.parindent") or SILE.settings:get("document.parindent"))
   SILE.settings:set("current.parindent", nil)
   local hangIndent = SILE.settings:get("current.hangIndent")
@@ -469,7 +512,7 @@ function base.newPar (typesetter)
 end
 
 -- WARNING: not called as class method
-function base.endPar (typesetter)
+function class.endPar (typesetter)
   typesetter:pushVglue(SILE.settings:get("document.parskip"))
   if SILE.settings:get("current.hangIndent") then
     SILE.settings:set("current.hangIndent", nil)
@@ -481,14 +524,14 @@ function base.endPar (typesetter)
   end
 end
 
-function base:newPage ()
+function class:newPage ()
   SILE.outputter:newPage()
   self:runHooks("newpage")
   -- Any other output-routiney things will be done here by inheritors
   return self:initialFrame()
 end
 
-function base:endPage ()
+function class:endPage ()
   SILE.typesetter.frame:leave(SILE.typesetter)
   self:runHooks("endpage")
   -- I'm trying to call up a new frame here, don't cause a page break in the current one
@@ -496,7 +539,7 @@ function base:endPage ()
   -- Any other output-routiney things will be done here by inheritors
 end
 
-function base:finish ()
+function class:finish ()
   SILE.inputter:postamble()
   SILE.call("vfill")
   while not SILE.typesetter:isQueueEmpty() do
@@ -516,4 +559,4 @@ function base:finish ()
   self:runHooks("finish")
 end
 
-return base
+return class
diff --git a/classes/bible.lua b/classes/bible.lua
index 6261fd63a..0e572d9e1 100644
--- a/classes/bible.lua
+++ b/classes/bible.lua
@@ -1,11 +1,11 @@
 local plain = require("classes.plain")
 
-local bible = pl.class(plain)
-bible._name = "bible"
+local class = pl.class(plain)
+class._name = "bible"
 
 if not SILE.scratch.headers then SILE.scratch.headers = {} end
 
-bible.defaultFrameset = {
+class.defaultFrameset = {
   content = {
     left = "8.3%pw",
     right = "86%pw",
@@ -32,18 +32,23 @@ bible.defaultFrameset = {
   }
 }
 
-function bible:singleColumnMaster()
+function class:singleColumnMaster()
   self:defineMaster({
     id = "right",
     firstContentFrame = self.firstContentFrame,
     frames = self.defaultFrameset
   })
-  self:loadPackage("twoside", { oddPageMaster = "right", evenPageMaster = "left" })
-  self:mirrorMaster("right", "left")
-  self:loadPackage("footnotes", { insertInto = "footnotes", stealFrom = { "content" } })
+  self:loadPackage("twoside", {
+    oddPageMaster = "right",
+    evenPageMaster = "left"
+  })
+  self:loadPackage("footnotes", {
+    insertInto = "footnotes",
+    stealFrom = { "content" }
+  })
 end
 
-function bible:twoColumnMaster()
+function class:twoColumnMaster()
   self.firstContentFrame = "contentA"
   self:defineMaster({
       id = "right",
@@ -169,9 +174,9 @@ end
 local _twocolumns
 local _gutterwidth
 
-function bible:_init(options)
-  self:loadPackage("masters")
+function class:_init(options)
   plain._init(self, options)
+  self:loadPackage("masters")
   self:loadPackage("infonode")
   self:loadPackage("chapterverse")
   self:registerPostinit(function (self_)
@@ -182,10 +187,9 @@ function bible:_init(options)
       self_:singleColumnMaster()
     end
   end)
-  return self
 end
 
-function bible:endPage ()
+function class:endPage ()
   if (self:oddPage() and SILE.scratch.headers.right) then
     SILE.typesetNaturally(SILE.getFrame("runningHead"), function ()
       SILE.settings:set("current.parindent", SILE.nodefactory.glue())
@@ -208,7 +212,7 @@ function bible:endPage ()
   return plain.endPage(self)
 end
 
-function bible:declareOptions ()
+function class:declareOptions ()
   plain.declareOptions(self)
   self:declareOption("twocolumns", function(_, value)
     if value then
@@ -224,13 +228,13 @@ function bible:declareOptions ()
   end)
 end
 
-function bible:setOptions (options)
+function class:setOptions (options)
   options.twocolumns = options.twocolumns or false
   options.gutter = options.gutter or "3%pw"
   plain.setOptions(self, options)
 end
 
-function bible:registerCommands ()
+function class:registerCommands ()
 
   plain.registerCommands(self)
 
@@ -285,4 +289,4 @@ function bible:registerCommands ()
 
 end
 
-return bible
+return class
diff --git a/classes/book.lua b/classes/book.lua
index 0bceda104..3380f5eaf 100644
--- a/classes/book.lua
+++ b/classes/book.lua
@@ -1,9 +1,9 @@
 local plain = require("classes.plain")
 
-local book = pl.class(plain)
-book._name = "book"
+local class = pl.class(plain)
+class._name = "book"
 
-book.defaultFrameset = {
+class.defaultFrameset = {
   content = {
     left = "8.3%pw",
     right = "86%pw",
@@ -30,7 +30,7 @@ book.defaultFrameset = {
   }
 }
 
-function book:_init (options)
+function class:_init (options)
   self:loadPackage("counters")
   self:loadPackage("masters", {{
       id = "right",
@@ -48,10 +48,9 @@ function book:_init (options)
       stealFrom = { "content" }
     })
   if not SILE.scratch.headers then SILE.scratch.headers = {} end
-  return self
 end
 
-function book:endPage ()
+function class:endPage ()
   if (self:oddPage() and SILE.scratch.headers.right) then
     SILE.typesetNaturally(SILE.getFrame("runningHead"), function ()
       SILE.settings:toplevelState()
@@ -76,12 +75,12 @@ function book:endPage ()
   return plain.endPage(self)
 end
 
-function book:finish ()
+function class:finish ()
   local ret = plain.finish(self)
   return ret
 end
 
-function book:registerCommands ()
+function class:registerCommands ()
 
   plain.registerCommands(self)
 
@@ -247,4 +246,4 @@ function book:registerCommands ()
 
 end
 
-return book
+return class
diff --git a/classes/diglot.lua b/classes/diglot.lua
index 82bf3c83a..ee63d5afe 100644
--- a/classes/diglot.lua
+++ b/classes/diglot.lua
@@ -1,9 +1,9 @@
 local plain = require("classes.plain")
 
-local diglot = pl.class(plain)
-diglot._name = "diglot"
+local class = pl.class(plain)
+class._name = "diglot"
 
-function diglot:_init (options)
+function class:_init (options)
   plain._init(self, options)
   self:loadPackage("counters")
   self:registerPostinit(function ()
@@ -13,7 +13,6 @@ function diglot:_init (options)
   self:declareFrame("b",    { left = "52%pw",   right = "100%pw-left(a)", top = "top(a)",         bottom = "bottom(a)"      })
   self:declareFrame("folio",{ left = "left(a)", right = "right(b)",       top = "bottom(a)+3%ph", bottom = "bottom(a)+8%ph" })
   self:loadPackage("parallel", { frames = { left = "a", right = "b" } })
-  return self
 end
 
-return diglot
+return class
diff --git a/classes/docbook.lua b/classes/docbook.lua
index 5133923a9..937d7a4a9 100644
--- a/classes/docbook.lua
+++ b/classes/docbook.lua
@@ -1,14 +1,14 @@
 local plain = require("classes.plain")
 
-local docbook = pl.class(plain)
-docbook._name = "docbook"
+local class = pl.class(plain)
+class._name = "docbook"
 
 SILE.scratch.docbook = {
   seclevel = 0,
   seccount = {}
 }
 
-function docbook:_init (options)
+function class:_init (options)
   plain._init(self, options)
   self:loadPackage("image")
   self:loadPackage("simpletable", {
@@ -19,30 +19,29 @@ function docbook:_init (options)
   self:loadPackage("rules")
   self:loadPackage("verbatim")
   self:loadPackage("footnotes")
-  return self
 end
 
-function docbook.push (t, val)
+function class.push (t, val)
   if not SILE.scratch.docbook[t] then SILE.scratch.docbook[t] = {} end
   local q = SILE.scratch.docbook[t]
   q[#q+1] = val
 end
 
-function docbook.pop (t)
+function class.pop (t)
   local q = SILE.scratch.docbook[t]
   q[#q] = nil
 end
 
-function docbook.val (t)
+function class.val (t)
   local q = SILE.scratch.docbook[t]
   return q[#q]
 end
 
-function docbook.wipe (tbl)
+function class.wipe (tbl)
   while((#tbl) > 0) do tbl[#tbl] = nil end
 end
 
-docbook.registerCommands = function (self)
+class.registerCommands = function (self)
 
   plain.registerCommands(self)
 
@@ -412,4 +411,4 @@ docbook.registerCommands = function (self)
 
 end
 
-return docbook
+return class
diff --git a/classes/jbook.lua b/classes/jbook.lua
index 40f573bc2..7cd2e5f53 100644
--- a/classes/jbook.lua
+++ b/classes/jbook.lua
@@ -1,18 +1,17 @@
 local tbook = require("classes.tbook")
 
-local jbook = pl.class(tbook)
-jbook._name = "jbook"
+local class = pl.class(tbook)
+class._name = "jbook"
 
-function jbook:_init (options)
+function class:_init (options)
   tbook._init(self, options)
   SILE.languageSupport.loadLanguage("ja")
   SILE.settings:set("document.language", "ja", true)
   SILE.settings:set("font.family", "Noto Sans CJK JP", true)
-  return self
 end
 
-jbook.declareOptions = tbook.declareOptions
+class.declareOptions = tbook.declareOptions
 
-jbook.setOptions = tbook.setOptions
+class.setOptions = tbook.setOptions
 
-return jbook
+return class
diff --git a/classes/jplain.lua b/classes/jplain.lua
index a3895a05b..b5350ef60 100644
--- a/classes/jplain.lua
+++ b/classes/jplain.lua
@@ -1,15 +1,14 @@
 -- Basic! Transitional! In development! Not very good! Don't use it!
 local tplain = require("classes.tplain")
 
-local jplain = pl.class(tplain)
-jplain._name = "jplain"
+local class = pl.class(tplain)
+class._name = "jplain"
 
-function jplain:_init (options)
+function class:_init (options)
   tplain._init(self, options)
   SILE.languageSupport.loadLanguage("ja")
   SILE.settings:set("document.language", "ja", true)
   SILE.settings:set("font.family", "Noto Sans CJK JP", true)
-  return self
 end
 
-return jplain
+return class
diff --git a/classes/letter.lua b/classes/letter.lua
index 4287316b2..d812da98e 100644
--- a/classes/letter.lua
+++ b/classes/letter.lua
@@ -1,9 +1,9 @@
 local plain = require("classes.plain")
 
-local letter = pl.class(plain)
-letter._name = "letter"
+local class = pl.class(plain)
+class._name = "letter"
 
-letter.defaultFrameset = {
+class.defaultFrameset = {
   content = {
       left = "5%pw",
       right = "95%pw",
@@ -12,7 +12,7 @@ letter.defaultFrameset = {
     }
 }
 
-function letter:_init (options)
+function class:_init (options)
   plain._init(self, options)
   SILE.scratch.letter = {
     sender = nil,
@@ -20,10 +20,9 @@ function letter:_init (options)
     recipient = "",
     salutation = ""
   }
-  return self
 end
 
-function letter:registerCommands ()
+function class:registerCommands ()
 
   plain.registerCommands(self)
 
@@ -80,4 +79,4 @@ function letter:registerCommands ()
 
 end
 
-return letter
+return class
diff --git a/classes/markdown.lua b/classes/markdown.lua
index 4556cfb69..e2a997e73 100644
--- a/classes/markdown.lua
+++ b/classes/markdown.lua
@@ -2,8 +2,8 @@
 -- for the AST writer.
 
 local book = require("classes.book")
-local markdown = pl.class(book)
-markdown._name = "markdown"
+local class = pl.class(book)
+class._name = "markdown"
 
 SILE.inputs.markdown = {
   order = 2,
@@ -22,14 +22,13 @@ SILE.inputs.markdown = {
   end
 }
 
-function markdown:_init (options)
+function class:_init (options)
   book._init(self, options)
   self:loadPackage("url")
   self:loadPackage("image")
-  return self
 end
 
-function markdown:registerCommands ()
+function class:registerCommands ()
 
   book.registerCommands(self)
 
@@ -68,4 +67,4 @@ function markdown:registerCommands ()
 
 end
 
-return markdown
+return class
diff --git a/classes/pecha.lua b/classes/pecha.lua
index 74085d27a..8138650e6 100644
--- a/classes/pecha.lua
+++ b/classes/pecha.lua
@@ -1,7 +1,7 @@
 local plain = require("classes.plain")
 
-local pecha = pl.class(plain)
-pecha._name = "pecha"
+local class = pl.class(plain)
+class._name = "pecha"
 
 local tibetanNumber = function (n)
   local out = ""
@@ -13,7 +13,7 @@ local tibetanNumber = function (n)
   return out
 end
 
-pecha.defaultFrameset = {
+class.defaultFrameset = {
   content = {
     left = "5%pw",
     right = "95%pw",
@@ -36,7 +36,7 @@ pecha.defaultFrameset = {
   }
 }
 
-function pecha:_init(options)
+function class:_init(options)
   plain._init(self, options)
   self:loadPackage("rotate")
   self:registerPostinit(function ()
@@ -45,10 +45,9 @@ function pecha:_init(options)
     SILE.settings:set("typesetter.parfillskip", SILE.nodefactory.glue())
     SILE.settings:set("document.parindent", SILE.nodefactory.glue())
   end)
-  return self
 end
 
-function pecha:endPage()
+function class:endPage()
   local folioframe = SILE.getFrame("folio")
   SILE.typesetNaturally(folioframe, function ()
     SILE.settings:pushState()
@@ -68,12 +67,12 @@ function pecha:endPage()
   return plain.endPage(self)
 end
 
-function pecha:newPage()
+function class:newPage()
   SILE.outputter:newPage()
   SILE.outputter:debugFrame(SILE.getFrame("content"))
   return self:initialFrame()
 end
 
-return pecha
+return class
 
 -- \right-running-head{\font[size=15pt]{\center{ཤེས་རབ་སྙིང་པོ་ }}}
diff --git a/classes/plain.lua b/classes/plain.lua
index 3d742593d..d27ad3652 100644
--- a/classes/plain.lua
+++ b/classes/plain.lua
@@ -1,9 +1,9 @@
 local base = require("classes.base")
 
-local plain = pl.class(base)
-plain._name = "plain"
+local class = pl.class(base)
+class._name = "plain"
 
-plain.defaultFrameset = {
+class.defaultFrameset = {
   content = {
     left = "5%pw",
     right = "95%pw",
@@ -23,7 +23,7 @@ plain.defaultFrameset = {
     bottom = "90%ph"
   }
 }
-plain.firstContentFrame = "content"
+class.firstContentFrame = "content"
 
 local skips = {
   small = "3pt plus 1pt minus 1pt",
@@ -31,14 +31,13 @@ local skips = {
   big = "12pt plus 4pt minus 4pt"
 }
 
-function plain:_init (options)
+function class:_init (options)
   base._init(self, options)
   self:loadPackage("bidi")
   self:loadPackage("folio")
-  return self
 end
 
-function plain:declareOptions ()
+function class:declareOptions ()
   base.declareOptions(self)
   self:declareOption("direction", function (_, value)
     if value then
@@ -53,12 +52,12 @@ function plain:declareOptions ()
   end)
 end
 
-function plain:setOptions (options)
+function class:setOptions (options)
   -- TODO: set a default direction here?
   base.setOptions(self, options)
 end
 
-function plain:declareSettings ()
+function class:declareSettings ()
   base.declareSettings(self)
   for k, v in pairs(skips) do
     SILE.settings:declare({
@@ -70,7 +69,7 @@ function plain:declareSettings ()
   end
 end
 
-function plain:registerCommands ()
+function class:registerCommands ()
 
   SILE.classes.base.registerCommands(self)
 
@@ -363,4 +362,4 @@ function plain:registerCommands ()
 
 end
 
-return plain
+return class
diff --git a/classes/tbook.lua b/classes/tbook.lua
index bcbe3ff0d..11c2e8721 100644
--- a/classes/tbook.lua
+++ b/classes/tbook.lua
@@ -1,10 +1,10 @@
 local book = require("classes.book")
 local tplain = require("classes.tplain")
 
-local tbook = pl.class(book)
-tbook._name = "tbook"
+local class = pl.class(book)
+class._name = "tbook"
 
-tbook.defaultFrameset = {
+class.defaultFrameset = {
   runningHead = {
     left = "left(content) + 9pt",
     right = "right(content) - 9pt",
@@ -33,15 +33,14 @@ tbook.defaultFrameset = {
   }
 }
 
-function tbook:_init (options)
-  if self._legacy and not self._deprecated then return self:_deprecator(tbook) end
+function class:_init (options)
   book._init(self, options)
   tplain._t_common(self)
   return self
 end
 
-tbook.declareOptions = tplain.declareOptions
+class.declareOptions = tplain.declareOptions
 
-tbook.setOptions = tplain.setOptions
+class.setOptions = tplain.setOptions
 
-return tbook
+return class
diff --git a/classes/tplain.lua b/classes/tplain.lua
index 8b9397497..55f30de72 100644
--- a/classes/tplain.lua
+++ b/classes/tplain.lua
@@ -1,10 +1,10 @@
 -- Basic! Transitional! In development! Not very good! Don't use it!
 local plain = require("classes.plain")
 
-local tplain = pl.class(plain)
-tplain._name = "tplain"
+local class = pl.class(plain)
+class._name = "tplain"
 
-tplain.defaultFrameset.content = {
+class.defaultFrameset.content = {
   left = "8.3%pw",
   top = "11.6%ph",
   gridsize = 10,
@@ -13,26 +13,26 @@ tplain.defaultFrameset.content = {
   linecount = 30
 }
 
-function tplain:_t_common ()
+-- The classes tplain and tbook inherit from plain and book respectively but also
+-- have this bit in common; this makes it accessable
+function class:_t_common ()
   self:loadPackage("font-fallback")
   self:loadPackage("hanmenkyoshi")
-  self:registerPostinit(function (class)
-    class:bidiDisableTypesetter(SILE.typesetter)
-    class:bidiDisableTypesetter(SILE.defaultTypesetter)
+  self:registerPostinit(function (class_)
+    class_:bidiDisableTypesetter(SILE.typesetter)
+    class_:bidiDisableTypesetter(SILE.defaultTypesetter)
   end)
   self.defaultFrameset.content.tate = self.options.layout == "tate"
   self:declareHanmenFrame("content", self.defaultFrameset.content)
   SILE.settings:set("document.parindent", SILE.nodefactory.glue("10pt"))
 end
 
-function tplain:_init (options)
-  if self._legacy and not self._deprecated then return self:_deprecator(tplain) end
+function class:_init (options)
   plain._init(self, options)
-  tplain._t_common(self)
-  return self
+  class._t_common(self)
 end
 
-function tplain:declareOptions ()
+function class:declareOptions ()
   plain.declareOptions(self)
   self:declareOption("layout", function (_, value)
     if value then
@@ -43,9 +43,9 @@ function tplain:declareOptions ()
   end)
 end
 
-function tplain:setOptions (options)
+function class:setOptions (options)
   options.layout = options.layout or "yoko"
   plain.setOptions(self, options)
 end
 
-return tplain
+return class
diff --git a/classes/triglot.lua b/classes/triglot.lua
index 57778e817..eb4204c74 100644
--- a/classes/triglot.lua
+++ b/classes/triglot.lua
@@ -1,9 +1,9 @@
 local book = require("classes.book")
 
-local triglot = pl.class(book)
-triglot._name = "triglot"
+local class = pl.class(book)
+class._name = "triglot"
 
-function triglot:_init (options)
+function class:_init (options)
   book._init(self, options)
   self:loadPackage("counters")
   self:registerPostinit(function ()
@@ -16,7 +16,6 @@ function triglot:_init (options)
   self:loadPackage("parallel", { frames = { left = "a", middle = "b", right= "c" } })
   SILE.settings:set("linebreak.tolerance", 5000)
   SILE.settings:set("document.parindent", SILE.nodefactory.glue())
-  return self
 end
 
-return triglot
+return class
diff --git a/core/cli.lua b/core/cli.lua
index f0d97ad4d..942bb844a 100644
--- a/core/cli.lua
+++ b/core/cli.lua
@@ -22,13 +22,13 @@ cli.parseArguments = function ()
   cliargs:option("-e, --evaluate=VALUE", "evaluate Lua expression before processing input", {})
   cliargs:option("-E, --evaluate-after=VALUE", "evaluate Lua expression after processing input", {})
   cliargs:option("-f, --fontmanager=VALUE", "choose an alternative font manager")
+  cliargs:option("-I, --include=FILE", "deprecated, see --use, --preamble, or --postamble", {})
   cliargs:option("-m, --makedeps=FILE", "generate a list of dependencies in Makefile format")
   cliargs:option("-o, --output=FILE", "explicitly set output file name")
-  cliargs:option("-O, --options=PARAMETER=VALUE", "set document class options", {})
-  cliargs:option("-I, --include=FILE", "deprecated, see --require, --preamble, or --postamble", {})
-  cliargs:option("-r, --require=MODULE", "require a resource to be loaded before processing input", {})
-  cliargs:option("-p, --preamble=FILE", "include an SIL, XML, or other content before the input document", {})
-  cliargs:option("-P, --postamble=FILE", "include an SIL, XML, or other content after the input document", {})
+  cliargs:option("-O, --options=PARAMETER=VALUE[,PARAMETER=VALUE]", "set document class options", {})
+  cliargs:option("-p, --preamble=FILE", "process SIL, XML, or other content before the input document", {})
+  cliargs:option("-P, --postamble=FILE", "process SIL, XML, or other content after the input document", {})
+  cliargs:option("-u, --use=MODULE[[PARAMETER=VALUE][,PARAMETER=VALUE]]", "load and initialize a module before processing input", {})
   cliargs:flag("-t, --traceback", "display detailed location trace on errors and warnings")
   cliargs:flag("-h, --help", "display this help, then exit")
   cliargs:flag("-v, --version", "display version information, then exit", print_version)
@@ -44,10 +44,11 @@ cli.parseArguments = function ()
     if opts.INPUT == "STDIO" then
       opts.INPUT = "-"
     end
+    SILE.input.filename = opts.INPUT
     -- Turn slashes around in the event we get passed a path from a Windows shell
-    SILE.inputFile = opts.INPUT:gsub("\\", "/")
+    local filename = opts.INPUT:gsub("\\", "/")
     -- Strip extension
-    SILE.masterFilename = string.match(SILE.inputFile, "(.+)%..-$") or SILE.inputFile
+    SILE.masterFilename = string.match(filename, "(.+)%..-$") or filename
     SILE.masterDir = SILE.masterFilename:match("(.-)[^%/]+$")
   end
   if opts.backend then
@@ -81,12 +82,12 @@ cli.parseArguments = function ()
     SILE.outputFilename = opts.output
   end
   for _, option in ipairs(opts.options) do
-    local parameters = SILE.parserBits.parameters
-    local options = parameters:match(option)
+    local options = SILE.parserBits.parameters:match(option)
     pl.tablex.merge(SILE.input.options, options, true)
   end
-  for _, path in ipairs(opts.require) do
-    table.insert(SILE.input.requires, path)
+  for _, use in ipairs(opts.use) do
+    local spec = SILE.parserBits.cliuse:match(use)
+    table.insert(SILE.input.uses, spec)
   end
   for _, path in ipairs(opts.preamble) do
     table.insert(SILE.input.preambles, path)
@@ -95,7 +96,7 @@ cli.parseArguments = function ()
     table.insert(SILE.input.postambles, path)
   end
   for _, path in ipairs(opts.include) do
-    SU.deprecated("-I/--include", "-r/--require or -p/--preamble", "0.14.0", "0.15.0")
+    SU.deprecated("-I/--include", "-u/--use or -p/--preamble", "0.14.0", "0.15.0")
     table.insert(SILE.input.includes, path)
   end
   -- http://lua-users.org/wiki/VarargTheSecondClassCitizen
diff --git a/core/parserbits.lua b/core/parserbits.lua
index 6516ec83f..f77a0ebe0 100644
--- a/core/parserbits.lua
+++ b/core/parserbits.lua
@@ -72,4 +72,10 @@ bits.silidentifier = (ID + S":-")^1
 local pair = Cg(C(bits.silidentifier) * bits.ws * "=" * bits.ws * C(value)) * pairsep^-1 / unwrapper
 bits.parameters = Cf(Ct"" * pair^0, rawset)
 
+local wrapper = function (a) return type(a)=="table" and a or {} end
+local useparams = (P"[" * bits.parameters * P"]")^-1 / wrapper
+local modpart = C((1 - P"." - P"/" - P"[")^1)
+local module = C(modpart * (P"." * modpart)^0)
+bits.cliuse = Ct(Cg(module, "module") * Cg(useparams^-1, "options"))
+
 return bits
diff --git a/core/sile.lua b/core/sile.lua
index 212aab56b..ddcba0d5a 100644
--- a/core/sile.lua
+++ b/core/sile.lua
@@ -58,10 +58,11 @@ SILE.rawHandlers = {}
 -- needed for a user to use a SILE-as-a-library verion to produce documents
 -- programatically.
 SILE.input = {
+  filename = "",
   evaluates = {},
   evaluateAfters = {},
   includes = {},
-  requires = {},
+  uses = {},
   options = {},
   preambles = {},
   postambles = {},
@@ -135,6 +136,45 @@ SILE.init = function ()
   runEvals(SILE.input.evaluates, "evaluate")
 end
 
+SILE.use = function (module, options)
+  local pack
+  if type(module) == "string" then
+    pack = require(module)
+  elseif type(module) == "table" then
+    pack = module
+  end
+  local name = pack._name
+  local class = SILE.documentState.documentClass
+  if not pack.type then
+    SU.error("Modules must declare their type")
+  elseif pack.type == "class" then
+    SILE.classes[name] = pack
+    if class then
+      SU.error("Cannot load a class after one is already instantiated")
+    end
+    SILE.sratch.class_from_uses = pack
+  elseif pack.type == "inputter" then
+    SILE.inputters[name] = pack
+    SILE.inputter = pack(options)
+  elseif pack.type == "outputter" then
+    SILE.outputters[name] = pack
+    SILE.outputter = pack(options)
+  elseif pack.type == "shaper" then
+    SILE.shapers[name] = pack
+    SILE.shaper = pack(options)
+  elseif pack.type == "typesetter" then
+    SILE.typesetters[name] = pack
+    SILE.typesetter = pack(options)
+  elseif pack.type == "package" then
+    SILE.packages[name] = pack
+    if class then
+      pack(options)
+    else
+      table.insert(SILE.input.preambles, { pack = pack, options = options })
+    end
+  end
+end
+
 SILE.require = function (dependency, pathprefix, deprecation_ack)
   if pathprefix and not deprecation_ack then
     local notice = string.format([[
@@ -195,12 +235,12 @@ SILE.process = function (ast)
   end
 end
 
-local defaultinputters = { "xml", "lua", "sil" }
+local preloadedinputters = { "xml", "lua", "sil" }
 
 local function detectFormat (doc, filename)
   -- Preload default reader types so content detection has something to work with
   if #SILE.inputters == 0 then
-    for _, format in ipairs(defaultinputters) do
+    for _, format in ipairs(preloadedinputters) do
       local _ = SILE.inputters[format]
     end
   end
@@ -221,23 +261,36 @@ local function detectFormat (doc, filename)
   SU.error(("Unable to pick inputter to process input from '%s'"):format(filename))
 end
 
-function SILE.processString (doc, format, filename)
+function SILE.processString (doc, format, filename, options)
   local cpf
   if not filename then
     cpf = SILE.currentlyProcessingFile
     local caller = debug.getinfo(2, "Sl")
     SILE.currentlyProcessingFile = caller.short_src..":"..caller.currentline
   end
-  format = format or detectFormat(doc, filename)
-  io.stderr:write(("<%s> as %s\n"):format(SILE.currentlyProcessingFile, format))
-  SILE.inputter = SILE.inputters[format]()
+  -- In the event we're processing the master file *and* the user gave us
+  -- a specific inputter to use, use it at the exclusion of all content type
+  -- detection
+  local inputter
+  if filename and filename:gsub("STDIN", "-") == SILE.input.filename and SILE.inputter then
+    inputter = SILE.inputter
+  else
+    format = format or detectFormat(doc, filename)
+    io.stderr:write(("<%s> as %s\n"):format(SILE.currentlyProcessingFile, format))
+    inputter = SILE.inputters[format](options)
+    -- If we did content detection *and* this is the master file, save the
+    -- inputter for posterity and postambles
+    if filename and filename:gsub("STDIN", "-") == SILE.input.filename then
+      SILE.inputter = inputter
+    end
+  end
   local pId = SILE.traceStack:pushDocument(SILE.currentlyProcessingFile, doc)
-  SILE.inputter:process(doc)
+  inputter:process(doc)
   SILE.traceStack:pop(pId)
   if cpf then SILE.currentlyProcessingFile = cpf end
 end
 
-function SILE.processFile (filename, format)
+function SILE.processFile (filename, format, options)
   local doc
   if filename == "-" then
     filename = "STDIN"
@@ -263,7 +316,7 @@ function SILE.processFile (filename, format)
   end
   SILE.currentlyProcessingFile = filename
   local pId = SILE.traceStack:pushDocument(filename, doc)
-  local ret = SILE.processString(doc, format, filename)
+  local ret = SILE.processString(doc, format, filename, options)
   SILE.traceStack:pop(pId)
   return ret
 end
diff --git a/core/utilities.lua b/core/utilities.lua
index 2e233a650..295a50b17 100644
--- a/core/utilities.lua
+++ b/core/utilities.lua
@@ -27,8 +27,12 @@ utilities.boolean = function (value, default)
   return default
 end
 
+local _skip_traceback_levels = 2
+
 utilities.error = function(message, bug)
+  _skip_traceback_levels = 3
   utilities.warn(message, bug)
+  _skip_traceback_levels = 2
   io.stderr:flush()
   SILE.outputter:finish() -- Only really useful from the REPL but no harm in trying
   error(nil, 2)
@@ -38,7 +42,7 @@ utilities.warn = function(message, bug)
   io.stderr:write("\n! " .. message)
   if SILE.traceback or bug then
     io.stderr:write(" at:\n" .. SILE.traceStack:locationTrace())
-    io.stderr:write(debug.traceback(nil, 2) or "\t! debug.traceback() did not identify code location")
+    io.stderr:write(debug.traceback(nil, _skip_traceback_levels) or "\t! debug.traceback() did not identify code location")
   else
     io.stderr:write(" at " .. SILE.traceStack:locationHead())
   end
diff --git a/documentation/c02-gettingstarted.sil b/documentation/c02-gettingstarted.sil
index 9aef2217f..e2cff065e 100644
--- a/documentation/c02-gettingstarted.sil
+++ b/documentation/c02-gettingstarted.sil
@@ -282,47 +282,46 @@ You may download these executables by selecting the latest build from \url{https
 
 \section{Running SILE}
 
-Let’s move to a new directory, and in a text editor, create the file
-\code{hello.sil}. Copy in the content above and save the file. Now at your
-command line run:
+Let’s move to a new directory, and in a text editor, create the file \code{hello.sil}.
+Copy in the content above and save the file.
+Now at your command line run:
 
 \terminal{$ sile hello.sil}
 
 Once again, this should produce an output file \code{hello.pdf}.
 Congratulations—you have just typeset your first document with SILE!
 
-\note{SILE output filenames are generated by filing off the extension from
-the first input filename and adding \code{.pdf}. If you want to write to
-a different filename altogether, use the \code{-o file.pdf} command line
-option to SILE. You can use \code{-o -} to write the PDF to standard output,
-if you wish to use SILE as part of a pipeline.}
+All the available CLI options are documented both in the help output (run \code{sile --help}) and in the man page (run \code{man sile}).
+This manual will only mention a few in passing as they come up in other other topics.
+
+\begin{note}
+SILE output filenames are generated by filing off the extension from the master input filename and adding the proper extension for the outputter.
+For most outputters this will be \code{.pdf} but for example the text backend will append \code{txt} instead.
+If you want to write to a different filename altogether, use the \code{--output file.pdf} command line option.
+You can use \code{--output -} to write the output directly to the system IO stream—useful if you wish to use SILE as part of a pipeline.
+\end{note}
 
 \section{Let’s Do Something Cool}
 
-In \url{https://sile-typesetter.org/examples/docbook.xml}, you will find a typical DocBook 5.0
-article. Normally turning DocBook to print involves a curious dance of XSLT
-processors, format object processors and/or strange LaTeX packages. But SILE
-can read XML files and it also comes with a \code{docbook} class, which tells
-SILE how to render (admittedly, a subset of) the DocBook tags onto a page.
+In \url{https://sile-typesetter.org/examples/docbook.xml}, you will find a typical DocBook 5.0 article.
+Normally turning DocBook to print involves a curious dance of XSLT processors, format object processors and/or strange LaTeX packages.
+But SILE can read XML files and it also comes with a \code{docbook} class, which tells SILE how to render (admittedly, a subset of) the DocBook tags onto a page.
 
 Turning \code{dockbook.xml} into \code{docbook.pdf} is now as simple as:
 
 \begin{verbatim}
 \line
-$ ./sile -I docbook.sil docbook.xml
+$ ./sile --class docbook docbook.xml
 \sileversion
 Loading docbook
 <classes/docbook.sil><docbook.xml>[1] [2] [3]
 \line
 \end{verbatim}
 
-The \code{-I} flag adds a \em{preamble}, which is to say that it loads up
-a \em{class} before reading the input file; the \code{docbook} preamble
-provides processing expectations for DocBook tags, which means that once this
-is loaded, DocBook files can be processed directly as SILE documents.
+The \code{-c} flag sets the default class; a necessary step because docbook XML files do not come wrapped in a tag that specifies a SILE class.
+The docbook class will provide the commands necessary to process the tags typically found in docbook files.
 
-In Chapter 9, we’ll look at how the \code{docbook} class works, and how you
-can define processing expectations for other XML formats.
+In Chapter 9, we’ll look at how the \code{docbook} class works, and how you can define processing expectations for other XML formats.
 
 \section{Running SILE remotely as a CI job}
 
diff --git a/documentation/c05-packages.sil b/documentation/c05-packages.sil
index 3f1560c68..074f0713e 100644
--- a/documentation/c05-packages.sil
+++ b/documentation/c05-packages.sil
@@ -13,11 +13,11 @@ which runs Lua code. By convention packages live in the \code{packages/} subdire
 of either your input file’s location, your current working directory or SILE’s
 installation directory. For instance, we’ll soon be talking about the
 \code{grid} package, which normally can be found as
-\code{/usr/local/lib/sile/packages/grid.lua}. To load this, we’d say:
+\code{/usr/local/lib/sile/packages/grid/init.lua}. To load this, we’d say:
 
 \begin{verbatim}
 \line
-\\script[src=packages/grid]
+\\use[module=packages.grid]
 \line
 \end{verbatim}
 
@@ -32,85 +32,85 @@ or package files into subdirectories, you will need to provide a full relative
 path to them.}
 
 \section{image}
-\package-documentation[src=packages/image]
+\package-documentation{image}
 
 \section{folio}
-\package-documentation[src=packages/folio]
+\package-documentation{folio}
 
 \section{rules}
-\package-documentation[src=packages/rules]
+\package-documentation{rules}
 
 \section{color}
-\package-documentation[src=packages/color]
+\package-documentation{color}
 
 \section{background}
-\package-documentation[src=packages/background]
+\package-documentation{background}
 
 \section{rotate}
-\package-documentation[src=packages/rotate]
+\package-documentation{rotate}
 
 \section{features}
-\package-documentation[src=packages/features]
+\package-documentation{features}
 
 \section{unichar}
-\package-documentation[src=packages/unichar]
+\package-documentation{unichar}
 
 \section{bidi}
-\package-documentation[src=packages/bidi]
+\package-documentation{bidi}
 
 \section{pullquote}
-\package-documentation[src=packages/pullquote]
+\package-documentation{pullquote}
 
 \section{raiselower}
-\package-documentation[src=packages/raiselower]
+\package-documentation{raiselower}
 
 \section{grid}
-\package-documentation[src=packages/grid]
+\package-documentation{grid}
 
 \section{linespacing}
-\package-documentation[src=packages/linespacing]
+\package-documentation{linespacing}
 
 \section{verbatim}
-\package-documentation[src=packages/verbatim]
+\package-documentation{verbatim}
 
 \section{font-fallback}
-\package-documentation[src=packages/font-fallback]
+\package-documentation{font-fallback}
 
 \section{boustrophedon}
-\package-documentation[src=packages/boustrophedon]
+\package-documentation{boustrophedon}
 
 \section{chordmode}
-\package-documentation[src=packages/chordmode]
+\package-documentation{chordmode}
 
 \section{converters}
-\package-documentation[src=packages/converters]
+\package-documentation{converters}
 
 \section{cropmarks}
-\package-documentation[src=packages/cropmarks]
+\package-documentation{cropmarks}
 
 \section{date}
-\package-documentation[src=packages/date]
+\package-documentation{date}
 
 \section{debug}
-\package-documentation[src=packages/debug]
+\package-documentation{debug}
 
 \section{dropcaps}
-\package-documentation[src=packages/dropcaps]
+\package-documentation{dropcaps}
 
 \section{leaders}
-\package-documentation[src=packages/leaders]
+\package-documentation{leaders}
 
 \section{lorem}
-\package-documentation[src=packages/lorem]
+\package-documentation{lorem}
 
 \section{specimen}
-\package-documentation[src=packages/specimen]
+\package-documentation{specimen}
 
 \section{textcase}
-\package-documentation[src=packages/textcase]
+\package-documentation{textcase}
 
 \section{url}
-\package-documentation[src=packages/url]
+\package-documentation{url}
 
 \section{Packages usually used by other packages}
 
@@ -120,84 +120,84 @@ basic functionality to other packages and classes. Classes such as the
 \code{book} class compose functionality from different auxiliary packages.
 
 \subsection{footnotes}
-\package-documentation[src=packages/footnotes]
+\package-documentation{footnotes}
 
 \subsection{color-fonts}
-\package-documentation[src=packages/color-fonts]
+\package-documentation{color-fonts}
 
 \subsection{counters}
-\package-documentation[src=packages/counters]
+\package-documentation{counters}
 
 \subsection{pdf}
-\package-documentation[src=packages/pdf]
+\package-documentation{pdf}
 
 \subsection{ifattop}
-\package-documentation[src=packages/ifattop]
+\package-documentation{ifattop}
 
 \subsection{frametricks}
-\package-documentation[src=packages/frametricks]
+\package-documentation{frametricks}
 
 \subsection{insertions}
-\package-documentation[src=packages/insertions]
+\package-documentation{insertions}
 
 \subsection{twoside}
-\package-documentation[src=packages/twoside]
+\package-documentation{twoside}
 
 \subsection{masters}
-\package-documentation[src=packages/masters]
+\package-documentation{masters}
 
 \subsection{infonode}
-\package-documentation[src=packages/infonode]
+\package-documentation{infonode}
 
 \subsection{inputfilter}
-\package-documentation[src=packages/inputfilter]
+\package-documentation{inputfilter}
 
 \subsection{break-firstfit}
-\package-documentation[src=packages/break-firstfit]
+\package-documentation{break-firstfit}
 
 \subsection{chapterverse}
-\package-documentation[src=packages/chapterverse]
+\package-documentation{chapterverse}
 
 \subsection{parallel}
-\package-documentation[src=packages/parallel]
+\package-documentation{parallel}
 
 \subsection{rebox}
-\package-documentation[src=packages/rebox]
+\package-documentation{rebox}
 
 \subsection{tableofcontents}
-\package-documentation[src=packages/tableofcontents]
+\package-documentation{tableofcontents}
 
 \subsection{xmltricks}
-\package-documentation[src=packages/xmltricks]
+\package-documentation{xmltricks}
 
 \section{Experimental packages}
 
 \subsection{autodoc}
-\package-documentation[src=packages/autodoc]
+\package-documentation{autodoc}
 
 \subsection{balanced-frames}
-\package-documentation[src=packages/balanced-frames]
+\package-documentation{balanced-frames}
 
 \subsection{bibtex}
-\package-documentation[src=packages/bibtex]
+\package-documentation{bibtex}
 
 \subsection{gutenberg}
-\package-documentation[src=packages/gutenberg]
+\package-documentation{gutenberg}
 
 \subsection{indexer}
-\package-documentation[src=packages/indexer]
+\package-documentation{indexer}
 
 \subsection{lists}
-\package-documentation[src=packages/lists]
+\package-documentation{lists}
 
 \subsection{math}
-\package-documentation[src=packages/math]
+\package-documentation{math}
 
 \subsection{pdfstructure}
-\package-documentation[src=packages/pdfstructure]
+\package-documentation{pdfstructure}
 
 \subsection{svg}
-\package-documentation[src=packages/svg]
+\package-documentation{svg}
 
 \section{The Package Manager}
 
diff --git a/documentation/c08-language.sil b/documentation/c08-language.sil
index 8c06b2a92..5f6df9ce7 100644
--- a/documentation/c08-language.sil
+++ b/documentation/c08-language.sil
@@ -219,11 +219,11 @@ The easiest way to set up the other elements of Japanese typesetting such as the
 For other languages with similar layout requriements more generic \code{tplain} and \code{tbook} classes are available that setup the layout elements without also setting the default language and font to Japanese specific values.
 These are also good condidates to use as bases classes and extend for more language specific classes.
 
-\package-documentation[src=packages/hanmenkyoshi]
+\package-documentation{hanmenkyoshi}
 
-\package-documentation[src=packages/tate]
+\package-documentation{tate}
 
-\package-documentation[src=packages/ruby]
+\package-documentation{ruby}
 
 \subsection{Syllabic languages}
 
diff --git a/documentation/developers.sil b/documentation/developers.sil
index e31f64770..28055f889 100644
--- a/documentation/developers.sil
+++ b/documentation/developers.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a4,class=book]{document}
-\script[src=packages/pdf]
-\script[src=packages/image]
+\use[module=packages.pdf]
+\use[module=packages.image]
 \include[src=documentation/macros.xml]
 \define[command=silehp]{\url{http://www.sile-typesetter.org/}}
 \font[size=11pt,family=Gentium Book Basic]
diff --git a/documentation/macros.sil b/documentation/macros.sil
index 1b80a1ccf..4a74dd78a 100644
--- a/documentation/macros.sil
+++ b/documentation/macros.sil
@@ -1,20 +1,20 @@
 \begin{document}
-\script[src=packages/grid]
-\script[src=packages/url]
-\script[src=packages/autodoc]
-\script[src=packages/verbatim]
-\script[src=packages/color]
-\script[src=packages/image]
-\script[src=packages/frametricks]
-\script[src=packages/linespacing]
-\script[src=packages/lists]
-\script[src=packages/lorem]
-\script[src=packages/bidi]\bidi-off
-\script[src=packages/pdf]
-\script[src=packages/pullquote]
-\script[src=packages/rules]
-\script[src=packages/font-fallback]
-\script[src=packages/simpletable]
+\use[module=packages.grid]
+\use[module=packages.url]
+\use[module=packages.autodoc]
+\use[module=packages.verbatim]
+\use[module=packages.color]
+\use[module=packages.image]
+\use[module=packages.frametricks]
+\use[module=packages.linespacing]
+\use[module=packages.lists]
+\use[module=packages.lorem]
+\use[module=packages.bidi]\bidi-off
+\use[module=packages.pdf]
+\use[module=packages.pullquote]
+\use[module=packages.rules]
+\use[module=packages.font-fallback]
+\use[module=packages.simpletable]
 \define[command=notehead]{\noindent\bf{\process}\par\medskip}
 \define[command=line]{\novbreak\skip[height=5pt]\noindent\hrule[width=450pt,height=0.3pt]\par\novbreak\skip[height=5pt]}
 \define[command=note]{\medskip
diff --git a/documentation/sile.sil b/documentation/sile.sil
index 1dbe552e2..e80974378 100644
--- a/documentation/sile.sil
+++ b/documentation/sile.sil
@@ -1,7 +1,7 @@
 \begin[class=book]{document}
 \include[src=documentation/macros.sil]
 \define[command=silehp]{\url{http://www.sile-typesetter.org/}}
-\define[command=sileversion]{\script{SILE.typesetter:typeset(SILE.full_version)}}
+\define[command=sileversion]{\lua{SILE.typesetter:typeset(SILE.full_version)}}
 \set[parameter=document.baselineskip,value=3ex]
 \font[size=11pt,family=Gentium Book Basic]
 \nofolios
@@ -15,7 +15,7 @@ The\break
 \img[src=documentation/sile-logo.pdf,height=125pt]\break
 Book
 
-\font[size=12pt]{for SILE version \script{SILE.typesetter:typeset(SILE.version)}}
+\font[size=12pt]{for SILE version \lua{SILE.typesetter:typeset(SILE.version)}}
 \vfill
 \vfill
 \end{set}
diff --git a/inputters/base.lua b/inputters/base.lua
index fac09653a..1f5b5293b 100644
--- a/inputters/base.lua
+++ b/inputters/base.lua
@@ -1,23 +1,25 @@
 local _deprecated = [[
   You appear to be using a document class '%s' programmed for SILE <= v0.12.5.
   This system was refactored in v0.13.0 and the shims trying to make it
-  work temporarily withouth refactoring your classes have been removed
+  work temporarily without refactoring your classes have been removed
   in v0.14.0. Please see v0.13.0 release notes for help.
 ]]
 
-local base = pl.class()
-base.type = "inputter"
-base._name = "base"
+local inputter = pl.class()
+inputter.type = "inputter"
+inputter._name = "base"
 
-base._docclass = nil
+inputter._docclass = nil
 
-function base._init (_) end
+function inputter:_init (options)
+  if options then self.options = options end
+end
 
-function base:classInit (options)
+function inputter:classInit (options)
   options = pl.tablex.merge(options, SILE.input.options, true)
   local constructor, class
-  if SILE.scratch.required_class then
-    constructor = SILE.scratch.required_class
+  if SILE.scratch.class_from_uses then
+    constructor = SILE.scratch.class_from_uses
     class = constructor._name
   end
   class = SILE.input.class or class or options.class or "plain"
@@ -28,7 +30,7 @@ function base:classInit (options)
   SILE.documentState.documentClass = constructor(options)
 end
 
-function base:requireClass (tree)
+function inputter:requireClass (tree)
   local root = SILE.documentState.documentClass == nil
   if root then
     if #tree ~= 1
@@ -40,7 +42,7 @@ function base:requireClass (tree)
   end
 end
 
-function base.packageInit (_, pack)
+function inputter.packageInit (_, pack)
   local class = SILE.documentState.documentClass
   if not class then
     SU.error("Cannot load a package before instantiating a document class")
@@ -49,14 +51,14 @@ function base.packageInit (_, pack)
   end
 end
 
-function base:process (doc)
+function inputter:process (doc)
   local tree = self:parse(doc)
   self:requireClass(tree)
   return SILE.process(tree)
 end
 
 -- Just a simple one-level find. We're not reimplementing XPath here.
-function base.findInTree (_, tree, command)
+function inputter.findInTree (_, tree, command)
   for i=1, #tree do
     if type(tree[i]) == "table" and tree[i].command == command then
       return tree[i]
@@ -64,16 +66,26 @@ function base.findInTree (_, tree, command)
   end
 end
 
-function base.preamble (_)
-  for _, path in ipairs(SILE.input.preambles) do
-    SILE.processFile(path)
+function inputter.preamble (_)
+  for _, preamble in ipairs(SILE.input.preambles) do
+    if type(preamble) == "string" then
+      SILE.processFile(preamble)
+    elseif type(preamble) == "table" then
+      local options = {}
+      if preamble.pack then preamble, options = preamble.pack, preamble.options end
+      if preamble.type == "package" then
+        preamble(options)
+      else
+        SILE.documentState.documentClass:initPackage(preamble, options)
+      end
+    end
   end
 end
 
-function base.postamble (_)
+function inputter.postamble (_)
   for _, path in ipairs(SILE.input.postambles) do
     SILE.processFile(path)
   end
 end
 
-return base
+return inputter
diff --git a/inputters/lua.lua b/inputters/lua.lua
index da6ab4e7b..f5c04480a 100644
--- a/inputters/lua.lua
+++ b/inputters/lua.lua
@@ -1,38 +1,33 @@
 local base = require("inputters.base")
 
-local lua = pl.class(base)
-lua._name = "lua"
+local inputter = pl.class(base)
+inputter._name = "lua"
 
-lua.order = 99
+inputter.order = 99
 
-function lua.appropriate (round, filename, doc)
+function inputter.appropriate (round, filename, doc)
   if round == 1 then
     return filename:match(".lua$")
   elseif round == 2 then
     local sniff = doc:sub(1, 100)
     local promising = sniff:match("^%-%-") or sniff:match("^local") or sniff:match("^return")
-    return promising and lua.appropriate(3, filename, doc)
+    return promising and inputter.appropriate(3, filename, doc)
   elseif round == 3 then
     local status, _ = pcall(load, doc)
     return status
   end
 end
 
-function lua.parse (_, doc)
+function inputter.parse (_, doc)
   local result, err = load(doc)
   if not result then SU.error(err) end
   return result
 end
 
-local load_sile_module = function (module)
-  local name = module._name
-  SILE[name .. "s"][name] = module
-end
-
-function lua:process (doc)
+function inputter:process (doc)
   local tree = self:parse(doc)()
   if type(tree) == "string" then
-    return SILE.processString(tree)
+    return SILE.processString(tree, nil, nil, self.args)
   elseif type(tree) == "function" then
     SILE.process(tree)
   elseif type(tree) == "table" then
@@ -42,19 +37,9 @@ function lua:process (doc)
       self:requireClass(tree)
       return SILE.process(tree)
     else
-      load_sile_module(tree)
-      if tree.type == "class" then
-        if SILE.documentState.documentClass then
-          SU.error("Cannot load a class after one is already instantiated")
-        end
-        self._docclass = tree
-      elseif tree.type == "package" then
-        return self:packageInit(tree)
-      end
-      -- other module types like inputters, outputters, shappers, etc. don't
-      -- need instantiation on load, only when they are used
+      SILE.use(tree, self.args)
     end
   end
 end
 
-return lua
+return inputter
diff --git a/inputters/sil.lua b/inputters/sil.lua
index 702c33c44..528111da3 100644
--- a/inputters/sil.lua
+++ b/inputters/sil.lua
@@ -2,20 +2,20 @@ local base = require("inputters.base")
 
 local epnf = require("epnf")
 
-local sil = pl.class(base)
-sil._name = "sil"
+local inputter = pl.class(base)
+inputter._name = "sil"
 
-sil.order = 50
+inputter.order = 50
 
-sil.appropriate = function (round, filename, doc)
+inputter.appropriate = function (round, filename, doc)
   if round == 1 then
     return filename:match(".sil$")
   elseif round == 2 then
     local sniff = doc:sub(1, 100)
     local promising = sniff:match("\\begin") or sniff:match("\\document") or sniff:match("\\sile")
-    return promising and sil.appropriate(3, filename, doc)
+    return promising and inputter.appropriate(3, filename, doc)
   elseif round == 3 then
-    local _parser = epnf.define(sil._grammar)
+    local _parser = epnf.define(inputter._grammar)
     local status, tree = pcall(epnf.parsestring, _parser, doc)
     local cmd = tree[1][1].command
     return status and (cmd == "document" or cmd == "sile")
@@ -25,17 +25,18 @@ end
 local bits = SILE.parserBits
 
 
-sil.passthroughCommands = {
+inputter.passthroughCommands = {
   ftl = true,
   lua = true,
   math = true,
   raw = true,
   script = true,
   sil = true,
+  use = true,
   xml = true
 }
 
-function sil:_init ()
+function inputter:_init ()
   -- Save time when parsing strings by only setting up the grammar once per
   -- instantiation then re-using it on every use.
   self._parser = self:rebuildParser()
@@ -43,9 +44,9 @@ function sil:_init ()
 end
 
 -- luacheck: push ignore
-function sil._grammar (_ENV)
+function inputter._grammar (_ENV)
   local isPassthrough = function (_, _, command)
-    return sil.passthroughCommands[command] or false
+    return inputter.passthroughCommands[command] or false
   end
   local isNotPassthrough = function (...)
     return not isPassthrough(...)
@@ -186,11 +187,11 @@ local function massage_ast (tree, doc)
   return tree
 end
 
-function sil:rebuildParser ()
+function inputter:rebuildParser ()
   return epnf.define(self._grammar)
 end
 
-function sil:parse (doc)
+function inputter:parse (doc)
   local tree = epnf.parsestring(self._parser, doc)[1]
   if not tree then
     return SU.error("Unable to parse input document to an AST tree")
@@ -200,4 +201,4 @@ function sil:parse (doc)
   return ast
 end
 
-return sil
+return inputter
diff --git a/inputters/xml.lua b/inputters/xml.lua
index 0fec7639f..a2bf5d950 100644
--- a/inputters/xml.lua
+++ b/inputters/xml.lua
@@ -1,10 +1,10 @@
 local base = require("inputters.base")
 local lxp = require("lxp")
 
-local xml = pl.class(base)
-xml._name = "xml"
+local inputter = pl.class(base)
+inputter._name = "xml"
 
-xml.order = 2
+inputter.order = 2
 
 local function startcommand (parser, command, options)
   local stack = parser:getcallbacks().stack
@@ -56,20 +56,20 @@ local function parse (doc)
   return content.stack[1][1]
 end
 
-function xml.appropriate (round, filename, doc)
+function inputter.appropriate (round, filename, doc)
   if round == 1 then
     return filename:match(".xml$")
   elseif round == 2 then
     local sniff = doc:sub(1, 100):gsub("begin.*", "") or ""
     local promising = sniff:match("<")
-    return promising and xml.appropriate(3, filename, doc)
+    return promising and inputter.appropriate(3, filename, doc)
   elseif round == 3 then
     local _, err = parse(doc)
     return not err
   end
 end
 
-function xml.parse (_, doc)
+function inputter.parse (_, doc)
   local tree, err = parse(doc)
   if not tree then
     SU.error(err)
@@ -85,4 +85,4 @@ function xml.parse (_, doc)
   return { tree }
 end
 
-return xml
+return inputter
diff --git a/outputters/base.lua b/outputters/base.lua
index 127d7c99b..00f94ff48 100644
--- a/outputters/base.lua
+++ b/outputters/base.lua
@@ -1,43 +1,43 @@
-local base = pl.class()
-base.type = "outputter"
-base._name = "base"
+local outputter = pl.class()
+outputter.type = "outputter"
+outputter._name = "base"
 
-function base._init () end
+function outputter._init () end
 
-function base.newPage () end
+function outputter.newPage () end
 
-function base.finish () end
+function outputter.finish () end
 
-function base.getCursor () end
+function outputter.getCursor () end
 
-function base.setCursor (_, _, _, _) end
+function outputter.setCursor (_, _, _, _) end
 
-function base.setColor () end
+function outputter.setColor () end
 
-function base.pushColor () end
+function outputter.pushColor () end
 
-function base.popColor () end
+function outputter.popColor () end
 
-function base.drawHbox (_, _, _) end
+function outputter.drawHbox (_, _, _) end
 
-function base.setFont (_, _) end
+function outputter.setFont (_, _) end
 
-function base.drawImage (_, _, _, _, _, _) end
+function outputter.drawImage (_, _, _, _, _, _) end
 
-function base.getImageSize (_, _) end
+function outputter.getImageSize (_, _) end
 
-function base.drawSVG () end
+function outputter.drawSVG () end
 
-function base.drawRule (_, _, _, _, _) end
+function outputter.drawRule (_, _, _, _, _) end
 
-function base.debugFrame (_, _, _) end
+function outputter.debugFrame (_, _, _) end
 
-function base.debugHbox (_, _, _, _) end
+function outputter.debugHbox (_, _, _, _) end
 
-function base.getOutputFilename (_, ext)
+function outputter.getOutputFilename (_, ext)
   if SILE.outputFilename then return SILE.outputFilename end
   if SILE.masterFilename then return SILE.masterFilename .. "." .. ext end
   SU.error("Cannot guess output filename without an input name")
 end
 
-return base
+return outputter
diff --git a/outputters/cairo.lua b/outputters/cairo.lua
index b0056a438..80f5c73f4 100644
--- a/outputters/cairo.lua
+++ b/outputters/cairo.lua
@@ -17,10 +17,10 @@ local cr
 local move -- See https://github.com/pavouk/lgi/issues/48
 local sgs
 
-local cairo = pl.class(base)
-cairo._name = "cairo"
+local outputter = pl.class(base)
+outputter._name = "cairo"
 
-function cairo:_init ()
+function outputter:_init ()
   local fname = self:getOutputFilename("pdf")
   local surface = cairolgi.PdfSurface.create(fname == "-" and "/dev/stdout" or fname, SILE.documentState.paperSize[1], SILE.documentState.paperSize[2])
   cr = cairolgi.Context.create(surface)
@@ -28,26 +28,26 @@ function cairo:_init ()
   sgs = cr.show_glyph_string
 end
 
-function cairo.newPage (_)
+function outputter.newPage (_)
   cr:show_page()
 end
 
-function cairo.getCursor (_)
+function outputter.getCursor (_)
   return cursorX, cursorY
 end
 
-function cairo.setCursor (_, x, y, relative)
+function outputter.setCursor (_, x, y, relative)
   local offset = relative and { x = cursorX, y = cursorY } or { x = 0, y = 0 }
   cursorX = offset.x + x
   cursorY = offset.y - y
   move(cr, cursorX, cursorY)
 end
 
-function cairo.setColor (_, color)
+function outputter.setColor (_, color)
   cr:set_source_rgb(color.r, color.g, color.b)
 end
 
-function cairo.drawHbox (_, value, _)
+function outputter.drawHbox (_, value, _)
   if not value then return end
   if value.pgs then
     sgs(cr, value.font, value.pgs)
@@ -56,12 +56,12 @@ function cairo.drawHbox (_, value, _)
   end
 end
 
-function cairo.setFont (_, options)
+function outputter.setFont (_, options)
   cr:select_font_face(options.font, options.style:lower() == "italic" and 1 or 0, options.weight > 100 and 0 or 1)
   cr:set_font_size(options.size)
 end
 
-function cairo.drawImage (_, src, x, y, width, height)
+function outputter.drawImage (_, src, x, y, width, height)
   local image = cairolgi.ImageSurface.create_from_png(src)
   if not image then SU.error("Could not load image "..src) end
   local src_width = image:get_width()
@@ -84,7 +84,7 @@ function cairo.drawImage (_, src, x, y, width, height)
   cr:restore()
 end
 
-function cairo.getImageSize (_, src)
+function outputter.getImageSize (_, src)
   local box_width, box_height, err = imagesize.imgsize(src)
   if not box_width then
     SU.error(err.." loading image")
@@ -92,12 +92,12 @@ function cairo.getImageSize (_, src)
   return box_width, box_height
 end
 
-function cairo.drawRule (_, x, y, width, depth)
+function outputter.drawRule (_, x, y, width, depth)
   cr:rectangle(x, y, width, depth)
   cr:fill()
 end
 
-function cairo.debugFrame (_, frame)
+function outputter.debugFrame (_, frame)
   cr:set_source_rgb(0.8, 0, 0)
   cr:set_line_width(0.5)
   cr:rectangle(frame:left(), frame:top(), frame:width(), frame:height())
@@ -107,7 +107,7 @@ function cairo.debugFrame (_, frame)
   cr:set_source_rgb(0, 0, 0)
 end
 
-function cairo.debugHbox (_, typesetter, hbox, scaledWidth)
+function outputter.debugHbox (_, typesetter, hbox, scaledWidth)
   cr:set_source_rgb(0.9, 0.9, 0.9)
   cr:set_line_width(0.5)
   cr:rectangle(typesetter.frame.state.cursorX, typesetter.frame.state.cursorY-(hbox.height), scaledWidth, hbox.height+hbox.depth)
@@ -117,4 +117,4 @@ function cairo.debugHbox (_, typesetter, hbox, scaledWidth)
   cr:move_to(typesetter.frame.state.cursorX, typesetter.frame.state.cursorY)
 end
 
-return cairo
+return outputter
diff --git a/outputters/debug.lua b/outputters/debug.lua
index da845af2e..1947b2fd6 100644
--- a/outputters/debug.lua
+++ b/outputters/debug.lua
@@ -22,14 +22,14 @@ local function _round (input)
   return string.format("%.4f", input)
 end
 
-local debug = pl.class(base)
-debug._name = "debug"
+local outputter = pl.class(base)
+outputter._name = "debug"
 
 -- The outputter init can't actually initialize output (as logical as it might
 -- have seemed) because that requires a page size which we don't know yet.
--- function debug:_init () end
+-- function outputter:_init () end
 
-function debug:_ensureInit ()
+function outputter:_ensureInit ()
   if not started then
     started = true -- keep this before self:_writeline or it will be a race condition!
     local fname = self:getOutputFilename("debug")
@@ -39,7 +39,7 @@ function debug:_ensureInit ()
   end
 end
 
-function debug:_writeline (...)
+function outputter:_writeline (...)
   self:_ensureInit()
 	local args = table.pack(...)
 	for i = 1, #args do
@@ -49,22 +49,22 @@ function debug:_writeline (...)
 	outfile:write("\n")
 end
 
-function debug:newPage ()
+function outputter:newPage ()
   self:_writeline("New page")
 end
 
-function debug:finish ()
+function outputter:finish ()
   if SILE.status.unsupported then self:_writeline("UNSUPPORTED") end
   self:_writeline("End page")
   self:_writeline("Finish")
   outfile:close()
 end
 
-function debug.getCursor (_)
+function outputter.getCursor (_)
   return cursorX, cursorY
 end
 
-function debug:setCursor (x, y, relative)
+function outputter:setCursor (x, y, relative)
   x = SU.cast("number", x)
   y = SU.cast("number", y)
   local oldx, oldy = self:getCursor()
@@ -75,7 +75,7 @@ function debug:setCursor (x, y, relative)
   if _round(oldy) ~= _round(cursorY) then self:_writeline("My ", _round(y)) end
 end
 
-function debug:setColor (color)
+function outputter:setColor (color)
   if color.r then
     self:_writeline("Set color", _round(color.r), _round(color.g), _round(color.b))
   elseif color.c then
@@ -85,7 +85,7 @@ function debug:setColor (color)
   end
 end
 
-function debug:pushColor (color)
+function outputter:pushColor (color)
   if color.r then
     self:_writeline("Push color", _round(color.r), _round(color.g), _round(color.b))
   elseif color.c then
@@ -95,11 +95,11 @@ function debug:pushColor (color)
   end
 end
 
-function debug:popColor ()
+function outputter:popColor ()
   self:_writeline("Pop color")
 end
 
-function debug:drawHbox (value, width)
+function outputter:drawHbox (value, width)
   if not value.glyphString then return end
   width = SU.cast("number", width)
   local buf
@@ -121,7 +121,7 @@ function debug:drawHbox (value, width)
   self:_writeline("T", buf, "(" .. tostring(value.text) .. ")")
 end
 
-function debug:setFont (options)
+function outputter:setFont (options)
   local font = SILE.font._key(options)
   if lastFont ~= font then
     self:_writeline("Set font ", font)
@@ -129,7 +129,7 @@ function debug:setFont (options)
   end
 end
 
-function debug:drawImage (src, x, y, width, height)
+function outputter:drawImage (src, x, y, width, height)
   x = SU.cast("number", x)
   y = SU.cast("number", y)
   width = SU.cast("number", width)
@@ -137,13 +137,13 @@ function debug:drawImage (src, x, y, width, height)
   self:_writeline("Draw image", src, _round(x), _round(y), _round(width), _round(height))
 end
 
-function debug.getImageSize (_, src)
+function outputter.getImageSize (_, src)
   local pdf = require("justenoughlibtexpdf")
   local llx, lly, urx, ury = pdf.imagebbox(src)
   return (urx-llx), (ury-lly)
 end
 
-function debug:drawSVG (figure, _, x, y, width, height, scalefactor)
+function outputter:drawSVG (figure, _, x, y, width, height, scalefactor)
   x = SU.cast("number", x)
   y = SU.cast("number", y)
   width = SU.cast("number", width)
@@ -151,7 +151,7 @@ function debug:drawSVG (figure, _, x, y, width, height, scalefactor)
   self:_writeline("Draw SVG", _round(x), _round(y), _round(width), _round(height), figure, scalefactor)
 end
 
-function debug:drawRule (x, y, width, depth)
+function outputter:drawRule (x, y, width, depth)
   x = SU.cast("number", x)
   y = SU.cast("number", y)
   width = SU.cast("number", width)
@@ -159,4 +159,4 @@ function debug:drawRule (x, y, width, depth)
   self:_writeline("Draw line", _round(x), _round(y), _round(width), _round(depth))
 end
 
-return debug
+return outputter
diff --git a/outputters/dummy.lua b/outputters/dummy.lua
index 74a5ef5ea..d039b969d 100644
--- a/outputters/dummy.lua
+++ b/outputters/dummy.lua
@@ -1,13 +1,13 @@
 local base = require("outputters.base")
 
-local dummy = pl.class(base)
-dummy._name = "dummy"
+local outputter = pl.class(base)
+outputter._name = "dummy"
 
 -- Most of the base outputter functions are just empty prototypes, but for the
 -- few that actually do something override them...
 
 local _dummy = function () end
 
-dummy.getOutputFilename = _dummy
+outputter.getOutputFilename = _dummy
 
-return dummy
+return outputter
diff --git a/outputters/libtexpdf.lua b/outputters/libtexpdf.lua
index ccc32b8eb..cfb685606 100644
--- a/outputters/libtexpdf.lua
+++ b/outputters/libtexpdf.lua
@@ -18,14 +18,14 @@ local _dl = 0.5
 local _debugfont
 local _font
 
-local libtexpdf = pl.class(base)
-libtexpdf._name = "libtexpdf"
+local outputter = pl.class(base)
+outputter._name = "libtexpdf"
 
 -- The outputter init can't actually initialize output (as logical as it might
 -- have seemed) because that requires a page size which we don't know yet.
--- function libtexpdf:_init () end
+-- function outputter:_init () end
 
-function libtexpdf:_ensureInit ()
+function outputter:_ensureInit ()
   if not started then
     local w, h = SILE.documentState.paperSize[1], SILE.documentState.paperSize[2]
     local fname = self:getOutputFilename("pdf")
@@ -35,17 +35,17 @@ function libtexpdf:_ensureInit ()
   end
 end
 
-function libtexpdf:newPage ()
+function outputter:newPage ()
   self:_ensureInit()
   pdf.endpage()
   pdf.beginpage()
 end
 
 -- pdf stucture package needs a tie in here
-function libtexpdf._endHook (_)
+function outputter._endHook (_)
 end
 
-function libtexpdf:finish ()
+function outputter:finish ()
   self:_ensureInit()
   pdf.endpage()
   self:_endHook()
@@ -54,11 +54,11 @@ function libtexpdf:finish ()
   lastkey = nil
 end
 
-function libtexpdf.getCursor (_)
+function outputter.getCursor (_)
   return cursorX, cursorY
 end
 
-function libtexpdf.setCursor (_, x, y, relative)
+function outputter.setCursor (_, x, y, relative)
   x = SU.cast("number", x)
   y = SU.cast("number", y)
   local offset = relative and { x = cursorX, y = cursorY } or { x = 0, y = 0 }
@@ -66,33 +66,33 @@ function libtexpdf.setCursor (_, x, y, relative)
   cursorY = offset.y + (relative and 0 or SILE.documentState.paperSize[2]) - y
 end
 
-function libtexpdf:setColor (color)
+function outputter:setColor (color)
   self:_ensureInit()
   if color.r then pdf.setcolor_rgb(color.r, color.g, color.b) end
   if color.c then pdf.setcolor_cmyk(color.c, color.m, color.y, color.k) end
   if color.l then pdf.setcolor_gray(color.l) end
 end
 
-function libtexpdf:pushColor (color)
+function outputter:pushColor (color)
   self:_ensureInit()
   if color.r then pdf.colorpush_rgb(color.r, color.g, color.b) end
   if color.c then pdf.colorpush_cmyk(color.c, color.m, color.y, color.k) end
   if color.l then pdf.colorpush_gray(color.l) end
 end
 
-function libtexpdf:popColor ()
+function outputter:popColor ()
   self:_ensureInit()
   pdf.colorpop()
 end
 
-function libtexpdf:_drawString (str, width, x_offset, y_offset)
+function outputter:_drawString (str, width, x_offset, y_offset)
   local x, y = self:getCursor()
   pdf.colorpush_rgb(0,0,0)
   pdf.colorpop()
   pdf.setstring(x+x_offset, y+y_offset, str, string.len(str), _font, width)
 end
 
-function libtexpdf:drawHbox (value, width)
+function outputter:drawHbox (value, width)
   width = SU.cast("number", width)
   self:_ensureInit()
   if not value.glyphString then return end
@@ -121,7 +121,7 @@ function libtexpdf:drawHbox (value, width)
   end
 end
 
-function libtexpdf:_withDebugFont (callback)
+function outputter:_withDebugFont (callback)
   if not _debugfont then
     _debugfont = self:setFont(debugfont)
   end
@@ -131,7 +131,7 @@ function libtexpdf:_withDebugFont (callback)
   _font = oldfont
 end
 
-function libtexpdf:setFont (options)
+function outputter:setFont (options)
   self:_ensureInit()
   local key = SILE.font._key(options)
   if lastkey and key == lastkey then return _font end
@@ -150,7 +150,7 @@ function libtexpdf:setFont (options)
   return _font
 end
 
-function libtexpdf:drawImage (src, x, y, width, height)
+function outputter:drawImage (src, x, y, width, height)
   x = SU.cast("number", x)
   y = SU.cast("number", y)
   width = SU.cast("number", width)
@@ -159,13 +159,13 @@ function libtexpdf:drawImage (src, x, y, width, height)
   pdf.drawimage(src, x, y, width, height)
 end
 
-function libtexpdf:getImageSize (src)
+function outputter:getImageSize (src)
   self:_ensureInit() -- in case it's a PDF file
   local llx, lly, urx, ury = pdf.imagebbox(src)
   return (urx-llx), (ury-lly)
 end
 
-function libtexpdf:drawSVG (figure, x, y, _, height, scalefactor)
+function outputter:drawSVG (figure, x, y, _, height, scalefactor)
   self:_ensureInit()
   x = SU.cast("number", x)
   y = SU.cast("number", y)
@@ -179,7 +179,7 @@ function libtexpdf:drawSVG (figure, x, y, _, height, scalefactor)
   pdf.add_content("Q")
 end
 
-function libtexpdf:drawRule (x, y, width, height)
+function outputter:drawRule (x, y, width, height)
   x = SU.cast("number", x)
   y = SU.cast("number", y)
   width = SU.cast("number", width)
@@ -189,7 +189,7 @@ function libtexpdf:drawRule (x, y, width, height)
   pdf.setrule(x, paperY - y - height, width, height)
 end
 
-function libtexpdf:debugFrame (frame)
+function outputter:debugFrame (frame)
   self:_ensureInit()
   self:pushColor({ r = 0.8, g = 0, b = 0 })
   self:drawRule(frame:left()-_dl/2, frame:top()-_dl/2, frame:width()+_dl, _dl)
@@ -211,7 +211,7 @@ function libtexpdf:debugFrame (frame)
   self:popColor()
 end
 
-function libtexpdf:debugHbox (hbox, scaledWidth)
+function outputter:debugHbox (hbox, scaledWidth)
   self:_ensureInit()
   self:pushColor({ r = 0.8, g = 0.3, b = 0.3 })
   local paperY = SILE.documentState.paperSize[2]
@@ -227,4 +227,4 @@ function libtexpdf:debugHbox (hbox, scaledWidth)
   self:popColor()
 end
 
-return libtexpdf
+return outputter
diff --git a/outputters/podofo.lua b/outputters/podofo.lua
index 213b70542..639d612a5 100644
--- a/outputters/podofo.lua
+++ b/outputters/podofo.lua
@@ -17,10 +17,10 @@ local lastkey
 
 local podofoFaces = {}
 
-local podofo = pl.class(base)
-podofo._name = "podofo"
+local outputer = pl.class(base)
+outputer._name = "podofo"
 
-function podofo._init (_)
+function outputer._init (_)
   document = pdf.PdfMemDocument()
   pagesize = pdf.PdfRect()
   pagesize:SetWidth(SILE.documentState.paperSize[1])
@@ -30,33 +30,33 @@ function podofo._init (_)
   painter:SetPage(page)
 end
 
-function podofo.newPage (_)
+function outputer.newPage (_)
   painter:FinishPage()
   page = document:CreatePage(pagesize)
   painter:SetPage(page)
 end
 
-function podofo:finish ()
+function outputer:finish ()
   painter:FinishPage()
   local fname = self:getOutputFilename("pdf")
   document:Write(fname == "-" and "/dev/stdout" or fname)
 end
 
-function podofo.getCursor (_)
+function outputer.getCursor (_)
   return cursorX, cursorY
 end
 
-function podofo.setCursor (_, x, y, relative)
+function outputer.setCursor (_, x, y, relative)
   local offset = relative and { x = cursorX, y = cursorY } or { x = 0, y = 0 }
   cursorX = offset.x + x
   cursorY = offset.y + SILE.documentState.paperSize[2] - y
 end
 
-function podofo.setColor (_, color)
+function outputer.setColor (_, color)
   painter:SetColor(color.r, color.g, color.b)
 end
 
-function podofo:drawHbox (value, _)
+function outputer:drawHbox (value, _)
   local x, y = self:getCursor()
   if not value.glyphNames then return end
   for i = 1, #(value.glyphNames) do
@@ -64,7 +64,7 @@ function podofo:drawHbox (value, _)
   end
 end
 
-function podofo.setFont (_, options)
+function outputer.setFont (_, options)
   if SILE.font._key(options) == lastkey then return end
   lastkey = SILE.font._key(options)
   if not podofoFaces[lastkey] then
@@ -78,7 +78,7 @@ function podofo.setFont (_, options)
   SILE.fontCache[lastkey] = nil
 end
 
-function podofo.getImageSize (_, src)
+function outputer.getImageSize (_, src)
   local box_width, box_height, err = imagesize.imgsize(src)
   if not box_width then
     SU.error(err.." loading image")
@@ -86,13 +86,13 @@ function podofo.getImageSize (_, src)
   return box_width, box_height
 end
 
-function podofo.drawRule (_, x, y, width, depth)
+function outputer.drawRule (_, x, y, width, depth)
   painter:Rectangle(x, SILE.documentState.paperSize[2] - y, width, depth)
   painter:Close()
   painter:Fill()
 end
 
-function podofo.debugHbox (_, typesetter, hbox, scaledWidth)
+function outputer.debugHbox (_, typesetter, hbox, scaledWidth)
   painter:SetColor(0.9, 0.9, 0.9)
   painter:SetStrokeWidth(0.5)
   painter:Rectangle(typesetter.frame.state.cursorX, typesetter.frame.state.cursorY+(hbox.height), scaledWidth, hbox.height+hbox.depth)
@@ -102,4 +102,4 @@ function podofo.debugHbox (_, typesetter, hbox, scaledWidth)
   --cr:move_to(typesetter.frame.state.cursorX, typesetter.frame.state.cursorY)
 end
 
-return podofo
+return outputer
diff --git a/outputters/text.lua b/outputters/text.lua
index 819079a89..9d4275935 100644
--- a/outputters/text.lua
+++ b/outputters/text.lua
@@ -6,21 +6,21 @@ local cursorY = 0
 local outfile
 local started = false
 
-local text = pl.class(base)
-text._name = "text"
+local outputter = pl.class(base)
+outputter._name = "text"
 
 -- The outputter init can't actually initialize output (as logical as it might
 -- have seemed) because that requires a page size which we don't know yet.
--- function text:_init () end
+-- function outputter:_init () end
 
-function text:_ensureInit ()
+function outputter:_ensureInit ()
   if not outfile then
     local fname = self:getOutputFilename("text")
     outfile = fname == "-" and io.stdout or io.open(fname, "w+")
   end
 end
 
-function text:_writeline (...)
+function outputter:_writeline (...)
   self:_ensureInit()
   local args = table.pack(...)
   for i=1, #args do
@@ -28,21 +28,21 @@ function text:_writeline (...)
   end
 end
 
-function text:newPage ()
+function outputter:newPage ()
   self:_ensureInit()
   outfile:write("")
 end
 
-function text:finish ()
+function outputter:finish ()
   self:_ensureInit()
   outfile:close()
 end
 
-function text.getCursor (_)
+function outputter.getCursor (_)
   return cursorX, cursorY
 end
 
-function text:setCursor (x, y, relative)
+function outputter:setCursor (x, y, relative)
   self:_ensureInit()
   local bs = SILE.measurement("0.8bs"):tonumber()
   local spc = SILE.measurement("0.8spc"):tonumber()
@@ -69,7 +69,7 @@ function text:setCursor (x, y, relative)
   cursorX = newx
 end
 
-function text:drawHbox (value, width)
+function outputter:drawHbox (value, width)
   self:_ensureInit()
   width = SU.cast("number", width)
   if not value.text then return end
@@ -80,4 +80,4 @@ function text:drawHbox (value, width)
   end
 end
 
-return text
+return outputter
diff --git a/packages/autodoc/init.lua b/packages/autodoc/init.lua
index 7db43e37d..759967dc2 100644
--- a/packages/autodoc/init.lua
+++ b/packages/autodoc/init.lua
@@ -92,10 +92,11 @@ local function typesetAST (options, content)
   end
 end
 
-function package:_init (class, options)
+function package:_init (options)
 
-  class:loadPackage("inputfilter")
-  base._init(self, class)
+  base._init(self)
+
+  self.class:loadPackage("inputfilter")
 
   if options then pl.tablex.update(theme, options) end
 
@@ -140,12 +141,12 @@ function package:registerCommands ()
     return result
   end
 
-  class:registerCommand("package-documentation", function (options, _)
-    local pack = SU.required(options, "src", "src for package documentation")
-    SU.debug("autodoc", pack)
-    local pkg = require(pack)
+  class:registerCommand("package-documentation", function (_, content)
+    local packname = content[1]
+    SU.debug("autodoc", packname)
+    local pkg = require("packages."..packname)
     if type(pkg) ~= "table" or not pkg.documentation then
-      SU.error("Undocumented package " .. pack)
+      SU.error("Undocumented package " .. packname)
     end
     if type(pkg.registerCommands) == "function" then
       -- faking an uninstantiated package
@@ -294,7 +295,7 @@ This package extracts documentation information from other packages.
 It’s used to construct the SILE manual.
 Keeping package documentation in the package itself keeps the documentation near the implementation, which (in theory) makes it easy for documentation and implementation to be in sync.
 
-For that purpose, it provides the \autodoc:command{\package-documentation[src=<package>]} command.
+For that purpose, it provides the \autodoc:command{\package-documentation{<package>}} command.
 
 Properly documented packages should export a \code{documentation} string containing their documentation, as a SILE document.
 
diff --git a/packages/background/init.lua b/packages/background/init.lua
index e71e9a90e..0414af6af 100644
--- a/packages/background/init.lua
+++ b/packages/background/init.lua
@@ -11,10 +11,11 @@ local outputBackground = function (color)
   SILE.outputter:popColor()
 end
 
-function package:_init (class)
+function package:_init ()
 
-  class:loadPackage("color")
-  base._init(self, class)
+  base._init(self)
+
+  self.class:loadPackage("color")
 
 end
 
@@ -38,10 +39,9 @@ end
 
 package.documentation = [[
 \begin{document}
-\script[src=packages.background]
+\use[module=packages.background]
 The \autodoc:package{background} package allows you to set the color of the canvas background (by drawing a solid color block the full size of the page on page initialization).
-The package provides a \autodoc:command{\background} command which requires at least one parameter,
-\autodoc:parameter{color=<color specification>}, and sets the backgound of the current and all following pages to that color.
+The package provides a \autodoc:command{\background} command which requires at least one parameter, \autodoc:parameter{color=<color specification>}, and sets the backgound of the current and all following pages to that color.
 If setting only the current page background different from the default is desired, an extra parameter \autodoc:parameter{allpages=false} can be passed.
 The color specification in the same as specified in the \autodoc:package{color} package.
 
diff --git a/packages/base.lua b/packages/base.lua
index 4e507993f..075faa8e3 100644
--- a/packages/base.lua
+++ b/packages/base.lua
@@ -1,22 +1,28 @@
-local base = pl.class()
-base.type = "package"
-base._name = "base"
+local class = pl.class()
+class.type = "package"
+class._name = "base"
 
-base._initialized = false
-base.class = nil
+class._initialized = false
+class.class = nil
 
-function base:_init (class)
-  self.class = class
+function class:_init (_)
+  self.class = SILE.documentState.documentClass or SILE.scratch.half_initialized_class
+  if not self.class then
+    SU.error("Attempted to initialize package before class, should have been queued in the preamble", true)
+  end
   self:declareSettings()
   self:registerRawHandlers()
   self:registerCommands()
+end
+
+function class:_post_init ()
   self._initialized = true
 end
 
-function base.declareSettings (_) end
+function class.declareSettings (_) end
 
-function base.registerRawHandlers (_) end
+function class.registerRawHandlers (_) end
 
-function base.registerCommands (_) end
+function class.registerCommands (_) end
 
-return base
+return class
diff --git a/packages/bibtex/init.lua b/packages/bibtex/init.lua
index 01b35a669..e35bd7310 100644
--- a/packages/bibtex/init.lua
+++ b/packages/bibtex/init.lua
@@ -55,9 +55,9 @@ local parseBibtex = function (fn)
   return entries
 end
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
   SILE.scratch.bibtex = { bib = {} }
 
diff --git a/packages/bidi/init.lua b/packages/bidi/init.lua
index 865e49c5a..580fc61b4 100644
--- a/packages/bidi/init.lua
+++ b/packages/bidi/init.lua
@@ -248,20 +248,20 @@ local bidiDisableTypesetter = function (class, typesetter)
   typesetter.nobidi_boxUpNodes = nil
 end
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
   -- exports
-  class.reorder = reorder
-  class.bidiEnableTypesetter = bidiEnableTypesetter
-  class.bidiDisableTypesetter = bidiDisableTypesetter
+  self.class.reorder = reorder
+  self.class.bidiEnableTypesetter = bidiEnableTypesetter
+  self.class.bidiDisableTypesetter = bidiDisableTypesetter
 
   if SILE.typesetter then
-    class:bidiEnableTypesetter(SILE.typesetter)
+    self.class:bidiEnableTypesetter(SILE.typesetter)
   end
 
-  class:bidiEnableTypesetter(SILE.defaultTypesetter)
+  self.class:bidiEnableTypesetter(SILE.defaultTypesetter)
 end
 
 function package:registerCommands ()
diff --git a/packages/boustrophedon/init.lua b/packages/boustrophedon/init.lua
index fe17d1e4f..7090fc443 100644
--- a/packages/boustrophedon/init.lua
+++ b/packages/boustrophedon/init.lua
@@ -61,7 +61,7 @@ end
 
 package.documentation = [[
 \begin{document}
-\script[src=packages.boustrophedon]
+\use[module=packages.boustrophedon]
 Partly designed to show off SILE’s extensibility, and partly designed for real use by classicists, the \autodoc:package{boustrophedon} package allows you to typeset ancient Greek texts in the ‘ox-turning’ layout—the first line is written left to right as normal, but the next is set right to left, then left to right, and so on.
 To use it, you will need to set the font’s language to ancient Greek (\code{grc}) and wrap text in a \autodoc:environment{boustrophedon} environment:
 
diff --git a/packages/break-firstfit/init.lua b/packages/break-firstfit/init.lua
index 0515dbe45..f99b86c95 100644
--- a/packages/break-firstfit/init.lua
+++ b/packages/break-firstfit/init.lua
@@ -34,9 +34,9 @@ local firstfit = function (typesetter, nl, breakWidth)
   return typesetter:breakpointsToLines(breaks)
 end
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
   -- exports
   SILE.typesetter._breakIntoLines_firstfit = firstfit
diff --git a/packages/chapterverse/init.lua b/packages/chapterverse/init.lua
index 571263619..c32eedec7 100644
--- a/packages/chapterverse/init.lua
+++ b/packages/chapterverse/init.lua
@@ -3,11 +3,11 @@ local base = require("packages.base")
 local package = pl.class(base)
 package._name = "chapterverse"
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
-  class:loadPackage("infonode")
+  self.class:loadPackage("infonode")
 
   if not SILE.scratch.chapterverse then
     SILE.scratch.chapterverse = {}
diff --git a/packages/chordmode/init.lua b/packages/chordmode/init.lua
index 706f2fcd1..266569ed7 100644
--- a/packages/chordmode/init.lua
+++ b/packages/chordmode/init.lua
@@ -3,12 +3,12 @@ local base = require("packages.base")
 local package = pl.class(base)
 package._name = "chordmode"
 
-function package:_init (class)
+function package:_init ()
 
-  class:loadPackage("raiselower")
-  class:loadPackage("inputfilter")
+  base._init(self)
 
-  base._init(self, class)
+  self.class:loadPackage("raiselower")
+  self.class:loadPackage("inputfilter")
 
 end
 
@@ -132,7 +132,7 @@ end
 
 package.documentation = [[
 \begin{document}
-\script[src=packages/chordmode]
+\use[module=packages.chordmode]
 This package provides the \autodoc:environment{chordmode} environment, which transforms lines like:
 
 \begin{verbatim}
diff --git a/packages/color-fonts/init.lua b/packages/color-fonts/init.lua
index 67dc59571..fe5c01f23 100644
--- a/packages/color-fonts/init.lua
+++ b/packages/color-fonts/init.lua
@@ -3,9 +3,9 @@ local base = require("packages.base")
 local package = pl.class(base)
 package._name = "color-fonts"
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
   local harfbuzz = require("shapers.harfbuzz")
 
diff --git a/packages/complex-spaces/init.lua b/packages/complex-spaces/init.lua
index c8e820371..31b27fa77 100644
--- a/packages/complex-spaces/init.lua
+++ b/packages/complex-spaces/init.lua
@@ -3,9 +3,9 @@ local base = require("packages.base")
 local package = pl.class(base)
 package._name = "complex-spaces"
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
   if not SILE.languageSupport.languages["x-spaces-are-nodes"] then
     local xsan = pl.class(SILE.nodeMakers.unicode)
diff --git a/packages/converters/init.lua b/packages/converters/init.lua
index d1c51b625..fd9ff4188 100644
--- a/packages/converters/init.lua
+++ b/packages/converters/init.lua
@@ -69,9 +69,9 @@ local function extendCommand (name, func)
   end
 end
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
   if not SILE.scratch.converters then
     SILE.scratch.converters = {}
@@ -94,8 +94,8 @@ function package:_init (class)
   end)
 
   -- exports
-  class.register = register
-  class.check = checkConverters
+  self.class.register = register
+  self.class.check = checkConverters
 
 end
 
@@ -125,7 +125,7 @@ We do this by registering a converter with the \autodoc:command{\converters:regi
 
 \begin{verbatim}
 \line
-\\script[src=packages/converters]
+\\use[module=packages.converters]
 \\converters:register[from=.gif,to=.jpg,command=convert $SOURCE $TARGET]
 \line
 \end{verbatim}
diff --git a/packages/counters/init.lua b/packages/counters/init.lua
index 51c5f5631..b67e6eb42 100644
--- a/packages/counters/init.lua
+++ b/packages/counters/init.lua
@@ -51,19 +51,19 @@ SILE.formatMultilevelCounter = function (counter, options)
   return formatMultilevelCounter(nil, counter, options)
 end
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
   if not SILE.scratch.counters then
     SILE.scratch.counters = {}
   end
 
   --exports
-  class.formatCounter = formatCounter
-  class.formatMultilevelCounter = formatMultilevelCounter
-  class.getCounter = getCounter
-  class.getMultilevelCounter = getMultilevelCounter
+  self.class.formatCounter = formatCounter
+  self.class.formatMultilevelCounter = formatMultilevelCounter
+  self.class.getCounter = getCounter
+  self.class.getMultilevelCounter = getMultilevelCounter
 
 end
 
diff --git a/packages/cropmarks/init.lua b/packages/cropmarks/init.lua
index d5e8549b7..17a246501 100644
--- a/packages/cropmarks/init.lua
+++ b/packages/cropmarks/init.lua
@@ -55,11 +55,11 @@ local function reconstrainFrameset (fs)
   end
 end
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
-  class:loadPackage("date")
+  self.class:loadPackage("date")
 
 end
 
diff --git a/packages/date/init.lua b/packages/date/init.lua
index e272430e2..0759f3244 100644
--- a/packages/date/init.lua
+++ b/packages/date/init.lua
@@ -20,12 +20,12 @@ local date = function (class, options)
   return os.date(options.format, options.time)
 end
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
   --exports
-  date = date
+  self.class.date = date
 
 end
 
diff --git a/packages/dropcaps/init.lua b/packages/dropcaps/init.lua
index 42b7bcc1a..e9db4c9ff 100644
--- a/packages/dropcaps/init.lua
+++ b/packages/dropcaps/init.lua
@@ -3,12 +3,12 @@ local base = require("packages.base")
 local package = pl.class(base)
 package._name = "dropcaps"
 
-function package:_init (class)
+function package:_init ()
 
-  class:loadPackage("rebox")
-  class:loadPackage("raiselower")
+  base._init(self)
 
-  base._init(self, class)
+  self.class:loadPackage("rebox")
+  self.class:loadPackage("raiselower")
 
 end
 
diff --git a/packages/folio/init.lua b/packages/folio/init.lua
index b1cb668d7..4a887af5d 100644
--- a/packages/folio/init.lua
+++ b/packages/folio/init.lua
@@ -50,17 +50,17 @@ local _deprecate  = [[
   duplicate entries.
 ]]
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
-  class:loadPackage("counters")
+  self.class:loadPackage("counters")
   SILE.scratch.counters.folio = { value = 1, display = "arabic" }
-  class:registerHook("newpage", _incrementFolio)
-  class:registerHook("endpage", _outputFolio)
+  self.class:registerHook("newpage", _incrementFolio)
+  self.class:registerHook("endpage", _outputFolio)
 
   -- exports
-  class.outputFolio = function (self_)
+  self.class.outputFolio = function (self_)
     SU.deprecated("class:outputFolio", nil, "0.13.0", "0.15.0", _deprecate)
     return _outputFolio(self_)
   end
diff --git a/packages/font-fallback/init.lua b/packages/font-fallback/init.lua
index 4367c796c..370005e58 100644
--- a/packages/font-fallback/init.lua
+++ b/packages/font-fallback/init.lua
@@ -80,9 +80,9 @@ local fallbackQueue = pl.class({
 
 local activeFallbacks = {}
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
   local harfbuzz = require("shapers.harfbuzz")
 
diff --git a/packages/footnotes/init.lua b/packages/footnotes/init.lua
index 8d060fbf6..a0a922cdd 100644
--- a/packages/footnotes/init.lua
+++ b/packages/footnotes/init.lua
@@ -3,22 +3,22 @@ local base = require("packages.base")
 local package = pl.class(base)
 package._name = "footnotes"
 
-function package:_init (class, args)
+function package:_init (options)
 
-  base._init(self, class)
+  base._init(self)
 
-  class:loadPackage("counters")
-  class:loadPackage("raiselower")
-  class:loadPackage("insertions")
+  self.class:loadPackage("counters")
+  self.class:loadPackage("raiselower")
+  self.class:loadPackage("insertions")
 
   if not SILE.scratch.counters.footnotes then
     SILE.scratch.counters.footnote = { value = 1, display = "arabic" }
   end
 
-  args = args or {}
-  class:initInsertionClass("footnote", {
-    insertInto = args.insertInto or "footnotes",
-    stealFrom = args.stealFrom or { "content" },
+  options = options or {}
+  self.class:initInsertionClass("footnote", {
+    insertInto = options.insertInto or "footnotes",
+    stealFrom = options.stealFrom or { "content" },
     maxHeight = SILE.length("75%ph"),
     topBox = SILE.nodefactory.vglue("2ex"),
     interInsertionSkip = SILE.length("1ex"),
diff --git a/packages/frametricks/init.lua b/packages/frametricks/init.lua
index 8157ea308..4b529e8f0 100644
--- a/packages/frametricks/init.lua
+++ b/packages/frametricks/init.lua
@@ -138,13 +138,14 @@ local mergeColumns = function ()
   SILE.typesetter:initNextFrame()
 end
 
-function package:_init (class)
+function package:_init ()
 
-  class:loadPackage("balanced-frames")
-  base._init(self, class)
+  base._init(self)
+
+  self.class:loadPackage("balanced-frames")
 
   -- exports
-  class.breakFrameVertical = breakFrameVertical
+  self.class.breakFrameVertical = breakFrameVertical
 
 end
 
diff --git a/packages/grid/init.lua b/packages/grid/init.lua
index 6e6a1d026..d2aabd525 100644
--- a/packages/grid/init.lua
+++ b/packages/grid/init.lua
@@ -139,9 +139,9 @@ end
 
 local oldPageBuilder, oldLeadingFor, oldPushVglue, oldPushExplicitVglue
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
   gridSpacing = SILE.measurement()
 
diff --git a/packages/hanmenkyoshi/init.lua b/packages/hanmenkyoshi/init.lua
index 3b405530a..5ae7d0d69 100644
--- a/packages/hanmenkyoshi/init.lua
+++ b/packages/hanmenkyoshi/init.lua
@@ -33,7 +33,10 @@ local showHanmenTate = function (frame)
   end
 end
 
-local declareHanmenFrame = function (self, id, spec)
+-- Warning: this function has side affects and if a real frame is
+-- passed as a spec it will be modified in addition to a frame
+-- being instantiated in the class page template.
+local declareHanmenFrame = function (class, id, spec)
   if spec then
     spec.id = id
   else
@@ -59,26 +62,27 @@ local declareHanmenFrame = function (self, id, spec)
   SILE.settings:set("document.parskip", SILE.nodefactory.vglue())
   local frame = SILE.newFrame(spec, spec.tate and SILE.tateFramePrototype or SILE.framePrototype)
   if spec.id then
-    self.pageTemplate.frames[spec.id] = frame
+    class.pageTemplate.frames[spec.id] = frame
   end
-  return frame
 end
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
-  class:loadPackage("tate")
+  self.class:loadPackage("tate")
 
   -- exports
-  class.declareHanmenFrame = declareHanmenFrame
+  self.class.declareHanmenFrame = declareHanmenFrame
 end
 
 function package:registerCommands ()
 
   self.class:registerCommand("show-hanmen", function (_, _)
     local frame = SILE.typesetter.frame
-    if not frame.hanmen then SU.error("show-hanmen called on a frame with no hanmen") end
+    if not frame.hanmen then
+      SU.error("show-hanmen called on a frame with no hanmen")
+    end
     SILE.outputter:pushColor({r = 1, g= 0.9, b = 0.9 })
     if frame:writingDirection() == "TTB" then
       showHanmenTate(frame)
diff --git a/packages/indexer/init.lua b/packages/indexer/init.lua
index 9291df16a..c5df650b6 100644
--- a/packages/indexer/init.lua
+++ b/packages/indexer/init.lua
@@ -25,16 +25,16 @@ end
   --   end
   -- end
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
   if not SILE.scratch.index then
     SILE.scratch.index = {}
   end
 
   -- exports
-  class.buildIndex = moveNodes
+  self.class.buildIndex = moveNodes
 
 end
 
diff --git a/packages/infonode/init.lua b/packages/infonode/init.lua
index 33eba702a..3f39ebec5 100644
--- a/packages/infonode/init.lua
+++ b/packages/infonode/init.lua
@@ -41,18 +41,18 @@ local _deprecate  = [[
   you are likely causing it to run twice and duplicate entries.
 ]]
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
   if not SILE.scratch.info then
     SILE.scratch.info = { thispage = {} }
   end
 
-  class:registerHook("newpage", _newPageInfo)
+  self.class:registerHook("newpage", _newPageInfo)
 
   -- exports
-  class.newPageInfo = function (self_)
+  self.class.newPageInfo = function (self_)
     SU.deprecated("class:newPageInfo", nil, "0.13.0", "0.15.0", _deprecate)
     return _newPageInfo(self_)
   end
diff --git a/packages/inputfilter/init.lua b/packages/inputfilter/init.lua
index 094af00e6..cc132dc53 100644
--- a/packages/inputfilter/init.lua
+++ b/packages/inputfilter/init.lua
@@ -35,13 +35,13 @@ local function createCommand (pos, col, line, command, options, content)
   return result
 end
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
   -- exports
-  class.createCommand = createCommand
-  class.transformContent = transformContent
+  self.class.createCommand = createCommand
+  self.class.transformContent = transformContent
 
 end
 
@@ -52,7 +52,7 @@ It does this by allowing you to rewrite the abstract syntax tree representing th
 
 Loading \autodoc:package{inputfilter} into your class with \code{class:loadPackage("inputfilter")} provides you with two new Lua functions: \code{transformContent} and \code{createCommand}.
 \code{transformContent} takes a content tree and applies a transformation function to the text within it.
-See \url{https://sile-typesetter.org/examples/inputfilter.sil} for a simple example, and \code{packages/chordmode.sil} for a more complete one.
+See \url{https://sile-typesetter.org/examples/inputfilter.sil} for a simple example, and \url{https://sile-typesetter.org/examples/chordmode.sil} for a more complete one.
 \end{document}
 ]]
 
diff --git a/packages/insertions/init.lua b/packages/insertions/init.lua
index 8f95f8ad6..36dfdf711 100644
--- a/packages/insertions/init.lua
+++ b/packages/insertions/init.lua
@@ -219,9 +219,9 @@ local insert = function (_, classname, vbox)
     })
 end
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
   if not SILE.scratch.insertions then
     SILE.scratch.insertions = { classes = {} }
@@ -417,7 +417,7 @@ function package:_init (class)
     return target
   end
 
-  class:registerPostinit(function (_)
+  self.class:registerPostinit(function (_)
 
     local typesetter = SILE.typesetter
 
@@ -448,9 +448,9 @@ function package:_init (class)
   end)
 
   -- exports
-  class.initInsertionClass = initInsertionClass
-  class.thisPageInsertionBoxForClass = thisPageInsertionBoxForClass
-  class.insert = insert
+  self.class.initInsertionClass = initInsertionClass
+  self.class.thisPageInsertionBoxForClass = thisPageInsertionBoxForClass
+  self.class.insert = insert
 
 end
 
diff --git a/packages/linespacing/init.lua b/packages/linespacing/init.lua
index dfe504ac4..330c39be4 100644
--- a/packages/linespacing/init.lua
+++ b/packages/linespacing/init.lua
@@ -92,9 +92,9 @@ local linespacingLeading = function (_, vbox, previous)
   SU.error("Unknown line spacing method "..method)
 end
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
   SILE.typesetter.leadingFor = linespacingLeading
 
diff --git a/packages/lists/init.lua b/packages/lists/init.lua
index 575bba5cc..82511f0ee 100644
--- a/packages/lists/init.lua
+++ b/packages/lists/init.lua
@@ -207,10 +207,11 @@ local doNestedList = function (_, listType, options, content)
   end
 end
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
-  class:loadPackage("counters")
+  base._init(self)
+
+  self.class:loadPackage("counters")
 
 end
 
diff --git a/packages/masters/init.lua b/packages/masters/init.lua
index 9d0eacb17..238908c5f 100644
--- a/packages/masters/init.lua
+++ b/packages/masters/init.lua
@@ -70,22 +70,24 @@ local function currentMaster (_)
   return _currentMaster
 end
 
-function package:_init (class, args)
+function package:_init (options)
 
-  base._init(self, class)
+  base._init(self, options)
 
   if not SILE.scratch.masters then
     SILE.scratch.masters = {}
   end
 
-  defineMasters(class, args)
-
   -- exports
-  class.switchMasterOnePage = switchMasterOnePage
-  class.switchMaster = switchMaster
-  class.defineMaster = defineMaster
-  class.defineMasters = defineMasters
-  class.currentMaster = currentMaster
+  self.class.switchMasterOnePage = switchMasterOnePage
+  self.class.switchMaster = switchMaster
+  self.class.defineMaster = defineMaster
+  self.class.defineMasters = defineMasters
+  self.class.currentMaster = currentMaster
+
+  if options then
+    self.class:defineMasters(options)
+  end
 
 end
 
diff --git a/packages/math/init.lua b/packages/math/init.lua
index 503d7ff0d..458f3f079 100644
--- a/packages/math/init.lua
+++ b/packages/math/init.lua
@@ -3,12 +3,12 @@ local base = require("packages.base")
 local package = pl.class(base)
 package._name = "math"
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
-  class:loadPackage("math.typesetter")
-  class:loadPackage("math.texlike")
+  self.class:loadPackage("math.typesetter")
+  self.class:loadPackage("math.texlike")
 
 end
 
@@ -69,13 +69,13 @@ end
 
 package.documentation = [[
 \begin{document}
-\script[src=packages/math]
+\use[module=packages.math]
 
 \set[parameter=math.font.family, value=Libertinus Math]
 \set[parameter=math.font.size, value=11]
 
 % Default verbatim font (Hack) is missing a few math symbols
-\script[src=packages/font-fallback]
+\use[module=packages.font-fallback]
 \font:add-fallback[family=Symbola]
 
 This package provides typesetting of formulas directly in a SILE document.
diff --git a/packages/pagebuilder-bestfit/init.lua b/packages/pagebuilder-bestfit/init.lua
index 2f4196f17..a0844884b 100644
--- a/packages/pagebuilder-bestfit/init.lua
+++ b/packages/pagebuilder-bestfit/init.lua
@@ -5,9 +5,9 @@ package._name = "pagebuilder-bestfit"
 
 local MAX_PAGES = 5
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
   SILE.typesetter.buildPage = function (typesetter, independent)
     -- Find last penalty
diff --git a/packages/pandoc/init.lua b/packages/pandoc/init.lua
index ff75e58de..b462a6123 100644
--- a/packages/pandoc/init.lua
+++ b/packages/pandoc/init.lua
@@ -45,17 +45,17 @@ local handlePandocArgs = function (options)
   return wrapper, options
 end
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
-  class:loadPackage("footnotes")
-  class:loadPackage("image")
-  class:loadPackage("pdf")
-  class:loadPackage("raiselower")
-  class:loadPackage("rules")
-  class:loadPackage("url")
-  class:loadPackage("verbatim")
+  self.class:loadPackage("footnotes")
+  self.class:loadPackage("image")
+  self.class:loadPackage("pdf")
+  self.class:loadPackage("raiselower")
+  self.class:loadPackage("rules")
+  self.class:loadPackage("url")
+  self.class:loadPackage("verbatim")
 
 end
 
diff --git a/packages/parallel/init.lua b/packages/parallel/init.lua
index 864c5eba1..24e66fb48 100644
--- a/packages/parallel/init.lua
+++ b/packages/parallel/init.lua
@@ -60,9 +60,9 @@ local addBalancingGlue = function (height)
   end)
 end
 
-function package:_init (class, options)
+function package:_init (options)
 
-  base._init(self, class)
+  base._init(self, options)
 
   SILE.typesetter = nulTypesetter(SILE.getFrame("page"))
   for frame, typesetter in pairs(options.frames) do
@@ -74,11 +74,11 @@ function package:_init (class, options)
     -- Fixed leading here is obviously a bug, but n-way leading calculations
     -- get very complicated...
     -- typesetterPool[frame].leadingFor = function() return SILE.nodefactory.vglue(SILE.settings:get("document.lineskip")) end
-    class:registerCommand(frame, function (_, _) -- \left ...
+    self.class:registerCommand(frame, function (_, _) -- \left ...
       SILE.typesetter = typesetterPool[frame]
       SILE.call(frame..":font")
     end)
-    class:registerCommand(frame..":font", function (_, _) end) -- to be overridden
+    self.class:registerCommand(frame..":font", function (_, _) end) -- to be overridden
   end
   if not options.folios then
     folioOrder = { {} }
@@ -88,16 +88,16 @@ function package:_init (class, options)
   else
     folioOrder = options.folios -- As usual we trust the user knows what they're doing
   end
-  class.newPage = function(self_)
+  self.class.newPage = function(self_)
     allTypesetters(function (frame, _)
       calculations[frame] = { mark = 0 }
     end)
-    class._base.newPage(self_)
+    self.class._base.newPage(self_)
     SILE.call("sync")
   end
   allTypesetters(function (frame, _) calculations[frame] = { mark = 0 } end)
-  local oldfinish = class.finish
-  class.finish = function (self_)
+  local oldfinish = self.class.finish
+  self.class.finish = function (self_)
     parallelPagebreak()
     oldfinish(self_)
   end
diff --git a/packages/pdf/init.lua b/packages/pdf/init.lua
index fe508b4cb..bb27dd73d 100644
--- a/packages/pdf/init.lua
+++ b/packages/pdf/init.lua
@@ -24,9 +24,9 @@ local function validate_date (date)
   return string.match(date, [[^D:%d+%s*-%s*%d%d%s*'%s*%d%d%s*'?$]]) ~= nil
 end
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
   pdf = require("justenoughlibtexpdf")
 
diff --git a/packages/pdfstructure/init.lua b/packages/pdfstructure/init.lua
index 30cd23c0a..198939ee1 100644
--- a/packages/pdfstructure/init.lua
+++ b/packages/pdfstructure/init.lua
@@ -68,9 +68,9 @@ local function dumpTree (node)
   return ref
 end
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
   pdf = require("justenoughlibtexpdf")
 
@@ -83,7 +83,7 @@ function package:_init (class)
   local stRoot = stNode("Document")
   stPointer = stRoot
 
-  class:loadPackage("pdf")
+  self.class:loadPackage("pdf")
 
   function SILE.outputters.libtexpdf._endHook (_)
     local catalog = pdf.get_dictionary("Catalog")
@@ -130,7 +130,7 @@ end
 
 package.documentation = [[
 \begin{document}
-\script[src=packages.pdfstructure]
+\use[module=packages.pdfstructure]
 \pdf:structure[type=P]{%
 For PDF documents to be considered accessible, they must contain a description of the PDF’s document structure.
 This package allows structure trees to be created and saved to the PDF file.
diff --git a/packages/pullquote/init.lua b/packages/pullquote/init.lua
index 70feec873..39af9253a 100644
--- a/packages/pullquote/init.lua
+++ b/packages/pullquote/init.lua
@@ -25,13 +25,13 @@ local typesetMark = function (open, setback, scale, color, mark)
   end)
 end
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
-  class:loadPackage("color")
-  class:loadPackage("raiselower")
-  class:loadPackage("rebox")
+  self.class:loadPackage("color")
+  self.class:loadPackage("raiselower")
+  self.class:loadPackage("rebox")
 
 end
 
diff --git a/packages/rotate/init.lua b/packages/rotate/init.lua
index 3e708a682..e79339cb0 100644
--- a/packages/rotate/init.lua
+++ b/packages/rotate/init.lua
@@ -52,9 +52,9 @@ local outputRotatedHbox = function (self, typesetter, line)
   typesetter.frame:advanceWritingDirection(self.width)
 end
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
   if SILE.typesetter.frame then
     enter(SILE.typesetter.frame, SILE.typesetter)
@@ -110,7 +110,7 @@ end
 
 package.documentation = [[
 \begin{document}
-\script[src=packages/rotate]
+\use[module=packages.rotate]
 The \autodoc:package{rotate} package allows you to rotate things. You can rotate entire
 frames, by adding the \autodoc:parameter{rotate=<angle>} declaration to your frame declaration,
 and you can rotate any content by issuing the command \autodoc:command{\rotate[angle=<angle>]{<content>}},
diff --git a/packages/ruby/init.lua b/packages/ruby/init.lua
index b8b511834..11c0dc172 100644
--- a/packages/ruby/init.lua
+++ b/packages/ruby/init.lua
@@ -27,12 +27,12 @@ local checkIfSpacerNeeded = function (reading)
   SILE.typesetter:pushGlue(SILE.settings:get("ruby.latinspacer"))
 end
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
   -- Japaneese language support defines units which are useful here
-  class:loadPackage("font-fallback")
+  self.class:loadPackage("font-fallback")
   SILE.call("font:add-fallback", { family = "Noto Sans CJK JP" })
 
   SILE.languageSupport.loadLanguage("ja")
@@ -119,7 +119,7 @@ end
 package.documentation = [[
 \begin{document}
 \font:add-fallback[family=Noto Sans CJK JP]
-\script[src=packages.ruby]
+\use[module=packages.ruby]
 Japanese texts often contain pronunciation hints (called \em{furigana}) for difficult kanji or foreign words.
 These hints are traditionally placed either above (in horizontal typesetting) or beside (in vertical typesetting) the word that they explain.
 The typesetting term for these glosses is \em{ruby}.
diff --git a/packages/rules/init.lua b/packages/rules/init.lua
index f76581af1..9f6ce7ee4 100644
--- a/packages/rules/init.lua
+++ b/packages/rules/init.lua
@@ -42,12 +42,12 @@ function hrulefillglue:outputYourself (typesetter, line)
   typesetter.frame:advancePageDirection(self.raise)
 end
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
-  class:loadPackage("raiselower")
-  class:loadPackage("rebox")
+  self.class:loadPackage("raiselower")
+  self.class:loadPackage("rebox")
 
 end
 
diff --git a/packages/simpletable/init.lua b/packages/simpletable/init.lua
index 3310283f7..13ef96c91 100644
--- a/packages/simpletable/init.lua
+++ b/packages/simpletable/init.lua
@@ -5,28 +5,28 @@ package._name = "simpletable"
 
 local tableTag, trTag, tdTag
 
-function package:_init (class, args)
+function package:_init (options)
 
-  base._init(self, class)
+  base._init(self, options)
 
   if not SILE.scratch.simpletable then
     SILE.scratch.simpletable = { tables = {} }
   end
 
-  args = args or {
+  options = options and #options >= 3 or {
     tableTag = "table",
     trTag = "tr",
     tdTag = "td"
   }
 
-  tableTag = SU.required(args, "tableTag", "setting up table class")
-  trTag = SU.required(args, "trTag", "setting up table class")
-  tdTag = SU.required(args, "tdTag", "setting up table class")
+  tableTag = SU.required(options, "tableTag", "setting up table class")
+  trTag = SU.required(options, "trTag", "setting up table class")
+  tdTag = SU.required(options, "tdTag", "setting up table class")
 
   -- This is a post init calback instead of the usual early command registration
   -- method using our package loader because we don't know what commands to register
   -- until we've been instantiated.
-  class:registerPostinit(function (_)
+  self.class:registerPostinit(function (class)
 
     class:registerCommand(trTag, function(_, content)
       local tbl = SILE.scratch.simpletable.tables[#(SILE.scratch.simpletable.tables)]
diff --git a/packages/specimen/init.lua b/packages/specimen/init.lua
index 26bfa8bd9..0ff7c6488 100644
--- a/packages/specimen/init.lua
+++ b/packages/specimen/init.lua
@@ -63,7 +63,7 @@ end
 
 package.documentation = [[
 \begin{document}
-\script[src=packages.specimen]
+\use[module=packages.specimen]
 SILE has found itself becoming well used by type designers, who often want to create specimen documents to show off their new fonts.
 This package provides a few commands to help create test documents.
 (The \code{fontproof} class, available from the package manager, contains many more tools for creating specimens.)
diff --git a/packages/tableofcontents/init.lua b/packages/tableofcontents/init.lua
index fd5494aac..70b3f294f 100644
--- a/packages/tableofcontents/init.lua
+++ b/packages/tableofcontents/init.lua
@@ -79,25 +79,25 @@ local _deprecate  = [[
   twice and duplicate entries.
 ]]
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
   if not SILE.scratch.tableofcontents then
     SILE.scratch.tableofcontents = {}
   end
 
-  class:loadPackage("infonode")
-  class:loadPackage("leaders")
+  self.class:loadPackage("infonode")
+  self.class:loadPackage("leaders")
 
-  class:registerHook("endpage", _moveTocNodes)
-  class:registerHook("finish", _writeToc)
+  self.class:registerHook("endpage", _moveTocNodes)
+  self.class:registerHook("finish", _writeToc)
 
   --exports
-  class.writeToc = function (_)
+  self.class.writeToc = function (_)
     SU.deprecated("class:writeToc", nil, "0.13.0", "0.14.0", _deprecate)
   end
-  class.moveTocNodes = function (_)
+  self.class.moveTocNodes = function (_)
     SU.deprecated("class:moveTocNodes", nil, "0.13.0", "0.14.0", _deprecate)
   end
 end
diff --git a/packages/textcase/init.lua b/packages/textcase/init.lua
index 6f0948748..73598b162 100644
--- a/packages/textcase/init.lua
+++ b/packages/textcase/init.lua
@@ -26,16 +26,16 @@ local titlecase = function (input, extraArgs)
   return icu.case(input, lang, "title")
 end
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
-  class:loadPackage("inputfilter")
+  self.class:loadPackage("inputfilter")
 
   -- exports
-  class.uppercase = uppercase
-  class.lowercase = lowercase
-  class.titlecase = titlecase
+  self.class.uppercase = uppercase
+  self.class.lowercase = lowercase
+  self.class.titlecase = titlecase
 
 end
 
@@ -59,7 +59,7 @@ end
 
 package.documentation = [[
 \begin{document}
-\script[src=packages/textcase]
+\use[module=packages.textcase]
 The \autodoc:package{textcase} package provides commands for language-aware case conversion of input text.
 For example, when language is set to English, then \autodoc:command{\uppercase{hij}} will return \examplefont{\uppercase{hij}}.
 However, when language is set to Turkish, it will return \examplefont{\font[language=tr]{\uppercase{hij}}}.
diff --git a/packages/twoside/init.lua b/packages/twoside/init.lua
index 7bac99fba..e7d22bcb9 100644
--- a/packages/twoside/init.lua
+++ b/packages/twoside/init.lua
@@ -49,28 +49,28 @@ local _deprecate  = [[
   you are likely causing it to run twice and duplicate entries.
 ]]
 
-function package:_init (class, args)
+function package:_init (options)
 
-  base._init(self, class)
+  base._init(self)
 
   if not SILE.scratch.masters then
     SU.error("Cannot load twoside package before masters.")
   end
 
   -- exports
-  class.oddPage = oddPage
-  class.mirrorMaster = mirrorMaster
-  class.switchPage = function (_)
+  self.class.oddPage = oddPage
+  self.class.mirrorMaster = mirrorMaster
+  self.class.switchPage = function (class)
     SU.deprecated("class:switchPage", nil, "0.13.0", "0.15.0", _deprecate)
     return _switchPage(class)
   end
 
-  class.oddPageMaster = args.oddPageMaster
-  class.evenPageMaster = args.evenPageMaster
-  class:registerPostinit(function (self_)
-    self_:mirrorMaster(args.oddPageMaster, args.evenPageMaster)
+  self.class.oddPageMaster = options.oddPageMaster
+  self.class.evenPageMaster = options.evenPageMaster
+  self.class:registerPostinit(function (self_)
+    self_:mirrorMaster(options.oddPageMaster, options.evenPageMaster)
   end)
-  class:registerHook("newpage", _switchPage)
+  self.class:registerHook("newpage", _switchPage)
 
 end
 
diff --git a/packages/unichar/init.lua b/packages/unichar/init.lua
index ba8775fff..2cd912864 100644
--- a/packages/unichar/init.lua
+++ b/packages/unichar/init.lua
@@ -21,7 +21,7 @@ end
 
 package.documentation = [[
 \begin{document}
-\script[src=packages/unichar]
+\use[module=packages.unichar]
 SILE is Unicode compatible, and expects its input files to be in the UTF-8 encoding.
 (The actual range of Unicode characters supported will depend on the supported ranges of the fonts that SILE is using to typeset.)
 Some Unicode characters are hard to locate on a standard keyboard, and so are difficult to enter into SILE documents.
diff --git a/packages/url/init.lua b/packages/url/init.lua
index c15f97857..68caf487a 100644
--- a/packages/url/init.lua
+++ b/packages/url/init.lua
@@ -19,16 +19,16 @@ end
 
 local breakPattern = "["..escapeRegExpMinimal(preferBreakBefore..preferBreakAfter..alwaysBreakAfter).."]"
 
-function package:_init (class)
+function package:_init ()
 
-  base._init(self, class)
+  base._init(self)
 
-  class:loadPackage("verbatim")
-  class:loadPackage("inputfilter")
+  self.class:loadPackage("verbatim")
+  self.class:loadPackage("inputfilter")
 
   pdf = SILE.outputter == SILE.outputters.libtexpdf
 
-  if pdf then class:loadPackage("pdf") end
+  if pdf then self.class:loadPackage("pdf") end
 
 end
 
@@ -158,7 +158,7 @@ end
 
 package.documentation = [[
 \begin{document}
-\script[src=packages/url]
+\use[module=packages.url]
 This package enhances the typesetting of URLs in two ways.
 First, it provides the \autodoc:command{\href[src=<url>]{<content>}} command which inserts PDF hyperlinks, \href[src=http://www.sile-typesetter.org/]{like this}.
 
diff --git a/shapers/base.lua b/shapers/base.lua
index 7b5b70ec5..dc2ee6deb 100644
--- a/shapers/base.lua
+++ b/shapers/base.lua
@@ -33,15 +33,15 @@ local function shapespace (spacewidth)
   return SILE.length(length, stretch, shrink)
 end
 
-local base = pl.class()
-base.type = "shaper"
-base._name = "base"
+local shaper = pl.class()
+shaper.type = "shaper"
+shaper._name = "base"
 
 -- Return the length of a space character
 -- with a particular set of font options,
 -- giving preference to document.spaceskip
 -- Caching this has no significant speedup
-function base:measureSpace (options)
+function shaper:measureSpace (options)
   local ss = SILE.settings:get("document.spaceskip")
   if ss then
     SILE.settings:temporarily(function ()
@@ -60,7 +60,7 @@ function base:measureSpace (options)
   return shapespace(width and width.length or items[1].width)
 end
 
-function base:measureChar (char)
+function shaper:measureChar (char)
   local options = SILE.font.loadDefaults({})
   options.tracking = SILE.settings:get("shaper.tracking")
   local items = self:shapeToken(char, options)
@@ -72,24 +72,24 @@ function base:measureChar (char)
 end
 
 -- Given a text and some font options, return a bunch of boxes
-function base.shapeToken (_, _, _)
+function shaper.shapeToken (_, _, _)
   SU.error("Abstract function shapeToken called", true)
 end
 
 -- Given font options, select a font. We will handle
 -- caching here. Returns an arbitrary, implementation-specific
 -- object (ie a PAL for Pango, font number for libtexpdf, ...)
-function base.getFace (_)
+function shaper.getFace (_)
   SU.error("Abstract function getFace called", true)
 end
 
-function base.addShapedGlyphToNnodeValue (_, _, _)
+function shaper.addShapedGlyphToNnodeValue (_, _, _)
   SU.error("Abstract function addShapedGlyphToNnodeValue called", true)
 end
 
-function base.preAddNodes (_, _, _) end
+function shaper.preAddNodes (_, _, _) end
 
-function base:createNnodes (token, options)
+function shaper:createNnodes (token, options)
   options.tracking = SILE.settings:get("shaper.tracking")
   local items, _ = self:shapeToken(token, options)
   if #items < 1 then return {} end
@@ -103,7 +103,7 @@ function base:createNnodes (token, options)
   return nodes
 end
 
-function base:formNnode (contents, token, options)
+function shaper:formNnode (contents, token, options)
   local nnodeContents = {}
   -- local glyphs = {}
   local totalWidth = 0
@@ -146,7 +146,7 @@ function base:formNnode (contents, token, options)
     })
 end
 
-function base.makeSpaceNode (_, options, item)
+function shaper.makeSpaceNode (_, options, item)
   local width
   if SILE.settings:get("shaper.variablespaces") then
     width = shapespace(item.width)
@@ -156,6 +156,6 @@ function base.makeSpaceNode (_, options, item)
   return SILE.nodefactory.glue(width)
 end
 
-function base.debugVersions (_) end
+function shaper.debugVersions (_) end
 
-return base
+return shaper
diff --git a/shapers/harfbuzz.lua b/shapers/harfbuzz.lua
index f3aad3670..9c9b61457 100644
--- a/shapers/harfbuzz.lua
+++ b/shapers/harfbuzz.lua
@@ -26,10 +26,10 @@ end
 local substwarnings = {}
 local usedfonts = {}
 
-local harfbuzz = pl.class(base)
-harfbuzz._name = "harfbuzz"
+local shaper = pl.class(base)
+shaper._name = "harfbuzz"
 
-function harfbuzz:shapeToken (text, options)
+function shaper:shapeToken (text, options)
   local items
   if #text < smallTokenSize then items = shapeCache[_key(options, text)]; if items then return items end end
   local face = SILE.font.cache(options, self.getFace)
@@ -64,7 +64,7 @@ function harfbuzz:shapeToken (text, options)
 end
 
 -- TODO: normalize this method to accept self as first arg
-function harfbuzz.getFace (opts)
+function shaper.getFace (opts)
   local face = SILE.fontManager:face(opts)
   SU.debug("fonts", "Resolved font family '" .. tostring(opts.family) .. "' -> " .. tostring(face and face.filename))
   if not face or not face.filename then SU.error("Couldn't find face '"..opts.family.."'") end
@@ -79,7 +79,7 @@ function harfbuzz.getFace (opts)
   return face
 end
 
-function harfbuzz.preAddNodes (_, items, nnodeValue) -- Check for complex nodes
+function shaper.preAddNodes (_, items, nnodeValue) -- Check for complex nodes
   for i = 1, #items do
     if items[i].y_offset or items[i].x_offset or items[i].width ~= items[i].glyphAdvance then
       nnodeValue.complex = true; break
@@ -87,7 +87,7 @@ function harfbuzz.preAddNodes (_, items, nnodeValue) -- Check for complex nodes
   end
 end
 
-function harfbuzz.addShapedGlyphToNnodeValue (_, nnodevalue, shapedglyph)
+function shaper.addShapedGlyphToNnodeValue (_, nnodevalue, shapedglyph)
   if nnodevalue.complex then
 
     if not nnodevalue.items then nnodevalue.items = {} end
@@ -99,7 +99,7 @@ function harfbuzz.addShapedGlyphToNnodeValue (_, nnodevalue, shapedglyph)
   table.insert(nnodevalue.glyphNames, shapedglyph.name)
 end
 
-function harfbuzz.debugVersions (_)
+function shaper.debugVersions (_)
   local ot = require("core.opentype-parser")
   print("Harfbuzz version: "..hb.version())
   print("Shapers enabled: ".. table.concat({ hb.shapers() }, ", "))
@@ -120,7 +120,7 @@ function harfbuzz.debugVersions (_)
   end
 end
 
-function harfbuzz.checkHBProblems (_, text, face)
+function shaper.checkHBProblems (_, text, face)
   if hb.version_lessthan(1, 0, 4) and #text < 1 then
     return true
   end
@@ -134,4 +134,4 @@ function harfbuzz.checkHBProblems (_, text, face)
   return false
 end
 
-return harfbuzz
+return shaper
diff --git a/shapers/pango.lua b/shapers/pango.lua
index 6ccd5fc0f..8c207d06c 100644
--- a/shapers/pango.lua
+++ b/shapers/pango.lua
@@ -20,11 +20,11 @@ local function _shape(text, item)
   return pgs
 end
 
-local pango = pl.class(base)
-pango._name = "pango"
+local shaper = pl.class(base)
+shaper._name = "pango"
 
 -- TODO: refactor so method accepts self
-function pango.getFace (options)
+function shaper.getFace (options)
   local pal
   if options.pal then
     return options.pal
@@ -49,7 +49,7 @@ function pango.getFace (options)
   return pal
 end
 
-function pango:shapeToken (text, options)
+function shaper:shapeToken (text, options)
   local pal = SILE.font.cache(options, self.getFace)
   local rv = {}
   local items = pangolgi.itemize(pango_context, text, 0, string.len(text), pal, nil)
@@ -76,9 +76,9 @@ function pango:shapeToken (text, options)
   return rv, twidth
 end
 
-function pango.addShapedGlyphToNnodeValue (_, nnodevalue, shapedglyph)
+function shaper.addShapedGlyphToNnodeValue (_, nnodevalue, shapedglyph)
   nnodevalue.pgs = shapedglyph.pgs
   nnodevalue.font = shapedglyph.font
 end
 
-return pango
+return shaper
diff --git a/sile.1.in b/sile.1.in
index 168d3c27a..8aca422da 100644
--- a/sile.1.in
+++ b/sile.1.in
@@ -12,10 +12,12 @@
 .SH DESCRIPTION
 The SILE typesetter reads a single input file, by default in either SIL or XML format, and processes it to generate a single output file, by default in PDF format.
 The output will be written to the same name as the input file with the extension changed to .pdf unless the \fB\-\-output\fR flag is used.
-Additional input or output formats can be handled by requiring a module with \fB\-\-require\fR to add support for them first.
+Additional input or output formats can be handled by loading a module with \fB\-\-use\fR to add support for them first.
 .SH OPTIONS
-.B @TRANSFORMED_PACKAGE_NAME@
-accepts the following options:
+.B @TRANSFORMED_PACKAGE_NAME@ accepts the following feature flags:
+.TP
+.BR \-t ", " \-\-traceback
+Display detailed location trace on errors and warnings.
 .TP
 .BR \-h ", " \-\-help
 Print help message and exit.
@@ -23,6 +25,8 @@ Print help message and exit.
 .BR \-v ", " \-\-version
 Print version information and exit.
 .TP
+.B @TRANSFORMED_PACKAGE_NAME@ accepts the following options with values:
+.TP
 .BR \-b ", " \-\-backend= \fIvalue\fR
 Choose an alternative output backend.
 The default backend for producing PDF files is \fIlibtexpdf\fR.
@@ -67,15 +71,7 @@ May be specified more than once.
 .TP
 .BR \-I ", " \-\-include= \fIfilename\fR
 Deprecated, will be removed.
-Please use \-\-require, \-\-preamble, or \-\-postamble.
-.TP
-.BR \-r ", " \-\-require= \fImodule\fR
-Require a class, package, or other resource to be loaded before processing the main input.
-The value should be a loadable module name (without a file extension, using \fI.\fR as a path separator) and will be loaded using SILE's module search path.
-This is particularly useful when the input document is not a natively recognized SIL or XML scheme and SILE needs to be taught new tricks before trying to read the input file.
-If the module is a valid document class, it will replace \fIplain\fR as the default class for processing documents.
-Because this executes before loading the document, it may even change the way the parser works or add an input parser to support new file formats.
-May be specified more than once.
+Please use \-\-use, \-\-preamble, or \-\-postamble.
 .TP
 .BR \-p ", " \-\-preamble= \fIfilename\fR
 Include an SIL, XML, or other content resource before the input document.
@@ -87,5 +83,12 @@ Include an SIL, XML, or other content resource after the input document.
 The value should be a full filename with a path relative to PWD or an absolute path.
 May be specified more than once.
 .TP
-.BR \-t ", " \-\-traceback
-Display detailed location trace on errors and warnings.
+.BR \-u ", " \-\-use= \fImodule\fR [[\fIparameter=value[,parameter=value]]]\fR
+Load and initialize a class, inputter, shaper, or other module before processing the main input.
+The value should be a loadable module name (with no extension, using \fI'.'\fR as a path separator) and will be loaded using SILE's module search path.
+Options may be passed to the module by enclosing \fIkey=value\fR pairs in square brackets following the module name.
+This is particularly useful when the input document is not SIL or a natively recognized XML scheme and SILE needs to be taught new tricks before even trying to read the input file.
+If the module is a document class, it will replace \fIplain\fR as the default class for processing documents that do not specifically identify one to use.
+Because this executes before loading the document, it may even add an input parser or modify an existing one to support new file formats.
+Package modules will be added to the preamble to be loaded after the class is initialized.
+May be specified more than once.
diff --git a/sile.in b/sile.in
index 90276e1bb..c4cd4b4e7 100755
--- a/sile.in
+++ b/sile.in
@@ -97,16 +97,8 @@ if SILE.masterFilename then
     ProFi:start()
   end
 
-  for _, path in ipairs(SILE.input.requires) do
-    local module = require(path)
-    if type(module) == "table" and module._name then
-      if module.type == "class" then
-        SILE.classes[module._name] = module
-        SILE.sratch.required_class = module
-      elseif module.type == "inputter" then
-        SILE.inputters[module._name] = module
-      end
-    end
+  for _, spec in ipairs(SILE.input.uses) do
+    SILE.use(spec.module, spec.options)
   end
 
   -- Deprecated, notice given in core.cli when argument used
@@ -121,14 +113,14 @@ if SILE.masterFilename then
   end
 
   local main, err = xpcall(function()
-      return SILE.processFile(SILE.inputFile)
-    end, SILE.errorHandler)
+    return SILE.processFile(SILE.input.filename)
+  end, SILE.errorHandler)
 
   if not main then
     if type(err) == "string" and err:match(": interrupted!") then
       SILE.outputter:finish()
     else
-      io.stderr:write("\nError detected:\n\t"..err.."\n")
+      io.stderr:write("\nError detected:\n\t" .. tostring(err) .. "\n")
     end
     os.exit(1)
   end
diff --git a/tests/049EPH.xml b/tests/049EPH.xml
index 803eefefe..20ee14880 100644
--- a/tests/049EPH.xml
+++ b/tests/049EPH.xml
@@ -2,9 +2,9 @@
 <set parameter="linebreak.tolerance" value="5000"/>
 <set-counter id="footnote" display="alpha"/>
 <font language="en"/>
-<script src="packages/xmltricks"/>
-<script src="packages/frametricks"/>
-<script src="packages/raiselower"/>
+<use module="packages.xmltricks"/>
+<use module="packages.frametricks"/>
+<use module="packages.raiselower"/>
 <define command="bible:chapter-head"><bigskip/><font size="24pt"><process/></font></define>
 <define command="bible:verse-number"><raise height="3pt"><font size="9pt"><process/></font></raise></define>
 <xmltricks:ignore>style-id style-ide style-h style-h1 style-h2 style-h3</xmltricks:ignore>
diff --git a/tests/alignment.sil b/tests/alignment.sil
index 9fb82f651..f50f413a7 100644
--- a/tests/alignment.sil
+++ b/tests/alignment.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a6]{document}
 \nofolios
-\script[src=packages/lorem]
+\use[module=packages.lorem]
 \font[family=Libertinus Serif]
 \set[parameter=document.parskip,value=10pt]
 
diff --git a/tests/arabic-scripts.sil b/tests/arabic-scripts.sil
index c1b428a51..10ec342c1 100644
--- a/tests/arabic-scripts.sil
+++ b/tests/arabic-scripts.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a6]{document}
 \nofolios
-\script[src=packages/bidi]
+\use[module=packages.bidi]
 \font[family=LateefGR,size=16pt,language=en]
 Standard Arabic \font[direction=RTL,language=ar]{ههه};
 Sindi \font[direction=RTL,language=snd]{ههه};
diff --git a/tests/balanced.sil b/tests/balanced.sil
index 1611e1e0b..e98732ff2 100644
--- a/tests/balanced.sil
+++ b/tests/balanced.sil
@@ -1,11 +1,11 @@
 \begin[papersize=a5]{document}% KNOWNBAD
 \nofolios
-\script[src=packages/lorem]
-\script[src=packages/grid]
+\use[module=packages.lorem]
+\use[module=packages.grid]
 \grid[spacing=15pt]
 \set[parameter=document.parskip,value=6pt]
-\script[src=packages/frametricks]
-\script[src=packages/balanced-frames]
+\use[module=packages.frametricks]
+\use[module=packages.balanced-frames]
 \font[family=Libertinus Serif]
 \begin[first-content-frame=leftCol1]{pagetemplate}
 \frame[id=gutter,width=3%pw]
@@ -16,20 +16,20 @@
 \frame[id=rightCol2,left=right(gutter),right=right(content),top=top(leftCol2),bottom=bottom(content),width=width(leftCol2),balanced=1]
 \end{pagetemplate}
 
-But so that you may know that the Son of Man has authority on earth to forgive sins,”–he said to the paralytic– 
-“I tell you, stand up, take your stretcher, and go home.” 
+But so that you may know that the Son of Man has authority on earth to forgive sins,”–he said to the paralytic–
+“I tell you, stand up, take your stretcher, and go home.”
 And immediately the man stood up, took his stretcher, and went out in front of them all. They were all amazed and glorified God, saying, “We have never seen anything like this!”
 
-Jesus went out again by the sea. The whole crowd came to him, and he taught them. 
-As he went along, he saw Levi, the son of Alphaeus, sitting at the tax booth. “Follow me,” he said to him. And he got up and followed him. 
-As Jesus was having a meal in Levi’s home, many tax collectors and sinners were eating with Jesus and his disciples, for there were many who followed him. 
-When the experts in the law and the Pharisees saw that he was eating with sinners and tax collectors, they said to his disciples, “Why does he eat with tax collectors and sinners?” 
+Jesus went out again by the sea. The whole crowd came to him, and he taught them.
+As he went along, he saw Levi, the son of Alphaeus, sitting at the tax booth. “Follow me,” he said to him. And he got up and followed him.
+As Jesus was having a meal in Levi’s home, many tax collectors and sinners were eating with Jesus and his disciples, for there were many who followed him.
+When the experts in the law and the Pharisees saw that he was eating with sinners and tax collectors, they said to his disciples, “Why does he eat with tax collectors and sinners?”
 When Jesus heard this he said to them, “Those who are healthy don’t need a physician, but those who are sick do. I have not come to call the righteous, but sinners.”
 
-Now John’s disciples and the Pharisees were fasting. So they came to Jesus and said, “Why do the disciples of John and the disciples of the Pharisees fast, but your disciples don’t fast?” 
-Jesus said to them, “The wedding guests cannot fast while the bridegroom is with them, can they? As long as they have the bridegroom with them they do not fast. 
-But the days are coming when the bridegroom will be taken from them, and at that time they will fast. 
-No one sews a patch of unshrunk cloth on an old garment; otherwise, the patch pulls away from it, the new from the old, and the tear becomes worse. 
+Now John’s disciples and the Pharisees were fasting. So they came to Jesus and said, “Why do the disciples of John and the disciples of the Pharisees fast, but your disciples don’t fast?”
+Jesus said to them, “The wedding guests cannot fast while the bridegroom is with them, can they? As long as they have the bridegroom with them they do not fast.
+But the days are coming when the bridegroom will be taken from them, and at that time they will fast.
+No one sews a patch of unshrunk cloth on an old garment; otherwise, the patch pulls away from it, the new from the old, and the tear becomes worse.
 And no one pours new wine into old wineskins; otherwise, the wine will burst the skins, and both the wine and the skins will be destroyed. Instead new wine is poured into new wineskins.”
 \balancecolumns
 \hbox{}
@@ -37,14 +37,14 @@ And no one pours new wine into old wineskins; otherwise, the wine will burst the
 \center{\font[size=18pt,family=Libertinus Sans,weight=800]{Chapter 3}}
 \vfill\framebreak
 
-Then Jesus entered the synagogue again, and a man was there who had a withered hand. 
-They watched Jesus closely to see if he would heal him on the Sabbath, so that they could accuse him. 
-So he said to the man who had the withered hand, “Stand up among all these people.” 
-Then he said to them, “Is it lawful to do good on the Sabbath, or evil, to save a life or destroy it?” But they were silent. 
-After looking around at them in anger, grieved by the hardness of their hearts, he said to the man, “Stretch out your hand.” He stretched it out, and his hand was restored. 
+Then Jesus entered the synagogue again, and a man was there who had a withered hand.
+They watched Jesus closely to see if he would heal him on the Sabbath, so that they could accuse him.
+So he said to the man who had the withered hand, “Stand up among all these people.”
+Then he said to them, “Is it lawful to do good on the Sabbath, or evil, to save a life or destroy it?” But they were silent.
+After looking around at them in anger, grieved by the hardness of their hearts, he said to the man, “Stretch out your hand.” He stretched it out, and his hand was restored.
 So the Pharisees went out immediately and began plotting with the Herodians, as to how they could assassinate him.
 
-Then Jesus went away with his disciples to the sea, and a great multitude from Galilee followed him. And from Judea, 
-Jerusalem, Idumea, beyond the Jordan River, and around Tyre and Sidon a great multitude came to him when they heard about the things he had done. 
+Then Jesus went away with his disciples to the sea, and a great multitude from Galilee followed him. And from Judea,
+Jerusalem, Idumea, beyond the Jordan River, and around Tyre and Sidon a great multitude came to him when they heard about the things he had done.
 
 \end{document}
diff --git a/tests/bibtex.sil b/tests/bibtex.sil
index 5913bfab8..d50221a8a 100644
--- a/tests/bibtex.sil
+++ b/tests/bibtex.sil
@@ -1,7 +1,7 @@
 \begin[papersize=a6]{document}
 \neverindent
 \nofolios
-\script[src=packages/bibtex]
+\use[module=packages.bibtex]
 \language[main=en]
 \set[parameter=document.parskip, value=4pt]
 
diff --git a/tests/bidi.sil b/tests/bidi.sil
index 1d8329a88..1d6ab9bc2 100644
--- a/tests/bidi.sil
+++ b/tests/bidi.sil
@@ -4,7 +4,7 @@
 \set[parameter=document.baselineskip,value=29pt]
 \set[parameter=document.parindent,value=0pt]
 \set[parameter=current.parindent,value=0pt]
-\script[src=packages/frametricks]
+\use[module=packages.frametricks]
 My hovercraft is full of eels.
 
 הרחפת שלי מלאה בצלופחים
diff --git a/tests/bug-1005.sil b/tests/bug-1005.sil
index b59deb0f6..c6a8c5061 100644
--- a/tests/bug-1005.sil
+++ b/tests/bug-1005.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a6]{document}
 \nofolios
 \neverindent
-\script[src=packages/svg]
+\use[module=packages.svg]
 \svg[width=265pt, src=./tests/bug-1005.svg]
 \end{document}
diff --git a/tests/bug-1244-fallback-after-color.sil b/tests/bug-1244-fallback-after-color.sil
index 918fcedef..255ab51e9 100644
--- a/tests/bug-1244-fallback-after-color.sil
+++ b/tests/bug-1244-fallback-after-color.sil
@@ -1,8 +1,8 @@
 \begin[papersize=a6]{document}
 \nofolios
 \neverindent
-\script[src=packages/color-fonts]
-\script[src=packages/font-fallback]
+\use[module=packages.color-fonts]
+\use[module=packages.font-fallback]
 \font:add-fallback[family=Noto Serif CJK JP]
 んa
 
diff --git a/tests/bug-1259.sil b/tests/bug-1259.sil
index dcc6150e8..55f47dfa1 100644
--- a/tests/bug-1259.sil
+++ b/tests/bug-1259.sil
@@ -1,7 +1,7 @@
 \begin[papersize=a5]{document}
 \neverindent
 \nofolios
-\script[src=packages/rules]
+\use[module=packages.rules]
 
 Have some \underline{underlining} relative to font.
 
diff --git a/tests/bug-132.sil b/tests/bug-132.sil
index f6ea32a0a..fefff7fe9 100644
--- a/tests/bug-132.sil
+++ b/tests/bug-132.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a6]{document}
 \nofolios
-\script[src=packages/verbatim]
+\use[module=packages.verbatim]
 \define[command=verbatim:font]{\font[family=Hack]}
 \font[family=Hack]
 \begin{verbatim}
diff --git a/tests/bug-1320.sil b/tests/bug-1320.sil
index 7b12b0340..285d12f93 100644
--- a/tests/bug-1320.sil
+++ b/tests/bug-1320.sil
@@ -1,7 +1,7 @@
 \begin[papersize=a6]{document}
 \neverindent
 \nofolios
-\script[src=packages/rules]
+\use[module=packages.rules]
 \language[main=und]% Disable hyphenation to have shrinked§stretched lines easily
 
 % Manually forced stretch
diff --git a/tests/bug-1321.sil b/tests/bug-1321.sil
index a6568901c..c9ecfdf16 100644
--- a/tests/bug-1321.sil
+++ b/tests/bug-1321.sil
@@ -1,7 +1,7 @@
 \begin[direction=RTL,papersize=a7]{document}
 \nofolios
 \neverindent
-\script[src=packages/footnotes]
+\use[module=packages.footnotes]
 \font[family=Amiri]
 عَرَبي\footnote{عَرَبي}
 \begin{script}
diff --git a/tests/bug-1343-dotfill-stretch.sil b/tests/bug-1343-dotfill-stretch.sil
index e0eaeb57f..265b76f81 100644
--- a/tests/bug-1343-dotfill-stretch.sil
+++ b/tests/bug-1343-dotfill-stretch.sil
@@ -1,7 +1,7 @@
 \begin[papersize=a5]{document}
 \nofolios
-\script[src=packages/leaders]
-\script[src=packages/lorem]
+\use[module=packages.leaders]
+\use[module=packages.lorem]
 \lorem[words=15]\par% for visual comparison dotfill vs. justified text line
 |\hfill|\par% for visual comparison dotfill vs. hfill
 |\dotfill|\par
diff --git a/tests/bug-214.sil b/tests/bug-214.sil
index 997522e7c..347a82a21 100644
--- a/tests/bug-214.sil
+++ b/tests/bug-214.sil
@@ -2,7 +2,7 @@
 \nofolios
 % We are testing that the footnote's top skip (separator) gets retained
 % and that the main content doesn't overflow its frame.
-\script[src=packages/rules]
+\use[module=packages.rules]
 \footnote:separator{\noindent\hrule[width=20pt]\skip[height=12pt]}
 Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
 tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
diff --git a/tests/bug-243.sil b/tests/bug-243.sil
index 329573fbf..bf1377f40 100644
--- a/tests/bug-243.sil
+++ b/tests/bug-243.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a5]{document}
 \nofolios
-\script[src=packages/leaders]
+\use[module=packages.leaders]
 \font[size=50pt]
 \set[parameter=typesetter.parfillskip,value=0pt]
 F\dotfill{}bar\par
diff --git a/tests/bug-248.sil b/tests/bug-248.sil
index 88963d08a..f3906e52b 100644
--- a/tests/bug-248.sil
+++ b/tests/bug-248.sil
@@ -6,8 +6,8 @@
 % The troublesome \makecolumns & \mergecolumns are commented out until
 % this stops killing the rest of our CI runners' work.
 % --------------
-\script[src=packages/frametricks]
-\script[src=packages/lorem]
+\use[module=packages.frametricks]
+\use[module=packages.lorem]
 \nofolios
 \font[family=Libertinus Serif,size=10pt]
 \set[parameter=document.baselineskip,value=1.3em]
diff --git a/tests/bug-252.sil b/tests/bug-252.sil
index a569b77a0..3c1f8e81f 100644
--- a/tests/bug-252.sil
+++ b/tests/bug-252.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a7,class=book]{document}
-\script[src=packages/frametricks]
-\script[src=packages/lorem]
+\use[module=packages.frametricks]
+\use[module=packages.lorem]
 This is a test\footnote{What happens to frames?}.
 This is a test\footnote{What happens to frames?}.
 This is a test\footnote{What happens to frames?}.
diff --git a/tests/bug-252a.sil b/tests/bug-252a.sil
index 1023b6baf..b9a4c8840 100644
--- a/tests/bug-252a.sil
+++ b/tests/bug-252a.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a7,class=book]{document}
-\script[src=packages/lorem]
-\script[src=packages/rebox]
+\use[module=packages.lorem]
+\use[module=packages.rebox]
 \footnote:separator{\skip[height=5pt]}
 \define[command=deepbox]{\rebox[depth=60pt,height=10pt]{x}}
 This is a test\footnote{\deepbox}.
diff --git a/tests/bug-255.sil b/tests/bug-255.sil
index ed3c96e52..bf9a2d019 100644
--- a/tests/bug-255.sil
+++ b/tests/bug-255.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a6]{document}
-\script[src=packages/frametricks]
+\use[module=packages.frametricks]
 \script{
   SILE.registerCommand("donothing", function (options, content)
     SILE.settings:pushState()
diff --git a/tests/bug-255b.sil b/tests/bug-255b.sil
index ffe6526e7..6c0293fe1 100644
--- a/tests/bug-255b.sil
+++ b/tests/bug-255b.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a6]{document}
-\script[src=packages/frametricks]
+\use[module=packages.frametricks]
 \script{
 SILE.registerCommand("donothing", function(o,c)
   SILE.settings:pushState()
diff --git a/tests/bug-262.sil b/tests/bug-262.sil
index e9f0a0c57..091d21bbd 100644
--- a/tests/bug-262.sil
+++ b/tests/bug-262.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a5]{document}
 \nofolios
-\script[src=packages/lorem]
+\use[module=packages.lorem]
 \hbox
 \begin[first-content-frame=a]{pagetemplate}
   \frame[id=a,left=2%pw,right=44%pw,top=2%ph,bottom=98%ph,next=b]
diff --git a/tests/bug-264b.sil b/tests/bug-264b.sil
index 3c1514110..ecaef722e 100644
--- a/tests/bug-264b.sil
+++ b/tests/bug-264b.sil
@@ -1,7 +1,7 @@
 \begin[papersize=a5,class=book]{document}
 % Note: The failure case for this involves an infinite loop when it
 % erroniously matched penalty types that aren�t actually page breaks
-\script[src=packages/linespacing]
+\use[module=packages.linespacing]
 \set[parameter=linespacing.method,value=fit-font]
 \set[parameter=linespacing.fit-font.extra-space,value=15pt plus 0.5pt minus 0.5pt]
 x\skip[height=385pt].\footnote{Matthew 24:13}
diff --git a/tests/bug-269.sil b/tests/bug-269.sil
index 6d4bcf193..091780cde 100644
--- a/tests/bug-269.sil
+++ b/tests/bug-269.sil
@@ -1,7 +1,7 @@
 \begin[papersize=a6,class=book]{document}
 \nofolios
 \font[family=Gentium Plus,size=10.1pt,language=tr]
-\script[src=packages/footnotes]
+\use[module=packages.footnotes]
 Müjde, Hristiyan bireylere ve kiliseye verilmiş en büyük hazinedir.
 Müjde, Hristiyan bireylere ve kiliseye verilmiş en büyük hazinedir.
 Müjde, birçok şeyin arasındaki herhangi bir mesaj değildir, her şeyin
diff --git a/tests/bug-301.sil b/tests/bug-301.sil
index 68c4643f9..379286497 100644
--- a/tests/bug-301.sil
+++ b/tests/bug-301.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a7,class=book]{document}
-\script[src=packages/linespacing]
+\use[module=packages.linespacing]
 \nofolios
 \font[family=Gentium Plus,size=9.5pt]
 \set[parameter=linespacing.method,value=fit-font]
diff --git a/tests/bug-303b.sil b/tests/bug-303b.sil
index a4bcc5abf..f4fc092b2 100644
--- a/tests/bug-303b.sil
+++ b/tests/bug-303b.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a5,class=book]{document}
 \nofolios
-\script[src=packages/lorem]
+\use[module=packages.lorem]
 \begin{script}
 SILE.registerCommand("section", function (options, content)
   SILE.typesetter:leaveHmode()
diff --git a/tests/bug-308.sil b/tests/bug-308.sil
index 8b6ad2112..7127c1f1e 100644
--- a/tests/bug-308.sil
+++ b/tests/bug-308.sil
@@ -1,6 +1,6 @@
 \begin[class=book,papersize=a6]{document}
 \nofolios
-\script[src=packages/frametricks]
+\use[module=packages.frametricks]
 \begin[first-content-frame=column1]{pagetemplate}
   \frame[id=column1,left=left(content),top=top(content),bottom=bottom(content),width=53pt]
 \end{pagetemplate}
diff --git a/tests/bug-317.sil b/tests/bug-317.sil
index 23421b31b..dc0a729c2 100644
--- a/tests/bug-317.sil
+++ b/tests/bug-317.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a6]{document}
 \nofolios
-\script[src=packages/verbatim]
+\use[module=packages.verbatim]
 \define[command=verbatim:font]{\set[parameter=font.family,value=Hack]}
 \begin{verbatim}
 pseudocode \{ here \}
diff --git a/tests/bug-342.sil b/tests/bug-342.sil
index 020a9e373..ffae2d8ca 100644
--- a/tests/bug-342.sil
+++ b/tests/bug-342.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a5,class=book]{document}% KNOWNBAD
-\script[src=packages/ifattop]
-\script[src=packages/lorem]
+\use[module=packages.ifattop]
+\use[module=packages.lorem]
 \nofolios
 
 One, two. Buckle my shoe.
diff --git a/tests/bug-344.sil b/tests/bug-344.sil
index 8069985fe..22a5388ae 100644
--- a/tests/bug-344.sil
+++ b/tests/bug-344.sil
@@ -4,7 +4,7 @@
 
 % Note this is not specific to using non-default linespacing methods,
 % this MWE shows the same problem with or without these lines
-\script[src=packages/linespacing]
+\use[module=packages.linespacing]
 \set[parameter=linespacing.method,value=fit-font]
 \set[parameter=linespacing.fit-font.extra-space,value=6pt]
 
diff --git a/tests/bug-353.sil b/tests/bug-353.sil
index c129f41db..636cb60e5 100644
--- a/tests/bug-353.sil
+++ b/tests/bug-353.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a5]{document}
-\script[src=packages/background]
+\use[module=packages.background]
 \nofolios
 \background[color=#e9d8ba]
 \color[color=#5a4129]{Sepia baby.}
diff --git a/tests/bug-39.sil b/tests/bug-39.sil
index 708bc2c65..87f1c6697 100644
--- a/tests/bug-39.sil
+++ b/tests/bug-39.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a5,class=book]{document}
-\script[src=packages/frametricks]
+\use[module=packages.frametricks]
 \right-running-head{First Line of Header\skip[height=8mm] Second Line of Header}
 \showframe[id=all]
 
diff --git a/tests/bug-420.sil b/tests/bug-420.sil
index 198a0fac3..f16e3e4c2 100644
--- a/tests/bug-420.sil
+++ b/tests/bug-420.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a5,class=book]{document}
-\script[src=packages/lorem]
-\script[src=packages/frametricks]
+\use[module=packages.lorem]
+\use[module=packages.frametricks]
 \nofolios
 \font[size=16pt]
 
diff --git a/tests/bug-524.sil b/tests/bug-524.sil
index 95e1073ab..ed2381bc6 100644
--- a/tests/bug-524.sil
+++ b/tests/bug-524.sil
@@ -2,7 +2,7 @@
 \nofolios
 \neverindent
 \show-hanmen
-\script[src=packages/ruby]
+\use[module=packages.ruby]
 私は
 
 {}私は
diff --git a/tests/bug-525.sil b/tests/bug-525.sil
index b64da8490..600f37cf1 100644
--- a/tests/bug-525.sil
+++ b/tests/bug-525.sil
@@ -1,7 +1,7 @@
 \begin[class=jplain,papersize=a6]{document}
 \nofolios
 \neverindent
-\script[src=packages/ruby]
+\use[module=packages.ruby]
 \ruby[reading=日本語]{私}\ruby[reading=Latin]{私}
 
 \ruby[reading=Latin]{私}\ruby[reading=日本語]{私}
diff --git a/tests/bug-526.sil b/tests/bug-526.sil
index 6f3c5aa2a..63395abe4 100644
--- a/tests/bug-526.sil
+++ b/tests/bug-526.sil
@@ -1,7 +1,7 @@
 \begin[class=plain,papersize=a6]{document}
 \nofolios
-\script[src=packages/frametricks]
-\script[src=packages/lorem]
+\use[module=packages.frametricks]
+\use[module=packages.lorem]
 \begin[first-content-frame=leftCol]{pagetemplate}
   \frame[id=leftCol,left=left(content),right=50%pw,top=top(content),bottom=bottom(content),next=rightCol]
   \frame[id=rightCol,left=right(leftCol),top=top(content),bottom=bottom(content),width=width(leftCol)]
diff --git a/tests/bug-530.sil b/tests/bug-530.sil
index af5ef3991..40ab0f816 100644
--- a/tests/bug-530.sil
+++ b/tests/bug-530.sil
@@ -1,5 +1,5 @@
 \begin[direction=RTL]{document}
-\script[src=packages/lorem]
+\use[module=packages.lorem]
 
 \lorem
 
diff --git a/tests/bug-556.sil b/tests/bug-556.sil
index cf7a6aa24..99460bd1f 100644
--- a/tests/bug-556.sil
+++ b/tests/bug-556.sil
@@ -1,5 +1,5 @@
 \begin[class=triglot]{document}
-\script[src=packages/pdf]
+\use[module=packages.pdf]
 \left
 \pdf:bookmark[title=test,dest=test]
 \sync
diff --git a/tests/bug-556b.sil b/tests/bug-556b.sil
index 04de77c25..5173f0c7d 100644
--- a/tests/bug-556b.sil
+++ b/tests/bug-556b.sil
@@ -1,5 +1,5 @@
 \begin[class=triglot]{document}
-\script[src=packages/pdf]
+\use[module=packages.pdf]
 \left
 \pdf:destination[name=test]
 \sync
diff --git a/tests/bug-61.sil b/tests/bug-61.sil
index 350266ac2..8a4efd80a 100644
--- a/tests/bug-61.sil
+++ b/tests/bug-61.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a6,class=book]{document}
-\script[src=packages/frametricks]
+\use[module=packages.frametricks]
 \begin[first-content-frame=frame1]{pagetemplate}
 \frame[id=frame1,left=left(content),right=right(content),height=18pt,top=top(content),next=frame2]
 \frame[id=frame2,left=left(frame1),right=right(frame1),top=bottom(frame1),bottom=bottom(content)]
diff --git a/tests/bug-723.sil b/tests/bug-723.sil
index 4d5913cb4..e7ac10a04 100644
--- a/tests/bug-723.sil
+++ b/tests/bug-723.sil
@@ -17,7 +17,7 @@
 \hrule[width=1em,height=2em]
 \par
 \end{define}
-\script[src=packages/rules]
+\use[module=packages.rules]
 \dots\framebreak
 \dots\framebreak
 \dots\framebreak
diff --git a/tests/bug-76.sil b/tests/bug-76.sil
index 350b4b4c8..dc7bdd0bc 100644
--- a/tests/bug-76.sil
+++ b/tests/bug-76.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a6]{document}
 \nofolios
-\script[src=packages/bidi]
+\use[module=packages.bidi]
 \thisframeRTL
 \font[family=Amiri,language=urd,direction=RTL,script=Arab]
 \font[size=18pt]
diff --git a/tests/bug-859.sil b/tests/bug-859.sil
index af4ef8d49..51cf64f14 100644
--- a/tests/bug-859.sil
+++ b/tests/bug-859.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a6]{document}
 \nofolios
-\script[src=packages/leaders]
+\use[module=packages.leaders]
 \set[parameter=typesetter.parfillskip,value=0pt]
 \noindent\font[size=20pt]{A\dotfill{}A}\medskip
 \noindent\font[size=10pt]{DEF\dotfill{}10}\medskip
diff --git a/tests/bug-865.sil b/tests/bug-865.sil
index 418a8fe90..8b2bb3012 100644
--- a/tests/bug-865.sil
+++ b/tests/bug-865.sil
@@ -1,7 +1,7 @@
 \begin[papersize=a6]{document}
 \nofolios
-\script[src=packages/pullquote]
-\script[src=packages/lorem]
+\use[module=packages.pullquote]
+\use[module=packages.lorem]
 \lorem[words=12]
 
 \begin[author=Joe Smith]{pullquote}% remove this comment to also test 1020
diff --git a/tests/bug-915.sil b/tests/bug-915.sil
index e0fa71cab..db52b8b7d 100644
--- a/tests/bug-915.sil
+++ b/tests/bug-915.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a6,class=jbook]{document}
-\script[src=packages/frametricks]
+\use[module=packages.frametricks]
 \noindent
 \nofolios
 よ
diff --git a/tests/bug-926.sil b/tests/bug-926.sil
index 040aafd97..15b355e3d 100644
--- a/tests/bug-926.sil
+++ b/tests/bug-926.sil
@@ -2,7 +2,7 @@
 \nofolios
 \neverindent
 \language[main=en]
-\script[src=packages/ruby]
+\use[module=packages.ruby]
 \ruby[reading=日本語]{私}\ruby[reading=Latin]{私}
 
 \ruby[reading=Latin]{私}\ruby[reading=日本語]{私}
diff --git a/tests/bug-979.sil b/tests/bug-979.sil
index b471b0646..b9a1fe4df 100644
--- a/tests/bug-979.sil
+++ b/tests/bug-979.sil
@@ -1,7 +1,7 @@
 \begin[papersize=a6]{document}
 \nofolios
 \noindent
-\script[src=packages/unichar]
+\use[module=packages.unichar]
 \font[family=Symbola]
 \raggedright{
 â\break
diff --git a/tests/centering.sil b/tests/centering.sil
index de536b1cc..702321fff 100644
--- a/tests/centering.sil
+++ b/tests/centering.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a5]{document}
-\script[src=packages/lorem]
+\use[module=packages.lorem]
 
 \ragged[right=true,left=true]{\lorem[words=100]}
 
diff --git a/tests/color-fonts.sil b/tests/color-fonts.sil
index 1d298d9bc..9ad86f640 100644
--- a/tests/color-fonts.sil
+++ b/tests/color-fonts.sil
@@ -1,10 +1,10 @@
 \begin[class=book,papersize=129mm x 198mm]{document}% KNOWNBAD
-\script[src=packages/color-fonts]
+\use[module=packages.color-fonts]
 \bidi-on
 \font[filename=tests/TestCLR-Regular.ttf]
 a☺a
 
-a☺🏻a 
+a☺🏻a
 
-ع☺🏻ع 
+ع☺🏻ع
 \end{document}
diff --git a/tests/counters.sil b/tests/counters.sil
index 344eb147c..d52c4b906 100644
--- a/tests/counters.sil
+++ b/tests/counters.sil
@@ -2,7 +2,7 @@
 \nofolios
 \set[parameter=document.parindent,value=0pt]
 \font[family=Libertinus Serif]
-\script[src=packages/counters]
+\use[module=packages.counters]
 \set-counter[id=test,value=1234]
 
 Default: \show-counter[id=test]
diff --git a/tests/eject.sil b/tests/eject.sil
index 06e34bc76..970473ed3 100644
--- a/tests/eject.sil
+++ b/tests/eject.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a5]{document}
-\script[src=packages/lorem]
+\use[module=packages.lorem]
 \pagetemplate[first-content-frame=a]{
 \frame[id=a,top=5%ph,bottom=95%ph,next=b,left=5%pw,right=28%pw]
 \frame[id=b,top=5%ph,bottom=95%ph,next=c,left=33%pw,right=62%pw]
diff --git a/tests/feat-1092-raw.sil b/tests/feat-1092-raw.sil
index 405707c66..cc36d2ef0 100644
--- a/tests/feat-1092-raw.sil
+++ b/tests/feat-1092-raw.sil
@@ -1,7 +1,7 @@
 \begin[papersize=a6]{document}
 \nofolios
 \neverindent
-\script[src=packages/svg]
+\use[module=packages.svg]
 % Test Case 001: Direct use of a raw SVG.
 \raw[type=svg, height=1em]{<?xml version="1.0"?>
 <svg viewBox="0 0 319.99756 192.49629"><path d="M 160.36936,64.6995 287.47181,192.49629 319.99756,160.32293 160.62175,0 0,159.07464 32.273754,191.50079 Z"/></svg>}
diff --git a/tests/feat-1365-lists-alternate.sil b/tests/feat-1365-lists-alternate.sil
index dc4323d02..87709ad6c 100644
--- a/tests/feat-1365-lists-alternate.sil
+++ b/tests/feat-1365-lists-alternate.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a6, class=book]{document}
-\script[src=packages/lists]
+\use[module=packages.lists]
 \font[family=Libertinus Serif]% Default Gentium lacks the white circle.
 \nofolios
 \neverindent
diff --git a/tests/feat-1365-lists-compact.sil b/tests/feat-1365-lists-compact.sil
index 01b6a55b4..3040fae45 100644
--- a/tests/feat-1365-lists-compact.sil
+++ b/tests/feat-1365-lists-compact.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a6, class=book]{document}
-\script[src=packages/lists]
+\use[module=packages.lists]
 \font[family=Libertinus Serif]% Default Gentium lacks the white circle.
 \nofolios
 \neverindent
diff --git a/tests/feat-1365-lists-footnote.sil b/tests/feat-1365-lists-footnote.sil
index 7119f9728..5418482a7 100644
--- a/tests/feat-1365-lists-footnote.sil
+++ b/tests/feat-1365-lists-footnote.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a6, class=book]{document}
-\script[src=packages/lists]
+\use[module=packages.lists]
 \font[family=Libertinus Serif]% Default Gentium lacks the white circle.
 \nofolios
 \neverindent
diff --git a/tests/feat-875-dotfill-alignment.sil b/tests/feat-875-dotfill-alignment.sil
index 431978e4b..cb3c3d8a6 100644
--- a/tests/feat-875-dotfill-alignment.sil
+++ b/tests/feat-875-dotfill-alignment.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a5]{document}
-\script[src=packages/leaders]
+\use[module=packages.leaders]
 \nofolios
 \neverindent
 % Test case: vertical alignment for \dotfill
diff --git a/tests/feat-875-leaders-alignment.sil b/tests/feat-875-leaders-alignment.sil
index a9defd557..fda2cdc09 100644
--- a/tests/feat-875-leaders-alignment.sil
+++ b/tests/feat-875-leaders-alignment.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a5]{document}
-\script[src=packages/leaders]
+\use[module=packages.leaders]
 \nofolios
 \neverindent
 % Test case: vertical alignement for \leaders with custom pattern
diff --git a/tests/feat-875-leaders-fixed-alignment.sil b/tests/feat-875-leaders-fixed-alignment.sil
index cade1a672..6878f0acb 100644
--- a/tests/feat-875-leaders-fixed-alignment.sil
+++ b/tests/feat-875-leaders-fixed-alignment.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a5]{document}
-\script[src=packages/leaders]
+\use[module=packages.leaders]
 \nofolios
 \neverindent
 % Test case: \leaders[width=...] with a given fixed width.
diff --git a/tests/feat-fullrule-hfillrule.sil b/tests/feat-fullrule-hfillrule.sil
index 155a75d5a..2ba797163 100644
--- a/tests/feat-fullrule-hfillrule.sil
+++ b/tests/feat-fullrule-hfillrule.sil
@@ -1,7 +1,7 @@
 \begin[papersize=a6]{document}
 \nofolios
 \neverindent
-\script[src=packages/rules]
+\use[module=packages.rules]
 
 Default\hrulefill
 
diff --git a/tests/font-fallback-empty.sil b/tests/font-fallback-empty.sil
index 8020a04ba..a6dfbd7ee 100644
--- a/tests/font-fallback-empty.sil
+++ b/tests/font-fallback-empty.sil
@@ -1,7 +1,7 @@
 \begin[papersize=a6]{document}
 \nofolios
 \noindent
-\script[src=packages/font-fallback]
+\use[module=packages.font-fallback]
 \font:add-fallback[family=Noto Serif CJK JP]
 \font:remove-fallback
 ん
diff --git a/tests/font-features-cvXX.sil b/tests/font-features-cvXX.sil
index 14f6ac69f..de0b9826d 100644
--- a/tests/font-features-cvXX.sil
+++ b/tests/font-features-cvXX.sil
@@ -1,7 +1,7 @@
 \begin[papersize=a6]{document}
 \nofolios
 \neverindent
-\script[src=packages/features]
+\use[module=packages.features]
 \set[parameter=harfbuzz.subshapers,value=ot]
 һ ồ
 
diff --git a/tests/font-features.sil b/tests/font-features.sil
index d815044ec..f63739fd5 100644
--- a/tests/font-features.sil
+++ b/tests/font-features.sil
@@ -1,7 +1,7 @@
 \begin[papersize=a6]{document}
 \neverindent
 \nofolios
-\script[src=packages/features]
+\use[module=packages.features]
 \set[parameter=harfbuzz.subshapers,value=ot]
 \font[Letters=SmallCaps]{SmallCaps}
 
diff --git a/tests/footnote-skip.sil b/tests/footnote-skip.sil
index 63bed7dc1..104cee520 100644
--- a/tests/footnote-skip.sil
+++ b/tests/footnote-skip.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a5,class=book]{document}
-\script[src=packages/lorem]
-\script[src=packages/rules]
+\use[module=packages.lorem]
+\use[module=packages.rules]
 \begin{script}
 SILE.scratch.insertions.classes.footnote.topSkip = SILE.length("5pt")
 SILE.scratch.insertions.classes.footnote.interInsertionSkip = SILE.length("24pt")
diff --git a/tests/footnote.sil b/tests/footnote.sil
index fce27911a..53432aec3 100644
--- a/tests/footnote.sil
+++ b/tests/footnote.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a6,class=book]{document}
 \nofolios
-\script[src=packages/lorem]
+\use[module=packages.lorem]
 \font[family=Gentium Plus,size=10pt]
 \footnote:separator{\em{Separator}\skip[height=5mm]}
 \footnote:options[interInsertionSkip=3mm]
diff --git a/tests/footnotes-twocols.sil b/tests/footnotes-twocols.sil
index 01625ac7c..e877e52b3 100644
--- a/tests/footnotes-twocols.sil
+++ b/tests/footnotes-twocols.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a5,class=testtwocol]{document}% KNOWNBAD
-\script[src=packages/lorem]
+\use[module=packages.lorem]
 \set[parameter=document.baselineskip,value=15pt plus 2pt]
 \font[size=13pt]
 ABC \lorem[words=90]
diff --git a/tests/frametricks.sil b/tests/frametricks.sil
index c35323f4a..969f193e3 100644
--- a/tests/frametricks.sil
+++ b/tests/frametricks.sil
@@ -1,5 +1,5 @@
 \begin[papersize=129mm x 198mm]{document}% KNOWNBAD https://github.com/sile-typesetter/sile/issues/740
-\script[src=packages/frametricks]
+\use[module=packages.frametricks]
 \makecolumns[columns=2]
 \font[size=15pt]
 \set[parameter=document.baselineskip,value=3ex]
diff --git a/tests/grid.sil b/tests/grid.sil
index 95763b5ac..b5bdbd28f 100644
--- a/tests/grid.sil
+++ b/tests/grid.sil
@@ -1,7 +1,7 @@
 \begin[papersize=a5,class=book]{document}
-\script[src=packages/lorem]
-\script[src=packages/grid]
-\script[src=packages/frametricks]
+\use[module=packages.lorem]
+\use[module=packages.grid]
+\use[module=packages.frametricks]
 \set[parameter=document.parskip,value=0pt]
 \showframe
 \grid[spacing=20pt]
diff --git a/tests/hangafter.sil b/tests/hangafter.sil
index 37f0fc880..72e36f057 100644
--- a/tests/hangafter.sil
+++ b/tests/hangafter.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a6]{document}
 \nofolios
-\script[src=packages/lorem]
+\use[module=packages.lorem]
 \set[parameter=document.parskip,value=1ex]
 \begin{script}
 
diff --git a/tests/ifattop.sil b/tests/ifattop.sil
index 302d52bfd..8ed7cb4ec 100644
--- a/tests/ifattop.sil
+++ b/tests/ifattop.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a5]{document}
-\script[src=packages/ifattop]
+\use[module=packages.ifattop]
 \ifattop{Hello, top of frame.}
 
 \ifnotattop{Hello, middle of frame.}
diff --git a/tests/image.sil b/tests/image.sil
index fd77c22d1..ea50fbd0f 100644
--- a/tests/image.sil
+++ b/tests/image.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a5]{document}
-\script[src=packages/image]
+\use[module=packages.image]
 One two \img[src=documentation/fig1.png,width=50] three
 
 \bigskip
diff --git a/tests/inputter-args-from-declarative.sil b/tests/inputter-args-from-declarative.sil
new file mode 100644
index 000000000..64fce4786
--- /dev/null
+++ b/tests/inputter-args-from-declarative.sil
@@ -0,0 +1,14 @@
+\begin[papersize=a6]{document}
+\begin[myopt=myval]{lua}
+local base = require("packages.base")
+local foo = pl.class(base)
+foo._name = "foo"
+
+function foo:_init (args)
+	SU.dump(args)
+end
+
+return foo
+\end{lua}
+foo
+\end{document}
diff --git a/tests/insertions-sidenotes.sil b/tests/insertions-sidenotes.sil
index 11887b45f..0b1eaf2df 100644
--- a/tests/insertions-sidenotes.sil
+++ b/tests/insertions-sidenotes.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a7,class=testsidenote]{document}
-\script[src=packages/rules]
+\use[module=packages.rules]
 
 \hrule[width=2pt, height=40pt] \footnote{fn 1}
 \skip[height=90pt]
diff --git a/tests/linespacing.sil b/tests/linespacing.sil
index 5b248d64a..e90724b75 100644
--- a/tests/linespacing.sil
+++ b/tests/linespacing.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a6]{document}
-\script[src=packages/lorem]
+\use[module=packages.lorem]
 \nofolios
 \font[family=Libertinus Serif,size=12pt]
 Small text
diff --git a/tests/linespacing2.sil b/tests/linespacing2.sil
index 47a568125..a56580c73 100644
--- a/tests/linespacing2.sil
+++ b/tests/linespacing2.sil
@@ -1,8 +1,8 @@
 \begin{document}
 \nofolios
 \font[family=Libertinus Serif,size=11pt]
-\script[src=packages/linespacing]
-\script[src=packages/frametricks]\showframe
+\use[module=packages.linespacing]
+\use[module=packages.frametricks]\showframe
 \set[parameter=linespacing.minimumfirstlineposition,value=50pt]
 First baseline should be positioned 50 points from the top of the frame.
 
diff --git a/tests/malayalam.sil b/tests/malayalam.sil
index 703db3578..6d9cfc79b 100644
--- a/tests/malayalam.sil
+++ b/tests/malayalam.sil
@@ -1,5 +1,5 @@
 \begin[class=book]{document}
-\script[src=packages/frametricks]
+\use[module=packages.frametricks]
 \begin[first-content-frame=column1]{pagetemplate}
   \frame[id=gutter,width=3%]
   \frame[id=title, top=top(content), bottom=top(title)+5%, left=left(content), right=right(content),next=column1]
diff --git a/tests/masters.sil b/tests/masters.sil
index 179e70921..3455d3642 100644
--- a/tests/masters.sil
+++ b/tests/masters.sil
@@ -1,7 +1,7 @@
 \begin{document}
-\script[src=packages/masters]
-\script[src=packages/lorem]
-\script[src=packages/frametricks]
+\use[module=packages.masters]
+\use[module=packages.lorem]
+\use[module=packages.frametricks]
 \define-master-template[id=title,first-content-frame=t]{
 \frame[id=gutter,width=3%pw]
 \frame[id=t,next=l,left=5%pw,right=95%pw,top=3%ph,bottom=15%ph,next=l]
diff --git a/tests/math-bigops.xml b/tests/math-bigops.xml
index 689342055..e3d55fe67 100644
--- a/tests/math-bigops.xml
+++ b/tests/math-bigops.xml
@@ -1,6 +1,5 @@
 <sile>
-
-<script src="packages/math" />
+<use module="packages.math"/>
 
 Big operators, text, MathML:
 <mathml>
diff --git a/tests/math-fractions.xml b/tests/math-fractions.xml
index 368ea6b96..64e6e347d 100644
--- a/tests/math-fractions.xml
+++ b/tests/math-fractions.xml
@@ -1,5 +1,5 @@
 <sile>
-<script src="packages/math" />
+<use module="packages.math"/>
 
 Fractions, text, MathML:
 <mathml>
diff --git a/tests/math-macros.sil b/tests/math-macros.sil
index 2f4e4bc27..ded711f96 100644
--- a/tests/math-macros.sil
+++ b/tests/math-macros.sil
@@ -1,5 +1,5 @@
 \begin[class=plain]{document}
-\script[src=packages/math]
+\use[module=packages.math]
 
 \begin[mode=display]{math}
   \def{ndiff}{#1^{(#2)}}
diff --git a/tests/math-stretchy.xml b/tests/math-stretchy.xml
index 5ccde1a1b..2dbb47547 100644
--- a/tests/math-stretchy.xml
+++ b/tests/math-stretchy.xml
@@ -1,6 +1,5 @@
 <sile>
-
-<script src="packages/math" />
+<use module="packages.math"/>
 
 Stretchy parentheses, text, MathML:
 <mathml>
diff --git a/tests/math-subsup.xml b/tests/math-subsup.xml
index f89e2be8a..0b5bb7663 100644
--- a/tests/math-subsup.xml
+++ b/tests/math-subsup.xml
@@ -1,5 +1,5 @@
 <sile>
-<script src="packages/math" />
+<use module="packages.math"/>
 
 Sub-superscripts, text, MathML:
 <mathml>
diff --git a/tests/math-tables-mathml.xml b/tests/math-tables-mathml.xml
index ff4a59962..79ecabb10 100644
--- a/tests/math-tables-mathml.xml
+++ b/tests/math-tables-mathml.xml
@@ -1,6 +1,5 @@
 <sile>
-
-<script src="packages/math" />
+<use module="packages.math"/>
 
 Number matrix, text, MathML:
 <mathml>
diff --git a/tests/math-tables-tex.sil b/tests/math-tables-tex.sil
index c48c5c1ad..e22730bad 100644
--- a/tests/math-tables-tex.sil
+++ b/tests/math-tables-tex.sil
@@ -1,6 +1,6 @@
 \begin[class=plain]{document}
 
-\script[src=packages/math]
+\use[module=packages.math]
 
 Number matrix, text, TeX:
 \begin{math}
diff --git a/tests/math-variants.xml b/tests/math-variants.xml
index 4c58bd216..832a456a7 100644
--- a/tests/math-variants.xml
+++ b/tests/math-variants.xml
@@ -1,5 +1,5 @@
 <sile>
-<script src="packages/math" />
+<use module="packages.math"/>
 
 Math variants, text, MathML:
 <mathml>
diff --git a/tests/mini-arabic.sil b/tests/mini-arabic.sil
index 678cff809..039b64745 100644
--- a/tests/mini-arabic.sil
+++ b/tests/mini-arabic.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a6]{document}
 \nofolios
-\script[src=packages/bidi]
+\use[module=packages.bidi]
 \thisframeRTL
 \font[family=Amiri,language=urd,script=Arab,size=14pt]
 1955 میں ان کا انتقال ہوا۔
diff --git a/tests/mlcounter.sil b/tests/mlcounter.sil
index 43c300dff..f0c62a1ea 100644
--- a/tests/mlcounter.sil
+++ b/tests/mlcounter.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a5]{document}
-\script[src=packages/counters]
+\use[module=packages.counters]
 
 \increment-multilevel-counter[id=test]
 \show-multilevel-counter[id=test] Aa
diff --git a/tests/openpaths.sil b/tests/openpaths.sil
index f6b13b55e..52cf124a5 100644
--- a/tests/openpaths.sil
+++ b/tests/openpaths.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a6]{document}
 \nofolios
 \neverindent
-\script[src=packages/svg]
+\use[module=packages.svg]
 \svg[width=265pt, src=./tests/openpaths.svg]
 \end{document}
diff --git a/tests/parshaping-simple.sil b/tests/parshaping-simple.sil
index a9df804be..1f7e8ebfc 100644
--- a/tests/parshaping-simple.sil
+++ b/tests/parshaping-simple.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a7]{document}
 \nofolios
-\script[src=packages/lorem]
+\use[module=packages.lorem]
 \begin{script}
 
 local testshape = {
diff --git a/tests/pdf.sil b/tests/pdf.sil
index dbd218c21..f088accb7 100644
--- a/tests/pdf.sil
+++ b/tests/pdf.sil
@@ -1,5 +1,5 @@
 \begin[class=book]{document}
-\script[src=packages/pdfstructure]
+\use[module=packages.pdfstructure]
 \language[main=en]
 \begin[type=Art,block=1]{pdf:structure}
 \pdf:structure[type=H,lang=en-US]{\section{Hello World}}
diff --git a/tests/percent-units.sil b/tests/percent-units.sil
index 9e1f06c53..0b5b9d374 100644
--- a/tests/percent-units.sil
+++ b/tests/percent-units.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a7]{document}
-\script[src=packages/rules]
+\use[module=packages.rules]
 \set[parameter=document.parindent,value=0]
 \nofolios
 % If relative units aren’t calculated correctly
diff --git a/tests/process-commands.sil b/tests/process-commands.sil
index e092f87f5..f2824d6b0 100644
--- a/tests/process-commands.sil
+++ b/tests/process-commands.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a5]{document}% KNOWNBAD
-\script[src=packages/lorem]
+\use[module=packages.lorem]
 \pagetemplate[first-content-frame=a]{
 	\frame[id=a,top=5%ph,bottom=95%ph,next=b,left=5%pw,right=50%pw]
 	\frame[id=b,top=5%ph,bottom=95%ph,left=55%pw,right=95%pw]
diff --git a/tests/rotate.sil b/tests/rotate.sil
index 93c557a03..fe4bff62f 100644
--- a/tests/rotate.sil
+++ b/tests/rotate.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a6]{document}
 \frame[id=folio,left=10pt,top=10pt,rotate=-15]
-\script[src=packages/rotate]
+\use[module=packages.rotate]
 \noindent
 \rotate[angle=90]{issue 913}
 
diff --git a/tests/rskip.sil b/tests/rskip.sil
index 2e2581953..1c2e1b393 100644
--- a/tests/rskip.sil
+++ b/tests/rskip.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a5,class=book]{document}
-\script[src=packages/lorem]
+\use[module=packages.lorem]
 \set[parameter=document.rskip,value=20mm]
 \lorem[words=50]
 \end{document}
diff --git a/tests/simplesvg.sil b/tests/simplesvg.sil
index c0fbc73e1..9f1d5828e 100644
--- a/tests/simplesvg.sil
+++ b/tests/simplesvg.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a5]{document}
-\script[src=packages/svg]
+\use[module=packages.svg]
 
 Foo
 
diff --git a/tests/space-after-command.sil b/tests/space-after-command.sil
index 1a752d0d0..b08604748 100644
--- a/tests/space-after-command.sil
+++ b/tests/space-after-command.sil
@@ -1,6 +1,6 @@
 \begin[papersize=a5]{document}% KNOWNBAD
 % Test leading comment and package lines
-\script[src=packages/color]
+\use[module=packages.color]
 
 \noindent Note: each sentence below should be in its own paragraph with no
 line breaks or words slammed together. All paragraphs (except this one) should
diff --git a/tests/split-footnote.sil b/tests/split-footnote.sil
index a37f6c392..9c5201416 100644
--- a/tests/split-footnote.sil
+++ b/tests/split-footnote.sil
@@ -1,5 +1,5 @@
 \begin[papersize=a6,class=book]{document}
-\script[src=packages/lorem]
+\use[module=packages.lorem]
 \nofolios
 Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
 tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
diff --git a/tests/sura-2.sil b/tests/sura-2.sil
index d319149b2..100d411a7 100644
--- a/tests/sura-2.sil
+++ b/tests/sura-2.sil
@@ -1,7 +1,7 @@
 \begin[papersize=a6,direction=RTL]{document}% KNOWNBAD
 \nofolios
 \font[family=Amiri Quran,size=20pt]
-\script[src=packages/linespacing]
+\use[module=packages.linespacing]
 \set[parameter=linespacing.method,value=fixed]
 \set[parameter=linespacing.fixed.baselinedistance,value=75pt]
  الۤمۤ ۝١