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.
 
 
 
 

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