您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

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