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.

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