rssbot
Advanced tools
| # This file is placed in the Public Domain. | ||
| "an object for a string." | ||
| class Broker: | ||
| objects = {} | ||
| def add(obj): | ||
| "add object to the broker, key is repr(obj)." | ||
| Broker.objects[repr(obj)] = obj | ||
| def broker(origin): | ||
| "return object by repr(obj)." | ||
| return Broker.objects.get(origin) | ||
| def objs(attr): | ||
| "return object with a certain attribute." | ||
| for obj in Broker.objects.values(): | ||
| if attr in dir(obj): | ||
| yield obj | ||
| def like(txt): | ||
| "return all objects that have a substring in their key." | ||
| for orig in Broker.objects: | ||
| if orig.split()[0] in orig.split()[0]: | ||
| yield orig | ||
| def __dir__(): | ||
| return ( | ||
| 'add', | ||
| 'broker', | ||
| 'like', | ||
| 'objs' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "configuration" | ||
| from .objects import Default | ||
| class Config(Default): | ||
| debug = False | ||
| gets = Default() | ||
| ignore = "" | ||
| init = "" | ||
| level = "info" | ||
| name = "" | ||
| opts = "" | ||
| sets = Default() | ||
| version = 0 | ||
| def __dir__(): | ||
| return ( | ||
| 'Config', | ||
| ) |
| # This file is placed in the Public Domain. | ||
| # ruff: noqa F401 | ||
| "definitions" | ||
| from .brokers import broker, add, like, objs | ||
| from .clients import Client, CLI, Output | ||
| from .command import Commands, cmds, command, enable, scan, scanner | ||
| from .configs import Config | ||
| from .handler import Handler | ||
| from .loggers import level | ||
| from .message import Message | ||
| from .methods import deleted, edit, fmt, fqn, parse, search | ||
| from .objects import Default, Object | ||
| from .objects import asdict , construct, items, keys, update, values | ||
| from .persist import attrs, cache, last, find, put, read, sync, write | ||
| from .repeats import Repeater, Timed | ||
| from .serials import dump, dumps, load, loads | ||
| from .statics import MONTH, SYSTEMD | ||
| from .threads import launch, name | ||
| from .timings import NoDate, date, day, elapsed, extract, fntime, hour, time | ||
| from .timings import parsetxt, today | ||
| from .utility import cdir, ident, md5sum, spl, where, wrapped | ||
| from .workdir import Workdir, getpath, long, moddir, pidname, skel, storage, kinds | ||
| def __dir__(): | ||
| return ( | ||
| 'MONTH', | ||
| 'SYSTEMD', | ||
| 'Client', | ||
| 'CLI', | ||
| 'Commands', | ||
| 'Config', | ||
| 'Default', | ||
| 'Handler', | ||
| 'Message', | ||
| 'Object', | ||
| 'Output', | ||
| 'Repeater', | ||
| 'Timed', | ||
| 'Workdir', | ||
| 'add', | ||
| 'asdict', | ||
| 'cache', | ||
| 'cdir', | ||
| 'cmds', | ||
| 'construct', | ||
| 'command', | ||
| 'date', | ||
| 'day', | ||
| 'deleted', | ||
| 'dump', | ||
| 'dumps', | ||
| 'edit', | ||
| 'elapsed', | ||
| 'enable', | ||
| 'extract', | ||
| 'fntime', | ||
| 'find', | ||
| 'fmt', | ||
| 'fqn', | ||
| 'getpath', | ||
| 'hour', | ||
| 'ident', | ||
| 'items', | ||
| 'keys', | ||
| 'kinds', | ||
| 'last', | ||
| 'launch', | ||
| 'level', | ||
| 'like', | ||
| 'load', | ||
| 'loads', | ||
| 'long', | ||
| 'moddir', | ||
| 'md5sum', | ||
| 'objs', | ||
| 'parse', | ||
| 'parsetxt', | ||
| 'pidname', | ||
| 'put', | ||
| 'read', | ||
| 'scan', | ||
| 'scanner', | ||
| 'search', | ||
| 'skel', | ||
| 'storage', | ||
| 'sync', | ||
| 'spl', | ||
| 'time', | ||
| 'today', | ||
| 'update', | ||
| 'values', | ||
| 'where', | ||
| 'wrapped', | ||
| 'write' | ||
| ) | ||
| __all__ = __dir__() |
| # This file is placed in the Public Domain. | ||
| "only message." | ||
| import threading | ||
| import time | ||
| from .brokers import broker | ||
| from .objects import Default | ||
| class Message(Default): | ||
| def __init__(self): | ||
| super().__init__() | ||
| self._ready = threading.Event() | ||
| self.result = {} | ||
| self.thr = None | ||
| self.args = [] | ||
| self.index = 0 | ||
| self.kind = "event" | ||
| self.orig = "" | ||
| def display(self): | ||
| "call display on originating client." | ||
| bot = broker(self.orig) | ||
| bot.display(self) | ||
| def ready(self): | ||
| "flag message as ready." | ||
| self._ready.set() | ||
| def reply(self, text): | ||
| "add text to result." | ||
| self.result[time.time()] = text | ||
| def wait(self, timeout=0.0): | ||
| "wait for completion." | ||
| if self.thr: | ||
| self.thr.join(timeout) | ||
| self._ready.wait(timeout or None) | ||
| def __dir__(): | ||
| return ( | ||
| 'Message', | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "modules" | ||
| from . import atr as atr | ||
| from . import flt as flt | ||
| from . import fnd as fnd | ||
| from . import irc as irc | ||
| from . import lst as lst | ||
| from . import rss as rss | ||
| from . import sil as sil | ||
| from . import thr as thr | ||
| from . import upt as upt | ||
| def __dir__(): | ||
| return ( | ||
| 'atr', | ||
| 'flt', | ||
| 'fnd', | ||
| 'irc', | ||
| 'lst', | ||
| 'rss', | ||
| 'sil', | ||
| 'thr', | ||
| 'upt', | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "fields" | ||
| from rssbot.defines import attrs, kinds | ||
| def atr(event): | ||
| if not event.rest: | ||
| res = sorted({x.split('.')[-1].lower() for x in kinds()}) | ||
| if res: | ||
| event.reply(",".join(res)) | ||
| else: | ||
| event.reply("no types") | ||
| return | ||
| itms = attrs(event.args[0]) | ||
| if not itms: | ||
| event.reply("no fields") | ||
| else: | ||
| event.reply(",".join(itms)) |
| # This file is placed in the Public Domain. | ||
| "realisation of serialisation" | ||
| import json | ||
| import types | ||
| class Encoder(json.JSONEncoder): | ||
| def default(self, o): | ||
| if isinstance(o, dict): | ||
| return o.items() | ||
| if isinstance(o, list): | ||
| return iter(o) | ||
| if isinstance(o, types.MappingProxyType): | ||
| return dict(o) | ||
| try: | ||
| return json.JSONEncoder.default(self, o) | ||
| except TypeError: | ||
| try: | ||
| return vars(o) | ||
| except TypeError: | ||
| return repr(o) | ||
| def dump(*args, **kw): | ||
| "dump object to disk." | ||
| kw["cls"] = Encoder | ||
| return json.dump(*args, **kw) | ||
| def dumps(*args, **kw): | ||
| "dump object to string." | ||
| kw["cls"] = Encoder | ||
| return json.dumps(*args, **kw) | ||
| def load(s, *args, **kw): | ||
| "load object from disk." | ||
| return json.load(s, *args, **kw) | ||
| def loads(s, *args, **kw): | ||
| "load object from string." | ||
| return json.loads(s, *args, **kw) | ||
| def __dir__(): | ||
| return ( | ||
| 'dump', | ||
| 'dumps', | ||
| 'load', | ||
| 'loads' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "by definition." | ||
| MONTH = { | ||
| 'Jan': 1, | ||
| 'Feb': 2, | ||
| 'Mar': 3, | ||
| 'Apr': 4, | ||
| 'May': 5, | ||
| 'Jun': 6, | ||
| 'Jul': 7, | ||
| 'Aug': 8, | ||
| 'Sep': 9, | ||
| 'Oct': 10, | ||
| 'Nov': 11, | ||
| 'Dec': 12 | ||
| } | ||
| SYSTEMD = """[Unit] | ||
| Description=%s | ||
| After=multi-user.target | ||
| [Service] | ||
| Type=simple | ||
| User=%s | ||
| Group=%s | ||
| ExecStart=/home/%s/.local/bin/%s -s | ||
| [Install] | ||
| WantedBy=multi-user.target""" | ||
| TIMES = [ | ||
| "%Y-%M-%D %H:%M:%S", | ||
| "%Y-%m-%d %H:%M:%S", | ||
| "%Y-%m-%d", | ||
| "%d-%m-%Y", | ||
| "%d-%m", | ||
| "%m-%d" | ||
| ] | ||
| def __dir__(): | ||
| return ( | ||
| 'MONTH', | ||
| 'SYSTEMD', | ||
| 'TIMES' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "things are repeating" | ||
| import datetime | ||
| import os | ||
| import re | ||
| import time | ||
| from .statics import MONTH, TIMES | ||
| class NoDate(Exception): | ||
| pass | ||
| def date(daystr): | ||
| "return date from string." | ||
| daystr = daystr.encode('utf-8', 'replace').decode("utf-8") | ||
| res = time.time() | ||
| for fmat in TIMES: | ||
| try: | ||
| res = time.mktime(time.strptime(daystr, fmat)) | ||
| break | ||
| except ValueError: | ||
| pass | ||
| return res | ||
| def day(daystr): | ||
| "day part in a string." | ||
| days = None | ||
| month = None | ||
| yea = None | ||
| try: | ||
| ymdre = re.search(r'(\d+)-(\d+)-(\d+)', daystr) | ||
| if ymdre: | ||
| (days, month, yea) = ymdre.groups() | ||
| except ValueError: | ||
| try: | ||
| ymre = re.search(r'(\d+)-(\d+)', daystr) | ||
| if ymre: | ||
| (days, month) = ymre.groups() | ||
| yea = time.strftime("%Y", time.localtime()) | ||
| except Exception as ex: | ||
| raise NoDate(daystr) from ex | ||
| if days: | ||
| days = int(days) | ||
| month = int(month) | ||
| yea = int(yea) | ||
| dte = f"{days} {MONTH[month]} {yea}" | ||
| return time.mktime(time.strptime(dte, r"%d %b %Y")) | ||
| raise NoDate(daystr) | ||
| def elapsed(seconds, short=True): | ||
| "seconds to string." | ||
| 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 | ||
| hou = 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 / hou) | ||
| nsec -= hours * hou | ||
| minutes = int(nsec / minute) | ||
| nsec -= minutes * minute | ||
| sec = int(nsec / 1) | ||
| nsec -= nsec - sec | ||
| if yeas: | ||
| txt += f"{yeas}y" | ||
| if weeks: | ||
| nrdays += weeks * 7 | ||
| if nrdays: | ||
| txt += f"{nrdays}d" | ||
| if hours: | ||
| txt += f"{hours}h" | ||
| if short and txt: | ||
| return txt.strip() | ||
| if minutes: | ||
| txt += f"{minutes}m" | ||
| if sec: | ||
| txt += f"{sec}s" | ||
| txt = txt.strip() | ||
| return txt | ||
| def extract(daystr): | ||
| "extract date/time from string." | ||
| previous = "" | ||
| line = "" | ||
| daystr = str(daystr) | ||
| res = None | ||
| for word in daystr.split(): | ||
| line = previous + " " + word | ||
| previous = word | ||
| try: | ||
| res = date(line.strip()) | ||
| break | ||
| except ValueError: | ||
| res = None | ||
| line = "" | ||
| return res | ||
| def fntime(daystr): | ||
| "return time from path." | ||
| datestr = " ".join(daystr.split(os.sep)[-2:]) | ||
| datestr = datestr.replace("_", " ") | ||
| if "." in datestr: | ||
| datestr, rest = datestr.rsplit(".", 1) | ||
| else: | ||
| rest = "" | ||
| timd = time.mktime(time.strptime(datestr, "%Y-%m-%d %H:%M:%S")) | ||
| if rest: | ||
| timd += float("." + rest) | ||
| return float(timd) | ||
| def hour(daystr): | ||
| "return hour in string." | ||
| try: | ||
| hmsre = re.search(r'(\d+):(\d+):(\d+)', str(daystr)) | ||
| hours = 60 * 60 * (int(hmsre.group(1))) | ||
| hoursmin = hours + int(hmsre.group(2)) * 60 | ||
| hmsres = hoursmin + int(hmsre.group(3)) | ||
| except AttributeError: | ||
| pass | ||
| except ValueError: | ||
| pass | ||
| try: | ||
| hmre = re.search(r'(\d+):(\d+)', str(daystr)) | ||
| hours = 60 * 60 * (int(hmre.group(1))) | ||
| hmsres = hours + int(hmre.group(2)) * 60 | ||
| except AttributeError: | ||
| return 0 | ||
| except ValueError: | ||
| return 0 | ||
| return hmsres | ||
| def timed(txt): | ||
| "scan string for date/time." | ||
| try: | ||
| target = day(txt) | ||
| except NoDate: | ||
| target = extract(today()) | ||
| hours = hour(txt) | ||
| if hours: | ||
| target += hours | ||
| return target | ||
| def parsetxt(txt): | ||
| "parse text for date/time." | ||
| seconds = 0 | ||
| target = 0 | ||
| txt = str(txt) | ||
| for word in txt.split(): | ||
| if word.startswith("+"): | ||
| seconds = int(word[1:]) | ||
| return time.time() + seconds | ||
| if word.startswith("-"): | ||
| seconds = int(word[1:]) | ||
| return time.time() - seconds | ||
| if not target: | ||
| try: | ||
| target = day(txt) | ||
| except NoDate: | ||
| target = extract(today()) | ||
| hours = hour(txt) | ||
| if hours: | ||
| target += hours | ||
| return target | ||
| def today(): | ||
| "return start of the day." | ||
| return str(datetime.datetime.today()).split()[0] | ||
| def __dir__(): | ||
| return ( | ||
| 'date', | ||
| 'day', | ||
| 'elapsed', | ||
| 'extract', | ||
| 'fntime', | ||
| 'hour', | ||
| 'time', | ||
| 'parsetxt', | ||
| 'today' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "where objects are stored." | ||
| import os | ||
| import pathlib | ||
| from .utility import ident | ||
| class Workdir: | ||
| wdr = "" | ||
| def getpath(obj): | ||
| "return path for object." | ||
| return storage(ident(obj)) | ||
| def long(name): | ||
| "match full qualified name by substring." | ||
| split = name.split(".")[-1].lower() | ||
| res = name | ||
| for names in kinds(): | ||
| if split == names.split(".")[-1].lower(): | ||
| res = names | ||
| break | ||
| return res | ||
| def moddir(modname: str = ""): | ||
| "return modules string." | ||
| return os.path.join(Workdir.wdr, modname or "mods") | ||
| def pidname(name: str): | ||
| "return name of pidfile." | ||
| return os.path.join(Workdir.wdr, f"{name}.pid") | ||
| def skel(): | ||
| "create directories." | ||
| path = storage() | ||
| pth = pathlib.Path(path) | ||
| pth.mkdir(parents=True, exist_ok=True) | ||
| pth = pathlib.Path(moddir()) | ||
| pth.mkdir(parents=True, exist_ok=True) | ||
| def storage(fnm: str = ""): | ||
| "return path to store." | ||
| return os.path.join(Workdir.wdr, "store", fnm) | ||
| def kinds(): | ||
| "return stored types." | ||
| return os.listdir(storage()) | ||
| def __dir__(): | ||
| return ( | ||
| 'Workdir', | ||
| 'getpath', | ||
| 'long', | ||
| 'moddir', | ||
| 'pidname', | ||
| 'skel', | ||
| 'storage', | ||
| 'types' | ||
| ) |
+1
-1
| Metadata-Version: 2.4 | ||
| Name: rssbot | ||
| Version: 655 | ||
| Version: 656 | ||
| Summary: 24/7 IRC Feed Fetcher, a contribution back to society. Public Domain. | ||
@@ -5,0 +5,0 @@ Author-email: Bart Thate <rssbotd@gmail.com> |
+1
-1
@@ -12,3 +12,3 @@ [build-system] | ||
| description = "24/7 IRC Feed Fetcher, a contribution back to society. Public Domain." | ||
| version = "655" | ||
| version = "656" | ||
| authors = [ | ||
@@ -15,0 +15,0 @@ {name = "Bart Thate",email = "rssbotd@gmail.com"}, |
| Metadata-Version: 2.4 | ||
| Name: rssbot | ||
| Version: 655 | ||
| Version: 656 | ||
| Summary: 24/7 IRC Feed Fetcher, a contribution back to society. Public Domain. | ||
@@ -5,0 +5,0 @@ Author-email: Bart Thate <rssbotd@gmail.com> |
@@ -5,14 +5,20 @@ README.rst | ||
| bin/rssbot | ||
| rssbot/brokers.py | ||
| rssbot/clients.py | ||
| rssbot/command.py | ||
| rssbot/configs.py | ||
| rssbot/defines.py | ||
| rssbot/handler.py | ||
| rssbot/loggers.py | ||
| rssbot/marshal.py | ||
| rssbot/message.py | ||
| rssbot/methods.py | ||
| rssbot/objects.py | ||
| rssbot/package.py | ||
| rssbot/persist.py | ||
| rssbot/repeats.py | ||
| rssbot/serials.py | ||
| rssbot/statics.py | ||
| rssbot/threads.py | ||
| rssbot/timings.py | ||
| rssbot/utility.py | ||
| rssbot/workdir.py | ||
| rssbot.egg-info/PKG-INFO | ||
@@ -22,2 +28,4 @@ rssbot.egg-info/SOURCES.txt | ||
| rssbot.egg-info/top_level.txt | ||
| rssbot/modules/__init__.py | ||
| rssbot/modules/atr.py | ||
| rssbot/modules/flt.py | ||
@@ -27,3 +35,2 @@ rssbot/modules/fnd.py | ||
| rssbot/modules/lst.py | ||
| rssbot/modules/mod.py | ||
| rssbot/modules/rss.py | ||
@@ -33,3 +40,2 @@ rssbot/modules/sil.py | ||
| rssbot/modules/upt.py | ||
| rssbot/network/__init__.py | ||
| tests/test_none.py |
+35
-65
| # This file is placed in the Public Domain. | ||
| "handle your own events" | ||
| import logging | ||
| import queue | ||
| import threading | ||
| import _thread | ||
| from .brokers import add | ||
| from .command import command | ||
| from .handler import Handler | ||
@@ -12,20 +19,13 @@ from .threads import launch | ||
| class Config: | ||
| name = "tob" | ||
| opts = "" | ||
| sets: dict[str,str] = {} | ||
| version = 141 | ||
| class Client(Handler): | ||
| def __init__(self): | ||
| Handler.__init__(self) | ||
| super().__init__() | ||
| self.olock = threading.RLock() | ||
| self.oqueue = queue.Queue() | ||
| self.silent = True | ||
| Fleet.add(self) | ||
| add(self) | ||
| def announce(self, text): | ||
| "announce text to all channels." | ||
| if not self.silent: | ||
@@ -35,25 +35,40 @@ self.raw(text) | ||
| def display(self, event): | ||
| "display event results." | ||
| with self.olock: | ||
| for tme in sorted(event.result): | ||
| self.dosay( | ||
| event.channel, | ||
| event.result[tme] | ||
| ) | ||
| for tme in event.result: | ||
| txt = event.result.get(tme) | ||
| self.dosay(event.channel, txt) | ||
| def dosay(self, channel, text): | ||
| "say called by display." | ||
| self.say(channel, text) | ||
| def raw(self, text): | ||
| "raw output." | ||
| raise NotImplementedError("raw") | ||
| def say(self, channel, text): | ||
| "say text in channel." | ||
| self.raw(text) | ||
| def wait(self): | ||
| self.oqueue.join() | ||
| "wait for output to finish." | ||
| try: | ||
| self.oqueue.join() | ||
| except Exception as ex: | ||
| logging.exception(ex) | ||
| _thread.interrupt_main() | ||
| class CLI(Client): | ||
| def __init__(self): | ||
| super().__init__() | ||
| self.register("command", command) | ||
| class Output(Client): | ||
| def output(self): | ||
| "output loop." | ||
| while True: | ||
@@ -68,2 +83,3 @@ event = self.oqueue.get() | ||
| def start(self): | ||
| "start output loop." | ||
| launch(self.output) | ||
@@ -73,2 +89,3 @@ super().start() | ||
| def stop(self): | ||
| "stop output loop." | ||
| self.oqueue.put(None) | ||
@@ -78,54 +95,7 @@ super().stop() | ||
| class Fleet: | ||
| clients: dict[str, Client] = {} | ||
| @staticmethod | ||
| def add(client): | ||
| Fleet.clients[repr(client)] = client | ||
| @staticmethod | ||
| def all(): | ||
| return Fleet.clients.values() | ||
| @staticmethod | ||
| def announce(text): | ||
| for client in Fleet.all(): | ||
| client.announce(text) | ||
| @staticmethod | ||
| def display(event): | ||
| client = Fleet.get(event.orig) | ||
| if client: | ||
| client.display(event) | ||
| @staticmethod | ||
| def get(origin): | ||
| return Fleet.clients.get(origin, None) | ||
| @staticmethod | ||
| def like(origin): | ||
| for orig in Fleet.clients: | ||
| if origin.split()[0] in orig.split()[0]: | ||
| yield orig | ||
| @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.wait() | ||
| client.stop() | ||
| def __dir__(): | ||
| return ( | ||
| 'CLI', | ||
| 'Client', | ||
| 'Config', | ||
| 'Fleet', | ||
| 'Output' | ||
| ) | ||
| ) |
+38
-20
| # This file is placed in the Public Domain. | ||
| import inspect | ||
| "write your own commands." | ||
| from typing import Callable | ||
| import inspect | ||
| from .clients import Fleet | ||
| from .methods import parse | ||
| from .utility import spl | ||
@@ -16,39 +16,57 @@ | ||
| cmds: dict[str, Callable] = {} | ||
| names: dict[str, str] = {} | ||
| cmds = {} | ||
| names = {} | ||
| @staticmethod | ||
| def add(*args): | ||
| for func in args: | ||
| name = func.__name__ | ||
| Commands.cmds[name] = func | ||
| Commands.names[name] = func.__module__.split(".")[-1] | ||
| @staticmethod | ||
| def get(cmd): | ||
| return Commands.cmds.get(cmd, None) | ||
| def cmds(cmd): | ||
| "return command." | ||
| return Commands.cmds.get(cmd, None) | ||
| def command(evt): | ||
| "command callback." | ||
| parse(evt, evt.text) | ||
| func = Commands.get(evt.cmd) | ||
| func = cmds(evt.cmd) | ||
| if func: | ||
| func(evt) | ||
| Fleet.display(evt) | ||
| evt.display() | ||
| evt.ready() | ||
| def enable(*args): | ||
| "add functions to commands." | ||
| for func in args: | ||
| name = func.__name__ | ||
| Commands.cmds[name] = func | ||
| Commands.names[name] = func.__module__.split(".")[-1] | ||
| def scan(module): | ||
| "scan a module for command, function with event as first argument." | ||
| for key, cmdz in inspect.getmembers(module, inspect.isfunction): | ||
| if key.startswith("cb"): | ||
| if 'event' not in inspect.signature(cmdz).parameters: | ||
| continue | ||
| if 'event' in inspect.signature(cmdz).parameters: | ||
| Commands.add(cmdz) | ||
| enable(cmdz) | ||
| def scanner(pkg, names=None): | ||
| "scan package for commands." | ||
| if names is None: | ||
| names = ",".join(dir(pkg)) | ||
| mods = [] | ||
| for name in spl(names): | ||
| module = getattr(pkg, name, None) | ||
| if not module: | ||
| continue | ||
| scan(module) | ||
| return mods | ||
| def __dir__(): | ||
| return ( | ||
| 'Comamnds', | ||
| 'Commands', | ||
| 'cmds', | ||
| 'command', | ||
| 'enable', | ||
| 'scan' | ||
| ) |
+22
-39
| # This file is placed in the Public Domain. | ||
| import queue | ||
| import threading | ||
| import time | ||
| "handle your own events" | ||
| from typing import Callable | ||
| import queue | ||
@@ -15,43 +13,22 @@ | ||
| class Event: | ||
| def __init__(self): | ||
| self._ready = threading.Event() | ||
| self._thr = None | ||
| self.channel = "" | ||
| self.ctime = time.time() | ||
| self.orig = "" | ||
| self.result = {} | ||
| self.type = "event" | ||
| def ready(self): | ||
| self._ready.set() | ||
| def reply(self, text): | ||
| self.result[time.time()] = text | ||
| def wait(self, timeout=None): | ||
| self._ready.wait() | ||
| if self._thr: | ||
| self._thr.join(timeout) | ||
| class Handler: | ||
| def __init__(self): | ||
| self.cbs: dict[str, Callable] = {} | ||
| self.cbs = {} | ||
| self.queue = queue.Queue() | ||
| def callback(self, event): | ||
| func = self.cbs.get(event.type, None) | ||
| if func: | ||
| name = event.text and event.text.split()[0] | ||
| event._thr = launch(func, event, name=name) | ||
| else: | ||
| "run callback function with event." | ||
| func = self.cbs.get(event.kind, None) | ||
| if not func: | ||
| event.ready() | ||
| return | ||
| name = event.text and event.text.split()[0] | ||
| event._thr = launch(func, event, name=name) | ||
| def loop(self): | ||
| "event loop." | ||
| while True: | ||
| event = self.poll() | ||
| if event is None: | ||
| if not event: | ||
| break | ||
@@ -62,14 +39,19 @@ event.orig = repr(self) | ||
| def poll(self): | ||
| "return an event to process." | ||
| return self.queue.get() | ||
| def put(self, event): | ||
| "put event on queue." | ||
| self.queue.put(event) | ||
| def register(self, type, callback): | ||
| self.cbs[type] = callback | ||
| def register(self, kind, callback): | ||
| "register callback." | ||
| self.cbs[kind] = callback | ||
| def start(self): | ||
| "start event handler loop." | ||
| launch(self.loop) | ||
| def stop(self): | ||
| "stop event handler loop." | ||
| self.queue.put(None) | ||
@@ -80,4 +62,5 @@ | ||
| return ( | ||
| 'Event', | ||
| 'Handler' | ||
| ) | ||
| 'CLI', | ||
| 'Client', | ||
| 'Output' | ||
| ) |
+20
-31
| # This file is placed in the Public Domain. | ||
| import logging | ||
| "log exceptions." | ||
| LEVELS = { | ||
| 'debug': logging.DEBUG, | ||
| 'info': logging.INFO, | ||
| 'warning':logging. WARNING, | ||
| 'warn': logging.WARNING, | ||
| 'error': logging.ERROR, | ||
| 'critical': logging.CRITICAL | ||
| } | ||
| import logging | ||
| class Logging: | ||
| datefmt = "%H:%M:%S" | ||
| format = "%(module).3s %(message)s" | ||
| class Format(logging.Formatter): | ||
@@ -30,22 +17,24 @@ | ||
| def level(loglevel="debug"): | ||
| if loglevel != "none": | ||
| lvl = LEVELS.get(loglevel) | ||
| if not lvl: | ||
| return | ||
| logger = logging.getLogger() | ||
| for handler in logger.handlers: | ||
| logger.removeHandler(handler) | ||
| logger.setLevel(lvl) | ||
| formatter = Format(Logging.format, datefmt=Logging.datefmt) | ||
| ch = logging.StreamHandler() | ||
| ch.setFormatter(formatter) | ||
| logger.addHandler(ch) | ||
| class Log: | ||
| datefmt = "%H:%M:%S" | ||
| format = "%(module).3s %(message)s" | ||
| def level(loglevel): | ||
| "set log level." | ||
| formatter = Format(Log.format, Log.datefmt) | ||
| stream = logging.StreamHandler() | ||
| stream.setFormatter(formatter) | ||
| logging.basicConfig( | ||
| level=loglevel.upper(), | ||
| handlers=[stream,], | ||
| force=True | ||
| ) | ||
| def __dir__(): | ||
| return ( | ||
| 'LEVELS', | ||
| 'Logging', | ||
| 'Log', | ||
| 'level' | ||
| ) | ||
| ) |
+53
-29
| # This file is placed in the Public Domain. | ||
| from .objects import fqn, items | ||
| "functions with an object as the first argument" | ||
| def edit(obj, setter, skip=True): | ||
| from .objects import Default, items | ||
| def deleted(obj): | ||
| "check whether obj had deleted flag set." | ||
| return "__deleted__" in dir(obj) and obj.__deleted__ | ||
| def edit(obj, setter={}, skip=False): | ||
| "update object with dict." | ||
| for key, val in items(setter): | ||
@@ -30,4 +39,5 @@ if skip and val == "": | ||
| def fmt(obj, args=[], skip=[], plain=False, empty=False): | ||
| if not args: | ||
| args = obj.__dict__.keys() | ||
| "format object info printable string." | ||
| if args == []: | ||
| args = list(obj.__dict__.keys()) | ||
| txt = "" | ||
@@ -52,28 +62,22 @@ for key in args: | ||
| txt += f"{key}={fqn(value)}((value))" | ||
| if txt == "": | ||
| txt = "{}" | ||
| return txt.strip() | ||
| def name(obj, short=False): | ||
| typ = type(obj) | ||
| res = "" | ||
| if "__builtins__" in dir(typ): | ||
| res = obj.__name__ | ||
| elif "__self__" in dir(obj): | ||
| res = f"{obj.__self__.__class__.__name__}.{obj.__name__}" | ||
| elif "__class__" in dir(obj) and "__name__" in dir(obj): | ||
| res = f"{obj.__class__.__name__}.{obj.__name__}" | ||
| elif "__class__" in dir(obj): | ||
| res = f"{obj.__class__.__module__}.{obj.__class__.__name__}" | ||
| elif "__name__" in dir(obj): | ||
| res = f"{obj.__class__.__name__}.{obj.__name__}" | ||
| if short: | ||
| res = res.split(".")[-1] | ||
| return res | ||
| def fqn(obj): | ||
| "full qualified name." | ||
| kin = str(type(obj)).split()[-1][1:-2] | ||
| if kin == "type": | ||
| tpe = type(obj) | ||
| kin = f"{tpe.__module__}.{tpe.__name__}" | ||
| return kin | ||
| def parse(obj, text): | ||
| "parse text for command." | ||
| data = { | ||
| "args": [], | ||
| "cmd": "", | ||
| "gets": {}, | ||
| "gets": Default(), | ||
| "index": None, | ||
@@ -84,4 +88,4 @@ "init": "", | ||
| "rest": "", | ||
| "silent": {}, | ||
| "sets": {}, | ||
| "silent": Default(), | ||
| "sets": Default(), | ||
| "text": text | ||
@@ -102,12 +106,12 @@ } | ||
| key, value = spli.split("-=", maxsplit=1) | ||
| obj.silent[key] = value | ||
| obj.gets[key] = value | ||
| setattr(obj.silent, key, value) | ||
| setattr(obj.gets, key. value) | ||
| continue | ||
| if "==" in spli: | ||
| key, value = spli.split("==", maxsplit=1) | ||
| obj.gets[key] = value | ||
| setattr(obj.gets, key, value) | ||
| continue | ||
| if "=" in spli: | ||
| key, value = spli.split("=", maxsplit=1) | ||
| obj.sets[key] = value | ||
| setattr(obj.sets, key, value) | ||
| continue | ||
@@ -128,8 +132,28 @@ nr += 1 | ||
| def search(obj, selector={}, matching=False): | ||
| "check whether object matches search criteria." | ||
| res = False | ||
| for key, value in items(selector): | ||
| val = getattr(obj, key, None) | ||
| if not val: | ||
| res = False | ||
| break | ||
| if matching and value != val: | ||
| res = False | ||
| break | ||
| if str(value).lower() not in str(val).lower(): | ||
| res = False | ||
| break | ||
| res = True | ||
| return res | ||
| def __dir__(): | ||
| return ( | ||
| 'deleted', | ||
| 'edit', | ||
| 'fmt', | ||
| 'name', | ||
| 'parse' | ||
| 'fqn', | ||
| 'parse', | ||
| 'search' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| from rssbot.clients import Fleet | ||
| from rssbot.methods import fmt | ||
| from rssbot.threads import name | ||
| from rssbot.defines import fmt, name, objs | ||
| def flt(event): | ||
| clts = objs("announce") | ||
| if event.args: | ||
| clts = Fleet.all() | ||
| index = int(event.args[0]) | ||
| if index < len(clts): | ||
| event.reply(fmt(list(Fleet.all())[index], empty=True)) | ||
| event.reply(fmt(list(clts)[index]), empty=True) | ||
| else: | ||
| event.reply(f"only {len(clts)} clients in fleet.") | ||
| return | ||
| event.reply(' | '.join([name(o) for o in Fleet.all()])) | ||
| event.reply(' | '.join([name(o) for o in clts])) |
@@ -7,5 +7,3 @@ # This file is placed in the Public Domain. | ||
| from rssbot.methods import fmt | ||
| from rssbot.persist import find, fntime, types | ||
| from rssbot.utility import elapsed | ||
| from rssbot.defines import elapsed, find, fmt, fntime, kinds | ||
@@ -15,3 +13,3 @@ | ||
| if not event.rest: | ||
| res = sorted([x.split('.')[-1].lower() for x in types()]) | ||
| res = sorted([x.split('.')[-1].lower() for x in kinds()]) | ||
| if res: | ||
@@ -24,3 +22,3 @@ event.reply(",".join(res)) | ||
| nmr = 0 | ||
| for fnm, obj in list(find(otype, event.gets)): | ||
| for fnm, obj in sorted(find(otype, event.gets), key=lambda x: fntime(x[0])): | ||
| event.reply(f"{nmr} {fmt(obj)} {elapsed(time.time()-fntime(fnm))}") | ||
@@ -27,0 +25,0 @@ nmr += 1 |
+44
-47
@@ -14,21 +14,12 @@ # This file is placed in the Public Domain. | ||
| from rssbot.clients import Config as Main | ||
| from rssbot.clients import Output | ||
| from rssbot.command import Fleet, command | ||
| from rssbot.handler import Event as IEvent | ||
| from rssbot.loggers import LEVELS | ||
| from rssbot.methods import edit, fmt | ||
| from rssbot.objects import Object, keys | ||
| from rssbot.persist import getpath, last, write | ||
| from rssbot.threads import launch | ||
| from rssbot.utility import where | ||
| IGNORE = ["PING", "PONG", "PRIVMSG"] | ||
| from rssbot.defines import Config as Main | ||
| from rssbot.defines import Message, Object, Output | ||
| from rssbot.defines import broker, command, edit, fmt, getpath, last, launch | ||
| from rssbot.defines import keys, write | ||
| lock = threading.RLock() | ||
| def init(cfg): | ||
| def init(): | ||
| irc = IRC() | ||
@@ -38,3 +29,3 @@ irc.start() | ||
| if irc.events.joined.is_set(): | ||
| logging.warning(fmt(irc.cfg, skip=["name", "password", "realname", "username"])) | ||
| logging.warning("%s", fmt(irc.cfg, skip=["name", "word", "realname", "username"])) | ||
| else: | ||
@@ -45,10 +36,11 @@ irc.stop() | ||
| class Config: | ||
| class Config(Object): | ||
| channel = f"#{Main.name}" | ||
| commands = False | ||
| commands = True | ||
| control = "!" | ||
| ignore = ["PING", "PONG", "PRIVMSG"] | ||
| name = Main.name | ||
| nick = Main.name | ||
| password = "" | ||
| word = "" | ||
| port = 6667 | ||
@@ -65,2 +57,3 @@ realname = Main.name | ||
| def __init__(self): | ||
| super().__init__() | ||
| self.channel = Config.channel | ||
@@ -75,5 +68,10 @@ self.commands = Config.commands | ||
| def __getattr__(self, name): | ||
| if name not in self: | ||
| return "" | ||
| return self.__getattribute__(name) | ||
| class Event(IEvent): | ||
| class Event(Message): | ||
| def __init__(self): | ||
@@ -94,3 +92,3 @@ super().__init__() | ||
| def dosay(self, txt): | ||
| bot = Fleet.get(self.orig) | ||
| bot = broker(self.orig) | ||
| bot.dosay(self.channel, txt) | ||
@@ -161,3 +159,3 @@ | ||
| self.events.joined.clear() | ||
| if self.cfg.password: | ||
| if self.cfg.word or self.cfg.password: | ||
| logging.debug("using SASL") | ||
@@ -199,3 +197,3 @@ self.cfg.sasl = True | ||
| def display(self, event): | ||
| for key in sorted(event.result, key=lambda x: x): | ||
| for key in sorted(event.result): | ||
| txt = event.result.get(key) | ||
@@ -333,3 +331,3 @@ if not txt: | ||
| rawstr = rawstr.replace("\001", "") | ||
| rlog("debug", txt, IGNORE) | ||
| rlog(txt) | ||
| obj = Event() | ||
@@ -349,3 +347,3 @@ obj.args = [] | ||
| obj.command = arguments[1] | ||
| obj.type = obj.command | ||
| obj.kind = obj.command | ||
| if len(arguments) > 2: | ||
@@ -389,3 +387,3 @@ txtlist = [] | ||
| obj.text = obj.text.strip() | ||
| obj.type = obj.command | ||
| obj.kind = obj.command | ||
| return obj | ||
@@ -423,3 +421,3 @@ | ||
| text = text.rstrip() | ||
| rlog("debug", text, IGNORE) | ||
| rlog(text) | ||
| text = text[:500] | ||
@@ -490,2 +488,3 @@ text += "\r\n" | ||
| def start(self): | ||
| last(self.cfg) | ||
| if self.cfg.channel not in self.channels: | ||
@@ -518,9 +517,9 @@ self.channels.append(self.cfg.channel) | ||
| def cb_auth(evt): | ||
| bot = Fleet.get(evt.orig) | ||
| bot.docommand(f"AUTHENTICATE {bot.cfg.password}") | ||
| bot = broker(evt.orig) | ||
| bot.docommand(f"AUTHENTICATE {bot.cfg.word or bot.cfg.password}") | ||
| def cb_cap(evt): | ||
| bot = Fleet.get(evt.orig) | ||
| if bot.cfg.password and "ACK" in evt.arguments: | ||
| bot = broker(evt.orig) | ||
| if (bot.cfg.word or bot.cfg.password) and "ACK" in evt.arguments: | ||
| bot.direct("AUTHENTICATE PLAIN") | ||
@@ -532,3 +531,3 @@ else: | ||
| def cb_error(evt): | ||
| bot = Fleet.get(evt.orig) | ||
| bot = broker(evt.orig) | ||
| bot.state.nrerror += 1 | ||
@@ -540,3 +539,3 @@ bot.state.error = evt.text | ||
| def cb_h903(evt): | ||
| bot = Fleet.get(evt.orig) | ||
| bot = broker(evt.orig) | ||
| bot.direct("CAP END") | ||
@@ -547,3 +546,3 @@ bot.events.authed.set() | ||
| def cb_h904(evt): | ||
| bot = Fleet.get(evt.orig) | ||
| bot = broker(evt.orig) | ||
| bot.direct("CAP END") | ||
@@ -562,3 +561,3 @@ bot.events.authed.set() | ||
| def cb_ready(evt): | ||
| bot = Fleet.get(evt.orig) | ||
| bot = broker(evt.orig) | ||
| bot.events.ready.set() | ||
@@ -568,3 +567,3 @@ | ||
| def cb_001(evt): | ||
| bot = Fleet.get(evt.orig) | ||
| bot = broker(evt.orig) | ||
| bot.events.logon.set() | ||
@@ -574,3 +573,3 @@ | ||
| def cb_notice(evt): | ||
| bot = Fleet.get(evt.orig) | ||
| bot = broker(evt.orig) | ||
| if evt.text.startswith("VERSION"): | ||
@@ -582,3 +581,3 @@ txt = f"\001VERSION {Config.name.upper()} {Config.version} - {bot.cfg.username}\001" | ||
| def cb_privmsg(evt): | ||
| bot = Fleet.get(evt.orig) | ||
| bot = broker(evt.orig) | ||
| if not bot.cfg.commands: | ||
@@ -602,3 +601,3 @@ return | ||
| def cb_quit(evt): | ||
| bot = Fleet.get(evt.orig) | ||
| bot = broker(evt.orig) | ||
| logging.debug("quit from %s", bot.cfg.server) | ||
@@ -622,3 +621,3 @@ bot.state.nrerror += 1 | ||
| keys(config), | ||
| skip="control,name,password,realname,sleep,username".split(","), | ||
| skip="control,name,word,realname,sleep,username".split(",") | ||
| ) | ||
@@ -636,3 +635,3 @@ ) | ||
| return | ||
| bot = Fleet.get(event.orig) | ||
| bot = broker(event.orig) | ||
| if "cache" not in dir(bot): | ||
@@ -668,8 +667,6 @@ event.reply("bot is missing cache") | ||
| def rlog(loglevel, txt, ignore=None): | ||
| if ignore is None: | ||
| ignore = [] | ||
| for ign in ignore: | ||
| def rlog(txt): | ||
| for ign in Config.ignore: | ||
| if ign in str(txt): | ||
| return | ||
| logging.log(LEVELS.get(loglevel), txt) | ||
| logging.debug(txt) |
| # This file is been placed in the Public Domain. | ||
| from rssbot.persist import types | ||
| from rssbot.defines import kinds | ||
| def lst(event): | ||
| tps = types() | ||
| tps = kinds() | ||
| if tps: | ||
| event.reply(",".join([x.split(".")[-1].lower() for x in tps])) | ||
| event.reply(",".join({x.split(".")[-1].lower() for x in tps})) | ||
| else: | ||
| event.reply("no data yet.") |
+12
-17
@@ -22,15 +22,8 @@ # This file is placed in the Public Domain. | ||
| from rssbot.clients import Fleet | ||
| from rssbot.methods import fmt | ||
| from rssbot.objects import Object, update | ||
| from rssbot.persist import find, fntime, getpath, last, write | ||
| from rssbot.repeats import Repeater | ||
| from rssbot.threads import launch | ||
| from rssbot.utility import elapsed, spl | ||
| from rssbot.defines import Config, Object, Repeater | ||
| from rssbot.defines import elapsed, fmt, find, last, launch, objs | ||
| from rssbot.defines import fntime, getpath, spl, update, write | ||
| DEBUG = False | ||
| def init(cfg): | ||
| def init(): | ||
| fetcher = Fetcher() | ||
@@ -41,3 +34,3 @@ fetcher.start() | ||
| else: | ||
| logging.warning("since %s", time.ctime(time.time())) | ||
| logging.warning("since %s", time.ctime(time.time()).replace(" ", " ")) | ||
| return fetcher | ||
@@ -48,3 +41,5 @@ | ||
| importlock = _thread.allocate_lock() | ||
| errors: dict[str, float] = {} | ||
| errors = {} | ||
| skipped = [] | ||
@@ -136,3 +131,3 @@ | ||
| txt2 = txt + self.display(obj) | ||
| for bot in Fleet.all(): | ||
| for bot in objs("announce"): | ||
| bot.announce(txt2) | ||
@@ -290,3 +285,3 @@ return counter | ||
| result = [Object(), Object()] | ||
| if DEBUG or url in errors and (time.time() - errors[url]) < 600: | ||
| if Config.debug or url in errors and (time.time() - errors[url]) < 600: | ||
| return result | ||
@@ -490,3 +485,3 @@ try: | ||
| feed.rss = event.args[0] | ||
| write(feed) | ||
| fnm = write(feed) | ||
| event.reply("ok") | ||
@@ -496,3 +491,3 @@ | ||
| def syn(event): | ||
| if DEBUG: | ||
| if Config.debug: | ||
| return | ||
@@ -499,0 +494,0 @@ fetcher = Fetcher() |
| # This file is placed in the Public Domain. | ||
| from rssbot.command import Fleet | ||
| from rssbot.defines import broker | ||
| def sil(event): | ||
| bot = Fleet.get(event.orig) | ||
| bot = broker(event.orig) | ||
| bot.silent = True | ||
@@ -14,4 +14,4 @@ event.reply("ok") | ||
| def lou(event): | ||
| bot = Fleet.get(event.orig) | ||
| bot = broker(event.orig) | ||
| bot.silent = False | ||
| event.reply("ok") |
@@ -8,3 +8,3 @@ # This file is placed in the Public Domain. | ||
| from rssbot.utility import elapsed | ||
| from rssbot.defines import elapsed | ||
@@ -23,5 +23,5 @@ | ||
| elif getattr(thread, "starttime", None): | ||
| uptime = int(time.time() - thread.starttime) | ||
| uptime = time.time() - thread.starttime | ||
| else: | ||
| uptime = int(time.time() - STARTTIME) | ||
| uptime = time.time() - STARTTIME | ||
| result.append((uptime, thread.name)) | ||
@@ -28,0 +28,0 @@ res = [] |
@@ -7,3 +7,3 @@ # This file is placed in the Public Domain. | ||
| from rssbot.utility import elapsed | ||
| from rssbot.defines import elapsed | ||
@@ -10,0 +10,0 @@ |
+44
-13
| # This file is placed in the Public Domain. | ||
| import json | ||
| "a clean namespace" | ||
| import types | ||
| class Reserved(Exception): | ||
| pass | ||
| class Object: | ||
@@ -24,2 +31,3 @@ | ||
| def construct(obj, *args, **kwargs): | ||
| "object contructor." | ||
| if args: | ||
@@ -37,10 +45,14 @@ val = args[0] | ||
| def fqn(obj): | ||
| kin = str(type(obj)).split()[-1][1:-2] | ||
| if kin == "type": | ||
| kin = f"{obj.__module__}.{obj.__name__}" | ||
| return kin | ||
| def asdict(obj): | ||
| "return object as dictionary." | ||
| res = {} | ||
| for key in dir(obj): | ||
| if key.startswith("_"): | ||
| continue | ||
| res[key] = getattr(obj, key) | ||
| return res | ||
| def items(obj): | ||
| "return object's key,valye pairs." | ||
| if isinstance(obj, dict): | ||
@@ -50,16 +62,23 @@ return obj.items() | ||
| return obj.items() | ||
| return obj.__dict__.items() | ||
| res = [] | ||
| for key in dir(obj): | ||
| if key.startswith("_"): | ||
| continue | ||
| res.append((key, getattr(obj, key))) | ||
| return res | ||
| def keys(obj): | ||
| "return object keys." | ||
| if isinstance(obj, dict): | ||
| return obj.keys() | ||
| return obj.__dict__.keys() | ||
| def update(obj, data={}, empty=True): | ||
| def update(obj, data, empty=True): | ||
| "update object," | ||
| if isinstance(obj, type): | ||
| for k, v in items(data): | ||
| if isinstance(getattr(obj, k, None), types.MethodType): | ||
| continue | ||
| raise Reserved(k) | ||
| setattr(obj, k, v) | ||
@@ -75,14 +94,26 @@ elif isinstance(obj, dict): | ||
| def values(obj): | ||
| "return object's values/" | ||
| if isinstance(obj, dict): | ||
| return obj.values() | ||
| return obj.__dict__.values() | ||
| res = [] | ||
| for key in dir(obj): | ||
| if key.startswith("_"): | ||
| continue | ||
| res.append(getattr(obj, key)) | ||
| return res | ||
| class Default(Object): | ||
| def __getattr__(self, key): | ||
| return self.__dict__.get(key, "") | ||
| def __dir__(): | ||
| return ( | ||
| 'Default', | ||
| 'Object', | ||
| 'asdict', | ||
| 'construct', | ||
| 'fqn', | ||
| 'items', | ||
@@ -89,0 +120,0 @@ 'keys', |
+53
-127
| # This file is placed in the Public Domain. | ||
| import datetime | ||
| "persistence through storage" | ||
| import os | ||
| import json | ||
| import os | ||
| import pathlib | ||
| import threading | ||
| import time | ||
| from .marshal import dump, load | ||
| from .objects import Object, items, update | ||
| from .methods import deleted, fqn, search | ||
| from .objects import Object, keys, update | ||
| from .serials import dump, load | ||
| from .timings import fntime | ||
| from .utility import cdir | ||
| from .workdir import getpath, long, storage | ||
@@ -21,30 +25,27 @@ | ||
| objs = Object() | ||
| objects = {} | ||
| @staticmethod | ||
| def add(path, obj): | ||
| setattr(Cache.objs, path, obj) | ||
| @staticmethod | ||
| def get(path): | ||
| return getattr(Cache.objs, path, None) | ||
| def attrs(kind): | ||
| "show attributes for kind of objects." | ||
| objs = list(find(kind)) | ||
| if objs: | ||
| return list(keys(objs[0][1])) | ||
| return [] | ||
| @staticmethod | ||
| def update(path, obj): | ||
| setattr(Cache.objs, path, obj) | ||
| def cache(path): | ||
| "return object from cache." | ||
| return Cache.objects.get(path, None) | ||
| def deleted(obj): | ||
| return "__deleted__" in dir(obj) and obj.__deleted__ | ||
| def find(type=None, selector=None, removed=False, matching=False): | ||
| if selector is None: | ||
| selector = {} | ||
| for pth in fns(type): | ||
| obj = Cache.get(pth) | ||
| def find(kind, selector={}, removed=False, matching=False): | ||
| "locate objects by matching atributes." | ||
| fullname = long(kind) | ||
| for pth in fns(fullname): | ||
| obj = cache(pth) | ||
| if not obj: | ||
| obj = Object() | ||
| read(obj, pth) | ||
| Cache.add(pth, obj) | ||
| put(pth, obj) | ||
| if not removed and deleted(obj): | ||
@@ -57,6 +58,5 @@ continue | ||
| def fns(type=None): | ||
| if type is not None: | ||
| type = type.lower() | ||
| path = store() | ||
| def fns(kind): | ||
| "return file names by kind of object." | ||
| path = storage(kind) | ||
| for rootdir, dirs, _files in os.walk(path, topdown=True): | ||
@@ -67,4 +67,2 @@ for dname in dirs: | ||
| ddd = os.path.join(rootdir, dname) | ||
| if type and type not in ddd.lower(): | ||
| continue | ||
| for fll in os.listdir(ddd): | ||
@@ -74,18 +72,4 @@ 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 last(obj, selector=None): | ||
| if selector is None: | ||
| selector = {} | ||
| def last(obj, selector={}): | ||
| "return last saved version." | ||
| result = sorted( | ||
@@ -103,3 +87,9 @@ find(fqn(obj), selector), | ||
| def put(path, obj): | ||
| "put object into cache." | ||
| Cache.objects[path] = obj | ||
| def read(obj, path): | ||
| "read object from path." | ||
| with lock: | ||
@@ -114,21 +104,14 @@ with open(path, "r", encoding="utf-8") as fpt: | ||
| def search(obj, selector, matching=False): | ||
| res = False | ||
| 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(): | ||
| res = True | ||
| else: | ||
| res = False | ||
| break | ||
| return res | ||
| def sync(path, obj): | ||
| "update cached object." | ||
| try: | ||
| update(Cache.objects[path], obj) | ||
| except KeyError: | ||
| put(path, obj) | ||
| def write(obj, path=None): | ||
| def write(obj, path=""): | ||
| "write object to disk." | ||
| with lock: | ||
| if path is None: | ||
| if path == "": | ||
| path = getpath(obj) | ||
@@ -138,75 +121,18 @@ cdir(path) | ||
| dump(obj, fpt, indent=4) | ||
| Cache.update(path, obj) | ||
| sync(path, obj) | ||
| return path | ||
| class Workdir: | ||
| wdr = "" | ||
| @staticmethod | ||
| def init(name): | ||
| Workdir.wdr = os.path.expanduser(f"~/.{name}") | ||
| def cdir(path): | ||
| pth = pathlib.Path(path) | ||
| pth.parent.mkdir(parents=True, exist_ok=True) | ||
| 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 moddir(modname=None): | ||
| return os.path.join(Workdir.wdr, modname or "mods") | ||
| def pidname(name): | ||
| assert Workdir.wdr | ||
| return os.path.join(Workdir.wdr, f"{name}.pid") | ||
| def skel(): | ||
| pth = pathlib.Path(store()) | ||
| pth.mkdir(parents=True, exist_ok=True) | ||
| pth = pathlib.Path(moddir()) | ||
| pth.mkdir(parents=True, exist_ok=True) | ||
| def store(fnm=""): | ||
| return os.path.join(Workdir.wdr, "store", fnm) | ||
| def types(): | ||
| return os.listdir(store()) | ||
| def __dir__(): | ||
| return ( | ||
| 'Cache', | ||
| 'Workdir', | ||
| 'cdir', | ||
| 'attrs', | ||
| 'cache', | ||
| 'find', | ||
| 'fntime', | ||
| 'fqn', | ||
| 'fqn', | ||
| 'getpath', | ||
| 'ident', | ||
| 'fns', | ||
| 'last', | ||
| 'put', | ||
| 'read', | ||
| 'skel', | ||
| 'types', | ||
| 'sync', | ||
| 'write' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| "things are repeating." | ||
| import threading | ||
@@ -35,2 +38,3 @@ import time | ||
| def run(self): | ||
| "run timed function." | ||
| self.timer.latest = time.time() | ||
@@ -40,2 +44,3 @@ self.func(*self.args) | ||
| def start(self): | ||
| "start timer." | ||
| self.kwargs["name"] = self.name | ||
@@ -47,2 +52,3 @@ timer = Timy(self.sleep, self.run, *self.args, **self.kwargs) | ||
| def stop(self): | ||
| "stop timer." | ||
| if self.timer: | ||
@@ -55,2 +61,3 @@ self.timer.cancel() | ||
| def run(self): | ||
| "run function and launch timer for next run." | ||
| launch(self.start) | ||
@@ -63,3 +70,3 @@ super().run() | ||
| 'Repeater', | ||
| 'Timed' | ||
| ) | ||
| 'Timed', | ||
| ) |
+44
-18
| # This file is placed in the Public Domain. | ||
| "make it not blocking" | ||
| import inspect | ||
| import logging | ||
@@ -11,5 +15,2 @@ import queue | ||
| from .methods import name | ||
| class Thread(threading.Thread): | ||
@@ -19,2 +20,3 @@ | ||
| super().__init__(None, self.run, None, (), daemon=daemon) | ||
| self.event = None | ||
| self.name = kwargs.get("name", name(func)) | ||
@@ -33,23 +35,47 @@ self.queue = queue.Queue() | ||
| def join(self, timeout=None): | ||
| super().join(timeout) | ||
| return self.result | ||
| def join(self, timeout=0.0): | ||
| "join thread and return result." | ||
| try: | ||
| super().join(timeout or None) | ||
| return self.result | ||
| except (KeyboardInterrupt, EOFError) as ex: | ||
| if self.event: | ||
| self.event.ready() | ||
| raise ex | ||
| def run(self): | ||
| "run function." | ||
| func, args = self.queue.get() | ||
| self.result = func(*args) | ||
| if args and hasattr(args[0], "ready"): | ||
| self.event = args[0] | ||
| try: | ||
| self.result = func(*args) | ||
| except (KeyboardInterrupt, EOFError): | ||
| if self.event: | ||
| self.event.ready() | ||
| _thread.interrupt_main() | ||
| except Exception as ex: | ||
| if self.event: | ||
| self.event.ready() | ||
| logging.exception(ex) | ||
| _thread.interrupt_main() | ||
| def launch(func, *args, **kwargs): | ||
| thread = Thread(func, *args, **kwargs) | ||
| thread.start() | ||
| return thread | ||
| "run function in a thread." | ||
| try: | ||
| thread = Thread(func, *args, **kwargs) | ||
| thread.start() | ||
| return thread | ||
| except (KeyboardInterrupt, EOFError): | ||
| _thread.interrupt_main() | ||
| def threadhook(args): | ||
| type, value, trace, thread = args | ||
| exc = value.with_traceback(trace) | ||
| if type not in (KeyboardInterrupt, EOFError): | ||
| logging.exception(exc) | ||
| _thread.interrupt_main() | ||
| def name(obj): | ||
| "return string of function/method." | ||
| if inspect.ismethod(obj): | ||
| return f"{obj.__self__.__class__.__name__}.{obj.__name__}" | ||
| if inspect.isfunction(obj): | ||
| return repr(obj).split()[1] | ||
| return repr(obj) | ||
@@ -61,3 +87,3 @@ | ||
| 'launch', | ||
| 'threadhook' | ||
| ) | ||
| 'name' | ||
| ) |
+21
-140
| # This file is placed in the Public Domain. | ||
| import logging | ||
| "dumpyard" | ||
| import datetime | ||
| import inspect | ||
| import os | ||
| import pathlib | ||
| import sys | ||
| import time | ||
| FORMATS = [ | ||
| "%Y-%M-%D %H:%M:%S", | ||
| "%Y-%m-%d %H:%M:%S", | ||
| "%Y-%m-%d", | ||
| "%d-%m-%Y", | ||
| "%d-%m", | ||
| "%m-%d" | ||
| ] | ||
| from .methods import fqn | ||
| def check(text): | ||
| args = sys.argv[1:] | ||
| for arg in args: | ||
| if not arg.startswith("-"): | ||
| continue | ||
| for char in text: | ||
| if char in arg: | ||
| return True | ||
| return False | ||
| def cdir(path): | ||
| "create directory." | ||
| pth = pathlib.Path(path) | ||
| pth.parent.mkdir(parents=True, exist_ok=True) | ||
| 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 ident(obj): | ||
| "return ident string for object." | ||
| return os.path.join(fqn(obj), *str(datetime.datetime.now()).split()) | ||
| 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 fmat in FORMATS: | ||
| try: | ||
| res = time.mktime(time.strptime(daystr, fmat)) | ||
| break | ||
| except ValueError: | ||
| pass | ||
| return res | ||
| def forever(): | ||
| while True: | ||
| try: | ||
| time.sleep(0.1) | ||
| except (KeyboardInterrupt, EOFError): | ||
| break | ||
| def md5sum(path): | ||
| "return md5 of a file." | ||
| import hashlib | ||
| with open(path, "r", encoding="utf-8") as file: | ||
| txt = file.read().encode("utf-8") | ||
| return hashlib.md5(txt).hexdigest() | ||
| return hashlib.md5(txt, usedforsecurity=False).hexdigest() | ||
| 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) | ||
| def spl(txt): | ||
| "return list from command seperated string." | ||
| try: | ||
@@ -145,3 +45,3 @@ result = txt.split(",") | ||
| def where(obj): | ||
| import inspect | ||
| "return path where object is defined." | ||
| return os.path.dirname(inspect.getfile(obj)) | ||
@@ -151,2 +51,3 @@ | ||
| def wrapped(func): | ||
| "wrap function in a try/except, silence ctrl-c/ctrl-d." | ||
| try: | ||
@@ -158,30 +59,10 @@ func() | ||
| 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 __dir__(): | ||
| return ( | ||
| 'check', | ||
| 'daemon', | ||
| 'elapsed', | ||
| 'extract_date', | ||
| 'forever', | ||
| 'cdir', | ||
| 'ident', | ||
| 'md5sum', | ||
| 'pidfile', | ||
| 'privileges', | ||
| 'spl', | ||
| 'where', | ||
| 'wrap', | ||
| 'wrapped' | ||
| ) | ||
| ) |
| # This file is placed in the Public Domain. | ||
| import json | ||
| import types | ||
| class Encoder(json.JSONEncoder): | ||
| def default(self, o): | ||
| if isinstance(o, dict): | ||
| return o.items() | ||
| if isinstance(o, list): | ||
| return iter(o) | ||
| if isinstance(o, types.MappingProxyType): | ||
| return dict(o) | ||
| try: | ||
| return json.JSONEncoder.default(self, o) | ||
| except TypeError: | ||
| try: | ||
| return vars(o) | ||
| except TypeError: | ||
| return repr(o) | ||
| def dump(*args, **kw): | ||
| kw["cls"] = Encoder | ||
| return json.dump(*args, **kw) | ||
| def dumps(*args, **kw): | ||
| kw["cls"] = Encoder | ||
| return json.dumps(*args, **kw) | ||
| def load(s, *args, **kw): | ||
| return json.load(s, *args, **kw) | ||
| def loads(s, *args, **kw): | ||
| return json.loads(s, *args, **kw) | ||
| def __dir__(): | ||
| return ( | ||
| 'dump', | ||
| 'dumps', | ||
| 'load', | ||
| 'loads' | ||
| ) |
| # This file is placed in the Public Domain. | ||
| from rssbot.package import modules | ||
| def mod(event): | ||
| event.reply(",".join(modules())) |
| # This file is placed in the Public Domain. | ||
| "network" |
| # This file is placed in the Public Domain. | ||
| import importlib | ||
| import importlib.util | ||
| import os | ||
| import sys | ||
| from .utility import spl, where | ||
| class Mods: | ||
| dirs: dict[str, str] = {} | ||
| ignore: list[str] = [] | ||
| @staticmethod | ||
| def add(name=None, path=None): | ||
| Mods.dirs[name] = path | ||
| @staticmethod | ||
| def init(name, ignore="", local=False): | ||
| if name: | ||
| pkg = importer(name) | ||
| if pkg: | ||
| Mods.add(name, pkg.__path__[0]) | ||
| if ignore: | ||
| Mods.ignore = spl(ignore) | ||
| if local: | ||
| Mods.add("mods", "mods") | ||
| def getmod(name): | ||
| mname = "" | ||
| pth = "" | ||
| if name in Mods.ignore: | ||
| return | ||
| for packname, path in Mods.dirs.items(): | ||
| modpath = os.path.join(path, name + ".py") | ||
| if os.path.exists(modpath): | ||
| pth = modpath | ||
| mname = f"{packname}.{name}" | ||
| break | ||
| return sys.modules.get(mname, None) or importer(mname, pth) | ||
| def importer(name, pth=None): | ||
| if pth and os.path.exists(pth): | ||
| spec = importlib.util.spec_from_file_location(name, pth) | ||
| else: | ||
| spec = importlib.util.find_spec(name) | ||
| if not spec: | ||
| return | ||
| mod = importlib.util.module_from_spec(spec) | ||
| if not mod: | ||
| return | ||
| sys.modules[name] = mod | ||
| spec.loader.exec_module(mod) | ||
| return mod | ||
| def modules(): | ||
| mods = [] | ||
| for name, path in Mods.dirs.items(): | ||
| if name in Mods.ignore: | ||
| continue | ||
| if not os.path.exists(path): | ||
| continue | ||
| mods.extend([ | ||
| x[:-3] for x in os.listdir(path) | ||
| if x.endswith(".py") and not x.startswith("__") and x not in Mods.ignore | ||
| ]) | ||
| return sorted(mods) | ||
| def __dir__(): | ||
| return ( | ||
| 'Mods', | ||
| 'getmod', | ||
| 'importer', | ||
| 'modules', | ||
| ) |
Sorry, the diff of this file is not supported yet
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
82370
15.28%39
18.18%2264
14.4%