177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
|
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
|
-
+
|
else:
return # file not found
# decode
r = json.load(f)
f.close()
return r
except Exception as e:
print(dbg.ERR, "PSON parsing error (in "+name+")", e)
print(dbg.ERR, "JSON parsing error (in "+name+")", e)
# recursive dict update
def update(self, with_new_data):
for key,value in with_new_data.items():
if type(value) == dict:
self[key].update(value)
|
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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
|
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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
|
-
+
-
-
-
-
-
-
-
-
-
+
-
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
+
+
+
-
-
+
-
|
return d+"/"+file
# Plugin meta data extraction
#
# Extremely crude version for Python and streamtuner2 plugin usage.
# Doesn't check top-level comment coherency.
# Fetches module source, or reads from filename / out of zip package.
# But supports plugins within python zip archives.
#
rx_zipfn = re.compile(r"""^(.+\.(?:zip|pyz|pyzw|pyzip)(?:\.py)?)/(\w.*)$""")
rx_meta = re.compile(r"""^ {0,4}# *([\w-]+): *(.+(\n *# +(?![\w-]+:).+)*)$""", re.M) # Python comments only
rx_lines = re.compile(r"""\n *# """) # strip multi-line prefixes
rx_config = re.compile(r"""[\{\<](.+?)[\}\>]""") # extract only from JSOL/YAML scheme
rx_fields = re.compile(r"""["']?(\w+)["']?\s*[:=]\s*["']?([^,]+)(?<!["'])""") # simple key: value entries
#
def plugin_meta(fn=None, frame=1, src=""):
def plugin_meta(fn=None, src=None, frame=1):
# filename of caller
if not fn:
fn = inspect.getfile(sys._getframe(frame))
# get source directly from caller
if not src and not fn:
module = inspect.getmodule(sys._getframe(frame))
fn = inspect.getsourcefile(module)
src = inspect.getcomments(module)
# within zip archive?
zip = rx_zipfn.match(fn)
if zip and zipfile.is_zipfile(zip.group(1)):
src = zipfile.ZipFile(zip.group(1), "r").read(zip.group(2))
else:
src = open(fn).read(4096)
# within zip archive or dir?
elif fn:
zip = rx.zipfn.match(fn)
if zip and zipfile.is_zipfile(zip.group(1)):
src = zipfile.ZipFile(zip.group(1), "r").read(zip.group(2))
else:
src = open(fn).read(4096)
# defaults
meta = {
"id": re.sub("\.\w+$", "", os.path.basename(fn or "")),
"fn": fn,
"title": fn, "description": "no description", "config": [],
"type": "module", "api": "python", "doc": ""
}
"id": os.path.basename(fn).replace(".py", "")
}
# extraction
for field in rx_meta.findall(src):
meta[field[0]] = rx_lines.sub("", field[1])
# extract coherent comment block, split doc section
src = rx.comment.search(src)
if not src:
__print__(dbg.ERR, "Couldn't read source meta information", fn)
return meta
src = src.group(0)
src = rx.hash.sub("", src).strip()
if src.find("\n\n") > 0:
src, meta["doc"] = src.split("\n\n", 1)
# split into dict
for field in rx.keyval.findall(src):
meta[field[0]] = field[1].strip()
meta["config"] = plugin_meta_config(meta.get("config") or "")
return meta
# unpack config: structures
meta["config"] = [
dict([field for field in rx_fields.findall(entry)])
# unpack config: structures
def plugin_meta_config(str):
config = []
for entry in rx.config.findall(str):
opt = { "type": None, "name": None, "description": "", "value": None }
for field in rx.options.findall(entry):
for entry in rx_config.findall(meta.get("config", ""))
]
return meta
opt[field[0]] = (field[1] or field[2] or field[3] or "").strip()
config.append(opt)
return config
# Comment extraction regexps
class rx:
zipfn = re.compile(r"""
^ (.+ \.(?:zip|pyz|pyzw|pyzip) # zip-wrapping extensions
(?:\.py)? ) /(\w.*) $
""", re.X)
comment = re.compile(r"""(^ {0,4}#.*\n)+""", re.M)
hash = re.compile(r"""(^ {0,4}# *)""", re.M)
keyval = re.compile(r"""
^([\w-]+):(.*$(?:\n(?![\w-]+:).+$)*) # plain key:value lines
""", re.M|re.X)
config = re.compile(r"""
[\{\<] (.+?) [\}\>] # JSOL/YAML scheme {...} dicts
""", re.X)
options = re.compile(r"""
["':$]? (\w+) ["']? # key or ":key" or '$key'
\s* [:=] \s* # "=" or ":"
(?: " ([^"]*) "
| ' ([^']*) ' # "quoted" or 'singl' values
| ([^,]*) # or unquoted literals
)
""", re.X)
# wrapper for all print statements
def __print__(*args):
if conf.debug:
if "debug" in conf:
print(" ".join([str(a) for a in args]))
# error colorization
dbg = type('obj', (object,), {
"ERR": r"[31m[ERR][0m", # red ERROR
"INIT": r"[31m[INIT][0m", # red INIT ERROR
"PROC": r"[32m[PROC][0m", # green PROCESS
"CONF": r"[33m[CONF][0m", # brown CONFIG DATA
"UI": r"[34m[UI][0m", # blue USER INTERFACE BEHAVIOUR
"HTTP": r"[35m[HTTP][0m", # magenta HTTP REQUEST
"DATA": r"[36m[DATA][0m", # cyan DATA
"INFO": r"[37m[INFO][0m", # gray INFO
"STAT": r"[37m[STATE][0m", # gray CONFIG STATE
})
#-- actually fill global conf instance
#-- populate global conf instance
conf = ConfigDict()
if conf:
__print__(dbg.PROC, "ConfigDict() initialized")
__print__(dbg.PROC, "ConfigDict() initialized")
|