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 ||| -----------------------------------------------------
|
-- ||| 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
|
-- This is a small library to discover which modules are importable using
|
||||||
-- `require`. It's useful for seeing which modules your Lua environment
|
-- `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.
|
-- this stuff is worth it, you can buy me a beer in return.
|
||||||
|
|
||||||
-- TODO: Ensure it works under both Windows and MacOS.
|
-- 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
|
local function error_check_package_table ()
|
||||||
-- On unix
|
local err_msg
|
||||||
SCAN_DIR_TEMPLATE = 'find -L "%s" -maxdepth 1 -mindepth 1 -print0'
|
if type(package) ~= 'table' then
|
||||||
SCAN_DIR_SEP_PATTERN = '%z'
|
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
|
else
|
||||||
-- On windows
|
-- On windows
|
||||||
SCAN_DIR_TEMPLATE = 'dir "%s" /b /ad'
|
error '[suggest-require]: Windows is not currently supported.'
|
||||||
SCAN_DIR_SEP_PATTERN = '\n'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Util
|
-- Util
|
||||||
|
|
||||||
local function get_module_paths (path_str)
|
local function split_string (str, split)
|
||||||
-- Gets the paths of contained in `path_str` based on the format in
|
assert(type(str) == 'string')
|
||||||
-- package.config.
|
assert(type(split) == 'string')
|
||||||
|
--
|
||||||
-- Error check
|
local seq, last_i = {}, 1
|
||||||
local path_str = path_str or package.path
|
while true do
|
||||||
assert(type(package.config) == 'string')
|
local next_i, after_i = str:find(split, last_i)
|
||||||
assert(type(path_str) == 'string')
|
if not next_i then break end
|
||||||
|
seq[#seq+1], last_i = str:sub(last_i, next_i - 1), after_i + 1
|
||||||
-- Work work
|
|
||||||
local paths = {}
|
|
||||||
for path in path_str:gmatch '[^;]+' do
|
|
||||||
paths[#paths+1] = path
|
|
||||||
end
|
end
|
||||||
-- Return
|
seq[#seq+1] = str:sub(last_i)
|
||||||
return paths
|
return seq
|
||||||
end
|
end
|
||||||
|
|
||||||
local function get_modules_fitting_path (root_path, module_names)
|
local function keys_of_table (t)
|
||||||
-- First builds up a list of paths to files in `root_path`, and then goes
|
-- Returns a new sequence, including only the keys from the given
|
||||||
-- through and ensures they match the possible paths for modules.
|
-- 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(root_path) == 'string')
|
||||||
assert(type(module_names) == 'table')
|
assert(type(modules) == 'table')
|
||||||
|
|
||||||
-- Use `find` to find files in folders below the given matching.
|
-- Construct a pattern for finding wildcards, in the paths of possible modules.
|
||||||
local pfile = io.popen ('find -L "'..(root_path:match '^(.-)?' or root_path)..'" -type f -not -path \'*/\\.*\' -print0')
|
local module_path_pattern = module_pattern_to_file_pattern(root_path)
|
||||||
local list_str = pfile:read '*all'
|
|
||||||
pfile:close()
|
|
||||||
|
|
||||||
-- Construct a pattern for the expected path of possible modules.
|
-- Look through the file list, and find probable files.
|
||||||
local module_path_pattern = '^'
|
for path in iterate_files_in_subfiles(root_path) do
|
||||||
.. root_path:gsub('[%^%(%)%.%[%]%*%+%-]', function (a) return '%' .. a end)
|
|
||||||
:gsub('?', '(.+)')
|
|
||||||
.. '$'
|
|
||||||
|
|
||||||
-- Look through the file list, and find importable modules.
|
-- Find wildcards, and ensure consistency
|
||||||
for path in list_str:gmatch '[^%z]+' do
|
local wildcard = consistent_module_wildcards(path:match(module_path_pattern))
|
||||||
|
if wildcard then
|
||||||
local matches = { path:match(module_path_pattern) }
|
modules[#modules+1] = wildcard:gsub(package_config().dir_sep, '.')
|
||||||
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('/', '.')
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -97,35 +149,13 @@ end
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Finding Module Names
|
-- Finding Module Names
|
||||||
|
|
||||||
local function get_loaded_module_names ()
|
local function get_module_names_from_path_list (path_list)
|
||||||
-- Get the names of modules already loaded.
|
-- Given a path list embedded into a string, using the
|
||||||
local l = {}
|
-- package.config format, return the modules that can be imported
|
||||||
for k in pairs(package.loaded) do l[#l+1] = k end
|
-- using "require", that fit that list.
|
||||||
return l
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_preloaded_module_names ()
|
assert(type(path_list) == 'string')
|
||||||
-- Get the names of preloaded modules.
|
local paths = split_string(path_list, package_config().temp_sep)
|
||||||
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)
|
|
||||||
|
|
||||||
local modules = {}
|
local modules = {}
|
||||||
for _, path in pairs(paths) do
|
for _, path in pairs(paths) do
|
||||||
|
@ -136,10 +166,17 @@ local function get_c_module_names_from_path ()
|
||||||
end
|
end
|
||||||
|
|
||||||
local PACKAGE_SEARCH_METHODS = {
|
local PACKAGE_SEARCH_METHODS = {
|
||||||
get_loaded_module_names,
|
-- Modules already loaded:
|
||||||
get_preloaded_module_names,
|
function () return keys_of_table(package.loaded) end,
|
||||||
get_module_names_from_path,
|
|
||||||
get_c_module_names_from_path,
|
-- 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 ()
|
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
|
-- Returns a sequence of strings, each of which can be directly used by
|
||||||
-- `require` to import the module.
|
-- `require` to import the module.
|
||||||
|
|
||||||
assert(type(package) == 'table')
|
error_check_package_table()
|
||||||
|
|
||||||
-- Find and De-duplicate
|
-- Find and De-duplicate
|
||||||
local dedub = {}
|
local dedub = {}
|
||||||
|
@ -175,8 +212,9 @@ end
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Run or return
|
-- Run or return
|
||||||
|
|
||||||
-- Can be run directly from the terminal to display the available modules, or
|
-- If run from terminal, it will display all available modules.
|
||||||
-- if imported using "require", will return itself as a library.
|
-- If imported from another lua file, it will return itself as a
|
||||||
|
-- library
|
||||||
|
|
||||||
if ... then
|
if ... then
|
||||||
return get_available_module_names
|
return get_available_module_names
|
||||||
|
|
Loading…
Reference in New Issue
Block a user