A bit of a rewrite, mostly to improve code quality, and to add some complaints when 'package' is corrupted.
This commit is contained in:
parent
ed3af9b3b0
commit
81626f6b1e
|
@ -1,7 +1,7 @@
|
|||
|
||||
-- ||| Suggest-Require ||| -----------------------------------------------------
|
||||
|
||||
-- Version 1.0.0 ( 11. October 2017 )
|
||||
-- Version 1, minor 2 ( 9. January 2018 )
|
||||
|
||||
-- This is a small library to discover which modules are importable using
|
||||
-- `require`. It's useful for seeing which modules your Lua environment
|
||||
|
@ -17,78 +17,130 @@
|
|||
-- this stuff is worth it, you can buy me a beer in return.
|
||||
|
||||
-- TODO: Ensure it works under both Windows and MacOS.
|
||||
-- TODO: Add support for alternative package.config
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Constants
|
||||
-- Platform dependant
|
||||
|
||||
local SCAN_DIR_TEMPLATE, SCAN_DIR_SEP_PATTERN
|
||||
local PACKAGE_CONFIG_PATTERN = '([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)'
|
||||
|
||||
if package.config:sub(1, 1) == '/' then
|
||||
-- On unix
|
||||
SCAN_DIR_TEMPLATE = 'find -L "%s" -maxdepth 1 -mindepth 1 -print0'
|
||||
SCAN_DIR_SEP_PATTERN = '%z'
|
||||
local function error_check_package_table ()
|
||||
local err_msg
|
||||
if type(package) ~= 'table' then
|
||||
err_msg = '"package" global must be a table, but was a '..type(package)..'.'
|
||||
elseif type(package.loaded) ~= 'table' then
|
||||
err_msg = '"package.loaded" must be a table, but was a '..type(package.loaded)..'.'
|
||||
elseif type(package.config) ~= 'string' then
|
||||
err_msg = '"package.config" must be a string, but was a '..type(package.config)..'.'
|
||||
elseif not package.config:find(PACKAGE_CONFIG_PATTERN) then
|
||||
err_msg = '"package.config" must fit the package.config format.'
|
||||
elseif type(package.path) ~= 'string' then
|
||||
err_msg = '"package.path" must be a string, but was a '..type(package.path)..'.'
|
||||
elseif type(package.cpath) ~= 'string' then
|
||||
err_msg = '"package.cpath" must be a string, but was a '..type(package.cpath)..'.'
|
||||
end
|
||||
|
||||
if err_msg then
|
||||
error('[suggest-require]: The package table is corrupted, and suggest-require cannot procede. '..err_msg..' Please note that the package table has been deliberately messed with.', 2)
|
||||
end
|
||||
end
|
||||
|
||||
local function package_config ()
|
||||
assert(type(package.config) == 'string')
|
||||
local l1, l2, l3, l4, l5 = package.config:match(PACKAGE_CONFIG_PATTERN)
|
||||
return {
|
||||
dir_sep = l1,
|
||||
temp_sep = l2,
|
||||
sub_char = l3,
|
||||
exec_dir = l4,
|
||||
ignore = l5,
|
||||
}
|
||||
end
|
||||
|
||||
local iterate_files_in_subfiles
|
||||
|
||||
if package_config().dir_sep == '/' then
|
||||
function iterate_files_in_subfiles (root_path)
|
||||
-- On unix
|
||||
-- Use `find` to find files in folders below the given matching.
|
||||
|
||||
assert(type(root_path) == 'string')
|
||||
local start_directory = root_path:match '^(.-)?' or root_path
|
||||
local pfile = io.popen ('find -L "'..start_directory..'" -type f ! -path \'*/\\.*\' -print0 2> /dev/null')
|
||||
local list_str = pfile:read '*all'
|
||||
pfile:close()
|
||||
|
||||
return list_str:gmatch '[^%z]+'
|
||||
end
|
||||
else
|
||||
-- On windows
|
||||
SCAN_DIR_TEMPLATE = 'dir "%s" /b /ad'
|
||||
SCAN_DIR_SEP_PATTERN = '\n'
|
||||
error '[suggest-require]: Windows is not currently supported.'
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Util
|
||||
|
||||
local function get_module_paths (path_str)
|
||||
-- Gets the paths of contained in `path_str` based on the format in
|
||||
-- package.config.
|
||||
|
||||
-- Error check
|
||||
local path_str = path_str or package.path
|
||||
assert(type(package.config) == 'string')
|
||||
assert(type(path_str) == 'string')
|
||||
|
||||
-- Work work
|
||||
local paths = {}
|
||||
for path in path_str:gmatch '[^;]+' do
|
||||
paths[#paths+1] = path
|
||||
local function split_string (str, split)
|
||||
assert(type(str) == 'string')
|
||||
assert(type(split) == 'string')
|
||||
--
|
||||
local seq, last_i = {}, 1
|
||||
while true do
|
||||
local next_i, after_i = str:find(split, last_i)
|
||||
if not next_i then break end
|
||||
seq[#seq+1], last_i = str:sub(last_i, next_i - 1), after_i + 1
|
||||
end
|
||||
-- Return
|
||||
return paths
|
||||
seq[#seq+1] = str:sub(last_i)
|
||||
return seq
|
||||
end
|
||||
|
||||
local function get_modules_fitting_path (root_path, module_names)
|
||||
-- First builds up a list of paths to files in `root_path`, and then goes
|
||||
-- through and ensures they match the possible paths for modules.
|
||||
local function keys_of_table (t)
|
||||
-- Returns a new sequence, including only the keys from the given
|
||||
-- table.
|
||||
assert(type(t) == 'table')
|
||||
local seq = {}
|
||||
for k in pairs(t) do seq[#seq+1] = k end
|
||||
return seq
|
||||
end
|
||||
|
||||
local function module_pattern_to_file_pattern (module_pattern)
|
||||
-- Escapes the module pattern, and replaces wildcards "?" with
|
||||
-- Lua's captures.
|
||||
assert(type(module_pattern) == 'string')
|
||||
local escaped = module_pattern:gsub('[%^%(%)%.%[%]%*%+%-]', function (a) return '%' .. a end)
|
||||
return string.format('^%s$', escaped:gsub(package_config().sub_char, '(.+)'))
|
||||
end
|
||||
|
||||
local function consistent_module_wildcards (...)
|
||||
-- For paths involving several wildcards, we need to ensure they are
|
||||
-- consistent. Therefore collect them and check them through.
|
||||
-- If they are consistent, return one of them.
|
||||
local first = (...)
|
||||
if not first then return false end
|
||||
--
|
||||
for i = 2, select('#', ...) do
|
||||
if first ~= select(i, ...) then return false end
|
||||
end
|
||||
--
|
||||
return first
|
||||
end
|
||||
|
||||
local function get_modules_fitting_path (root_path, modules)
|
||||
-- Finds modules that fit the given path, and places them in the
|
||||
-- modules table.
|
||||
|
||||
assert(type(root_path) == 'string')
|
||||
assert(type(module_names) == 'table')
|
||||
assert(type(modules) == 'table')
|
||||
|
||||
-- Use `find` to find files in folders below the given matching.
|
||||
local pfile = io.popen ('find -L "'..(root_path:match '^(.-)?' or root_path)..'" -type f -not -path \'*/\\.*\' -print0')
|
||||
local list_str = pfile:read '*all'
|
||||
pfile:close()
|
||||
-- Construct a pattern for finding wildcards, in the paths of possible modules.
|
||||
local module_path_pattern = module_pattern_to_file_pattern(root_path)
|
||||
|
||||
-- Construct a pattern for the expected path of possible modules.
|
||||
local module_path_pattern = '^'
|
||||
.. root_path:gsub('[%^%(%)%.%[%]%*%+%-]', function (a) return '%' .. a end)
|
||||
:gsub('?', '(.+)')
|
||||
.. '$'
|
||||
-- Look through the file list, and find probable files.
|
||||
for path in iterate_files_in_subfiles(root_path) do
|
||||
|
||||
-- Look through the file list, and find importable modules.
|
||||
for path in list_str:gmatch '[^%z]+' do
|
||||
|
||||
local matches = { path:match(module_path_pattern) }
|
||||
local identical = #matches > 0
|
||||
|
||||
for i = 1, #matches do
|
||||
if matches[1] ~= matches[i] then
|
||||
identical = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- Check if match
|
||||
if identical then
|
||||
module_names[#module_names+1] = matches[1]:gsub('/', '.')
|
||||
-- Find wildcards, and ensure consistency
|
||||
local wildcard = consistent_module_wildcards(path:match(module_path_pattern))
|
||||
if wildcard then
|
||||
modules[#modules+1] = wildcard:gsub(package_config().dir_sep, '.')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -97,35 +149,13 @@ end
|
|||
--------------------------------------------------------------------------------
|
||||
-- Finding Module Names
|
||||
|
||||
local function get_loaded_module_names ()
|
||||
-- Get the names of modules already loaded.
|
||||
local l = {}
|
||||
for k in pairs(package.loaded) do l[#l+1] = k end
|
||||
return l
|
||||
end
|
||||
local function get_module_names_from_path_list (path_list)
|
||||
-- Given a path list embedded into a string, using the
|
||||
-- package.config format, return the modules that can be imported
|
||||
-- using "require", that fit that list.
|
||||
|
||||
local function get_preloaded_module_names ()
|
||||
-- Get the names of preloaded modules.
|
||||
local l = {}
|
||||
for k in pairs(package.loaded) do l[#l+1] = k end
|
||||
return l
|
||||
end
|
||||
|
||||
local function get_module_names_from_path ()
|
||||
-- Get the names of modules not loaded yet.
|
||||
local paths = get_module_paths(package.path)
|
||||
|
||||
local modules = {}
|
||||
for _, path in pairs(paths) do
|
||||
get_modules_fitting_path(path, modules)
|
||||
end
|
||||
|
||||
return modules
|
||||
end
|
||||
|
||||
local function get_c_module_names_from_path ()
|
||||
-- Get the names of c-modules not loaded yet.
|
||||
local paths = get_module_paths(package.cpath)
|
||||
assert(type(path_list) == 'string')
|
||||
local paths = split_string(path_list, package_config().temp_sep)
|
||||
|
||||
local modules = {}
|
||||
for _, path in pairs(paths) do
|
||||
|
@ -136,10 +166,17 @@ local function get_c_module_names_from_path ()
|
|||
end
|
||||
|
||||
local PACKAGE_SEARCH_METHODS = {
|
||||
get_loaded_module_names,
|
||||
get_preloaded_module_names,
|
||||
get_module_names_from_path,
|
||||
get_c_module_names_from_path,
|
||||
-- Modules already loaded:
|
||||
function () return keys_of_table(package.loaded) end,
|
||||
|
||||
-- Preloaded modules:
|
||||
function () return keys_of_table(package.preload) end,
|
||||
|
||||
-- Lua modules not loaded yet:
|
||||
function () return get_module_names_from_path_list(package.path) end,
|
||||
|
||||
-- C modules not loaded yet:
|
||||
function () return get_module_names_from_path_list(package.cpath) end,
|
||||
}
|
||||
|
||||
local function get_available_module_names ()
|
||||
|
@ -149,7 +186,7 @@ local function get_available_module_names ()
|
|||
-- Returns a sequence of strings, each of which can be directly used by
|
||||
-- `require` to import the module.
|
||||
|
||||
assert(type(package) == 'table')
|
||||
error_check_package_table()
|
||||
|
||||
-- Find and De-duplicate
|
||||
local dedub = {}
|
||||
|
@ -175,8 +212,9 @@ end
|
|||
--------------------------------------------------------------------------------
|
||||
-- Run or return
|
||||
|
||||
-- Can be run directly from the terminal to display the available modules, or
|
||||
-- if imported using "require", will return itself as a library.
|
||||
-- If run from terminal, it will display all available modules.
|
||||
-- If imported from another lua file, it will return itself as a
|
||||
-- library
|
||||
|
||||
if ... then
|
||||
return get_available_module_names
|
||||
|
|
Loading…
Reference in New Issue
Block a user