console.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. """
  2. Обёртка над ScheduleParser для отправки расписания в консоль.
  3. Author: Milinuri Nirvalen
  4. Ver: 3.1
  5. """
  6. from sparser import SPMessages, load_file, days_str, timetable
  7. from datetime import datetime
  8. from shutil import get_terminal_size
  9. import argparse
  10. import re
  11. # Вспомогательные функции
  12. # =======================
  13. def parse_days(args):
  14. """Парсит имена дней из аргументов.
  15. Args:
  16. args (list): Список строковых аргументов
  17. Returns:
  18. list: Список номеров дней
  19. """
  20. days = []
  21. for x in args:
  22. if x == "сегодня":
  23. days.append(datetime.today().weekday())
  24. continue
  25. if x == "завтра":
  26. days.append(datetime.today().weekday()+1)
  27. continue
  28. # Если начало слова совпадает пятниц... а, у, ы.
  29. for i, d in enumerate(days_str):
  30. if x.startswith(d):
  31. days.append(i)
  32. continue
  33. return days
  34. def register_user(sp):
  35. """Проводит первоначальную регистрацию класса пользователя.
  36. Args:
  37. sp (ScheduleParser): Экземпляр парсера
  38. """
  39. print("Добро пожаловать!")
  40. print("Для использования SP, ему нужно знать ваш класс.")
  41. print("Для того, чтобы не указывать его каждый раз.")
  42. print("Вы всегда сможете сменить свой класс по умолчанию.")
  43. print(f"\nДоступные классы: {', '.join(sp.lessons)}")
  44. while True:
  45. print()
  46. class_let = input("\033[33mВаш класс\033[0m: ").lower().strip()
  47. if class_let in sp.lessons:
  48. print(sp.set_class(class_let))
  49. break
  50. # Вспомогательные функции отображения
  51. # ===================================
  52. def group_log(text):
  53. print(f'\033[96m:\033[94m: \033[0m\033[1m{text}\033[0m')
  54. def rc(text):
  55. """Очищает текст от цветовых кодов."""
  56. return re.sub(r'\033\[[0-9]*m', '', text)
  57. def row(text= None, color=35):
  58. """Вертикальный разделитель в консоли.
  59. Args:
  60. text (str, optional): Текст разделителя
  61. color (int, optional): Цвет текста
  62. """
  63. l = get_terminal_size()[0]
  64. if text:
  65. lc = l - (len(text) + 7)
  66. print(f"\033[90m===== \033[{color}m{text} \033[90m{'-'*lc}\033[0m")
  67. else:
  68. print("\033[90m"+"-"*l+"\033[0m")
  69. def enumerate_list(l, pt=False):
  70. """Отображает пронумерованный список.
  71. Args:
  72. l (list): Список для отображения
  73. pt (bool, optional): Отображать ли расписание (звонков)
  74. """
  75. for i, x in enumerate(l):
  76. if pt:
  77. tt = ""
  78. if i < len(timetable):
  79. tt = f" {timetable[i][0]}"
  80. print(f"\033[94m{i+1}\033[34m{tt}\033[90m| \033[0m{x}\033[0m")
  81. else:
  82. print(f"\033[34m{i+1}\033[90m|\033[0m {x}\033[0m")
  83. # Генератор сообщений для консоли
  84. # ===============================
  85. class SPConsole(SPMessages):
  86. """Генератор сообщений для консоли."""
  87. def __init__(self, uid):
  88. super(SPConsole, self).__init__(uid)
  89. def send_sc_changes(self, days=None, cl=None):
  90. """Отображает изменения в расписании.
  91. Args:
  92. days (list, optional): Фильтр по дням
  93. cl (str, optional): Фильтр по классу
  94. """
  95. sc_changes = load_file(self._scu_path)
  96. group_log("Изменения в расписании:")
  97. if cl is not None:
  98. cl = self.get_class(cl)
  99. # Пробегаемся по измененияв в расписании
  100. for x in sc_changes["changes"]:
  101. # заголовок изменений
  102. t = datetime.fromtimestamp(x["time"]).strftime("%H:%M:%S")
  103. print(f"\nПримерно в {t}")
  104. # Пробегаемся по дням
  105. for day, changes in enumerate(x["diff"]):
  106. if days and day not in days:
  107. continue
  108. if changes:
  109. print()
  110. row(f"На {days_str[day]}", color=36)
  111. # Пробегаемся по классам
  112. for k, v in changes.items():
  113. if cl and cl != k:
  114. continue
  115. row(k, color=94)
  116. res = []
  117. # Пробегаемся по урокам
  118. for o, n in v:
  119. if n:
  120. res.append(f"{o[0]} >> \033[32m{n[0]}\033[90m:{n[1]}")
  121. else:
  122. res.append(f"{o[0]}\033[90m:{o[1]}")
  123. enumerate_list(res)
  124. def send_day_lessons(self, today=0, cl=None):
  125. """Отображает расписанием уроков на день.
  126. Args:
  127. today (int, optional): День недели
  128. cl (str, optional): Для какого класса
  129. """
  130. # Ограничение дней
  131. today = today % 6
  132. cl = self.get_class(cl)
  133. lessons = self.get_lessons(cl)[today]["l"]
  134. row(f"На {days_str[today]}", color=36)
  135. # Собираем сообщение с расписанием
  136. res = []
  137. for x in lessons:
  138. res.append(f"{x[0]}\033[90m:{x[1]}")
  139. enumerate_list(res, pt=True)
  140. def send_lessons(self, days=[0], cl=None):
  141. """Отображает расписанием уроков.
  142. Args:
  143. days (list, optional): Для каких дней недели
  144. cl (str, optional): Для какого класса
  145. """
  146. cl = self.get_class(cl)
  147. if isinstance(days, int):
  148. days = [days]
  149. # Убираем повторы и несуществующие дни
  150. days = set(filter(lambda x: x < 6, days))
  151. # Собираем сообщение
  152. # ------------------
  153. print(f"\nРасписание для {cl}:")
  154. for day in days:
  155. print()
  156. self.send_day_lessons(day, cl)
  157. # Обновления в расписании
  158. # -----------------------
  159. if cl == self.user["class_let"]:
  160. updates = self.get_lessons_updates()
  161. if updates:
  162. print()
  163. group_log(f"Изменилось расписание!")
  164. updates = updates - days
  165. if len(updates) < 3:
  166. for day in updates:
  167. self.send_day_lessons(day)
  168. else:
  169. print(f"На {', '.join(map(lambda x: days_str[x], updates))}.")
  170. def count_lessons(self, cl=None, lessons=True):
  171. """Универсальная функция подсчёта кабинетов/уроков.
  172. Args:
  173. cl (str, optional): Для какого класс
  174. lessons (bool, optional): Подсчёт уроков, иначе кабинетов
  175. """
  176. if cl is not None:
  177. cl = self.get_class(cl)
  178. # Считаем частоту предметов/кабинетов
  179. # -----------------------------------
  180. res = {}
  181. index = self.l_index if lessons else self.c_index
  182. # Если obj - уроки, то another - кабинеты, и наоборот
  183. for obj, v in index.items():
  184. another = {}
  185. # Индекс уроков - указания кабинетов, и наоборот
  186. for a_k, a_v in v.items():
  187. if cl:
  188. c = sum(map(len, a_v.get(cl, [])))
  189. else:
  190. c = sum(map(lambda x: sum(map(len, x)), a_v.values()))
  191. if c:
  192. another[a_k] = c
  193. c = sum(another.values())
  194. if c:
  195. if str(c) not in res:
  196. res[str(c)] = {}
  197. res[str(c)][obj] = another
  198. # Собираем сообщение
  199. # ------------------
  200. group_msg = "Самые частые "
  201. if lessons:
  202. group_msg += "уроки"
  203. else:
  204. group_msg += "кабинеты"
  205. if cl:
  206. group_msg += f" у {cl}"
  207. group_log(group_msg)
  208. for k, v in sorted(res.items(), key=lambda x: int(x[0]), reverse=True):
  209. print()
  210. row(F"{k} раз(а)", 35)
  211. for obj, another in v.items():
  212. another_str = ""
  213. for a, n in another.items():
  214. if n > 1 and len(another) > 1:
  215. another_str += f"\033[33m{a}:\033[90m{n} "
  216. else:
  217. another_str += f"\033[33m{a} "
  218. print(f" * {obj} {another_str}\033[0m")
  219. def search_lesson(self, lesson, days=None, cl=None):
  220. """Поиск упоминаний об уроке.
  221. Args:
  222. lesson (str): Урок для поиска
  223. days (list, optional): Для каких дней
  224. cl (str, optional): Для какого класса
  225. """
  226. if lesson not in self.l_index:
  227. print("Неправильно указан предмет")
  228. print(f"Доступные предметы: {'; '.join(self.l_index)}")
  229. return False
  230. if cl is not None:
  231. cl = self.get_class(cl)
  232. days = set(filter(lambda x: x < 6, days or [0, 1, 2, 3, 4, 5]))
  233. data = self.search(lesson)
  234. # Собираем сообщение
  235. # ------------------
  236. search_str = f"Поиск упоминаний \"{lesson}\""
  237. if days == {0, 1, 2, 3, 4, 5}:
  238. search_str += f" за неделю"
  239. elif days:
  240. search_str += f" за {', '.join(map(lambda x: days_str[x], days))}"
  241. if cl:
  242. search_str += f" для {cl}"
  243. group_log(search_str)
  244. # Пробегаемся по результатам поиска
  245. for cabinet, v in data.items():
  246. print()
  247. row(cabinet, color=35)
  248. # Пробегаемся по указанным дням
  249. for day in days:
  250. ln = v[day]
  251. day_res = []
  252. res = []
  253. for i, cs in enumerate(ln):
  254. if cl and cl not in cs:
  255. continue
  256. if cs:
  257. res.append(", ".join(cs))
  258. if res:
  259. row(days_str[day], color=32)
  260. enumerate_list(res, pt=True)
  261. def search_cabinet(self, cabinet, lesson=None, days=None, cl=None):
  262. """Поиск упоминаний о кабинете.
  263. Когда (день), что (урок), для кого (класс), каким уроком.
  264. Args:
  265. cabinet (str): Кабинет для поиска
  266. lesson (str, optional): Для какого урока
  267. days (list, optional): Для каких дней
  268. cl (str, optional): Для какого класса
  269. """
  270. if cabinet not in self.c_index:
  271. print("Неправильно указан кабинет")
  272. print(f"Доступные кабинеты: {'; '.join(self.c_index)}")
  273. return False
  274. if cl is not None:
  275. cl = self.get_class(cl)
  276. days = set(filter(lambda x: x < 6, days or [0, 1, 2, 3, 4, 5]))
  277. data = self.search(cabinet)
  278. # Собираем сообщение
  279. # ------------------
  280. search_str = f"Поиск по кабнету {cabinet}"
  281. if days == {0, 1, 2, 3, 4, 5}:
  282. search_str += " за неделю"
  283. elif days:
  284. search_str += f" за {', '.join(map(lambda x: days_str[x], days))}"
  285. if cl:
  286. search_str += f" для {cl}"
  287. if lesson:
  288. search_str += f" ({lesson})"
  289. group_log(search_str)
  290. # Пробегаемся по результатам поиска
  291. res = [[[] for x in range(8)] for x in range(6)]
  292. for l, v in data.items():
  293. if lesson and lesson != l:
  294. continue
  295. # Пробегаемся по указанным дням
  296. for day in days:
  297. ln = v[day]
  298. for i, cs in enumerate(ln):
  299. if cl and cl not in cs:
  300. continue
  301. if cs:
  302. res[day][i].append(f"{l}:\033[33m{', '.join(cs)}\033[0m")
  303. for day, lessons in enumerate(res):
  304. if lessons:
  305. print()
  306. row(days_str[day], color=35)
  307. while lessons:
  308. if not lessons[-1]:
  309. lessons.pop()
  310. else:
  311. break
  312. day_lessons = []
  313. for l in lessons:
  314. if not l:
  315. day_lessons.append("===")
  316. else:
  317. day_lessons.append(", ".join(l))
  318. enumerate_list(day_lessons, pt=True)
  319. def main():
  320. days_str = ["понедельник", "вторник", "сред", "четверг", "пятниц", "суббот"]
  321. sp = SPConsole("Console")
  322. days = []
  323. parser = argparse.ArgumentParser()
  324. parser.add_argument("-p", "--parse", action="store_true",
  325. help="Принудительное обновление расписания")
  326. # Определние команд парсера
  327. # -------------------------
  328. subparsers = parser.add_subparsers(dest="cmd", metavar="command")
  329. subparsers.add_parser("status", help="Информация о парсере")
  330. lessons = subparsers.add_parser("lessons", help="Самые частые уроки")
  331. lessons.add_argument("class_let", nargs="?", default=None,
  332. help="Сортировка по классу")
  333. cabinets = subparsers.add_parser("cabinets", help="Самые частые кабинеты")
  334. cabinets.add_argument("class_let", nargs="?", default=None,
  335. help="Сортировка по классу")
  336. changes = subparsers.add_parser("changes", help="Изменения в расписании")
  337. changes.add_argument("-d", dest="days", nargs="+", default=[],
  338. help="Сортировка по дням (понедельник-суббота)")
  339. changes.add_argument("-c", dest="class_let", help="Сортировка по классу")
  340. search = subparsers.add_parser("search", help="Поиск в расписании")
  341. search.add_argument("args", nargs="+", help="Урок, кабинет или класс")
  342. week = subparsers.add_parser("week", help="Расписание на неделю")
  343. week.add_argument("class_let", nargs="?", default=None,
  344. help="Целевой класс")
  345. sc = subparsers.add_parser("sc", help="Расписание уроков")
  346. sc.add_argument("class_let", nargs="?", default=None, help="Целевой класс")
  347. sc.add_argument("-d", dest="days", nargs="+", default=[],
  348. help="Для каких дней (понедельник-суббота)")
  349. change_class = subparsers.add_parser("class",
  350. help="Изменить класс по умолчанию")
  351. change_class.add_argument("class_let", help="Целевой класс")
  352. # Обработка аргументов
  353. # ====================
  354. args = parser.parse_args()
  355. if not sp.user["set_class"]:
  356. register_user(sp)
  357. # Принудитекльно обновляем расписание
  358. if args.parse:
  359. sp.get_schedule(True)
  360. # Задаём дни недели
  361. if "days" in args:
  362. days = parse_days(args.days)
  363. if not args.cmd:
  364. sp.send_today_lessons()
  365. if args.cmd == "changes":
  366. sp.send_sc_changes(days, args.class_let)
  367. if args.cmd == "status":
  368. print(sp.send_status())
  369. if args.cmd == "lessons":
  370. sp.count_lessons(args.class_let)
  371. elif args.cmd == "cabinets":
  372. sp.count_lessons(args.class_let, lessons=False)
  373. elif args.cmd == "search":
  374. days = []
  375. cabinet = None
  376. lessons = None
  377. class_let = None
  378. for x in args.args:
  379. if x == "сегодня":
  380. days.append(datetime.today().weekday())
  381. continue
  382. if x == "завтра":
  383. days.append(datetime.today().weekday()+1)
  384. for i, d in enumerate(days_str):
  385. if x.startswith(d):
  386. days.append(i)
  387. continue
  388. if x in sp.lessons:
  389. class_let = x
  390. elif x in sp.l_index:
  391. lessons = x
  392. elif x in sp.c_index:
  393. cabinet = x
  394. if cabinet:
  395. sp.search_cabinet(cabinet, lessons, days, class_let)
  396. else:
  397. sp.search_lesson(lessons, days, class_let)
  398. elif args.cmd == "week":
  399. sp.send_lessons([0, 1, 2, 3, 4, 5], args.class_let)
  400. elif args.cmd == "sc":
  401. if days:
  402. sp.send_lessons(days, args.class_let)
  403. else:
  404. sp.send_today_lessons(args.class_let)
  405. if args.cmd == "class":
  406. print(sp.set_class(args.class_let))
  407. if not sp.user["set_class"]:
  408. print("\nНе указан класс по умолчанию для Console.")
  409. print("Используйте \"--help\" для получения Информации.")
  410. if __name__ == "__main__":
  411. main()