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.
 
 
 
 

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