aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/ywalk/main.py
diff options
context:
space:
mode:
Diffstat (limited to 'ywalk/main.py')
-rw-r--r--ywalk/main.py136
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()