commit 4fddf72901682effcb54688482a52bc77a4b14cc Author: Jon Michael Aanes Date: Fri Sep 29 09:36:56 2017 +0200 Initial commit of `suggest-require`. A simple library for finding modules to require. diff --git a/suggest-require.lua b/suggest-require.lua new file mode 100644 index 0000000..78c9b41 --- /dev/null +++ b/suggest-require.lua @@ -0,0 +1,149 @@ + +-- This is a tiny library to discover which modules can be imported using +-- `require`. It's intended to be included in an auto-complete system for Lua. + +-- Not sure if it works on a system without GNU find :/ + +--- +-- Constants + +local SCAN_DIR_TEMPLATE, SCAN_DIR_SEP_PATTERN + +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' +else + -- On windows + SCAN_DIR_TEMPLATE = 'dir "%s" /b /ad' + SCAN_DIR_SEP_PATTERN = '\n' +end + +-- TODO: Improve Windows support +-- TODO: Make sure works on MacOS. + +-------------- +-- Util + +local function get_module_paths (path_str) + -- TODO: Add support for alternative 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 + end + -- Return + return paths +end + +local function get_modules_fitting_path (root_path, module_names) + assert(type(root_path) == 'string') + assert(type(module_names) == '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 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 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('/', '.') + end + end +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 () + 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 () + local paths = get_module_paths(package.cpath) + + local modules = {} + for _, path in pairs(paths) do + get_modules_fitting_path(path, modules) + end + + return modules +end + +local function get_available_module_names () + assert(type(package) == 'table') + + local names_loaded = get_loaded_module_names() + local names_path = get_module_names_from_path() + local names_cpath = get_c_module_names_from_path() + + -- + + -- Dedub and Sort + local dedub = {} + for i = 1, #names_loaded do dedub[names_loaded[i]] = true end + for i = 1, #names_path do dedub[names_path[i]] = true end + for i = 1, #names_cpath do dedub[names_cpath[i]] = true end + local module_names = {} + for name in pairs(dedub) do module_names[#module_names+1] = name end + table.sort(module_names) + + -- Output assertions + assert(type(module_names) == 'table') + for i = 1, #module_names do + assert(type(module_names[i]) == 'string') + end + + -- Return + return module_names +end + +-------------- + +if ... then + return get_available_module_names +else + local names = get_available_module_names() + io.write 'Following modules are available: \n' + for _, module_name in ipairs(names) do + io.write ' - ' + io.write(module_name) + io.write '\n' + end +end