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