1
0

A bit of a rewrite, mostly to improve code quality, and to add some complaints when 'package' is corrupted.

This commit is contained in:
Jon Michael Aanes 2018-01-09 13:10:28 +01:00
parent ed3af9b3b0
commit 81626f6b1e

View File

@ -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