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.

main.py 9.2 KiB

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