--[[-- Additions to the core package module. The module table returned by `std.package` also contains all of the entries from the core `package` table. An hygienic way to import this module, then, is simply to override core `package` locally: local package = require "std.package" @module std.package ]] local base = require "std.base" local debug = require "std.debug" local catfile, escape_pattern, invert = base.catfile, base.escape_pattern, base.invert local ipairs, pairs, split, unpack = base.ipairs, base.pairs, base.split, base.unpack local M --- Make named constants for `package.config` -- (undocumented in 5.1; see luaconf.h for C equivalents). -- @table package -- @string dirsep directory separator -- @string pathsep path separator -- @string path_mark string that marks substitution points in a path template -- @string execdir (Windows only) replaced by the executable's directory in a path -- @string igmark Mark to ignore all before it when building `luaopen_` function name. local dirsep, pathsep, path_mark, execdir, igmark = string.match (package.config, "^([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)") local function pathsub (path) return path:gsub ("%%?.", function (capture) if capture == "?" then return path_mark elseif capture == "/" then return dirsep else return capture:gsub ("^%%", "", 1) end end) end local function find (pathstrings, patt, init, plain) local paths = split (pathstrings, pathsep) if plain then patt = escape_pattern (patt) end init = init or 1 if init < 0 then init = #paths - init end for i = init, #paths do if paths[i]:find (patt) then return i, paths[i] end end end local function normalize (...) local i, paths, pathstrings = 1, {}, table.concat ({...}, pathsep) for _, path in ipairs (split (pathstrings, pathsep)) do path = pathsub (path): gsub (catfile ("^[^", "]"), catfile (".", "%0")): gsub (catfile ("", "%.", ""), dirsep): gsub (catfile ("", "%.$"), ""): gsub (catfile ("^%.", "%..", ""), catfile ("..", "")): gsub (catfile ("", "$"), "") -- Carefully remove redundant /foo/../ matches. repeat local again = false path = path:gsub (catfile ("", "([^", "]+)", "%.%.", ""), function (dir1) if dir1 == ".." then -- don't remove /../../ return catfile ("", "..", "..", "") else again = true return dirsep end end): gsub (catfile ("", "([^", "]+)", "%.%.$"), function (dir1) if dir1 == ".." then -- don't remove /../.. return catfile ("", "..", "..") else again = true return "" end end) until again == false -- Build an inverted table of elements to eliminate duplicates after -- normalization. if not paths[path] then paths[path], i = i, i + 1 end end return table.concat (invert (paths), pathsep) end local function insert (pathstrings, ...) local paths = split (pathstrings, pathsep) table.insert (paths, ...) return normalize (unpack (paths)) end local function mappath (pathstrings, callback, ...) for _, path in ipairs (split (pathstrings, pathsep)) do local r = callback (path, ...) if r ~= nil then return r end end end local function remove (pathstrings, pos) local paths = split (pathstrings, pathsep) table.remove (paths, pos) return table.concat (paths, pathsep) end --[[ ================= ]]-- --[[ Public Interface. ]]-- --[[ ================= ]]-- local function X (decl, fn) return debug.argscheck ("std.package." .. decl, fn) end M = { --- Look for a path segment match of *patt* in *pathstrings*. -- @function find -- @string pathstrings `pathsep` delimited path elements -- @string patt a Lua pattern to search for in *pathstrings* -- @int[opt=1] init element (not byte index!) to start search at. -- Negative numbers begin counting backwards from the last element -- @bool[opt=false] plain unless false, treat *patt* as a plain -- string, not a pattern. Note that if *plain* is given, then *init* -- must be given as well. -- @return the matching element number (not byte index!) and full text -- of the matching element, if any; otherwise nil -- @usage i, s = find (package.path, "^[^" .. package.dirsep .. "/]") find = X ("find (string, string, ?int, ?boolean|:plain)", find), --- Insert a new element into a `package.path` like string of paths. -- @function insert -- @string pathstrings a `package.path` like string -- @int[opt=n+1] pos element index at which to insert *value*, where `n` is -- the number of elements prior to insertion -- @string value new path element to insert -- @treturn string a new string with the new element inserted -- @usage -- package.path = insert (package.path, 1, install_dir .. "/?.lua") insert = X ("insert (string, [int], string)", insert), --- Call a function with each element of a path string. -- @function mappath -- @string pathstrings a `package.path` like string -- @tparam mappathcb callback function to call for each element -- @param ... additional arguments passed to *callback* -- @return nil, or first non-nil returned by *callback* -- @usage mappath (package.path, searcherfn, transformfn) mappath = X ("mappath (string, function, [any...])", mappath), --- Normalize a path list. -- Removing redundant `.` and `..` directories, and keep only the first -- instance of duplicate elements. Each argument can contain any number -- of `pathsep` delimited elements; wherein characters are subject to -- `/` and `?` normalization, converting `/` to `dirsep` and `?` to -- `path_mark` (unless immediately preceded by a `%` character). -- @function normalize -- @param ... path elements -- @treturn string a single normalized `pathsep` delimited paths string -- @usage package.path = normalize (user_paths, sys_paths, package.path) normalize = X ("normalize (string...)", normalize), --- Remove any element from a `package.path` like string of paths. -- @function remove -- @string pathstrings a `package.path` like string -- @int[opt=n] pos element index from which to remove an item, where `n` -- is the number of elements prior to removal -- @treturn string a new string with given element removed -- @usage package.path = remove (package.path) remove = X ("remove (string, ?int)", remove), } M.dirsep = dirsep M.execdir = execdir M.igmark = igmark M.path_mark = path_mark M.pathsep = pathsep for k, v in pairs (package) do M[k] = M[k] or v end return M --- Types -- @section Types --- Function signature of a callback for @{mappath}. -- @function mappathcb -- @string element an element from a `pathsep` delimited string of -- paths -- @param ... additional arguments propagated from @{mappath} -- @return non-nil to break, otherwise continue with the next element