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.

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