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.

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