local function suggest_luarocks (module)
   local guessed_module_name = module:gsub(".*%.", "") .. ".sile"
   return ([[

      If the expected module is a 3rd party extension you may need to install
      it using LuaRocks. The details of how to do this are highly dependent on
      your system and preferred installation method, but as an example
      installing a 3rd party SILE module to a project-local tree where might
      look like this:

        luarocks --lua-version %s --tree lua_modules install %s

      This will install the LuaRock(s) to your project. Note this takes
      advantage of the fact that SILE checks for modules in the path
      'lua_modules' relative to the current input file by default. SILE also
      automatically checks the default system Lua path by default, so using
      `--global` will also work.

      In the event you use a different path to the LuaRocks tree, you must also
      set an environment variable to teach SILE about how to find the tree
      *before* it runs. This can be aided by asking LuaRocks to come up with a
      path and evaling the result in the shell before running SILE. This only
      needs to be done once in each shell, (obviously substituting 'path' for
      your actual path):

        eval $(luarocks --lua-version %s --tree path)

      Thereafter running `sile` as normal in the same shell should work as
      expected. This code can be used in your shell's initialization script
      to avoid having to do it manually in each new shell. This is true for
      user home directory installations using `--local` or any specific values
      for `--tree` other than 'lua_modules'.

      As an anternative to setting up environment variables when using a
      non-default tree location, you can use the `--luarocks-tree` option to
      add path(s) at runtime. This is simpler to type, but must be used on each
      and every invocation. The value for tree should be the same as used when
      installing the LuaRock(s), or an appropriate full path to the location
      used by `--local` (generally "$HOME/.luarocks"):

        sile --luarocks-tree path %s

    ]]):format(SILE.lua_version, guessed_module_name, SILE.lua_version, pl.stringx.join(" ", _G.arg or {}))
end

local function use (module, options, reload)
   local status, pack
   if type(module) == "string" then
      if module:match("/") then
         SU.warn(([[
            Module names should not include platform-specific path separators

            Using slashes like '/' or '\' in a module name looks like a path segment. This
            may appear to work in some cases, but breaks cross platform compatibility.
            Even on the platform with the matching separator, this can lead to packages
            getting loaded more than once because Lua will cache one each of the different
            formats. Please use '.' separators which are automatically translated to the
            correct platform one. For example a correct use statement would be:

              \use[module=%s] instead of \use[module=%s].
         ]]):format(module:gsub("/", "."), module))
      end
      status, pack = pcall(require, module)
      if not status then
         SU.error(
            ("Unable to use '%s':\n%s%s"):format(
               module,
               SILE.traceback and ("    Lua " .. pack) or "",
               suggest_luarocks(module)
            )
         )
      end
   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.scratch.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 == "pagebuilder" then
      SILE.pagebuilders[name] = pack
      SILE.pagebuilder = pack(options)
   elseif pack.type == "package" then
      SILE.packages[pack._name] = pack
      if class then
         class:loadPackage(pack, options, reload)
      else
         table.insert(SILE.input.preambles, { pack = pack, options = options })
      end
   end
end

return use
