322 lines
9.2 KiB

  1. #!/usr/bin/env python3
  2. import argparse
  3. import locale
  4. import re
  5. import sys
  6. from kosmorrolib import Position, get_ephemerides, get_events, get_moon_phase
  7. from kosmorrolib.__version__ import __version__ as kosmorrolib_version
  8. from kosmorrolib.exceptions import OutOfRangeDateError
  9. from datetime import date
  10. from termcolor import colored
  11. from . import dumper, environment, debug
  12. from .date import parse_date
  13. from .__version__ import __version__ as kosmorro_version
  14. from .exceptions import UnavailableFeatureError, OutOfRangeDateError as DateRangeError
  15. from _kosmorro.i18n.utils import _, SHORT_DATE_FORMAT
  16. def main():
  17. env_vars = environment.get_env_vars()
  18. output_formats = get_dumpers()
  19. args = get_args(list(output_formats.keys()))
  20. debug.show_debug_messages = args.show_debug_messages
  21. output_format = args.format
  22. if args.special_action is not None:
  23. return 0 if args.special_action() else 1
  24. try:
  25. compute_date = parse_date(args.date)
  26. except ValueError as error:
  27. print(colored(error.args[0], color="red", attrs=["bold"]))
  28. return -1
  29. position = None
  30. if args.latitude is not None or args.longitude is not None:
  31. position = Position(args.latitude, args.longitude)
  32. elif env_vars.latitude is not None and env_vars.longitude is not None:
  33. position = Position(float(env_vars.latitude), float(env_vars.longitude))
  34. if output_format == "pdf":
  35. print(
  36. _(
  37. "Save the planet and paper!\n"
  38. "Consider printing your PDF document only if really necessary, and use the other side of the sheet."
  39. )
  40. )
  41. if position is None:
  42. print()
  43. print(
  44. colored(
  45. _(
  46. "PDF output will not contain the ephemerides, because you didn't provide the observation "
  47. "coordinates."
  48. ),
  49. "yellow",
  50. )
  51. )
  52. timezone = args.timezone
  53. if timezone is None and env_vars.timezone is not None:
  54. timezone = int(env_vars.timezone)
  55. elif timezone is None:
  56. timezone = 0
  57. try:
  58. output = get_information(
  59. compute_date,
  60. position,
  61. timezone,
  62. output_format,
  63. args.colors,
  64. args.show_graph,
  65. )
  66. except UnavailableFeatureError as error:
  67. print(colored(error.msg, "red"))
  68. debug.debug_print(error)
  69. return 2
  70. except DateRangeError as error:
  71. print(colored(error.msg, "red"))
  72. debug.debug_print(error)
  73. return 1
  74. if args.output is not None:
  75. try:
  76. pdf_content = output.to_string()
  77. with open(args.output, "wb") as output_file:
  78. output_file.write(pdf_content)
  79. except UnavailableFeatureError as error:
  80. print(colored(error.msg, "red"))
  81. debug.debug_print(error)
  82. return 2
  83. except OSError as error:
  84. print(
  85. colored(
  86. _('The file could not be saved in "{path}": {error}').format(
  87. path=args.output, error=error.strerror
  88. ),
  89. "red",
  90. )
  91. )
  92. debug.debug_print(error)
  93. return 3
  94. elif not output.is_file_output_needed():
  95. print(output)
  96. else:
  97. print(
  98. colored(
  99. _("Please provide a file path to export in this format (--output)."),
  100. color="red",
  101. )
  102. )
  103. return 1
  104. return 0
  105. def get_information(
  106. compute_date: date,
  107. position: Position,
  108. timezone: int,
  109. output_format: str,
  110. colors: bool,
  111. show_graph: bool,
  112. ) -> dumper.Dumper:
  113. if position is not None:
  114. try:
  115. eph = get_ephemerides(
  116. date=compute_date, position=position, timezone=timezone
  117. )
  118. except OutOfRangeDateError as error:
  119. raise DateRangeError(error.min_date, error.max_date)
  120. else:
  121. eph = None
  122. try:
  123. moon_phase = get_moon_phase(compute_date)
  124. except OutOfRangeDateError as error:
  125. moon_phase = None
  126. print(
  127. colored(
  128. _(
  129. "Moon phase can only be displayed between {min_date} and {max_date}"
  130. ).format(
  131. min_date=error.min_date.strftime(SHORT_DATE_FORMAT),
  132. max_date=error.max_date.strftime(SHORT_DATE_FORMAT),
  133. ),
  134. "yellow",
  135. )
  136. )
  137. events_list = get_events(compute_date, timezone)
  138. return get_dumpers()[output_format](
  139. ephemerides=eph,
  140. moon_phase=moon_phase,
  141. events=events_list,
  142. date=compute_date,
  143. timezone=timezone,
  144. with_colors=colors,
  145. show_graph=show_graph,
  146. )
  147. def get_dumpers() -> {str: dumper.Dumper}:
  148. return {
  149. "text": dumper.TextDumper,
  150. "json": dumper.JsonDumper,
  151. "pdf": dumper.PdfDumper,
  152. }
  153. def output_version() -> bool:
  154. python_version = "%d.%d.%d" % (
  155. sys.version_info[0],
  156. sys.version_info[1],
  157. sys.version_info[2],
  158. )
  159. print("Kosmorro %s" % kosmorro_version)
  160. print(
  161. _(
  162. "Running on Python {python_version} "
  163. "with Kosmorrolib v{kosmorrolib_version}"
  164. ).format(python_version=python_version, kosmorrolib_version=kosmorrolib_version)
  165. )
  166. return True
  167. def clear_cache() -> bool:
  168. confirm = input(_("Do you really want to clear Kosmorro's cache? [yN] ")).upper()
  169. if re.match(locale.nl_langinfo(locale.YESEXPR), confirm) is not None:
  170. try:
  171. environment.clear_cache()
  172. except FileNotFoundError:
  173. debug.debug_print("No cache found, nothing done.")
  174. pass
  175. elif confirm != "" and re.match(locale.nl_langinfo(locale.NOEXPR), confirm) is None:
  176. print(_("Incorrect answer, cache not cleared."))
  177. return False
  178. return True
  179. def get_args(output_formats: [str]):
  180. today = date.today()
  181. parser = argparse.ArgumentParser(
  182. description=_(
  183. "Compute the ephemerides and the events for a given date and a given position on Earth."
  184. ),
  185. epilog=_(
  186. "By default, only the events will be computed for today ({date}).\n"
  187. "To compute also the ephemerides, latitude and longitude arguments"
  188. " are needed."
  189. ).format(date=today.strftime(dumper.FULL_DATE_FORMAT)),
  190. )
  191. parser.add_argument(
  192. "--version",
  193. "-v",
  194. dest="special_action",
  195. action="store_const",
  196. const=output_version,
  197. default=None,
  198. help=_("Show the program version"),
  199. )
  200. parser.add_argument(
  201. "--clear-cache",
  202. dest="special_action",
  203. action="store_const",
  204. const=clear_cache,
  205. default=None,
  206. help=_("Delete all the files from Kosmorro's cache."),
  207. )
  208. parser.add_argument(
  209. "--format",
  210. "-f",
  211. type=str,
  212. default=output_formats[0],
  213. choices=output_formats,
  214. help=_("The format to output the information to"),
  215. )
  216. parser.add_argument(
  217. "--latitude",
  218. "-lat",
  219. type=float,
  220. default=None,
  221. help=_(
  222. "The observer's latitude on Earth. Can also be set in the KOSMORRO_LATITUDE environment "
  223. "variable."
  224. ),
  225. )
  226. parser.add_argument(
  227. "--longitude",
  228. "-lon",
  229. type=float,
  230. default=None,
  231. help=_(
  232. "The observer's longitude on Earth. Can also be set in the KOSMORRO_LONGITUDE "
  233. "environment variable."
  234. ),
  235. )
  236. parser.add_argument(
  237. "--date",
  238. "-d",
  239. type=str,
  240. default=today.strftime("%Y-%m-%d"),
  241. help=_(
  242. "The date for which the ephemerides must be calculated. Can be in the YYYY-MM-DD format "
  243. 'or an interval in the "[+-]YyMmDd" format (with Y, M, and D numbers). '
  244. "Defaults to today ({default_date})."
  245. ).format(default_date=today.strftime("%Y-%m-%d")),
  246. )
  247. parser.add_argument(
  248. "--timezone",
  249. "-t",
  250. type=int,
  251. default=None,
  252. help=_(
  253. "The timezone to display the hours in (e.g. 2 for UTC+2 or -3 for UTC-3). "
  254. "Can also be set in the KOSMORRO_TIMEZONE environment variable."
  255. ),
  256. )
  257. parser.add_argument(
  258. "--no-colors",
  259. dest="colors",
  260. action="store_false",
  261. help=_("Disable the colors in the console."),
  262. )
  263. parser.add_argument(
  264. "--output",
  265. "-o",
  266. type=str,
  267. default=None,
  268. help=_(
  269. "A file to export the output to. If not given, the standard output is used. "
  270. "This argument is needed for PDF format."
  271. ),
  272. )
  273. parser.add_argument(
  274. "--no-graph",
  275. dest="show_graph",
  276. action="store_false",
  277. help=_(
  278. "Do not generate a graph to represent the rise and set times in the PDF format."
  279. ),
  280. )
  281. parser.add_argument(
  282. "--debug",
  283. dest="show_debug_messages",
  284. action="store_true",
  285. help=_("Show debugging messages"),
  286. )
  287. return parser.parse_args()