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.
 
 
 
 

324 lines
9.5 KiB

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