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.
 
 
 
 

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