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.

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