rssbot
Advanced tools
Sorry, the diff of this file is not supported yet
| # This file is placed in the Public Domain. | ||
| "commands" | ||
| import hashlib | ||
| import importlib | ||
| import importlib.util | ||
| import inspect | ||
| import logging | ||
| import os | ||
| import sys | ||
| import threading | ||
| import _thread | ||
| from .methods import j, rlog, spl | ||
| from .handler import Fleet | ||
| lock = threading.RLock() | ||
| class Commands: | ||
| cmds = {} | ||
| debug = False | ||
| md5s = {} | ||
| mod = j(os.path.dirname(__file__), "modules") | ||
| package = __name__.split(".")[0] + "." + "modules" | ||
| names = {} | ||
| @staticmethod | ||
| def add(func) -> None: | ||
| name = func.__name__ | ||
| modname = func.__module__.split(".")[-1] | ||
| Commands.cmds[name] = func | ||
| Commands.names[name] = modname | ||
| @staticmethod | ||
| def get(cmd): | ||
| func = Commands.cmds.get(cmd, None) | ||
| if func: | ||
| return func | ||
| name = Commands.names.get(cmd, None) | ||
| if not name: | ||
| return | ||
| module = getmod(name) | ||
| if not module: | ||
| return | ||
| scan(module) | ||
| if Commands.debug: | ||
| module.DEBUG = True | ||
| return Commands.cmds.get(cmd, None) | ||
| def command(evt): | ||
| parse(evt) | ||
| func = Commands.get(evt.cmd) | ||
| if func: | ||
| func(evt) | ||
| Fleet.display(evt) | ||
| evt.ready() | ||
| "modules" | ||
| def getmod(name, path=None): | ||
| with lock: | ||
| mname = Commands.package + "." + name | ||
| module = sys.modules.get(mname, None) | ||
| if module: | ||
| return module | ||
| if not path: | ||
| path = Commands.mod | ||
| pth = j(path, f"{name}.py") | ||
| if not os.path.exists(pth): | ||
| return | ||
| if name != "tbl" and md5sum(pth) != Commands.md5s.get(name, None): | ||
| rlog("warn", f"md5 error on {pth.split(os.sep)[-1]}") | ||
| return importer(mname, pth) | ||
| def modules(): | ||
| if not os.path.exists(Commands.mod): | ||
| return {} | ||
| return { | ||
| x[:-3] for x in os.listdir(Commands.mod) | ||
| if x.endswith(".py") and not x.startswith("__") | ||
| } | ||
| def scan(module): | ||
| for key, cmdz in inspect.getmembers(module, inspect.isfunction): | ||
| if key.startswith("cb"): | ||
| continue | ||
| if 'event' in inspect.signature(cmdz).parameters: | ||
| Commands.add(cmdz) | ||
| def scanner(names=None): | ||
| res = [] | ||
| for nme in sorted(modules()): | ||
| if names and nme not in spl(names): | ||
| continue | ||
| module = getmod(nme) | ||
| if not module: | ||
| continue | ||
| scan(module) | ||
| res.append(module) | ||
| return res | ||
| def table(checksum=""): | ||
| pth = j(Commands.mod, "tbl.py") | ||
| if os.path.exists(pth): | ||
| if checksum and md5sum(pth) != checksum: | ||
| rlog("warn", "table checksum error.") | ||
| tbl = getmod("tbl") | ||
| if tbl: | ||
| if "NAMES" in dir(tbl): | ||
| Commands.names.update(tbl.NAMES) | ||
| if "MD5" in dir(tbl): | ||
| Commands.md5s.update(tbl.MD5) | ||
| else: | ||
| scanner() | ||
| "utilities" | ||
| def importer(name, pth): | ||
| try: | ||
| spec = importlib.util.spec_from_file_location(name, pth) | ||
| if not spec: | ||
| rlog("info", f"misiing {pth}") | ||
| return | ||
| module = importlib.util.module_from_spec(spec) | ||
| if not module: | ||
| rlog("info", f"{pth} not importable") | ||
| return | ||
| sys.modules[name] = module | ||
| spec.loader.exec_module(module) | ||
| rlog("info", f"load {pth}") | ||
| return module | ||
| except Exception as ex: | ||
| logging.exception(ex) | ||
| _thread.interrupt_main() | ||
| def md5sum(path): | ||
| with open(path, "r", encoding="utf-8") as file: | ||
| txt = file.read().encode("utf-8") | ||
| return hashlib.md5(txt).hexdigest() | ||
| def parse(obj, txt=None): | ||
| if txt is None: | ||
| if "txt" in dir(obj): | ||
| txt = obj.txt | ||
| else: | ||
| txt = "" | ||
| args = [] | ||
| obj.args = getattr(obj, "args", []) | ||
| obj.cmd = getattr(obj, "cmd", "") | ||
| obj.gets = getattr(obj, "gets", "") | ||
| obj.index = getattr(obj, "index", None) | ||
| obj.inits = getattr(obj, "inits", "") | ||
| obj.mod = getattr(obj, "mod", "") | ||
| obj.opts = getattr(obj, "opts", "") | ||
| obj.result = getattr(obj, "result", "") | ||
| obj.sets = getattr(obj, "sets", {}) | ||
| obj.silent = getattr(obj, "silent", "") | ||
| obj.txt = txt or getattr(obj, "txt", "") | ||
| obj.otxt = obj.txt or getattr(obj, "otxt", "") | ||
| _nr = -1 | ||
| for spli in obj.otxt.split(): | ||
| if spli.startswith("-"): | ||
| try: | ||
| obj.index = int(spli[1:]) | ||
| except ValueError: | ||
| obj.opts += spli[1:] | ||
| continue | ||
| if "-=" in spli: | ||
| key, value = spli.split("-=", maxsplit=1) | ||
| obj.silent[key] = value | ||
| obj.gets[key] = value | ||
| continue | ||
| if "==" in spli: | ||
| key, value = spli.split("==", maxsplit=1) | ||
| obj.gets[key] = value | ||
| continue | ||
| if "=" in spli: | ||
| key, value = spli.split("=", maxsplit=1) | ||
| if key == "mod": | ||
| if obj.mod: | ||
| obj.mod += f",{value}" | ||
| else: | ||
| obj.mod = value | ||
| continue | ||
| obj.sets[key] = value | ||
| continue | ||
| _nr += 1 | ||
| if _nr == 0: | ||
| obj.cmd = spli | ||
| continue | ||
| args.append(spli) | ||
| if args: | ||
| obj.args = args | ||
| obj.txt = obj.cmd or "" | ||
| obj.rest = " ".join(obj.args) | ||
| obj.txt = obj.cmd + " " + obj.rest | ||
| else: | ||
| obj.txt = obj.cmd or "" | ||
| "interface" | ||
| def __dir__(): | ||
| return ( | ||
| 'Commands', | ||
| 'command', | ||
| 'getmod', | ||
| 'importer', | ||
| 'md5sum', | ||
| 'modules', | ||
| 'parse', | ||
| 'scan', | ||
| 'scanner', | ||
| 'table' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "event handler" | ||
| import queue | ||
| import threading | ||
| import time | ||
| import _thread | ||
| from .methods import fqn | ||
| from .runtime import launch | ||
| class Handler: | ||
| def __init__(self): | ||
| self.cbs = {} | ||
| self.type = fqn(self) | ||
| self.queue = queue.Queue() | ||
| self.ready = threading.Event() | ||
| self.stopped = threading.Event() | ||
| def available(self, event): | ||
| return event.type in self.cbs | ||
| def callback(self, event): | ||
| func = self.cbs.get(event.type, None) | ||
| if func: | ||
| event._thr = launch(func, event, name=event.txt and event.txt.split()[0]) | ||
| else: | ||
| event.ready() | ||
| def loop(self): | ||
| while not self.stopped.is_set(): | ||
| try: | ||
| event = self.poll() | ||
| if event is None or self.stopped.is_set(): | ||
| break | ||
| event.orig = repr(self) | ||
| self.callback(event) | ||
| except (KeyboardInterrupt, EOFError): | ||
| _thread.interrupt_main() | ||
| def poll(self): | ||
| return self.queue.get() | ||
| def put(self, event): | ||
| self.queue.put(event) | ||
| def register(self, typ, cbs): | ||
| self.cbs[typ] = cbs | ||
| def start(self, daemon=True): | ||
| self.stopped.clear() | ||
| launch(self.loop, daemon=daemon) | ||
| def stop(self): | ||
| self.stopped.set() | ||
| self.queue.put(None) | ||
| def wait(self): | ||
| pass | ||
| "clients" | ||
| class Client(Handler): | ||
| def __init__(self): | ||
| Handler.__init__(self) | ||
| self.olock = threading.RLock() | ||
| Fleet.add(self) | ||
| def announce(self, txt): | ||
| pass | ||
| def display(self, event): | ||
| with self.olock: | ||
| for tme in sorted(event.result): | ||
| self.dosay(event.channel, event.result[tme]) | ||
| def dosay(self, channel, txt): | ||
| self.say(channel, txt) | ||
| def raw(self, txt): | ||
| raise NotImplementedError("raw") | ||
| def say(self, channel, txt): | ||
| self.raw(txt) | ||
| class Output(Client): | ||
| def __init__(self): | ||
| Client.__init__(self) | ||
| self.olock = threading.RLock() | ||
| self.oqueue = queue.Queue() | ||
| self.ostop = threading.Event() | ||
| def oput(self, event): | ||
| self.oqueue.put(event) | ||
| def output(self): | ||
| while not self.ostop.is_set(): | ||
| event = self.oqueue.get() | ||
| if event is None: | ||
| self.oqueue.task_done() | ||
| break | ||
| self.display(event) | ||
| self.oqueue.task_done() | ||
| def start(self, daemon=True): | ||
| self.ostop.clear() | ||
| launch(self.output, daemon=daemon) | ||
| super().start() | ||
| def stop(self): | ||
| self.ostop.set() | ||
| self.oqueue.put(None) | ||
| super().stop() | ||
| def wait(self): | ||
| try: | ||
| self.oqueue.join() | ||
| except Exception: | ||
| _thread.interrupt_main() | ||
| "list of clients" | ||
| class Fleet: | ||
| clients = {} | ||
| @staticmethod | ||
| def add(client): | ||
| Fleet.clients[repr(client)] = client | ||
| @staticmethod | ||
| def all(): | ||
| return list(Fleet.clients.values()) | ||
| @staticmethod | ||
| def announce(txt): | ||
| for client in Fleet.all(): | ||
| client.announce(txt) | ||
| @staticmethod | ||
| def dispatch(evt): | ||
| client = Fleet.get(evt.orig) | ||
| client.put(evt) | ||
| @staticmethod | ||
| def display(evt): | ||
| client = Fleet.get(evt.orig) | ||
| client.display(evt) | ||
| @staticmethod | ||
| def first(): | ||
| clt = list(Fleet.all()) | ||
| res = None | ||
| if clt: | ||
| res = clt[0] | ||
| return res | ||
| @staticmethod | ||
| def get(orig): | ||
| return Fleet.clients.get(orig, None) | ||
| @staticmethod | ||
| def say(orig, channel, txt): | ||
| client = Fleet.get(orig) | ||
| if client: | ||
| client.say(channel, txt) | ||
| @staticmethod | ||
| def shutdown(): | ||
| for client in Fleet.all(): | ||
| client.stop() | ||
| @staticmethod | ||
| def wait(): | ||
| time.sleep(0.1) | ||
| for client in Fleet.all(): | ||
| client.wait() | ||
| "event" | ||
| class Event: | ||
| def __init__(self): | ||
| self._ready = threading.Event() | ||
| self._thr = None | ||
| self.args = [] | ||
| self.channel = "" | ||
| self.ctime = time.time() | ||
| self.orig = "" | ||
| self.rest = "" | ||
| self.result = {} | ||
| self.txt = "" | ||
| self.type = "event" | ||
| def done(self): | ||
| self.reply("ok") | ||
| def ready(self): | ||
| self._ready.set() | ||
| def reply(self, txt): | ||
| self.result[time.time()] = txt | ||
| def wait(self, timeout=None): | ||
| try: | ||
| self._ready.wait() | ||
| if self._thr: | ||
| self._thr.join() | ||
| except (KeyboardInterrupt, EOFError): | ||
| _thread.interrupt_main() | ||
| "interface" | ||
| def __dir__(): | ||
| return ( | ||
| 'Client', | ||
| 'Event', | ||
| 'Fleet', | ||
| 'Handler' | ||
| 'Output' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "methods" | ||
| import hashlib | ||
| import importlib | ||
| import importlib.util | ||
| import logging | ||
| import os | ||
| import sys | ||
| import time | ||
| import _thread | ||
| from .objects import items, keys | ||
| j = os.path.join | ||
| def edit(obj, setter, skip=True): | ||
| for key, val in items(setter): | ||
| if skip and val == "": | ||
| continue | ||
| try: | ||
| setattr(obj, key, int(val)) | ||
| continue | ||
| except ValueError: | ||
| pass | ||
| try: | ||
| setattr(obj, key, float(val)) | ||
| continue | ||
| except ValueError: | ||
| pass | ||
| if val in ["True", "true"]: | ||
| setattr(obj, key, True) | ||
| elif val in ["False", "false"]: | ||
| setattr(obj, key, False) | ||
| else: | ||
| setattr(obj, key, val) | ||
| def fmt(obj, args=None, skip=None, plain=False, empty=False, newline=False): | ||
| if args is None: | ||
| args = keys(obj) | ||
| if skip is None: | ||
| skip = [] | ||
| txt = "" | ||
| for key in args: | ||
| if key.startswith("__"): | ||
| continue | ||
| if key in skip: | ||
| continue | ||
| value = getattr(obj, key, None) | ||
| if value is None: | ||
| continue | ||
| if not empty and not value: | ||
| continue | ||
| if plain: | ||
| txt += f"{value} " | ||
| elif isinstance(value, str): | ||
| txt += f'{key}="{value}" ' | ||
| else: | ||
| txt += f"{key}={value} " | ||
| if newline: | ||
| txt += "\n" | ||
| return txt.strip() | ||
| def fqn(obj): | ||
| kin = str(type(obj)).split()[-1][1:-2] | ||
| if kin == "type": | ||
| kin = f"{obj.__module__}.{obj.__name__}" | ||
| return kin | ||
| def search(obj, selector, matching=False): | ||
| res = False | ||
| if not selector: | ||
| return res | ||
| for key, value in items(selector): | ||
| val = getattr(obj, key, None) | ||
| if not val: | ||
| continue | ||
| if matching and value == val: | ||
| res = True | ||
| elif str(value).lower() in str(val).lower() or value == "match": | ||
| res = True | ||
| else: | ||
| res = False | ||
| break | ||
| return res | ||
| "utilities" | ||
| def elapsed(seconds, short=True): | ||
| txt = "" | ||
| nsec = float(seconds) | ||
| if nsec < 1: | ||
| return f"{nsec:.2f}s" | ||
| yea = 365 * 24 * 60 * 60 | ||
| week = 7 * 24 * 60 * 60 | ||
| nday = 24 * 60 * 60 | ||
| hour = 60 * 60 | ||
| minute = 60 | ||
| yeas = int(nsec / yea) | ||
| nsec -= yeas * yea | ||
| weeks = int(nsec / week) | ||
| nsec -= weeks * week | ||
| nrdays = int(nsec / nday) | ||
| nsec -= nrdays * nday | ||
| hours = int(nsec / hour) | ||
| nsec -= hours * hour | ||
| minutes = int(nsec / minute) | ||
| nsec -= int(minute * minutes) | ||
| sec = int(nsec) | ||
| if yeas: | ||
| txt += f"{yeas}y" | ||
| if weeks: | ||
| nrdays += weeks * 7 | ||
| if nrdays: | ||
| txt += f"{nrdays}d" | ||
| if short and txt: | ||
| return txt.strip() | ||
| if hours: | ||
| txt += f"{hours}h" | ||
| if minutes: | ||
| txt += f"{minutes}m" | ||
| if sec: | ||
| txt += f"{sec}s" | ||
| txt = txt.strip() | ||
| return txt | ||
| def extract_date(daystr): | ||
| daystr = daystr.encode('utf-8', 'replace').decode("utf-8") | ||
| res = time.time() | ||
| for format in FORMATS: | ||
| try: | ||
| res = time.mktime(time.strptime(daystr, format)) | ||
| break | ||
| except ValueError: | ||
| pass | ||
| return res | ||
| def level(loglevel="debug"): | ||
| if loglevel != "none": | ||
| format_short = "%(asctime)-8s %(message)-80s" | ||
| datefmt = "%H:%M:%S" | ||
| logging.basicConfig(datefmt=datefmt, format=format_short, force=True) | ||
| logging.getLogger().setLevel(LEVELS.get(loglevel)) | ||
| def rlog(loglevel, txt, ignore=None): | ||
| if ignore is None: | ||
| ignore = [] | ||
| for ign in ignore: | ||
| if ign in str(txt): | ||
| return | ||
| logging.log(LEVELS.get(loglevel), txt) | ||
| def spl(txt): | ||
| try: | ||
| result = txt.split(",") | ||
| except (TypeError, ValueError): | ||
| result = [ | ||
| txt, | ||
| ] | ||
| return [x for x in result if x] | ||
| "data" | ||
| FORMATS = [ | ||
| "%Y-%M-%D %H:%M:%S", | ||
| "%Y-%m-%d %H:%M:%S", | ||
| "%Y-%m-%d", | ||
| "%d-%m-%Y", | ||
| "%d-%m", | ||
| "%m-%d", | ||
| ] | ||
| LEVELS = { | ||
| 'debug': logging.DEBUG, | ||
| 'info': logging.INFO, | ||
| 'warning': logging.WARNING, | ||
| 'warn': logging.WARNING, | ||
| 'error': logging.ERROR, | ||
| 'critical': logging.CRITICAL, | ||
| } | ||
| "interface" | ||
| def __dir__(): | ||
| return ( | ||
| 'edit', | ||
| 'elapsed', | ||
| 'extract_date', | ||
| 'fmt', | ||
| 'fqn', | ||
| 'j', | ||
| 'level', | ||
| 'rlog', | ||
| 'search', | ||
| 'spl' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| NAMES = { | ||
| "cfg": "irc", | ||
| "dpl": "rss", | ||
| "exp": "rss", | ||
| "imp": "rss", | ||
| "mre": "irc", | ||
| "nme": "rss", | ||
| "pwd": "irc", | ||
| "rem": "rss", | ||
| "res": "rss", | ||
| "rss": "rss", | ||
| "syn": "rss" | ||
| } | ||
| MD5 = { | ||
| "irc": "fc8ddf8baeadfe46b1bed8bf47efecab", | ||
| "rss": "b60c574764b55d7c766cd56122946904", | ||
| "tbl": "d41d8cd98f00b204e9800998ecf8427e", | ||
| } |
| # This file is placed in the Public Domain. | ||
| "a clean namespace" | ||
| import json | ||
| class Object: | ||
| def __contains__(self, key): | ||
| return key in dir(self) | ||
| def __iter__(self): | ||
| return iter(self.__dict__) | ||
| def __len__(self): | ||
| return len(self.__dict__) | ||
| def __str__(self): | ||
| return str(self.__dict__) | ||
| def construct(obj, *args, **kwargs): | ||
| if args: | ||
| val = args[0] | ||
| if isinstance(val, zip): | ||
| update(obj, dict(val)) | ||
| elif isinstance(val, dict): | ||
| update(obj, val) | ||
| elif isinstance(val, Object): | ||
| update(obj, vars(val)) | ||
| if kwargs: | ||
| update(obj, kwargs) | ||
| def items(obj): | ||
| if isinstance(obj, dict): | ||
| return obj.items() | ||
| return obj.__dict__.items() | ||
| def keys(obj): | ||
| if isinstance(obj, dict): | ||
| return obj.keys() | ||
| return obj.__dict__.keys() | ||
| def update(obj, data, empty=True): | ||
| for key, value in items(data): | ||
| if not empty and not value: | ||
| continue | ||
| setattr(obj, key, value) | ||
| def values(obj): | ||
| if isinstance(obj, dict): | ||
| return obj.values() | ||
| return obj.__dict__.values() | ||
| "decoder/encoder" | ||
| class Encoder(json.JSONEncoder): | ||
| def default(self, o): | ||
| if isinstance(o, dict): | ||
| return o.items() | ||
| if issubclass(type(o), Object): | ||
| return vars(o) | ||
| if isinstance(o, list): | ||
| return iter(o) | ||
| try: | ||
| return json.JSONEncoder.default(self, o) | ||
| except TypeError: | ||
| try: | ||
| return vars(o) | ||
| except TypeError: | ||
| return repr(o) | ||
| def dump(obj, fp, *args, **kw): | ||
| kw["cls"] = Encoder | ||
| json.dump(obj, fp, *args, **kw) | ||
| def dumps(obj, *args, **kw): | ||
| kw["cls"] = Encoder | ||
| return json.dumps(obj, *args, **kw) | ||
| def hook(objdict): | ||
| obj = Object() | ||
| construct(obj, objdict) | ||
| return obj | ||
| def load(fp, *args, **kw): | ||
| kw["object_hook"] = hook | ||
| return json.load(fp, *args, **kw) | ||
| def loads(s, *args, **kw): | ||
| kw["object_hook"] = hook | ||
| return json.loads(s, *args, **kw) | ||
| "interface" | ||
| def __dir__(): | ||
| return ( | ||
| 'Object', | ||
| 'construct', | ||
| 'dump', | ||
| 'dumps', | ||
| 'items', | ||
| 'keys', | ||
| 'load', | ||
| 'loads', | ||
| 'update', | ||
| 'values' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "persistence" | ||
| import datetime | ||
| import json | ||
| import os | ||
| import pathlib | ||
| import threading | ||
| import time | ||
| from .methods import fqn, j, search | ||
| from .objects import Object, dump, load, update | ||
| lock = threading.RLock() | ||
| class Cache: | ||
| objs = {} | ||
| @staticmethod | ||
| def add(path, obj): | ||
| Cache.objs[path] = obj | ||
| @staticmethod | ||
| def get(path): | ||
| return Cache.objs.get(path, None) | ||
| @staticmethod | ||
| def update(path, obj): | ||
| if not obj: | ||
| return | ||
| if path in Cache.objs: | ||
| update(Cache.objs[path], obj) | ||
| else: | ||
| Cache.add(path, obj) | ||
| def cdir(path): | ||
| pth = pathlib.Path(path) | ||
| pth.parent.mkdir(parents=True, exist_ok=True) | ||
| def read(obj, path): | ||
| with lock: | ||
| with open(path, "r", encoding="utf-8") as fpt: | ||
| try: | ||
| update(obj, load(fpt)) | ||
| except json.decoder.JSONDecodeError as ex: | ||
| ex.add_note(path) | ||
| raise ex | ||
| def write(obj, path=None): | ||
| with lock: | ||
| if path is None: | ||
| path = getpath(obj) | ||
| cdir(path) | ||
| with open(path, "w", encoding="utf-8") as fpt: | ||
| dump(obj, fpt, indent=4) | ||
| Cache.update(path, obj) | ||
| return path | ||
| "paths" | ||
| class Workdir: | ||
| name = __file__.rsplit(os.sep, maxsplit=2)[-2] | ||
| wdr = os.path.expanduser(f"~/.{name}") | ||
| def getpath(obj): | ||
| return store(ident(obj)) | ||
| def ident(obj): | ||
| return j(fqn(obj), *str(datetime.datetime.now()).split()) | ||
| def long(name): | ||
| split = name.split(".")[-1].lower() | ||
| res = name | ||
| for names in types(): | ||
| if split == names.split(".")[-1].lower(): | ||
| res = names | ||
| break | ||
| return res | ||
| def moddir(): | ||
| return j(Workdir.wdr, "mods") | ||
| def pidname(name): | ||
| return j(Workdir.wdr, f"{name}.pid") | ||
| def setwd(name, path=""): | ||
| path = path or os.path.expanduser(f"~/.{name}") | ||
| Workdir.wdr = path | ||
| skel() | ||
| def skel(): | ||
| if os.path.exists(store()): | ||
| return | ||
| pth = pathlib.Path(store()) | ||
| pth.mkdir(parents=True, exist_ok=True) | ||
| pth = pathlib.Path(moddir()) | ||
| pth.mkdir(parents=True, exist_ok=True) | ||
| return str(pth) | ||
| def store(pth=""): | ||
| return j(Workdir.wdr, "store", pth) | ||
| def strip(pth, nmr=2): | ||
| return j(pth.split(os.sep)[-nmr:]) | ||
| def types(): | ||
| skel() | ||
| return os.listdir(store()) | ||
| def wdr(pth): | ||
| return j(Workdir.wdr, pth) | ||
| "find" | ||
| def find(clz, selector=None, deleted=False, matching=False): | ||
| clz = long(clz) | ||
| if selector is None: | ||
| selector = {} | ||
| for pth in fns(clz): | ||
| obj = Cache.get(pth) | ||
| if not obj: | ||
| obj = Object() | ||
| read(obj, pth) | ||
| Cache.add(pth, obj) | ||
| if not deleted and isdeleted(obj): | ||
| continue | ||
| if selector and not search(obj, selector, matching): | ||
| continue | ||
| yield pth, obj | ||
| def fns(clz): | ||
| pth = store(clz) | ||
| for rootdir, dirs, _files in os.walk(pth, topdown=False): | ||
| for dname in dirs: | ||
| ddd = j(rootdir, dname) | ||
| for fll in os.listdir(ddd): | ||
| yield j(ddd, fll) | ||
| def fntime(daystr): | ||
| datestr = " ".join(daystr.split(os.sep)[-2:]) | ||
| datestr = datestr.replace("_", " ") | ||
| if "." in datestr: | ||
| datestr, rest = datestr.rsplit(".", 1) | ||
| else: | ||
| rest = "" | ||
| timed = time.mktime(time.strptime(datestr, "%Y-%m-%d %H:%M:%S")) | ||
| if rest: | ||
| timed += float("." + rest) | ||
| return float(timed) | ||
| def isdeleted(obj): | ||
| return "__deleted__" in dir(obj) and obj.__deleted__ | ||
| def last(obj, selector=None): | ||
| if selector is None: | ||
| selector = {} | ||
| result = sorted(find(fqn(obj), selector), key=lambda x: fntime(x[0])) | ||
| res = "" | ||
| if result: | ||
| inp = result[-1] | ||
| update(obj, inp[-1]) | ||
| res = inp[0] | ||
| return res | ||
| "interface" | ||
| def __dir__(): | ||
| return ( | ||
| 'Cache', | ||
| 'Workdir', | ||
| 'cdir', | ||
| 'find', | ||
| 'fntime', | ||
| 'last', | ||
| 'long', | ||
| 'pidname', | ||
| 'read', | ||
| 'setwd', | ||
| 'store', | ||
| 'strip', | ||
| 'types', | ||
| 'write' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "runtime" | ||
| import logging | ||
| import queue | ||
| import threading | ||
| import time | ||
| import _thread | ||
| class Thread(threading.Thread): | ||
| def __init__(self, func, *args, daemon=True, **kwargs): | ||
| super().__init__(None, self.run, None, (), daemon=daemon) | ||
| self.name = kwargs.get("name", name(func)) | ||
| self.queue = queue.Queue() | ||
| self.result = None | ||
| self.starttime = time.time() | ||
| self.stopped = threading.Event() | ||
| self.queue.put((func, args)) | ||
| def __iter__(self): | ||
| return self | ||
| def __next__(self): | ||
| yield from dir(self) | ||
| def run(self): | ||
| func, args = self.queue.get() | ||
| try: | ||
| self.result = func(*args) | ||
| except (KeyboardInterrupt, EOFError): | ||
| _thread.interrupt_main() | ||
| except Exception as ex: | ||
| logging.exception(ex) | ||
| _thread.interrupt_main() | ||
| def join(self, timeout=None): | ||
| try: | ||
| super().join(timeout) | ||
| return self.result | ||
| except (KeyboardInterrupt, EOFError): | ||
| _thread.interrupt_main() | ||
| "timer/repeater" | ||
| class Timy(threading.Timer): | ||
| def __init__(self, sleep, func, *args, **kwargs): | ||
| super().__init__(sleep, func) | ||
| self.name = kwargs.get("name", name(func)) | ||
| self.sleep = sleep | ||
| self.state = {} | ||
| self.state["latest"] = time.time() | ||
| self.state["starttime"] = time.time() | ||
| self.starttime = time.time() | ||
| class Timed: | ||
| def __init__(self, sleep, func, *args, thrname="", **kwargs): | ||
| self.args = args | ||
| self.func = func | ||
| self.kwargs = kwargs | ||
| self.sleep = sleep | ||
| self.name = thrname or kwargs.get("name", name(func)) | ||
| self.target = time.time() + self.sleep | ||
| self.timer = None | ||
| def run(self): | ||
| self.timer.latest = time.time() | ||
| self.func(*self.args) | ||
| def start(self): | ||
| self.kwargs["name"] = self.name | ||
| timer = Timy(self.sleep, self.run, *self.args, **self.kwargs) | ||
| timer.start() | ||
| self.timer = timer | ||
| def stop(self): | ||
| if self.timer: | ||
| self.timer.cancel() | ||
| class Repeater(Timed): | ||
| def run(self): | ||
| launch(self.start) | ||
| super().run() | ||
| "utilities" | ||
| def launch(func, *args, **kwargs): | ||
| thread = Thread(func, *args, **kwargs) | ||
| thread.start() | ||
| return thread | ||
| def name(obj): | ||
| typ = type(obj) | ||
| if "__builtins__" in dir(typ): | ||
| return obj.__name__ | ||
| if "__self__" in dir(obj): | ||
| return f"{obj.__self__.__class__.__name__}.{obj.__name__}" | ||
| if "__class__" in dir(obj) and "__name__" in dir(obj): | ||
| return f"{obj.__class__.__name__}.{obj.__name__}" | ||
| if "__class__" in dir(obj): | ||
| return f"{obj.__class__.__module__}.{obj.__class__.__name__}" | ||
| if "__name__" in dir(obj): | ||
| return f"{obj.__class__.__name__}.{obj.__name__}" | ||
| return "" | ||
| "interface" | ||
| def __dir__(): | ||
| return ( | ||
| 'Pool', | ||
| 'Repeater', | ||
| 'Thread', | ||
| 'Timed', | ||
| 'launch', | ||
| 'name' | ||
| ) |
+8
| # This file is placed in the Public Domain. | ||
| import setuptools | ||
| if __name__ == "__main__": | ||
| setuptools.setup(scripts=["bin/rssbot"]) |
| # This file is placed in the Public Domain. | ||
| "engine" | ||
| import unittest | ||
| class TestNone(unittest.TestCase): | ||
| def testnone(self): | ||
| self.assertTrue(True) |
+1
-1
| Metadata-Version: 2.4 | ||
| Name: rssbot | ||
| Version: 651 | ||
| Version: 652 | ||
| Summary: 24/7 Feed Fetcher. | ||
@@ -5,0 +5,0 @@ Author-email: Bart Thate <rssbotd@gmail.com> |
+1
-5
@@ -12,3 +12,3 @@ [build-system] | ||
| description = "24/7 Feed Fetcher." | ||
| version = "651" | ||
| version = "652" | ||
| authors = [ | ||
@@ -27,6 +27,2 @@ {name = "Bart Thate",email = "rssbotd@gmail.com"}, | ||
| [project.scripts] | ||
| "rssbot" = "rssbot.__main__:main" | ||
| [project.urls] | ||
@@ -33,0 +29,0 @@ "home" = "https://pypi.org/project/rssbot" |
| Metadata-Version: 2.4 | ||
| Name: rssbot | ||
| Version: 651 | ||
| Version: 652 | ||
| Summary: 24/7 Feed Fetcher. | ||
@@ -5,0 +5,0 @@ Author-email: Bart Thate <rssbotd@gmail.com> |
| README.rst | ||
| pyproject.toml | ||
| rssbot/__init__.py | ||
| rssbot/__main__.py | ||
| rssbot/auto.py | ||
| rssbot/client.py | ||
| rssbot/cmnd.py | ||
| rssbot/disk.py | ||
| rssbot/engine.py | ||
| rssbot/event.py | ||
| rssbot/find.py | ||
| rssbot/fleet.py | ||
| rssbot/method.py | ||
| rssbot/object.py | ||
| rssbot/output.py | ||
| rssbot/parse.py | ||
| rssbot/paths.py | ||
| rssbot/pool.py | ||
| rssbot/serial.py | ||
| rssbot/thread.py | ||
| rssbot/timer.py | ||
| rssbot/utils.py | ||
| setup.py | ||
| bin/rssbot | ||
| rssbot/command.py | ||
| rssbot/handler.py | ||
| rssbot/methods.py | ||
| rssbot/objects.py | ||
| rssbot/persist.py | ||
| rssbot/runtime.py | ||
| rssbot.egg-info/PKG-INFO | ||
| rssbot.egg-info/SOURCES.txt | ||
| rssbot.egg-info/dependency_links.txt | ||
| rssbot.egg-info/entry_points.txt | ||
| rssbot.egg-info/top_level.txt | ||
| rssbot/modules/__init__.py | ||
| rssbot/modules/cmd.py | ||
| rssbot/modules/dbg.py | ||
| rssbot/modules/fnd.py | ||
| rssbot/modules/irc.py | ||
| rssbot/modules/log.py | ||
| rssbot/modules/lst.py | ||
| rssbot/modules/rss.py | ||
| rssbot/modules/srv.py | ||
| rssbot/modules/tdo.py | ||
| rssbot/modules/thr.py | ||
| rssbot/modules/tbl.py | ||
| tests/test_none.py |
+26
-53
@@ -16,26 +16,19 @@ # This file is placed in the Public Domain. | ||
| from ..auto import Auto | ||
| from ..cmnd import command | ||
| from ..disk import write | ||
| from ..event import Event as IEvent | ||
| from ..find import last | ||
| from ..fleet import Fleet | ||
| from ..method import edit, fmt | ||
| from ..object import Object, keys | ||
| from ..output import Output | ||
| from ..paths import getpath, ident | ||
| from ..thread import launch | ||
| from ..utils import rlog | ||
| from rssbot.command import command | ||
| from rssbot.handler import Event as IEvent | ||
| from rssbot.handler import Fleet, Output | ||
| from rssbot.methods import edit, fmt, rlog | ||
| from rssbot.objects import Object, keys | ||
| from rssbot.persist import Workdir, getpath, last, write | ||
| from rssbot.runtime import launch | ||
| IGNORE = ["PING", "PONG", "PRIVMSG"] | ||
| NAME = Workdir.name | ||
| initlock = threading.RLock() | ||
| saylock = threading.RLock() | ||
| saylock = threading.RLock() | ||
| "init" | ||
| def init(): | ||
@@ -47,3 +40,3 @@ with initlock: | ||
| if irc.events.joined.is_set(): | ||
| rlog("debug", fmt(irc.cfg, skip=["password", "realname", "username"])) | ||
| rlog("warn", f"irc {fmt(irc.cfg, skip=["password", "realname", "username"])} channels {",".join(irc.channels)}") | ||
| else: | ||
@@ -54,19 +47,11 @@ irc.stop() | ||
| "config" | ||
| class Config: | ||
| class Main: | ||
| name = Auto.__module__.split(".")[-2] | ||
| class Config(Auto): | ||
| channel = f"#{Main.name}" | ||
| channel = f"#{NAME}" | ||
| commands = False | ||
| control = "!" | ||
| nick = Main.name | ||
| nick = NAME | ||
| password = "" | ||
| port = 6667 | ||
| realname = Main.name | ||
| realname = NAME | ||
| sasl = False | ||
@@ -76,7 +61,6 @@ server = "localhost" | ||
| sleep = 60 | ||
| username = Main.name | ||
| username = NAME | ||
| users = False | ||
| def __init__(self): | ||
| Auto.__init__(self) | ||
| self.channel = Config.channel | ||
@@ -91,5 +75,2 @@ self.commands = Config.commands | ||
| "event" | ||
| class Event(IEvent): | ||
@@ -110,5 +91,2 @@ | ||
| "wrapper" | ||
| class TextWrap(textwrap.TextWrapper): | ||
@@ -129,5 +107,2 @@ | ||
| "IRC" | ||
| class IRC(Output): | ||
@@ -138,3 +113,3 @@ | ||
| self.buffer = [] | ||
| self.cache = Auto() | ||
| self.cache = {} | ||
| self.cfg = Config() | ||
@@ -148,3 +123,2 @@ self.channels = [] | ||
| self.events.ready = threading.Event() | ||
| self.idents = [] | ||
| self.sock = None | ||
@@ -173,3 +147,2 @@ self.state = Object() | ||
| self.register("366", cb_ready) | ||
| self.ident = ident(self) | ||
@@ -309,5 +282,5 @@ def announce(self, txt): | ||
| def extend(self, channel, txtlist): | ||
| if channel not in dir(self.cache): | ||
| setattr(self.cache, channel, []) | ||
| chanlist = getattr(self.cache, channel) | ||
| if channel not in self.cache: | ||
| self.cache[channel] = [] | ||
| chanlist = self.cache.get(channel) | ||
| chanlist.extend(txtlist) | ||
@@ -318,3 +291,3 @@ | ||
| try: | ||
| che = getattr(self.cache, channel, None) | ||
| che = self.cache.get(channel, None) | ||
| if che: | ||
@@ -351,4 +324,4 @@ txt = che.pop(0) | ||
| def oput(self, event): | ||
| if event.channel and event.channel not in dir(self.cache): | ||
| setattr(self.cache, event.channel, []) | ||
| if event.channel and event.channel not in self.cache: | ||
| self.cache[event.channel] = [] | ||
| self.oqueue.put_nowait(event) | ||
@@ -488,4 +461,4 @@ | ||
| def size(self, chan): | ||
| if chan in dir(self.cache): | ||
| return len(getattr(self.cache, chan, [])) | ||
| if chan in self.cache: | ||
| return len(self.cache.get(chan, [])) | ||
| return 0 | ||
@@ -594,3 +567,3 @@ | ||
| if evt.txt.startswith("VERSION"): | ||
| txt = f"\001VERSION {Main.name.upper()} 140 - {bot.cfg.username}\001" | ||
| txt = f"\001VERSION {NAME.upper()} 140 - {bot.cfg.username}\001" | ||
| bot.docommand("NOTICE", evt.channel, txt) | ||
@@ -654,3 +627,3 @@ | ||
| return | ||
| if event.channel not in dir(bot.cache): | ||
| if event.channel not in bot.cache: | ||
| event.reply(f"no output in {event.channel} cache.") | ||
@@ -657,0 +630,0 @@ return |
+45
-64
@@ -24,14 +24,17 @@ # This file is placed in the Public Domain. | ||
| from ..auto import Auto | ||
| from ..disk import write | ||
| from ..fleet import Fleet | ||
| from ..find import find, fntime, last | ||
| from ..method import fmt | ||
| from ..object import Object, update | ||
| from ..paths import getpath | ||
| from ..thread import launch | ||
| from ..timer import Repeater | ||
| from ..utils import elapsed, rlog, spl | ||
| from rssbot.methods import elapsed, fmt, rlog, spl | ||
| from rssbot.handler import Fleet | ||
| from rssbot.objects import Object, update | ||
| from rssbot.persist import find, fntime, getpath, last, write | ||
| from rssbot.runtime import Repeater, launch | ||
| def init(): | ||
| fetcher = Fetcher() | ||
| fetcher.start() | ||
| if fetcher.seenfn: | ||
| rlog("warn", f"rss since {elapsed(time.time()-fntime(fetcher.seenfn))}") | ||
| return fetcher | ||
| DEBUG = False | ||
@@ -42,29 +45,16 @@ | ||
| importlock = _thread.allocate_lock() | ||
| errors = [] | ||
| errors = {} | ||
| skipped = [] | ||
| "init" | ||
| class Feed: | ||
| def init(): | ||
| fetcher = Fetcher() | ||
| fetcher.start() | ||
| return fetcher | ||
| "classes" | ||
| class Feed(Auto): | ||
| def __init__(self): | ||
| Auto.__init__(self) | ||
| self.link = "" | ||
| self.name = "" | ||
| class Rss(Auto): | ||
| class Rss: | ||
| def __init__(self): | ||
| Auto.__init__(self) | ||
| self.display_list = "title,link,author" | ||
@@ -76,3 +66,3 @@ self.insertid = None | ||
| class Urls(Auto): | ||
| class Urls: | ||
@@ -82,5 +72,2 @@ pass | ||
| "fetcher" | ||
| class Fetcher(Object): | ||
@@ -134,3 +121,3 @@ | ||
| if self.dosave: | ||
| write(fed, getpath(fed)) | ||
| write(fed) | ||
| result.append(fed) | ||
@@ -166,5 +153,2 @@ setattr(self.seen, feed.rss, urls) | ||
| "parser" | ||
| class Parser: | ||
@@ -221,3 +205,3 @@ | ||
| "opml" | ||
| "OPML" | ||
@@ -307,3 +291,3 @@ | ||
| result = [Object(), Object()] | ||
| if DEBUG or url in errors: | ||
| if DEBUG or url in errors and (time.time() - errors[url]) < 600: | ||
| return result | ||
@@ -314,3 +298,3 @@ try: | ||
| rlog("error", f"{url} {ex}") | ||
| errors.append(url) | ||
| errors[url] = time.time() | ||
| return result | ||
@@ -381,6 +365,6 @@ if rest: | ||
| setter = {"display_list": event.args[1]} | ||
| for fnm, rss in find("rss", {"rss": event.args[0]}): | ||
| if rss: | ||
| update(rss, setter) | ||
| write(rss, fnm) | ||
| for fnm, feed in find("rss", {"rss": event.args[0]}): | ||
| if feed: | ||
| update(feed, setter) | ||
| write(feed, fnm) | ||
| event.done() | ||
@@ -431,7 +415,7 @@ | ||
| continue | ||
| rss = Rss() | ||
| update(rss, obj) | ||
| rss.rss = obj.xmlUrl | ||
| rss.insertid = insertid | ||
| write(rss, getpath(rss)) | ||
| feed = Rss() | ||
| update(feed, obj) | ||
| feed.rss = obj.xmlUrl | ||
| feed.insertid = insertid | ||
| write(feed) | ||
| nrs += 1 | ||
@@ -449,5 +433,5 @@ if nrskip: | ||
| selector = {"rss": event.args[0]} | ||
| for fnm, rss in find("rss", selector): | ||
| for fnm, fed in find("rss", selector): | ||
| feed = Rss() | ||
| update(feed, rss) | ||
| update(feed, fed) | ||
| if feed: | ||
@@ -463,5 +447,5 @@ feed.name = str(event.args[1]) | ||
| return | ||
| for fnm, rss in find("rss"): | ||
| feed = Auto() | ||
| update(feed, rss) | ||
| for fnm, fed in find("rss"): | ||
| feed = Rss() | ||
| update(feed, fed) | ||
| if event.args[0] not in feed.rss: | ||
@@ -471,3 +455,3 @@ continue | ||
| feed.__deleted__ = True | ||
| write(feed, fnm) | ||
| write(feed) | ||
| event.done() | ||
@@ -481,5 +465,5 @@ break | ||
| return | ||
| for fnm, rss in find("rss", deleted=True): | ||
| feed = Auto() | ||
| update(feed, rss) | ||
| for fnm, fed in find("rss", deleted=True): | ||
| feed = Rss() | ||
| update(feed, fed) | ||
| if event.args[0] not in feed.rss: | ||
@@ -496,6 +480,6 @@ continue | ||
| nrs = 0 | ||
| for fnm, rss in find("rss"): | ||
| for fnm, fed in find("rss"): | ||
| nrs += 1 | ||
| elp = elapsed(time.time() - fntime(fnm)) | ||
| txt = fmt(rss) | ||
| txt = fmt(fed) | ||
| event.reply(f"{nrs} {txt} {elp}") | ||
@@ -513,5 +497,5 @@ if not nrs: | ||
| return | ||
| rss = Rss() | ||
| rss.rss = event.args[0] | ||
| write(rss, getpath(rss)) | ||
| feed = Rss() | ||
| feed.rss = event.args[0] | ||
| write(feed) | ||
| event.done() | ||
@@ -533,5 +517,2 @@ | ||
| "data" | ||
| TEMPLATE = """<opml version="1.0"> | ||
@@ -538,0 +519,0 @@ <head> |
| [console_scripts] | ||
| rssbot = rssbot.__main__:main |
| # This file is placed in the Public Domain. | ||
| __doc__ = __name__.upper() |
| # This file is placed in the Public Domain. | ||
| "main" | ||
| import os | ||
| import pathlib | ||
| import sys | ||
| import time | ||
| from .auto import Auto | ||
| from .client import Client | ||
| from .cmnd import Commands, command, scan | ||
| from .event import Event | ||
| from .parse import parse | ||
| from .paths import pidname, setwd | ||
| from .thread import launch | ||
| from .utils import level, spl | ||
| from . import modules as MODS | ||
| class Main(Auto): | ||
| init = "" | ||
| level = "warn" | ||
| name = Auto.__module__.split(".")[-2] | ||
| opts = Auto() | ||
| verbose = False | ||
| version = 651 | ||
| class CLI(Client): | ||
| def __init__(self): | ||
| Client.__init__(self) | ||
| self.register("command", command) | ||
| def raw(self, txt): | ||
| out(txt.encode('utf-8', 'replace').decode("utf-8")) | ||
| class Console(CLI): | ||
| def announce(self, txt): | ||
| pass | ||
| def callback(self, event): | ||
| super().callback(event) | ||
| event.wait() | ||
| def poll(self): | ||
| evt = Event() | ||
| evt.txt = input("> ") | ||
| evt.type = "command" | ||
| return evt | ||
| "daemon" | ||
| def daemon(verbose=False): | ||
| pid = os.fork() | ||
| if pid != 0: | ||
| os._exit(0) | ||
| os.setsid() | ||
| pid2 = os.fork() | ||
| if pid2 != 0: | ||
| os._exit(0) | ||
| if not verbose: | ||
| with open('/dev/null', 'r', encoding="utf-8") as sis: | ||
| os.dup2(sis.fileno(), sys.stdin.fileno()) | ||
| with open('/dev/null', 'a+', encoding="utf-8") as sos: | ||
| os.dup2(sos.fileno(), sys.stdout.fileno()) | ||
| with open('/dev/null', 'a+', encoding="utf-8") as ses: | ||
| os.dup2(ses.fileno(), sys.stderr.fileno()) | ||
| os.umask(0) | ||
| os.chdir("/") | ||
| os.nice(10) | ||
| def pidfile(filename): | ||
| if os.path.exists(filename): | ||
| os.unlink(filename) | ||
| path2 = pathlib.Path(filename) | ||
| path2.parent.mkdir(parents=True, exist_ok=True) | ||
| with open(filename, "w", encoding="utf-8") as fds: | ||
| fds.write(str(os.getpid())) | ||
| def privileges(): | ||
| import getpass | ||
| import pwd | ||
| pwnam2 = pwd.getpwnam(getpass.getuser()) | ||
| os.setgid(pwnam2.pw_gid) | ||
| os.setuid(pwnam2.pw_uid) | ||
| "commands" | ||
| def ver(event): | ||
| event.reply(f"{Main.name.upper()} {Main.version}") | ||
| "utilities" | ||
| def banner(mods): | ||
| tme = time.ctime(time.time()).replace(" ", " ") | ||
| out(f"{Main.name.upper()} {Main.version} since {tme} ({Main.level.upper()})") | ||
| out(f"loaded {".".join(dir(mods))}") | ||
| def check(txt): | ||
| args = sys.argv[1:] | ||
| for arg in args: | ||
| if not arg.startswith("-"): | ||
| continue | ||
| for char in txt: | ||
| if char in arg: | ||
| return True | ||
| return False | ||
| def forever(): | ||
| while True: | ||
| try: | ||
| time.sleep(0.1) | ||
| except (KeyboardInterrupt, EOFError): | ||
| print("") | ||
| sys.exit(1) | ||
| def inits(pkg, names): | ||
| modz = [] | ||
| for name in sorted(spl(names)): | ||
| mod = getattr(pkg, name, None) | ||
| if not mod: | ||
| continue | ||
| if "init" in dir(mod): | ||
| thr = launch(mod.init) | ||
| modz.append((mod, thr)) | ||
| return modz | ||
| def out(txt): | ||
| print(txt) | ||
| sys.stdout.flush() | ||
| "scripts" | ||
| def background(): | ||
| daemon("-v" in sys.argv) | ||
| privileges() | ||
| level(Main.level or "debug") | ||
| setwd(Main.name) | ||
| pidfile(pidname(Main.name)) | ||
| Commands.add(ver) | ||
| scan(MODS) | ||
| inits(MODS, Main.init or "irc,rss") | ||
| forever() | ||
| def console(): | ||
| import readline # noqa: F401 | ||
| parse(Main, " ".join(sys.argv[1:])) | ||
| Main.init = Main.sets.init or Main.init | ||
| Main.verbose = Main.sets.verbose or Main.verbose | ||
| Main.level = Main.sets.level or Main.level or "warn" | ||
| level(Main.level) | ||
| setwd(Main.name) | ||
| Commands.add(ver) | ||
| scan(MODS) | ||
| if "v" in Main.opts: | ||
| banner(MODS) | ||
| for _mod, thr in inits(MODS, Main.init): | ||
| if "w" in Main.opts: | ||
| thr.join(30.0) | ||
| csl = Console() | ||
| csl.start() | ||
| forever() | ||
| def control(): | ||
| if len(sys.argv) == 1: | ||
| return | ||
| parse(Main, " ".join(sys.argv[1:])) | ||
| level(Main.level or "warn") | ||
| setwd(Main.name) | ||
| Commands.scan(MODS.srv) | ||
| Commands.add(ver) | ||
| scan(MODS) | ||
| csl = CLI() | ||
| evt = Event() | ||
| evt.orig = repr(csl) | ||
| evt.type = "command" | ||
| evt.txt = Main.otxt | ||
| command(evt) | ||
| evt.wait() | ||
| def service(): | ||
| level(Main.level or "warn") | ||
| setwd(Main.name) | ||
| banner(MODS) | ||
| privileges() | ||
| pidfile(pidname(Main.name)) | ||
| Commands.add(ver) | ||
| scan(MODS) | ||
| inits(MODS, Main.init or "irc,rss") | ||
| forever() | ||
| "runtime" | ||
| def wrapped(func): | ||
| try: | ||
| func() | ||
| except (KeyboardInterrupt, EOFError): | ||
| out("") | ||
| def wrap(func): | ||
| import termios | ||
| old = None | ||
| try: | ||
| old = termios.tcgetattr(sys.stdin.fileno()) | ||
| except termios.error: | ||
| pass | ||
| try: | ||
| wrapped(func) | ||
| finally: | ||
| if old: | ||
| termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old) | ||
| def main(): | ||
| if check("a"): | ||
| Main.init = ",".join(dir(MODS)) | ||
| if check("v"): | ||
| setattr(Main.opts, "v", True) | ||
| if check("c"): | ||
| wrap(console) | ||
| elif check("d"): | ||
| background() | ||
| elif check("s"): | ||
| wrapped(service) | ||
| else: | ||
| wrapped(control) | ||
| if __name__ == "__main__": | ||
| main() |
| # This file is placed in the Public Domain. | ||
| "auto construct" | ||
| from .object import Object | ||
| class Auto(Object): | ||
| def __getattr__(self, key): | ||
| if key not in self: | ||
| setattr(self, key, "") | ||
| return self.__dict__.get(key, "") | ||
| def __dir__(): | ||
| return ( | ||
| 'Auto', | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "client" | ||
| import threading | ||
| from .fleet import Fleet | ||
| from .engine import Engine | ||
| class Client(Engine): | ||
| def __init__(self): | ||
| Engine.__init__(self) | ||
| self.olock = threading.RLock() | ||
| Fleet.add(self) | ||
| def announce(self, txt): | ||
| pass | ||
| def display(self, event): | ||
| with self.olock: | ||
| for tme in sorted(event.result): | ||
| self.dosay(event.channel, event.result[tme]) | ||
| def dosay(self, channel, txt): | ||
| self.say(channel, txt) | ||
| def raw(self, txt): | ||
| raise NotImplementedError("raw") | ||
| def say(self, channel, txt): | ||
| self.raw(txt) | ||
| def __dir__(): | ||
| return ( | ||
| 'Client', | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "command" | ||
| import inspect | ||
| from .fleet import Fleet | ||
| from .parse import parse | ||
| class Commands: | ||
| cmds = {} | ||
| names = {} | ||
| @staticmethod | ||
| def add(func, mod=None): | ||
| Commands.cmds[func.__name__] = func | ||
| if mod: | ||
| Commands.names[func.__name__] = mod.__name__.split(".")[-1] | ||
| @staticmethod | ||
| def get(cmd): | ||
| return Commands.cmds.get(cmd, None) | ||
| @staticmethod | ||
| def scan(mod): | ||
| for key, cmdz in inspect.getmembers(mod, inspect.isfunction): | ||
| if key.startswith("cb"): | ||
| continue | ||
| if "event" in cmdz.__code__.co_varnames: | ||
| Commands.add(cmdz, mod) | ||
| def command(evt): | ||
| parse(evt) | ||
| func = Commands.get(evt.cmd) | ||
| if func: | ||
| func(evt) | ||
| Fleet.display(evt) | ||
| evt.ready() | ||
| def scan(pkg): | ||
| mods = [] | ||
| for modname in dir(pkg): | ||
| mod = getattr(pkg, modname) | ||
| Commands.scan(mod) | ||
| mods.append(mod) | ||
| return mods | ||
| def __dir__(): | ||
| return ( | ||
| 'Commands', | ||
| 'command', | ||
| 'scan' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "disk" | ||
| import json | ||
| import json.decoder | ||
| import pathlib | ||
| import threading | ||
| from .object import update | ||
| from .serial import dump, load | ||
| lock = threading.RLock() | ||
| class Cache: | ||
| objs = {} | ||
| @staticmethod | ||
| def add(path, obj): | ||
| Cache.objs[path] = obj | ||
| @staticmethod | ||
| def get(path): | ||
| return Cache.objs.get(path, None) | ||
| @staticmethod | ||
| def update(path, obj): | ||
| if not obj: | ||
| return | ||
| if path in Cache.objs: | ||
| update(Cache.objs[path], obj) | ||
| else: | ||
| Cache.add(path, obj) | ||
| def cdir(path): | ||
| pth = pathlib.Path(path) | ||
| pth.parent.mkdir(parents=True, exist_ok=True) | ||
| def read(obj, path): | ||
| with lock: | ||
| with open(path, "r", encoding="utf-8") as fpt: | ||
| try: | ||
| update(obj, load(fpt)) | ||
| except json.decoder.JSONDecodeError as ex: | ||
| ex.add_note(path) | ||
| raise ex | ||
| def write(obj, path): | ||
| with lock: | ||
| cdir(path) | ||
| with open(path, "w", encoding="utf-8") as fpt: | ||
| dump(obj, fpt, indent=4) | ||
| Cache.update(path, obj) | ||
| return path | ||
| def __dir__(): | ||
| return ( | ||
| 'Cache', | ||
| 'cdir', | ||
| 'read', | ||
| 'write' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "callback engine" | ||
| import queue | ||
| import threading | ||
| from .thread import launch | ||
| class Engine: | ||
| def __init__(self): | ||
| self.cbs = {} | ||
| self.queue = queue.Queue() | ||
| self.ready = threading.Event() | ||
| self.stopped = threading.Event() | ||
| def available(self, event): | ||
| return event.type in self.cbs | ||
| def callback(self, event): | ||
| func = self.cbs.get(event.type, None) | ||
| if func: | ||
| event._thr = launch(func, event) | ||
| def loop(self): | ||
| while not self.stopped.is_set(): | ||
| event = self.poll() | ||
| if event is None: | ||
| break | ||
| event.orig = repr(self) | ||
| self.callback(event) | ||
| def poll(self): | ||
| return self.queue.get() | ||
| def put(self, event): | ||
| self.queue.put(event) | ||
| def register(self, typ, cbs): | ||
| self.cbs[typ] = cbs | ||
| def start(self, daemon=True): | ||
| self.stopped.clear() | ||
| launch(self.loop, daemon=daemon) | ||
| def stop(self): | ||
| self.stopped.set() | ||
| self.queue.put(None) | ||
| def wait(self): | ||
| pass | ||
| def __dir__(): | ||
| return ( | ||
| 'Engine', | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "event" | ||
| import threading | ||
| import time | ||
| from .auto import Auto | ||
| class Event(Auto): | ||
| def __init__(self): | ||
| Auto.__init__(self) | ||
| self._ready = threading.Event() | ||
| self._thr = None | ||
| self.ctime = time.time() | ||
| self.result = {} | ||
| self.type = "event" | ||
| def done(self): | ||
| self.reply("ok") | ||
| def ready(self): | ||
| self._ready.set() | ||
| def reply(self, txt): | ||
| self.result[time.time()] = txt | ||
| def wait(self, timeout=None): | ||
| self._ready.wait() | ||
| if self._thr: | ||
| self._thr.join() | ||
| def __dir__(): | ||
| return ( | ||
| 'Event', | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "find" | ||
| import os | ||
| import time | ||
| from .disk import Cache, read | ||
| from .object import Object, items, update | ||
| from .paths import fqn, long, store | ||
| def find(clz, selector=None, deleted=False, matching=False): | ||
| clz = long(clz) | ||
| if selector is None: | ||
| selector = {} | ||
| for pth in fns(clz): | ||
| obj = Cache.get(pth) | ||
| if not obj: | ||
| obj = Object() | ||
| read(obj, pth) | ||
| Cache.add(pth, obj) | ||
| if not deleted and isdeleted(obj): | ||
| continue | ||
| if selector and not search(obj, selector, matching): | ||
| continue | ||
| yield pth, obj | ||
| def fns(clz): | ||
| pth = store(clz) | ||
| for rootdir, dirs, _files in os.walk(pth, topdown=False): | ||
| for dname in dirs: | ||
| ddd = os.path.join(rootdir, dname) | ||
| for fll in os.listdir(ddd): | ||
| yield os.path.join(ddd, fll) | ||
| def fntime(daystr): | ||
| datestr = " ".join(daystr.split(os.sep)[-2:]) | ||
| datestr = datestr.replace("_", " ") | ||
| if "." in datestr: | ||
| datestr, rest = datestr.rsplit(".", 1) | ||
| else: | ||
| rest = "" | ||
| timed = time.mktime(time.strptime(datestr, "%Y-%m-%d %H:%M:%S")) | ||
| if rest: | ||
| timed += float("." + rest) | ||
| return float(timed) | ||
| def isdeleted(obj): | ||
| return "__deleted__" in dir(obj) and obj.__deleted__ | ||
| def last(obj, selector=None): | ||
| if selector is None: | ||
| selector = {} | ||
| result = sorted(find(fqn(obj), selector), key=lambda x: fntime(x[0])) | ||
| res = "" | ||
| if result: | ||
| inp = result[-1] | ||
| update(obj, inp[-1]) | ||
| res = inp[0] | ||
| return res | ||
| def search(obj, selector, matching=False): | ||
| res = False | ||
| if not selector: | ||
| return res | ||
| for key, value in items(selector): | ||
| val = getattr(obj, key, None) | ||
| if not val: | ||
| continue | ||
| if matching and value == val: | ||
| res = True | ||
| elif str(value).lower() in str(val).lower() or value == "match": | ||
| res = True | ||
| else: | ||
| res = False | ||
| break | ||
| return res | ||
| def __dir__(): | ||
| return ( | ||
| 'find', | ||
| 'fns', | ||
| 'fntime', | ||
| 'last', | ||
| 'search' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "fleet" | ||
| import time | ||
| class Fleet: | ||
| clients = {} | ||
| @staticmethod | ||
| def add(client): | ||
| Fleet.clients[repr(client)] = client | ||
| @staticmethod | ||
| def all(): | ||
| return Fleet.clients.values() | ||
| @staticmethod | ||
| def announce(txt): | ||
| for client in Fleet.all(): | ||
| client.announce(txt) | ||
| @staticmethod | ||
| def dispatch(evt): | ||
| client = Fleet.get(evt.orig) | ||
| client.put(evt) | ||
| @staticmethod | ||
| def display(evt): | ||
| client = Fleet.get(evt.orig) | ||
| client.display(evt) | ||
| @staticmethod | ||
| def first(): | ||
| clt = list(Fleet.all()) | ||
| res = None | ||
| if clt: | ||
| res = clt[0] | ||
| return res | ||
| @staticmethod | ||
| def get(orig): | ||
| return Fleet.clients.get(orig, None) | ||
| @staticmethod | ||
| def say(orig, channel, txt): | ||
| client = Fleet.get(orig) | ||
| if client: | ||
| client.say(channel, txt) | ||
| @staticmethod | ||
| def shutdown(): | ||
| for client in Fleet.all(): | ||
| client.stop() | ||
| @staticmethod | ||
| def wait(): | ||
| time.sleep(0.1) | ||
| for client in Fleet.all(): | ||
| client.wait() | ||
| def __dir__(): | ||
| return ( | ||
| 'Fleet', | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "methods" | ||
| from .object import items, keys | ||
| def edit(obj, setter, skip=True): | ||
| for key, val in items(setter): | ||
| if skip and val == "": | ||
| continue | ||
| try: | ||
| setattr(obj, key, int(val)) | ||
| continue | ||
| except ValueError: | ||
| pass | ||
| try: | ||
| setattr(obj, key, float(val)) | ||
| continue | ||
| except ValueError: | ||
| pass | ||
| if val in ["True", "true"]: | ||
| setattr(obj, key, True) | ||
| elif val in ["False", "false"]: | ||
| setattr(obj, key, False) | ||
| else: | ||
| setattr(obj, key, val) | ||
| def fmt(obj, args=None, skip=None, plain=False, empty=False): | ||
| if args is None: | ||
| args = keys(obj) | ||
| if skip is None: | ||
| skip = [] | ||
| txt = "" | ||
| for key in args: | ||
| if key.startswith("__"): | ||
| continue | ||
| if key in skip: | ||
| continue | ||
| value = getattr(obj, key, None) | ||
| if value is None: | ||
| continue | ||
| if not empty and not value: | ||
| continue | ||
| if plain: | ||
| txt += f"{value} " | ||
| elif isinstance(value, str): | ||
| txt += f'{key}="{value}" ' | ||
| else: | ||
| txt += f"{key}={value} " | ||
| return txt.strip() | ||
| def __dir__(): | ||
| return ( | ||
| 'edit', | ||
| 'fmt' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "modules" | ||
| from . import cmd, lst, thr | ||
| from . import irc, rss | ||
| from . import dbg, srv # noqa: F401 | ||
| from . import fnd | ||
| __all__ = ( | ||
| "cmd", | ||
| "fnd", | ||
| "irc", | ||
| "lst", | ||
| "rss", | ||
| "thr" | ||
| ) | ||
| def __dir__(): | ||
| return __all__ |
| # This file is been placed in the Public Domain. | ||
| "available commands" | ||
| from ..cmnd import Commands | ||
| def cmd(event): | ||
| event.reply(",".join(sorted(Commands.names))) |
| # This file is placed in the Public Domain. | ||
| "debug" | ||
| import time | ||
| from ..client import Fleet | ||
| def dbg(event): | ||
| event.reply("raising exception") | ||
| raise Exception("yo!") | ||
| def brk(event): | ||
| event.reply("borking") | ||
| for clt in Fleet.all(): | ||
| if "sock" in dir(clt): | ||
| event.reply(f"shutdown on {clt.cfg.server}") | ||
| time.sleep(2.0) | ||
| try: | ||
| clt.sock.shutdown(2) | ||
| except OSError: | ||
| pass |
| # This file is placed in the Public Domain. | ||
| "find" | ||
| import time | ||
| from ..find import find, fntime | ||
| from ..method import fmt | ||
| from ..paths import long, skel, types | ||
| from ..utils import elapsed | ||
| def fnd(event): | ||
| skel() | ||
| if not event.rest: | ||
| res = sorted([x.split('.')[-1].lower() for x in types()]) | ||
| if res: | ||
| event.reply(",".join(res)) | ||
| return | ||
| otype = event.args[0] | ||
| clz = long(otype) | ||
| nmr = 0 | ||
| for fnm, obj in list(find(clz, event.gets)): | ||
| event.reply(f"{nmr} {fmt(obj)} {elapsed(time.time()-fntime(fnm))}") | ||
| nmr += 1 | ||
| if not nmr: | ||
| event.reply("no result") |
| # This file is placed in the Public Domain. | ||
| "log text" | ||
| import time | ||
| from ..disk import write | ||
| from ..find import find, fntime | ||
| from ..object import Object | ||
| from ..paths import ident, store | ||
| from ..utils import elapsed | ||
| class Log(Object): | ||
| def __init__(self): | ||
| super().__init__() | ||
| self.txt = '' | ||
| def log(event): | ||
| if not event.rest: | ||
| nmr = 0 | ||
| for fnm, obj in find('log'): | ||
| lap = elapsed(time.time() - fntime(fnm)) | ||
| event.reply(f'{nmr} {obj.txt} {lap}') | ||
| nmr += 1 | ||
| if not nmr: | ||
| event.reply('no log') | ||
| return | ||
| obj = Log() | ||
| obj.txt = event.rest | ||
| write(obj, store(ident(obj))) | ||
| event.done() |
| # This file is been placed in the Public Domain. | ||
| "available types" | ||
| from ..paths import types | ||
| def ls(event): | ||
| event.reply(",".join([x.split(".")[-1].lower() for x in types()])) |
| # This file is placed in the Public Domain | ||
| "create service file" | ||
| from ..auto import Auto | ||
| class Main: | ||
| name = Auto.__module__.split(".")[-2] | ||
| TXT = """[Unit] | ||
| Description=%s | ||
| After=network-online.target | ||
| [Service] | ||
| Type=simple | ||
| User=%s | ||
| Group=%s | ||
| ExecStart=/home/%s/.local/bin/%s -s | ||
| [Install] | ||
| WantedBy=multi-user.target""" | ||
| def srv(event): | ||
| import getpass | ||
| name = getpass.getuser() | ||
| event.reply(TXT % (Main.name.upper(), name, name, name, Main.name)) |
| # This file is placed in the Public Domain. | ||
| "todo list" | ||
| import time | ||
| from ..disk import write | ||
| from ..find import find, fntime | ||
| from ..object import Object | ||
| from ..paths import ident, store | ||
| from ..utils import elapsed | ||
| class Todo(Object): | ||
| def __init__(self): | ||
| Object.__init__(self) | ||
| self.txt = '' | ||
| def dne(event): | ||
| if not event.args: | ||
| event.reply("dne <txt>") | ||
| return | ||
| selector = {'txt': event.args[0]} | ||
| nmr = 0 | ||
| for fnm, obj in find('todo', selector): | ||
| nmr += 1 | ||
| obj.__deleted__ = True | ||
| write(obj, fnm) | ||
| event.done() | ||
| break | ||
| if not nmr: | ||
| event.reply("nothing todo") | ||
| def tdo(event): | ||
| if not event.rest: | ||
| nmr = 0 | ||
| for fnm, obj in find('todo'): | ||
| lap = elapsed(time.time()-fntime(fnm)) | ||
| event.reply(f'{nmr} {obj.txt} {lap}') | ||
| nmr += 1 | ||
| if not nmr: | ||
| event.reply("no todo") | ||
| return | ||
| obj = Todo() | ||
| obj.txt = event.rest | ||
| write(obj, store(ident(obj))) | ||
| event.done() |
| # This file is placed in the Public Domain. | ||
| "running threads" | ||
| import threading | ||
| import time | ||
| from ..utils import elapsed | ||
| STARTTIME = time.time() | ||
| def thr(event): | ||
| result = [] | ||
| for thread in sorted(threading.enumerate(), key=lambda x: x.name): | ||
| if str(thread).startswith("<_"): | ||
| continue | ||
| if getattr(thread, "state", None) and getattr(thread, "sleep", None): | ||
| uptime = thread.sleep - int(time.time() - thread.state["latest"]) | ||
| elif getattr(thread, "starttime", None): | ||
| uptime = int(time.time() - thread.starttime) | ||
| else: | ||
| uptime = int(time.time() - STARTTIME) | ||
| result.append((uptime, thread.name)) | ||
| res = [] | ||
| for uptime, txt in sorted(result, key=lambda x: x[0]): | ||
| lap = elapsed(uptime) | ||
| res.append(f"{txt}/{lap}") | ||
| if res: | ||
| event.reply(" ".join(res)) | ||
| else: | ||
| event.reply("no threads") |
| # This file is placed in the Public Domain. | ||
| "a clean namespace" | ||
| class Object: | ||
| def __contains__(self, key): | ||
| return key in dir(self) | ||
| def __iter__(self): | ||
| return iter(self.__dict__) | ||
| def __len__(self): | ||
| return len(self.__dict__) | ||
| def __str__(self): | ||
| return str(self.__dict__) | ||
| def construct(obj, *args, **kwargs): | ||
| if args: | ||
| val = args[0] | ||
| if isinstance(val, zip): | ||
| update(obj, dict(val)) | ||
| elif isinstance(val, dict): | ||
| update(obj, val) | ||
| elif isinstance(val, Object): | ||
| update(obj, vars(val)) | ||
| if kwargs: | ||
| update(obj, kwargs) | ||
| def items(obj): | ||
| if isinstance(obj, dict): | ||
| return obj.items() | ||
| return obj.__dict__.items() | ||
| def keys(obj): | ||
| if isinstance(obj, dict): | ||
| return obj.keys() | ||
| return obj.__dict__.keys() | ||
| def update(obj, data): | ||
| if isinstance(data, dict): | ||
| return obj.__dict__.update(data) | ||
| obj.__dict__.update(vars(data)) | ||
| def values(obj): | ||
| if isinstance(obj, dict): | ||
| return obj.values() | ||
| return obj.__dict__.values() | ||
| def __dir__(): | ||
| return ( | ||
| 'Object', | ||
| 'construct', | ||
| 'items', | ||
| 'keys', | ||
| 'update', | ||
| 'values' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "output" | ||
| import queue | ||
| import threading | ||
| from .client import Client | ||
| from .thread import launch | ||
| class Output(Client): | ||
| def __init__(self): | ||
| Client.__init__(self) | ||
| self.olock = threading.RLock() | ||
| self.oqueue = queue.Queue() | ||
| self.ostop = threading.Event() | ||
| def display(self, event): | ||
| with self.olock: | ||
| for tme in sorted(event.result): | ||
| self.dosay(event.channel, event.result[tme]) | ||
| def dosay(self, channel, txt): | ||
| raise NotImplementedError("dosay") | ||
| def oput(self, event): | ||
| self.oqueue.put(event) | ||
| def output(self): | ||
| while not self.ostop.is_set(): | ||
| event = self.oqueue.get() | ||
| if event is None: | ||
| self.oqueue.task_done() | ||
| break | ||
| self.display(event) | ||
| self.oqueue.task_done() | ||
| def start(self): | ||
| self.ostop.clear() | ||
| launch(self.output) | ||
| super().start() | ||
| def stop(self): | ||
| self.ostop.set() | ||
| self.oqueue.put(None) | ||
| super.stop() | ||
| def wait(self): | ||
| self.oqueue.join() | ||
| def __dir__(): | ||
| return ( | ||
| 'Output', | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "argparse" | ||
| from .auto import Auto | ||
| def parse(obj, txt=""): | ||
| if txt == "": | ||
| if "txt" in dir(obj): | ||
| txt = obj.txt | ||
| else: | ||
| txt = "" | ||
| args = [] | ||
| obj.args = [] | ||
| obj.cmd = "" | ||
| obj.gets = Auto() | ||
| obj.index = None | ||
| obj.mod = "" | ||
| obj.opts = "" | ||
| obj.result = {} | ||
| obj.sets = Auto() | ||
| obj.silent = Auto() | ||
| obj.txt = txt | ||
| obj.otxt = obj.txt | ||
| _nr = -1 | ||
| for spli in obj.otxt.split(): | ||
| if spli.startswith("-"): | ||
| try: | ||
| obj.index = int(spli[1:]) | ||
| except ValueError: | ||
| obj.opts += spli[1:] | ||
| continue | ||
| if "-=" in spli: | ||
| key, value = spli.split("-=", maxsplit=1) | ||
| setattr(obj.silent, key, value) | ||
| setattr(obj.gets, key, value) | ||
| continue | ||
| if "==" in spli: | ||
| key, value = spli.split("==", maxsplit=1) | ||
| setattr(obj.gets, key, value) | ||
| continue | ||
| if "=" in spli: | ||
| key, value = spli.split("=", maxsplit=1) | ||
| if key == "mod": | ||
| if obj.mod: | ||
| obj.mod += f",{value}" | ||
| else: | ||
| obj.mod = value | ||
| continue | ||
| setattr(obj.sets, key, value) | ||
| continue | ||
| _nr += 1 | ||
| if _nr == 0: | ||
| obj.cmd = spli | ||
| continue | ||
| args.append(spli) | ||
| if args: | ||
| obj.args = args | ||
| obj.txt = obj.cmd or "" | ||
| obj.rest = " ".join(obj.args) | ||
| obj.txt = obj.cmd + " " + obj.rest | ||
| else: | ||
| obj.txt = obj.cmd or "" | ||
| def __dir__(): | ||
| return ( | ||
| 'parse', | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "paths" | ||
| import datetime | ||
| import os | ||
| import pathlib | ||
| class Workdir: | ||
| name = __file__.rsplit(os.sep, maxsplit=2)[-2] | ||
| wdr = "" | ||
| def fqn(obj): | ||
| kin = str(type(obj)).split()[-1][1:-2] | ||
| if kin == "type": | ||
| kin = f"{obj.__module__}.{obj.__name__}" | ||
| return kin | ||
| def getpath(obj): | ||
| return store(ident(obj)) | ||
| def ident(obj): | ||
| return os.path.join(fqn(obj), *str(datetime.datetime.now()).split()) | ||
| def long(name): | ||
| split = name.split(".")[-1].lower() | ||
| res = name | ||
| for names in types(): | ||
| if split == names.split(".")[-1].lower(): | ||
| res = names | ||
| break | ||
| return res | ||
| def pidname(name): | ||
| return os.path.join(Workdir.wdr, f"{name}.pid") | ||
| def setwd(name, path=""): | ||
| path = path or os.path.expanduser(f"~/.{name}") | ||
| Workdir.wdr = path | ||
| skel() | ||
| def skel(): | ||
| pth = pathlib.Path(store()) | ||
| pth.mkdir(parents=True, exist_ok=True) | ||
| return str(pth) | ||
| def store(pth=""): | ||
| return os.path.join(Workdir.wdr, "store", pth) | ||
| def strip(pth, nmr=2): | ||
| return os.path.join(pth.split(os.sep)[-nmr:]) | ||
| def types(): | ||
| return os.listdir(store()) | ||
| def wdr(pth): | ||
| return os.path.join(Workdir.wdr, pth) | ||
| def __dir__(): | ||
| return ( | ||
| 'Workdir', | ||
| 'fqn', | ||
| 'getpath', | ||
| 'long', | ||
| 'ident', | ||
| 'pidname', | ||
| 'setwd', | ||
| 'skel', | ||
| 'store', | ||
| 'strip', | ||
| 'wdr' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "client pool" | ||
| import os | ||
| import threading | ||
| from .fleet import Fleet | ||
| from .output import Output | ||
| class Pool: | ||
| clients = [] | ||
| lock = threading.RLock() | ||
| nrcpu = os.cpu_count() | ||
| nrlast = 0 | ||
| @staticmethod | ||
| def put(evt): | ||
| with Pool.lock: | ||
| if not Pool.clients: | ||
| for task in range(Pool.nrcpu): | ||
| clt = Output() | ||
| clt.start() | ||
| Pool.clients = list(Fleet.all()) | ||
| if Pool.nrlast >= Pool.nrcpu: | ||
| Pool.nrlast = 0 | ||
| print(Pool.clients) | ||
| Pool.clients[Pool.nrlast].put(evt) | ||
| Pool.nrlast += 1 | ||
| def __dir__(): | ||
| return ( | ||
| 'Pool', | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "decoder/encoder" | ||
| import json | ||
| from .object import Object, construct | ||
| class Encoder(json.JSONEncoder): | ||
| def default(self, o): | ||
| if isinstance(o, dict): | ||
| return o.items() | ||
| if issubclass(type(o), Object): | ||
| return vars(o) | ||
| if isinstance(o, list): | ||
| return iter(o) | ||
| try: | ||
| return json.JSONEncoder.default(self, o) | ||
| except TypeError: | ||
| try: | ||
| return vars(o) | ||
| except TypeError: | ||
| return repr(o) | ||
| def dump(obj, fp, *args, **kw): | ||
| kw["cls"] = Encoder | ||
| json.dump(obj, fp, *args, **kw) | ||
| def dumps(obj, *args, **kw): | ||
| kw["cls"] = Encoder | ||
| return json.dumps(obj, *args, **kw) | ||
| def hook(objdict): | ||
| obj = Object() | ||
| construct(obj, objdict) | ||
| return obj | ||
| def load(fp, *args, **kw): | ||
| kw["object_hook"] = hook | ||
| return json.load(fp, *args, **kw) | ||
| def loads(s, *args, **kw): | ||
| kw["object_hook"] = hook | ||
| return json.loads(s, *args, **kw) | ||
| def __dir__(): | ||
| return ( | ||
| 'dump', | ||
| 'dumps', | ||
| 'load', | ||
| 'loads' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "threads" | ||
| import logging | ||
| import queue | ||
| import time | ||
| import threading | ||
| import _thread | ||
| lock = threading.RLock() | ||
| class Thread(threading.Thread): | ||
| def __init__(self, func, thrname, *args, daemon=True, **kwargs): | ||
| super().__init__(None, self.run, thrname, (), daemon=daemon) | ||
| self.name = thrname or kwargs.get("name", name(func)) | ||
| self.queue = queue.Queue() | ||
| self.result = None | ||
| self.starttime = time.time() | ||
| self.stopped = threading.Event() | ||
| self.queue.put((func, args)) | ||
| def __iter__(self): | ||
| return self | ||
| def __next__(self): | ||
| yield from dir(self) | ||
| def run(self): | ||
| func, args = self.queue.get() | ||
| try: | ||
| self.result = func(*args) | ||
| except (KeyboardInterrupt, EOFError): | ||
| _thread.interrupt_main() | ||
| except Exception as ex: | ||
| logging.exception(ex) | ||
| _thread.interrupt_main() | ||
| def join(self, timeout=None): | ||
| super().join(timeout) | ||
| return self.result | ||
| def launch(func, *args, **kwargs): | ||
| with lock: | ||
| thread = Thread(func, None, *args, **kwargs) | ||
| thread.start() | ||
| return thread | ||
| def name(obj): | ||
| typ = type(obj) | ||
| if "__builtins__" in dir(typ): | ||
| return obj.__name__ | ||
| if "__self__" in dir(obj): | ||
| return f"{obj.__self__.__class__.__name__}.{obj.__name__}" | ||
| if "__class__" in dir(obj) and "__name__" in dir(obj): | ||
| return f"{obj.__class__.__name__}.{obj.__name__}" | ||
| if "__class__" in dir(obj): | ||
| return f"{obj.__class__.__module__}.{obj.__class__.__name__}" | ||
| if "__name__" in dir(obj): | ||
| return f"{obj.__class__.__name__}.{obj.__name__}" | ||
| return "" | ||
| def __dir__(): | ||
| return ( | ||
| 'Thread', | ||
| 'launch', | ||
| 'name' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "timer/repeater" | ||
| import threading | ||
| import time | ||
| from .thread import launch, name | ||
| class Timy(threading.Timer): | ||
| def __init__(self, sleep, func, *args, **kwargs): | ||
| super().__init__(sleep, func) | ||
| self.name = kwargs.get("name", name(func)) | ||
| self.sleep = sleep | ||
| self.state = {} | ||
| self.state["latest"] = time.time() | ||
| self.state["starttime"] = time.time() | ||
| self.starttime = time.time() | ||
| class Timed: | ||
| def __init__(self, sleep, func, *args, thrname="", **kwargs): | ||
| self.args = args | ||
| self.func = func | ||
| self.kwargs = kwargs | ||
| self.sleep = sleep | ||
| self.name = thrname or kwargs.get("name", name(func)) | ||
| self.target = time.time() + self.sleep | ||
| self.timer = None | ||
| def run(self): | ||
| self.timer.latest = time.time() | ||
| self.func(*self.args) | ||
| def start(self): | ||
| self.kwargs["name"] = self.name | ||
| timer = Timy(self.sleep, self.run, *self.args, **self.kwargs) | ||
| timer.start() | ||
| self.timer = timer | ||
| def stop(self): | ||
| if self.timer: | ||
| self.timer.cancel() | ||
| class Repeater(Timed): | ||
| def run(self): | ||
| launch(self.start) | ||
| super().run() | ||
| def __dir__(): | ||
| return ( | ||
| 'Repeater', | ||
| 'Timed' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "utilities" | ||
| import logging | ||
| LEVELS = { | ||
| 'debug': logging.DEBUG, | ||
| 'info': logging.INFO, | ||
| 'warning': logging.WARNING, | ||
| 'warn': logging.WARNING, | ||
| 'error': logging.ERROR, | ||
| 'critical': logging.CRITICAL, | ||
| } | ||
| def elapsed(seconds, short=True): | ||
| txt = "" | ||
| nsec = float(seconds) | ||
| if nsec < 1: | ||
| return f"{nsec:.2f}s" | ||
| yea = 365 * 24 * 60 * 60 | ||
| week = 7 * 24 * 60 * 60 | ||
| nday = 24 * 60 * 60 | ||
| hour = 60 * 60 | ||
| minute = 60 | ||
| yeas = int(nsec / yea) | ||
| nsec -= yeas * yea | ||
| weeks = int(nsec / week) | ||
| nsec -= weeks * week | ||
| nrdays = int(nsec / nday) | ||
| nsec -= nrdays * nday | ||
| hours = int(nsec / hour) | ||
| nsec -= hours * hour | ||
| minutes = int(nsec / minute) | ||
| nsec -= int(minute * minutes) | ||
| sec = int(nsec) | ||
| if yeas: | ||
| txt += f"{yeas}y" | ||
| if weeks: | ||
| nrdays += weeks * 7 | ||
| if nrdays: | ||
| txt += f"{nrdays}d" | ||
| if short and txt: | ||
| return txt.strip() | ||
| if hours: | ||
| txt += f"{hours}h" | ||
| if minutes: | ||
| txt += f"{minutes}m" | ||
| if sec: | ||
| txt += f"{sec}s" | ||
| txt = txt.strip() | ||
| return txt | ||
| def level(loglevel="debug"): | ||
| if loglevel != "none": | ||
| format_short = "%(message)-80s" | ||
| datefmt = "%H:%M:%S" | ||
| logging.basicConfig(datefmt=datefmt, format=format_short, force=True) | ||
| logging.getLogger().setLevel(LEVELS.get(loglevel)) | ||
| def rlog(loglevel, txt, ignore=None): | ||
| if ignore is None: | ||
| ignore = [] | ||
| for ign in ignore: | ||
| if ign in str(txt): | ||
| return | ||
| logging.log(LEVELS.get(loglevel), txt) | ||
| def spl(txt): | ||
| try: | ||
| result = txt.split(",") | ||
| except (TypeError, ValueError): | ||
| result = [ | ||
| txt, | ||
| ] | ||
| return [x for x in result if x] | ||
| def __dir__(): | ||
| return ( | ||
| 'elapsed', | ||
| 'level', | ||
| 'rlog', | ||
| 'spl' | ||
| ) |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
70076
-2.46%20
-50%1879
-14.55%