genocide 236__tar.gz → 237__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {genocide-236/genocide.egg-info → genocide-237}/PKG-INFO +1 -1
- genocide-237/genocide/booting.py +148 -0
- genocide-237/genocide/clients.py +206 -0
- genocide-237/genocide/command.py +192 -0
- genocide-237/genocide/configs.py +44 -0
- genocide-237/genocide/daemons.py +61 -0
- genocide-237/genocide/defines.py +86 -0
- genocide-237/genocide/encoder.py +107 -0
- genocide-237/genocide/excepts.py +48 -0
- genocide-237/genocide/handler.py +36 -0
- genocide-237/genocide/looping.py +73 -0
- {genocide-236 → genocide-237}/genocide/message.py +16 -24
- genocide-237/genocide/methods.py +243 -0
- {genocide-236/genocide/mirrors → genocide-237/genocide/minimal}/__index__.py +1 -1
- genocide-236/genocide/modules/__init__.py → genocide-237/genocide/modules/__index__.py +1 -1
- {genocide-236 → genocide-237}/genocide/modules/mdl.py +67 -72
- genocide-237/genocide/modules/pth.py +19 -0
- {genocide-236 → genocide-237}/genocide/modules/req.py +3 -5
- {genocide-236 → genocide-237}/genocide/modules/slg.py +3 -5
- genocide-237/genocide/modules/ver.py +13 -0
- genocide-237/genocide/namings/__index__.py +4 -0
- genocide-237/genocide/numbers/manual.html +148 -0
- genocide-237/genocide/objects.py +47 -0
- genocide-237/genocide/package.py +116 -0
- genocide-237/genocide/persist.py +323 -0
- genocide-237/genocide/pooling.py +82 -0
- genocide-237/genocide/repeats.py +64 -0
- genocide-237/genocide/require.py +100 -0
- genocide-237/genocide/runtime.py +180 -0
- genocide-237/genocide/statics.py +58 -0
- genocide-237/genocide/tasking.py +82 -0
- genocide-237/genocide/typings.py +34 -0
- genocide-237/genocide/utility.py +350 -0
- genocide-237/genocide/watcher.py +70 -0
- {genocide-236 → genocide-237/genocide.egg-info}/PKG-INFO +1 -1
- genocide-237/genocide.egg-info/SOURCES.txt +102 -0
- genocide-237/pyproject.toml +94 -0
- genocide-236/MANIFEST.in +0 -1
- genocide-236/bin/genocide +0 -19
- genocide-236/genocide/brokers.py +0 -67
- genocide-236/genocide/clients.py +0 -135
- genocide-236/genocide/command.py +0 -89
- genocide-236/genocide/configs.py +0 -31
- genocide-236/genocide/defines.py +0 -61
- genocide-236/genocide/engines.py +0 -71
- genocide-236/genocide/handler.py +0 -57
- genocide-236/genocide/loggers.py +0 -67
- genocide-236/genocide/modules/adm.py +0 -69
- genocide-236/genocide/modules/cfg.py +0 -44
- genocide-236/genocide/modules/eml.py +0 -94
- genocide-236/genocide/modules/fnd.py +0 -35
- genocide-236/genocide/modules/irc.py +0 -628
- genocide-236/genocide/modules/log.py +0 -28
- genocide-236/genocide/modules/man.py +0 -251
- genocide-236/genocide/modules/pth.py +0 -19
- genocide-236/genocide/modules/rss.py +0 -762
- genocide-236/genocide/modules/rst.py +0 -134
- genocide-236/genocide/modules/shw.py +0 -95
- genocide-236/genocide/modules/sil.py +0 -30
- genocide-236/genocide/modules/tdo.py +0 -45
- genocide-236/genocide/modules/tmr.py +0 -114
- genocide-236/genocide/modules/udp.py +0 -126
- genocide-236/genocide/modules/web.py +0 -143
- genocide-236/genocide/modules/wsd.py +0 -248
- genocide-236/genocide/network/manual.html +0 -364
- genocide-236/genocide/objects.py +0 -398
- genocide-236/genocide/package.py +0 -110
- genocide-236/genocide/parsers.py +0 -77
- genocide-236/genocide/persist.py +0 -231
- genocide-236/genocide/repeats.py +0 -76
- genocide-236/genocide/runtime.py +0 -273
- genocide-236/genocide/statics.py +0 -90
- genocide-236/genocide/threads.py +0 -97
- genocide-236/genocide/utility.py +0 -294
- genocide-236/genocide.egg-info/SOURCES.txt +0 -112
- genocide-236/pyproject.toml +0 -57
- {genocide-236 → genocide-237}/README.rst +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_images/ECHAabilify.png +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_images/ECHAclozapine.png +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_images/ECHAhaldol.png +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_images/ECHAzyprexa.png +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_images/OTP111.png +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_images/OTP222.png +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_images/abilifynew2.png +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_images/abilifypubchem.png +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_images/ack3.png +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_images/bevestigd.jpg +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_images/clozapinenew2.png +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_images/clozapinepubchem.png +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_images/genocide.png +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_images/haldolpubchem.png +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_images/haloperidolnew2.png +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_images/informed.jpg +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_images/kamer2.png +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_images/skull3.png +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_images/zyprexanew2.png +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_images/zyprexapubchem.png +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_static/alabaster.css +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_static/basic.css +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_static/custom.css +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_static/doctools.js +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_static/documentation_options.js +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_static/file.png +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_static/genocide.css +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_static/language_data.js +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_static/minus.png +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_static/opensearch.xml +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_static/otpcr.css +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_static/plus.png +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_static/pygments.css +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_static/searchtools.js +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_static/sitemap.xml +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_static/skull3.png +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/_static/sphinx_highlight.js +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/about.html +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/echa.html +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/evidence.html +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/genindex-all.html +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/genindex.html +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/googlea6568bcc05c8d50b.html +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/guilty.html +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/index.html +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/objects.inv +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/pdf/EM_Ack_OTP-CR-117_19.pdf +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/pdf/EM_T04_OTP-CR-117_19.pdf +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/pdf/EM_T07_OTP-CR-117_19_001.pdf +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/pdf/Kamer.pdf +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/pdf/Rome-Statute.pdf +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/pdf/bevestigd.pdf +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/pubchem.html +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/reconsider.html +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/reconsider2.html +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/request.html +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/robots.txt +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/search.html +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/searchindex.js +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/skull.html +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/writing.html +0 -0
- {genocide-236/genocide/network → genocide-237/genocide/numbers}/writings.html +0 -0
- {genocide-236 → genocide-237}/genocide.egg-info/dependency_links.txt +0 -0
- {genocide-236 → genocide-237}/genocide.egg-info/entry_points.txt +0 -0
- {genocide-236 → genocide-237}/genocide.egg-info/top_level.txt +0 -0
- {genocide-236 → genocide-237}/setup.cfg +0 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# This file is placed in the Public Domain.
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
"in the beginning"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
10
|
+
import _thread
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
from .clients import Broker, Client
|
|
14
|
+
from .command import Commands
|
|
15
|
+
from .configs import Main
|
|
16
|
+
from .package import Mods
|
|
17
|
+
from .persist import Workdir
|
|
18
|
+
from .tasking import Task
|
|
19
|
+
from .utility import MD5, Event, Log, Logging, Utils
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
logger = Log(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Boot:
|
|
26
|
+
|
|
27
|
+
"at startup"
|
|
28
|
+
|
|
29
|
+
running = Event()
|
|
30
|
+
stopped = Event()
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def banner(cls, force=False):
|
|
34
|
+
"hello"
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def boot(cls):
|
|
38
|
+
"prepare for runtime."
|
|
39
|
+
cls.configure()
|
|
40
|
+
MD5.core()
|
|
41
|
+
if Main.verbose:
|
|
42
|
+
cls.banner()
|
|
43
|
+
if Main.all:
|
|
44
|
+
Main.mods = ",".join(Mods.list())
|
|
45
|
+
Commands.table()
|
|
46
|
+
Mods.table()
|
|
47
|
+
if Main.scanner:
|
|
48
|
+
Commands.scanner()
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def configure(cls, wdr=""):
|
|
52
|
+
"setup basic variables"
|
|
53
|
+
Workdir.wdr = wdr or Main.wdr or Workdir.wdr or Workdir.home(Main.name)
|
|
54
|
+
Workdir.skel()
|
|
55
|
+
Main.level = Main.level or "warning"
|
|
56
|
+
Logging.size(len(Main.name))
|
|
57
|
+
Logging.level(Main.level)
|
|
58
|
+
# Mods.dir("modules", Workdir.moddir())
|
|
59
|
+
Mods.dir(f"{Main.name}.miminal", Mods.moddir("minimal"))
|
|
60
|
+
Mods.dir(f"{Main.name}.modules", Mods.moddir())
|
|
61
|
+
if Main.path:
|
|
62
|
+
for pth in Utils.spl(Main.path):
|
|
63
|
+
pth = os.path.normpath(pth)
|
|
64
|
+
pkgname = pth.rsplit(os.sep, maxsplit=1)[-1]
|
|
65
|
+
Mods.dir(pkgname, pth)
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def forever(cls):
|
|
69
|
+
"run forever until ctrl-c."
|
|
70
|
+
cls.running.set()
|
|
71
|
+
while cls.running.is_set():
|
|
72
|
+
try:
|
|
73
|
+
time.sleep(0.1)
|
|
74
|
+
except (KeyboardInterrupt, EOFError):
|
|
75
|
+
break
|
|
76
|
+
cls.stopped.set()
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def init(cls, names, wait=False):
|
|
80
|
+
"call init of modules that have an init function."
|
|
81
|
+
thrs = []
|
|
82
|
+
for name in Utils.spl(names):
|
|
83
|
+
mod = Mods.get(name)
|
|
84
|
+
if not mod or "init" not in dir(mod):
|
|
85
|
+
continue
|
|
86
|
+
thrs.append(Task.start(mod.init))
|
|
87
|
+
if thrs and wait:
|
|
88
|
+
for thr in thrs:
|
|
89
|
+
thr.join()
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def shutdown(cls, wait=True):
|
|
94
|
+
"call stop on clients."
|
|
95
|
+
logger.info("shutdown")
|
|
96
|
+
for client in Broker.objs("wait"):
|
|
97
|
+
logger.debug("wait %s", client)
|
|
98
|
+
try:
|
|
99
|
+
client.wait()
|
|
100
|
+
except (KeyboardInterrupt, EOFError):
|
|
101
|
+
pass
|
|
102
|
+
time.sleep(0.01)
|
|
103
|
+
for client in Broker.objs("stop"):
|
|
104
|
+
logger.debug("stop %s", client)
|
|
105
|
+
try:
|
|
106
|
+
client.stop()
|
|
107
|
+
except (KeyboardInterrupt, EOFError):
|
|
108
|
+
pass
|
|
109
|
+
time.sleep(0.01)
|
|
110
|
+
if wait:
|
|
111
|
+
while True:
|
|
112
|
+
if len(threading.enumerate()) <= 1:
|
|
113
|
+
break
|
|
114
|
+
time.sleep(0.01)
|
|
115
|
+
cls.stopped.set()
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def wrapped(cls, func, *args):
|
|
119
|
+
"wrap function in a try/except, silence ctrl-c/ctrl-d."
|
|
120
|
+
try:
|
|
121
|
+
func(*args)
|
|
122
|
+
except (KeyboardInterrupt, EOFError):
|
|
123
|
+
Client.block.set()
|
|
124
|
+
_thread.interrupt_main()
|
|
125
|
+
|
|
126
|
+
@classmethod
|
|
127
|
+
def wrap(cls, func, *args, dofinal=None):
|
|
128
|
+
"restore console."
|
|
129
|
+
import sys
|
|
130
|
+
import termios
|
|
131
|
+
try:
|
|
132
|
+
old = termios.tcgetattr(sys.stdin.fileno())
|
|
133
|
+
except termios.error:
|
|
134
|
+
old = None
|
|
135
|
+
try:
|
|
136
|
+
cls.wrapped(func, *args)
|
|
137
|
+
except (KeyboardInterrupt, EOFError):
|
|
138
|
+
pass
|
|
139
|
+
if old and old[0]:
|
|
140
|
+
termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old)
|
|
141
|
+
if dofinal:
|
|
142
|
+
dofinal()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def __dir__():
|
|
146
|
+
return (
|
|
147
|
+
'Boot',
|
|
148
|
+
)
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# This file is placed in the Public Domain.
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
"displaying messages"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
import _thread
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
from .handler import Handler
|
|
11
|
+
from .tasking import Task
|
|
12
|
+
from .utility import Event, RLock, Queue
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Broker:
|
|
16
|
+
|
|
17
|
+
"map repr(obj) to obj"
|
|
18
|
+
|
|
19
|
+
objects = {}
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def add(cls, obj):
|
|
23
|
+
"add object to the broker, key is repr(obj)."
|
|
24
|
+
cls.objects[repr(obj)] = obj
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def get(cls, origin):
|
|
28
|
+
"object by repr(obj)."
|
|
29
|
+
return cls.objects.get(origin)
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def has(cls, obj):
|
|
33
|
+
"whether the Broker has object."
|
|
34
|
+
return repr(obj) in cls.objects
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def like(cls, text):
|
|
38
|
+
"all keys with a substring in their key."
|
|
39
|
+
for orig in cls.objects:
|
|
40
|
+
if text in orig.split()[0]:
|
|
41
|
+
yield (orig, cls.get(orig))
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def objs(cls, attr):
|
|
45
|
+
"objects with a certain attribute."
|
|
46
|
+
for obj in cls.objects.values():
|
|
47
|
+
if attr in dir(obj):
|
|
48
|
+
yield obj
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def remove(cls, obj):
|
|
52
|
+
"remove object."
|
|
53
|
+
del cls.objects[repr(obj)]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class Display:
|
|
57
|
+
|
|
58
|
+
"unit of display"
|
|
59
|
+
|
|
60
|
+
block = Event()
|
|
61
|
+
last = ""
|
|
62
|
+
|
|
63
|
+
def __init__(self):
|
|
64
|
+
self.olock = RLock()
|
|
65
|
+
self.silent = False
|
|
66
|
+
Broker.add(self)
|
|
67
|
+
|
|
68
|
+
def announce(self, text):
|
|
69
|
+
"announce text to all channels."
|
|
70
|
+
if not self.silent:
|
|
71
|
+
self.raw(text)
|
|
72
|
+
|
|
73
|
+
def display(self, msg):
|
|
74
|
+
"display msg results."
|
|
75
|
+
with self.olock:
|
|
76
|
+
for txt in msg.result:
|
|
77
|
+
if self.block.is_set():
|
|
78
|
+
return
|
|
79
|
+
self.dosay(msg.channel, txt)
|
|
80
|
+
del txt
|
|
81
|
+
del msg
|
|
82
|
+
|
|
83
|
+
def dosay(self, channel, text):
|
|
84
|
+
"say called by display."
|
|
85
|
+
self.say(channel, text)
|
|
86
|
+
|
|
87
|
+
def raw(self, text):
|
|
88
|
+
"raw output."
|
|
89
|
+
raise NotImplementedError
|
|
90
|
+
|
|
91
|
+
def say(self, channel, text):
|
|
92
|
+
"say text in channel."
|
|
93
|
+
self.last = channel
|
|
94
|
+
self.raw(text)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class Output(Display):
|
|
98
|
+
|
|
99
|
+
"dedicated output loop"
|
|
100
|
+
|
|
101
|
+
def __init__(self):
|
|
102
|
+
Display.__init__(self)
|
|
103
|
+
self.oqueue = Queue()
|
|
104
|
+
self.ostopped = Event()
|
|
105
|
+
|
|
106
|
+
def display(self, msg):
|
|
107
|
+
"do actual display."
|
|
108
|
+
|
|
109
|
+
def output(self):
|
|
110
|
+
"output loop."
|
|
111
|
+
while not self.ostopped.is_set():
|
|
112
|
+
try:
|
|
113
|
+
msg = self.oqueue.get()
|
|
114
|
+
except (KeyboardInterrupt, EOFError):
|
|
115
|
+
_thread.interrupt_main()
|
|
116
|
+
if msg is None:
|
|
117
|
+
self.oqueue.task_done()
|
|
118
|
+
break
|
|
119
|
+
self.display(msg)
|
|
120
|
+
self.oqueue.task_done()
|
|
121
|
+
|
|
122
|
+
def raw(self, text):
|
|
123
|
+
"raw output."
|
|
124
|
+
raise NotImplementedError
|
|
125
|
+
|
|
126
|
+
def start(self, daemon=True, name=None):
|
|
127
|
+
"start output loop."
|
|
128
|
+
self.ostopped.clear()
|
|
129
|
+
Task.start(self.output, daemon=daemon, name=name)
|
|
130
|
+
|
|
131
|
+
def stop(self):
|
|
132
|
+
"stop output loop."
|
|
133
|
+
self.ostopped.set()
|
|
134
|
+
self.oqueue.put(None)
|
|
135
|
+
|
|
136
|
+
def wait(self):
|
|
137
|
+
"wait for output to finish."
|
|
138
|
+
try:
|
|
139
|
+
self.oqueue.join()
|
|
140
|
+
except (KeyboardInterrupt, EOFError):
|
|
141
|
+
_thread.interrupt_main()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class Client(Handler, Display):
|
|
145
|
+
|
|
146
|
+
"display with coupled handler"
|
|
147
|
+
|
|
148
|
+
def __init__(self):
|
|
149
|
+
Handler.__init__(self)
|
|
150
|
+
Display.__init__(self)
|
|
151
|
+
|
|
152
|
+
def raw(self, text):
|
|
153
|
+
"raw output."
|
|
154
|
+
raise NotImplementedError
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class BufferedClient(Handler, Output):
|
|
158
|
+
|
|
159
|
+
"buffered output"
|
|
160
|
+
|
|
161
|
+
def __init__(self):
|
|
162
|
+
Handler.__init__(self)
|
|
163
|
+
Output.__init__(self)
|
|
164
|
+
|
|
165
|
+
def raw(self, text):
|
|
166
|
+
"raw output."
|
|
167
|
+
raise NotImplementedError
|
|
168
|
+
|
|
169
|
+
def start(self, daemon=True, name=None):
|
|
170
|
+
"start output loop."
|
|
171
|
+
Handler.start(self)
|
|
172
|
+
Output.start(self, daemon=daemon, name=name)
|
|
173
|
+
|
|
174
|
+
def stop(self):
|
|
175
|
+
"stop output loop."
|
|
176
|
+
Handler.stop(self)
|
|
177
|
+
Output.stop(self)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class Clients:
|
|
181
|
+
|
|
182
|
+
"collection of clients"
|
|
183
|
+
|
|
184
|
+
@staticmethod
|
|
185
|
+
def announce(text):
|
|
186
|
+
"announce text on all clients."
|
|
187
|
+
for obj in Broker.objs("announce"):
|
|
188
|
+
obj.announce(text)
|
|
189
|
+
|
|
190
|
+
@staticmethod
|
|
191
|
+
def display(msg):
|
|
192
|
+
"display results."
|
|
193
|
+
bot = Broker.get(msg.orig)
|
|
194
|
+
if bot:
|
|
195
|
+
bot.display(msg)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def __dir__():
|
|
199
|
+
return (
|
|
200
|
+
'Broker',
|
|
201
|
+
'BufferedClient',
|
|
202
|
+
'Client',
|
|
203
|
+
'Clients',
|
|
204
|
+
'Display',
|
|
205
|
+
'Output'
|
|
206
|
+
)
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# This file is placed in the Public Domain.
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
"program your own commands"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
import inspect
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
from .clients import Clients
|
|
11
|
+
from .message import Message
|
|
12
|
+
from .methods import Method
|
|
13
|
+
from .package import Mods
|
|
14
|
+
from .utility import Utils
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Commands:
|
|
18
|
+
|
|
19
|
+
"command dispatch"
|
|
20
|
+
|
|
21
|
+
cmds = {}
|
|
22
|
+
names = {}
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def add(cls, *funcs):
|
|
26
|
+
"register a command."
|
|
27
|
+
for func in funcs:
|
|
28
|
+
name = Utils.funcname(func)
|
|
29
|
+
cls.cmds[name] = func
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def cmd(cls, txt):
|
|
33
|
+
"parse single line to command and run it."
|
|
34
|
+
msg = Message()
|
|
35
|
+
msg.text = txt
|
|
36
|
+
msg.kind = "command"
|
|
37
|
+
cls.command(msg)
|
|
38
|
+
return msg.result
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def command(cls, msg):
|
|
42
|
+
"command callback."
|
|
43
|
+
Parser.parse(msg, msg.text)
|
|
44
|
+
func = cls.cmds.get(msg.cmd, cls.ondemand(msg.cmd))
|
|
45
|
+
if func:
|
|
46
|
+
func(msg)
|
|
47
|
+
Clients.display(msg)
|
|
48
|
+
msg.ready()
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def list(cls):
|
|
52
|
+
"scan for a list of all commands."
|
|
53
|
+
result = []
|
|
54
|
+
for modname in Mods.list():
|
|
55
|
+
mod = Mods.get(modname)
|
|
56
|
+
if not mod:
|
|
57
|
+
continue
|
|
58
|
+
result.extend([Utils.funcname(x) for x in cls.scan(mod, True)])
|
|
59
|
+
return result
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def ondemand(cls, name):
|
|
63
|
+
"ondemand loading of commands."
|
|
64
|
+
modname = cls.names.get(name, None)
|
|
65
|
+
if not modname:
|
|
66
|
+
return None
|
|
67
|
+
mod = Mods.get(modname)
|
|
68
|
+
if not mod:
|
|
69
|
+
return None
|
|
70
|
+
cls.scan(mod)
|
|
71
|
+
return cls.cmds.get(name, None)
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def scan(cls, mod, skip=False):
|
|
75
|
+
"scan module for commands."
|
|
76
|
+
result = []
|
|
77
|
+
for nme, func in inspect.getmembers(mod, inspect.isfunction):
|
|
78
|
+
if "cb_" in nme:
|
|
79
|
+
continue
|
|
80
|
+
if 'msg' in inspect.signature(func).parameters:
|
|
81
|
+
if not skip:
|
|
82
|
+
cls.add(func)
|
|
83
|
+
result.append(func)
|
|
84
|
+
return result
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def scanner(cls):
|
|
88
|
+
"scan all modules."
|
|
89
|
+
for name in Mods.list():
|
|
90
|
+
mod = Mods.get(name)
|
|
91
|
+
if not mod:
|
|
92
|
+
continue
|
|
93
|
+
cls.scan(mod)
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def statics(cls):
|
|
97
|
+
"read table."
|
|
98
|
+
try:
|
|
99
|
+
from .statics import NAMES
|
|
100
|
+
cls.names.update(NAMES)
|
|
101
|
+
except (ImportError, SyntaxError, ValueError):
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def table(cls):
|
|
106
|
+
"read static tables."
|
|
107
|
+
cls.statics()
|
|
108
|
+
if not cls.names:
|
|
109
|
+
cls.scanner()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
cmd = Commands.cmd
|
|
113
|
+
typed = Method.typed
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class Parser:
|
|
117
|
+
|
|
118
|
+
"parsing for commands"
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def init(cls, obj, text, clean=False):
|
|
122
|
+
"set default values."
|
|
123
|
+
data = {
|
|
124
|
+
"args": [],
|
|
125
|
+
"cmd": "",
|
|
126
|
+
"gets": {},
|
|
127
|
+
"index": None,
|
|
128
|
+
"init": "",
|
|
129
|
+
"mod": "",
|
|
130
|
+
"opts": "",
|
|
131
|
+
"otxt": text,
|
|
132
|
+
"rest": "",
|
|
133
|
+
"silent": {},
|
|
134
|
+
"sets": {},
|
|
135
|
+
"text": text
|
|
136
|
+
}
|
|
137
|
+
for k, v in data.items():
|
|
138
|
+
if not clean:
|
|
139
|
+
setattr(obj, k, data.get(k, v) or v)
|
|
140
|
+
else:
|
|
141
|
+
setattr(obj, k, v)
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def parse(cls, obj, text, clean=False):
|
|
145
|
+
"parse text for command and arguments."
|
|
146
|
+
cls.init(obj, text, clean)
|
|
147
|
+
args = []
|
|
148
|
+
nr = -1
|
|
149
|
+
for spli in text.split():
|
|
150
|
+
if spli.startswith("--"):
|
|
151
|
+
obj.opts += f",{spli[2:]}"
|
|
152
|
+
continue
|
|
153
|
+
if spli.startswith("-"):
|
|
154
|
+
try:
|
|
155
|
+
obj.index = int(spli[1:])
|
|
156
|
+
except ValueError:
|
|
157
|
+
obj.opts += spli[1:]
|
|
158
|
+
continue
|
|
159
|
+
if "-=" in spli:
|
|
160
|
+
key, value = spli.split("-=", maxsplit=1)
|
|
161
|
+
typed(obj.silent, key, value)
|
|
162
|
+
typed(obj.gets, key, value)
|
|
163
|
+
continue
|
|
164
|
+
if "==" in spli:
|
|
165
|
+
key, value = spli.split("==", maxsplit=1)
|
|
166
|
+
typed(obj.gets, key, value)
|
|
167
|
+
continue
|
|
168
|
+
if "=" in spli:
|
|
169
|
+
key, value = spli.split("=", maxsplit=1)
|
|
170
|
+
typed(obj.sets, key, value)
|
|
171
|
+
continue
|
|
172
|
+
nr += 1
|
|
173
|
+
if nr == 0:
|
|
174
|
+
obj.cmd = spli
|
|
175
|
+
continue
|
|
176
|
+
args.append(spli)
|
|
177
|
+
if args:
|
|
178
|
+
obj.args = args
|
|
179
|
+
obj.rest = " ".join(obj.args)
|
|
180
|
+
obj.text = obj.cmd + " " + obj.rest
|
|
181
|
+
if "v" in obj.opts:
|
|
182
|
+
obj.verbose = True
|
|
183
|
+
if "t" in obj.opts:
|
|
184
|
+
obj.threaded = True
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def __dir__():
|
|
188
|
+
return (
|
|
189
|
+
'Commands',
|
|
190
|
+
'Parser',
|
|
191
|
+
'cmd'
|
|
192
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# This file is placed in the Public Domain.
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
"one config to rule them all"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
from .methods import Method
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Config(type):
|
|
11
|
+
|
|
12
|
+
"config meta class"
|
|
13
|
+
|
|
14
|
+
def __getattr__(cls, key):
|
|
15
|
+
if key in dir(cls):
|
|
16
|
+
return cls.__getattribute__(key)
|
|
17
|
+
return ""
|
|
18
|
+
|
|
19
|
+
def __str__(cls):
|
|
20
|
+
return str(Method.skip(cls))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Main(metaclass=Config):
|
|
24
|
+
|
|
25
|
+
"main config"
|
|
26
|
+
|
|
27
|
+
level = ""
|
|
28
|
+
mods = ""
|
|
29
|
+
name = Method.pkgname(Config)
|
|
30
|
+
otxt = ""
|
|
31
|
+
path = ""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Cfg(metaclass=Config):
|
|
35
|
+
|
|
36
|
+
"inheritable config"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def __dir__():
|
|
40
|
+
return (
|
|
41
|
+
'Cfg',
|
|
42
|
+
'Config',
|
|
43
|
+
'Main'
|
|
44
|
+
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# This file is placed in the Public Domain.
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
"background daemon"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
from .configs import Main
|
|
8
|
+
from .persist import Workdir
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Daemon:
|
|
12
|
+
|
|
13
|
+
"detach from console"
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def daemon(cls):
|
|
17
|
+
"run in the background."
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
pid = os.fork()
|
|
21
|
+
if pid != 0:
|
|
22
|
+
os._exit(0)
|
|
23
|
+
os.setsid()
|
|
24
|
+
pid2 = os.fork()
|
|
25
|
+
if pid2 != 0:
|
|
26
|
+
os._exit(0)
|
|
27
|
+
if not Main.verbose:
|
|
28
|
+
cls.null(sys.stdin)
|
|
29
|
+
cls.null(sys.stdout)
|
|
30
|
+
cls.null(sys.stderr)
|
|
31
|
+
os.umask(0o077)
|
|
32
|
+
os.chdir("/")
|
|
33
|
+
os.nice(10)
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def null(cls, iostream):
|
|
37
|
+
"route to /dev/null."
|
|
38
|
+
import os
|
|
39
|
+
with open('/dev/null', 'r', encoding="utf-8") as sis:
|
|
40
|
+
os.dup2(sis.fileno(), iostream.fileno())
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def pid(cls):
|
|
44
|
+
"return pid path."
|
|
45
|
+
return Workdir.pid(Main.name)
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def privileges(cls):
|
|
49
|
+
"drop privileges."
|
|
50
|
+
import getpass
|
|
51
|
+
import os
|
|
52
|
+
import pwd
|
|
53
|
+
pwnam2 = pwd.getpwnam(getpass.getuser())
|
|
54
|
+
os.setgid(pwnam2.pw_gid)
|
|
55
|
+
os.setuid(pwnam2.pw_uid)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def __dir__():
|
|
59
|
+
return (
|
|
60
|
+
'Daemon',
|
|
61
|
+
)
|