feedwarrior

Slim, portable tooling for creating and distributing decentralized append logs
git clone git://git.defalsify.org/logwarrior.git
Log | Files | Refs | README | LICENSE

commit 5bb0b20536a8cde047043adc0d74958ba38e14f4
parent 6557794f6ee296d4abf6014a98095aa4e0fc0be9
Author: nolash <dev@holbrook.no>
Date:   Sun, 28 Jun 2020 20:43:54 +0200

Add common name resolver for feeds

Diffstat:
Msrc/feedwarrior/__init__.py | 1+
Msrc/feedwarrior/cmd/entry.py | 2++
Dsrc/feedwarrior/cmd/log.py | 28----------------------------
Msrc/feedwarrior/feed.py | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/main.py | 42++++++++++++++++++++++++++++++++----------
5 files changed, 134 insertions(+), 40 deletions(-)

diff --git a/src/feedwarrior/__init__.py b/src/feedwarrior/__init__.py @@ -1,3 +1,4 @@ +from .feed import load as load_feed from .feed import feed from . import entry from .config import load_config diff --git a/src/feedwarrior/cmd/entry.py b/src/feedwarrior/cmd/entry.py @@ -44,3 +44,5 @@ def execute(config, feed, args): feeds_entry_path = os.path.join(feeds_entries_dir, uu) os.symlink(entry_path, feeds_entry_path) + + feed.add(entry) diff --git a/src/feedwarrior/cmd/log.py b/src/feedwarrior/cmd/log.py @@ -1,28 +0,0 @@ -# standard imports -import os -import sys -import json -import logging - -logg = logging.getLogger(__name__) - -# TODO: move to submodule asap -def parse_args(argparser): - pass - - -def check_args(argparser): - pass - - -def execute(config, feed, args): - if args.command == None: - uu = str(feed.uuid) - logg.debug('new log {}'.format(uu)) - log_path = os.path.join(config.feeds_dir, str(uu)) - os.mkdir(log_path) - - log_meta_path = os.path.join(log_path, '.log') - f = open(log_meta_path, 'x') - json.dump(feed.serialize(), f) - f.close() diff --git a/src/feedwarrior/feed.py b/src/feedwarrior/feed.py @@ -1,8 +1,31 @@ # standard imports +import email +import os import uuid import copy import time -from .common import parse_uuid +import json +import logging + +# local imports +from feedwarrior.common import parse_uuid + +logg = logging.getLogger() + + +class filegetter: + + def __init__(self, source_directory): + self.src = source_directory + + + def get(self, uu): + entry_path = os.path.join(self.src, str(uu)) + f = open(entry_path, 'r') + c = f.read() + f.close() + return c + class feed: @@ -11,7 +34,7 @@ class feed: if uu == None: self.uuid = uuid.uuid4() else: - self.uuid = parse_uuid(uu) + self.uuid = uu self.parent = None if parent != None: @@ -19,6 +42,8 @@ class feed: raise ValueError('wrong type for parent: {}'.format(type(parent).__name__)) self.parent = copy.copy(parent) + self.updated = 0 + self.created = 0 if created != None: self.created = created if updated == None: @@ -31,6 +56,12 @@ class feed: self.updated = updated self.entries = [] + self.entries_cursor = 0 + self.entries_sorted = False + + + def add(self, entry): + self.entries.append(entry) def serialize(self): @@ -43,3 +74,69 @@ class feed: o['parent_uuid'] = str(self.parent.uuid) return o + + + def _sort_entries(self): + new_entries = [] + for e in self.entries: + entry = self.getter.get(e) + o = json.loads(entry) + m = email.message_from_string(o['payload']) + d = email.utils.parsedate(m.get('Date')) + t = time.mktime(d) + ts = str(t) + if not m.is_multipart(): + raise ValueError('invalid entry {}'.format(e)) + logg.debug('date {} {}'.format(e, ts)) + new_entries.append('_'.join([ts, e])) + + self.entries = [] + for ne in new_entries: + logg.debug(ne) + e = ne.split('_', maxsplit=1) + self.entries.append(e[1]) + + self.entries_cursor = 0 + self.entries_sorted = True + + + def set_getter(self, getter): + self.getter = getter + + + def next_entry(self): + if not self.entries_sorted: + self._sort_entries() + if self.entries_cursor == len(self.entries): + raise IndexError('no more entries') + + e = self.getter.get(self.entries[self.entries_cursor]) + self.entries_cursor += 1 + return e + + + +# TODO: add input checking for timestamps +# TODO: check state of symlink index +def load(path): + feed_meta_path = os.path.join(path, '.log') + f = open(feed_meta_path, 'r') + o = json.load(f) + uu = parse_uuid(o['uuid']) + puu = None + p = None + if o.get('parent_uuid') != None: + puu = parse_uuid(o['parent_uuid']) + p = feed(puu) + feed_loaded = feed(uu, p, int(o['created']), int(o['updated'])) + + feed_entries_path = os.path.join(path, 'entries') + for entry in os.listdir(feed_entries_path): + feed_loaded.entries.append(entry) + + fg = filegetter(os.path.join(path, 'entries')) + feed_loaded.set_getter(fg) + + return feed_loaded + + diff --git a/src/main.py b/src/main.py @@ -14,23 +14,29 @@ import logging # local imports import feedwarrior -from feedwarrior.cmd import log as cmd_log +from feedwarrior.cmd import create as cmd_create from feedwarrior.cmd import entry as cmd_entry +from feedwarrior.cmd import show as cmd_show logging.basicConfig(level=logging.ERROR) logg = logging.getLogger() -argparser = argparse.ArgumentParser(description='create and manipulate feedwarrior logs') +argparser = argparse.ArgumentParser(description='create and manipulate feedwarrior feeds') argparser.add_argument('-l', help='feed log to operate on') argparser.add_argument('-c', required=True, type=str, help='configuration file') argparser.add_argument('-v', action='store_true', help='be verbose') sub = argparser.add_subparsers() # TODO: add subparser to same level flags as main parser sub.dest = 'command' -sub_entry = sub.add_parser('entry', help='add entry to log') +sub_entry = sub.add_parser('entry', help='add entry to feed') cmd_entry.parse_args(sub_entry) +sub_show = sub.add_parser('show', help='view feed log') +cmd_show.parse_args(sub_show) +sub_create = sub.add_parser('create', help='create new feed') +cmd_create.parse_args(sub_create) + args = argparser.parse_args(sys.argv[1:]) if args.v: @@ -40,27 +46,43 @@ logg.debug('loading config {}'.format(args.c)) config = feedwarrior.load_config(args.c) +def get_feed_by_name(s): + index_path = os.path.join(config.feeds_dir, 'names', s) + resolved_path = os.path.realpath(index_path) + logg.debug('attempting to resolve feed with path {}'.format(resolved_path)) + os.stat(resolved_path) + return os.path.basename(resolved_path) + feed_current = None if args.l != None: try: - feed_current = feedwarrior.feed(args.l) - except ValueError as e: - logg.error('invalid parent {}: {}'.format(args.l, e)) - sys.exit(1) + uu = feedwarrior.common.parse_uuid(args.l) + feed_current = feedwarrior.feed(uu) + except ValueError: + try: + uu = get_feed_by_name(args.l) + feed_current = feedwarrior.feed(uu) + except FileNotFoundError as e: + sys.stderr.write('cannot resolve feed {}\n'.format(args.l)) + sys.exit(1) cmd_mod = None -if args.command == None: +if args.command == 'create': feed_current = feedwarrior.feed(parent=feed_current) - cmd_mod = cmd_log + cmd_mod = cmd_create elif args.command == 'entry': cmd_mod = cmd_entry +elif args.command == 'show' or args.command == None: + feed_current = feedwarrior.load_feed(os.path.join(config.feeds_dir, str(feed_current.uuid))) + cmd_mod = cmd_show else: - log.error('invalid command {}'.format(args.command)) + sys.stderr.write('invalid command {}\n'.format(args.command)) sys.exit(1) try: os.makedirs(config.entries_dir, mode=0o777, exist_ok=False) os.makedirs(config.feeds_dir, mode=0o777, exist_ok=False) + os.makedirs(os.path.join(config.feeds_dir, 'names'), mode=0o777, exist_ok=False) logg.debug('creating datadir {}'.format(config.data_dir)) except FileExistsError as e: logg.debug('using datadir {}'.format(config.data_dir))