diff options
Diffstat (limited to 'ywalk/main.py')
-rw-r--r-- | ywalk/main.py | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/ywalk/main.py b/ywalk/main.py new file mode 100644 index 0000000..3ea4145 --- /dev/null +++ b/ywalk/main.py @@ -0,0 +1,136 @@ +import argparse +import configparser +import math +import os +import sys +import ywalk.parser as Parser + +from ywalk.types import Mode, Place +from ywalk.graph import Graph + +class ParseModeRestriction(argparse.Action): + def __call__(self, parser, namespace, args, option_string=None): + arglist = set(args.split(',')) + + for arg in arglist: + if arg.upper() not in Mode.__members__: + errx(f'Not a valid Mode: {arg}') + + modes = [Mode[arg.upper()] for arg in arglist] + + saved = getattr(namespace, self.dest) or [] + saved.extend(modes) + setattr(namespace, self.dest, saved) + +# pylint: disable=line-too-long +cli = argparse.ArgumentParser() +cli.add_argument('place', type=Place, nargs='*', help='a place in Morrowind') +cli.add_argument('-w', dest='weight_spec', metavar='WEIGHTING_METHOD', help='the weighting method to apply when searching for a route') +cli.add_argument('-P', dest='deny_places', type=Place, action='append', metavar='PLACE', help='avoid traveling through this place') +cli.add_argument('-m', dest='allow_modes', action=ParseModeRestriction, metavar='MODES', help='allow only these modes of travel') +cli.add_argument('-M', dest='deny_modes', action=ParseModeRestriction, metavar='MODES', help='deny these modes of travel') +cli.add_argument('-T', dest='deny_teleport', action='store_true', help='deny teleportation') + +def errx(msg): + sys.exit(f'ywalk: {msg}') + +def pretty_print_path(path): + origin = path[0].origin + destination = path[-1].destination + + time = 0 + print(f'Start in {origin}') + for conn in path: + time = time + conn.time + + print(f' then {conn.mode.pretty} to {conn.destination}', end='') + if conn.time: + print(f' ({conn.time} {"hour" if conn.time == 1 else "hours"})') + else: + print() + + if time: + print(f'Arrive in {destination} after {time} hours.') + +def pretty_print_place(place, graph): + def pretty_print_connections(conns): + for conn in sorted(conns, key=lambda c: c.mode.value): + print(conn) + + print(f'Direct connections from {place}:') + pretty_print_connections(graph.get_connections_from(place)) + print() + + print(f'Direct connections to {place}:') + pretty_print_connections(graph.get_connections_to(place)) + + print() + print ('Reachability map:') + weights, _ = graph.shortest_paths(place) + for destination in sorted(weights.keys(), key=weights.get): + if place == destination: + continue + + reachability = "unreachable" if weights[destination] == math.inf else weights[destination] + print(f'{destination}: {reachability}') + +def get_xdg_home(xdg_type, fallback): + env = f'XDG_{xdg_type}_HOME' + if env in os.environ: + return os.environ[env] + return os.path.expanduser(fallback) + +def apply_predicates(args, graph): + if args.allow_modes: + graph.add_predicate(lambda c: c.mode in args.allow_modes) + + if args.deny_modes: + graph.add_predicate(lambda c: c.mode not in args.deny_modes) + + if args.deny_teleport: + graph.add_predicate(lambda c: not c.mode.teleport) + + if args.deny_places: + graph.add_predicate(lambda c: c.destination not in args.deny_places) + +def main(): + args = cli.parse_intermixed_args() + + config = configparser.ConfigParser() + config.read(os.path.join(get_xdg_home('CONFIG', '~/.config'), 'ywalk', 'config')) + + datafile = config.get('misc', 'data', fallback='goty') + datapath = os.path.join(get_xdg_home('DATA', '~/.local/share'), 'ywalk', f'{datafile}.tsv') + + graph = Graph() + + try: + graph.populate(Parser.parse_tsv(datapath)) + except (FileNotFoundError, Parser.InputError, ValueError) as err: + errx(err) + + recall = config.get('misc', 'recall', fallback=None) + if recall: + graph.add_recall(Place(recall)) + + for place in args.place: + if place not in graph: + errx(f'Can\'t find "{place}".') + + graph.set_weight(args.weight_spec or config.get('misc', 'weighting_method', fallback=None)) + apply_predicates(args, graph) + + if not args.place: + for place in sorted(graph.get_places(), key=lambda p: p.name): + print(place) + elif len(args.place) == 1: + pretty_print_place(args.place[0], graph) + elif len(args.place) > 1: + path = graph.find_path(*args.place) + if path is None: + errx(f'Graph exhausted, no connection between {", ".join(map(str, args.place))}') + + pretty_print_path(path) + +if __name__ == '__main__': + main() |