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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
|
#!/usr/bin/env lua5.1
local json = require "json"
local sha1 = require "sha1"
local lfs = require "lfs"
local inspect = require "inspect"
local argparse = require "argparse"
local parser = argparse() {description = "experimental DependencyControl feed generator"}
parser:option("--macros", "Macro Directory")
parser:option("--modules", "Module Directory")
parser:option("-o --output", "Output File", "DependencyControl.json")
parser:option("-c --config", "Feed Configuration file")
local args = parser:parse()
local config = loadfile(args.config)()
local function script_path()
local str = debug.getinfo(2, "S").source:sub(2)
return str:match("(.*/)")
end
package.path = script_path() .. "?.lua;" .. package.path -- add the script dir to the lua path so scripts can access the fake depctrl
local function valid_namespace(name)
--[[ #### Rules for a valid namespace: ####
1. contains _at least_ one dot
2. must **not** start or end with a dot
3. must **not** contain series of two or more dots
4. the character set is restricted to: `A-Z`, `a-z`, `0-9`, `.`, `_`, `-`
__Examples__:
* l0.ASSFoundation
* l0.ASSFoundation.Common (for a separately version-controlled 'submodule')
* l0.ASSWipe
* a-mo.LineCollection
]]
return name:match("^[^.][%a%d._-]*%.[%a%d._-]*[^.]$") ~= nil
-- not 100% sure this works. it matches the examples, but idk if it matches invalid ones as well
end
local function clean_path(path, file)
-- don't want to be pedantic about paths, but still don't want paths with // in them
if path:sub(-1, -1) == "/" then path = path:sub(1, -2) end
return path .. "/" .. file
end
local function join_itables(dst, src)
if dst == nil then return src end
if src == nil then return dst end
for i, v in ipairs(src) do
table.insert(dst, v)
end
return dst
end
local function join_ktables(dst, src)
if dst == nil then return src end
if src == nil then return dst end
for k, v in pairs(src) do
dst[k] = v
end
return dst
end
local function readfile(filename)
local f = io.open(filename)
local txt = f:read("*all")
f:close()
return txt
end
local function get_iso8601_date(time)
return os.date("%Y-%m-%d", time)
end
local function output_writer(file)
local is_file, f = pcall(io.open, file, "w")
if is_file then return f end
return io.stdout
end
local function err(msg)
if type(msg) == "table" then msg = inspect(msg) end
io.stderr:write(msg.."\n")
end
local function get_files(path, are_macros)
local files = {}
for file in lfs.dir(path) do
local name, extension = file:match("^(.*)%.(.*)$") -- anything.anything
local absolute = clean_path(path, file)
if file == "." or file == ".." then -- silently skip dir and 1-level-up dir
elseif pcall(lfs.dir, absolute) then file = join_itables(files, get_files(absolute)) -- search recursively
elseif extension ~= "lua" then err(absolute .. ": not a lua file, skipping")
elseif ((not valid_namespace(name)) and are_macros) then err(absolute .. ": invalid namespace, skipping")
else table.insert(files, absolute) end
end
return files
end
local function get_file_metadata(file)
local sha1 = sha1.sha1(readfile(file))
local lastmodified = get_iso8601_date(lfs.attributes(file, "modification"))
return sha1, lastmodified
end
local function get_macro_metadata(file)
local meta = {filename = file, name = nil, description = nil, version = nil, author = nil, namespace = nil, depctrl = nil, sha1 = nil, release = nil}
-- having all those nils in the table doesn't really do anything in terms of functionality, but it lets me see what i need to put in it
meta.sha1, meta.release = get_file_metadata(file)
function include() end -- so it doesnt die with karaskel imports and such
loadfile(file)()
-- script_name etc are now in our global scope
if config.macros.ignoreCondition() then
err(file .. ": ignored by config, skipping")
return nil
end
meta.name = script_name
meta.description = script_description
meta.version = script_version
meta.author = script_author
meta.namespace = script_namespace
meta.depctrl = __feedmaker_version
return meta
end
local function get_module_metadata(file)
local meta = {filename = nil, name = nil, description = nil, version = nil, author = nil, namespace = nil, depctrl = nil, sha1 = nil, release = nil}
meta.filename = file
meta.sha1, meta.release = get_file_metadata(file)
loadfile(file)()
-- script_name etc are now in our global scope
if config.modules.ignoreCondition() then
err(file .. ": ignored by config, skipping")
return nil
end
local depctrl = __feedmaker_version
meta.name = depctrl.name
meta.version = depctrl.version
meta.author = depctrl.author
meta.namespace = depctrl.moduleName
meta.depctrl = depctrl[1]
return meta
end
local function clean_depctrl(depctrl)
local required = {}
local feeds = {}
if #depctrl == 0 then return nil end
for _, mod in ipairs(depctrl) do
if type(mod[1]) ~= "string" then mod = mod[1] end
local modname = mod[1]
mod["moduleName"] = modname
mod[1] = nil
table.insert(required, mod)
feeds[modname] = mod["feed"]
end
return required, feeds
end
local function get_feed_entry(script)
local macro = {url = config.scriptUrl, author = script.author, name = script.name, description = script.description, channels = {}}
local channel_info = {version = script.version, released = script.release, default = true, files = {}}
local requiredModules, feeds = clean_depctrl(script.depctrl)
channel_info.requiredModules = requiredModules
table.insert(channel_info.files, {name = ".lua", url = config.fileUrl, sha1 = script.sha1})
macro.channels[config.channel] = channel_info
return macro, feeds
end
local function make_feed(meta)
local feed = {
dependencyControlFeedFormatVersion = "0.3.0",
name = config.name,
description = config.description,
knownFeeds = config.knownFeeds,
baseUrl = config.baseUrl,
url = config.url,
maintainer = config.maintainer,
fileBaseUrl = config.fileBaseUrl,
-- macros = {},
-- modules = {}
}
if next(meta.macros) then
config.macros.ignoreCondition = nil
feed.macros = join_ktables(feed.macros, config.macros) -- remove the ignore functions so they don't cause problems with the json conversion
for _, script in ipairs(meta.macros) do
local macro, feeds = get_feed_entry(script)
feed.knownFeeds = join_ktables(feed.knownFeeds, feeds)
feed.macros[script.namespace] = macro
end
end
if next(meta.modules) then
config.modules.ignoreCondition = nil
feed.modules = join_ktables(feed.modules, config.modules)
for _, script in ipairs(meta.modules) do
local mod, feeds = get_feed_entry(script)
feed.knownFeeds = join_ktables(feed.knownFeeds, feeds)
feed.modules[script.namespace] = mod
end
end
return json.encode(feed)
end
local function main()
local macros, modules
local meta = {macros = {}, modules = {}}
if args.macros then
macro_files = get_files(args.macros, true)
for _, file in ipairs(macro_files) do
table.insert(meta.macros, get_macro_metadata(file))
end
end
if args.modules then
module_files = get_files(args.modules)
for _, file in ipairs(module_files) do
table.insert(meta.modules, get_module_metadata(file))
end
end
local feed = make_feed(meta)
local out = output_writer(args.output)
out:write(feed)
out:close()
end
main()
|