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.
 
 
 
 

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