-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrequire.lua
More file actions
145 lines (131 loc) · 4.17 KB
/
require.lua
File metadata and controls
145 lines (131 loc) · 4.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
--[[
Inspired by:
https://www.lua.org/pil/8.1.html
https://www.lua.org/manual/5.3/manual.html#6.3
https://bitbucket.org/openshell/cc-require/src/default/assets/computercraft/lua/rom/autorun/require.lua
]]
-- CONSTANTS
local MODULE_SEPERATOR_PATTERN = "%."
local PATH_SEPERATOR_PATTERN = "/"
local PATH_PLACEHOLDER_PATTERN = "#"
local CURRENTDIR_PLACEHOLDER_PATTERN = "?"
local ALLOWED_PATHS = {
-- Local Files
"?#",
"?#/init",
-- Global APIs
"/apis/#",
"/apis/#/init",
-- OS APIs
"/rom/apis/#",
"/rom/apis/#/init"
}
-- IMPLEMENTATION
local getCurrentPath = function()
-- Absolute path to program
local program_path = PATH_SEPERATOR_PATTERN .. shell.getRunningProgram()
-- Parent dir
local parent_dir = program_path:match(PATH_SEPERATOR_PATTERN .. ".*" .. PATH_SEPERATOR_PATTERN) or PATH_SEPERATOR_PATTERN
return parent_dir
end
local createPathLoader = function(module_name, file)
local path_loader = function(module_name)
-- Setup environment
local env = {}
setmetatable(env, {__index = _G}) -- required to still reach global functions like "pairs"
-- Get a compiled chunk from file content
local chunk, compile_error = loadfile(file)
if not chunk then
error("File '"..file.."' is not valid.\n\nDetails:\n" .. compile_error, 3)
else
setfenv(chunk, env)
end
-- Call the chunk to execute its content
local ok, content = pcall(chunk)
if not ok then
error("Required file '" .. file .. "' could not be executed.\n\nCaused by:\n" .. content, 3)
else
local api = {}
if content then
-- Add content from "return" statement (default style)
api = content
else
-- Add content from environment (old API style)
for key, value in pairs(env) do
api[key] = value
end
end
return api
end
end
return path_loader
end
local preload_searcher = function(module_name)
if package.preload[module_name] then
return package.preload[module_name]
end
return "- Loading using Preload failed: No field '" .. module_name .. "' in package.preload"
end
local path_searcher = function(module_name)
local missing_files = {}
local relative_path = module_name:gsub(MODULE_SEPERATOR_PATTERN, PATH_SEPERATOR_PATTERN)
-- Check all allowed paths (and replace placeholder with current dir)
for path in package.path:gsub(CURRENTDIR_PLACEHOLDER_PATTERN, getCurrentPath()):gmatch("[^;]+") do
-- Build path
local check_path = path:gsub(PATH_PLACEHOLDER_PATTERN, relative_path)
if (fs.exists(check_path) == true) and (fs.isDir(check_path) == false) then
return createPathLoader(module_name, check_path)
else
table.insert(missing_files, check_path)
end
end
return "- Loading from Path failed. Missing files:\n\t" .. table.concat(missing_files, "\n\t")
end
local package = {
loaded = {},
preload = {},
path = table.concat(ALLOWED_PATHS, ";"),
searchers = {
preload_searcher,
path_searcher
-- OPTIONAL dir_searcher: loads entire dir assuming default inits
-- OPTIONAL github_searcher: download missing files from github
}
}
local require = function(module_name, force_refresh)
local errors = {}
-- Check if previously loaded
if package.loaded[module_name] and not force_refresh then
return package.loaded[module_name]
end
-- Execute available searchers
for _, searcher in pairs(package.searchers) do
local loader = searcher(module_name)
-- Check if searcher actually found a loader
if (type(loader) == "function") then
-- Load the module using the loader
local loader_output = loader(module_name)
-- Check if module loaded correctly
if (loader_output ~= nil) then
package.loaded[module_name] = loader_output
elseif (package.loaded[module_name] == nil) then
-- If loading failed prevent future loads of same module
package.loaded[module_name] = true
end
return package.loaded[module_name]
elseif (type(loader) == "string") then
-- Searcher returned an error
local searcher_errormessage = loader
table.insert(errors, searcher_errormessage)
end
end
-- Display errors from all searchers
error("Module '" .. module_name .. "' was not loaded correctly.\n\nDetails:\n" .. table.concat(errors, "\n"), 2)
end
-- EXPORT
if not _G.package then
_G.package = package
end
if not _G.require then
_G.require = require
end