You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

207 lines
6.0 KiB

7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
  1. #!/usr/bin/python3
  2. from dateutil.parser import parse as date_parse
  3. from dateutil import tz
  4. import argparse
  5. import ctabus
  6. import datetime
  7. import os
  8. import socket
  9. import re
  10. import time
  11. import urllib
  12. # for logging
  13. import os.path as osp
  14. import sys
  15. CHICAGO_TZ = tz.gettz("America/Chicago")
  16. # https://stackoverflow.com/a/5967539
  17. def atoi(text):
  18. return int(text) if text.isdigit() else text
  19. def numb_sort(text):
  20. '''
  21. alist.sort(key=natural_keys) sorts in human order
  22. http://nedbatchelder.com/blog/200712/human_sorting.html
  23. (See Toothy's implementation in the comments)
  24. '''
  25. return [atoi(c) for c in re.split(r'(\d+)', text)]
  26. def clearscr():
  27. os.system('cls' if os.name == 'nt' else 'clear')
  28. def pprint_delta(delta):
  29. delta = str(delta)
  30. days = None
  31. s1 = delta.split(', ')
  32. if len(s1) > 1:
  33. days, time = s1
  34. else:
  35. time = s1[0]
  36. time = time.split('.')[0]
  37. hour, minute, second = map(int, time.split(':'))
  38. time = ''
  39. if hour:
  40. time += '{hour} hour'.format(hour=hour) + ('s' if hour != 1 else '')
  41. if minute:
  42. if time and not time.endswith(', '):
  43. time += ', '
  44. time += '{minute} minute'.format(minute=minute) + \
  45. ('s' if minute != 1 else '')
  46. if second:
  47. if time and not time.endswith(', '):
  48. time += ', '
  49. time += '{second} second'.format(second=second) + \
  50. ('s' if second != 1 else '')
  51. ret = ''
  52. if days:
  53. ret = days + ', ' if time else ''
  54. ret += time
  55. return ret
  56. def gen_list(objs, data, *displays, key=None, sort=0, num_pic=True):
  57. k = displays[sort]
  58. display_data = {obj[k]: obj[data] for obj in objs}
  59. srt_keys = sorted(display_data.keys(), key=key)
  60. display = sorted(
  61. [
  62. [obj[d] for d in displays] for obj in objs
  63. ],
  64. key=lambda row: key(row[sort]) if key else row[sort]
  65. )
  66. if num_pic:
  67. display = [[i] + data for i, data in enumerate(display)]
  68. opts = {
  69. 'spacer': ' ',
  70. 'seperator': ' ',
  71. 'interactive': True,
  72. 'bottom': '=',
  73. 'l_end': '<',
  74. 'r_end': '>',
  75. }
  76. print2d(display, **opts)
  77. if num_pic:
  78. which = None
  79. while not which:
  80. try:
  81. which = input('Which one?: ')
  82. except KeyboardInterrupt:
  83. quit()
  84. try:
  85. which = srt_keys[int(which)]
  86. except ValueError:
  87. which = None
  88. return display_data[which]
  89. else:
  90. ret = None
  91. while not ret:
  92. try:
  93. ret = display_data[input('Which one?: ')]
  94. except KeyError:
  95. pass
  96. return ret
  97. config = '''\
  98. {route} - {end} ({direction})
  99. {nm}, stop {stop_id}
  100. {delta} ({t})\
  101. '''
  102. def show(data, rt_filter=None, _clear=False):
  103. times = data['prd']
  104. today = datetime.datetime.now(CHICAGO_TZ)
  105. arrivals = sorted(times, key=lambda t: t['prdtm'])
  106. if rt_filter is not None:
  107. arrivals = filter(lambda arrival: arrival['rt'] == rt_filter, arrivals)
  108. if _clear:
  109. clearscr()
  110. for bustime in arrivals:
  111. before = date_parse(bustime['prdtm'])
  112. arrival = before.replace(tzinfo=CHICAGO_TZ)
  113. if arrival > today:
  114. stop_id = bustime['stpid']
  115. delta = pprint_delta(arrival-today)
  116. t = arrival.strftime('%H:%M:%S')
  117. route = bustime['rt']
  118. direction = bustime['rtdir']
  119. end = bustime['des']
  120. nm = bustime['stpnm'].rstrip()
  121. print(
  122. config.format(**locals()), end='\n'*2
  123. )
  124. print("="*36)
  125. def main(args):
  126. args.arg = ' '.join(args.arg)
  127. if not args.arg.isdecimal():
  128. # save on import time slightly
  129. from print2d import print2d
  130. from search import Search, StopSearch
  131. # routes
  132. if not args.route:
  133. data = ctabus.get_routes()['routes']
  134. route = gen_list(data, 'rt', 'rt', 'rtnm',
  135. num_pic=False, key=numb_sort)
  136. else:
  137. route = args.route
  138. data = ctabus.get_directions(route)['directions']
  139. # direction
  140. if not args.direction:
  141. direction = gen_list(data, 'dir', 'dir')
  142. else:
  143. s = Search(args.direction)
  144. direction = sorted((obj['dir'] for obj in data), key=s)[0]
  145. # direction
  146. stops = ctabus.get_stops(route, direction)['stops']
  147. s = StopSearch(args.arg)
  148. if args.lucky:
  149. stop_id = sorted(stops, key=lambda stop: s(stop['stpnm']))[
  150. 0]['stpid']
  151. else:
  152. stop_id = gen_list(stops, 'stpid', 'stpnm', key=s)
  153. else:
  154. stop_id = args.arg
  155. data = ctabus.get_times(stop_id)
  156. if args.periodic is not None:
  157. _done = False
  158. while not _done:
  159. try:
  160. show(data, args.route, True)
  161. s = time.perf_counter()
  162. timeout = 1
  163. if args.periodic > timeout:
  164. timeout = args.periodic
  165. data = ctabus.get_times(stop_id, timeout=timeout)
  166. e = time.perf_counter() - s
  167. if e < args.periodic:
  168. time.sleep(args.periodic-e)
  169. except KeyboardInterrupt:
  170. _done = True
  171. except (urllib.error.URLError, socket.timeout):
  172. print("Error fetching times")
  173. else:
  174. show(data, args.route)
  175. if __name__ == '__main__':
  176. parser = argparse.ArgumentParser(prog='ctabus')
  177. parser.add_argument('-l', '--lucky', action='store_true',
  178. help='picks first result')
  179. parser.add_argument('-p', '--periodic', metavar='SEC',
  180. type=int, help='checks periodically')
  181. parser.add_argument('-r', '--route', default=None)
  182. parser.add_argument('-d', '--direction', default=None)
  183. parser.add_argument('arg', nargs='+', metavar='(stop-id | cross streets)')
  184. args = parser.parse_args()
  185. sys.stderr = open(osp.join(osp.dirname(__file__), 'stderr.log'), 'w')
  186. main(args)