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.

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