diff --git a/.gitignore b/.gitignore index 6beac87..4480c6d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ kosmorro.egg-info coverage.xml node_modules/ package-lock.json +/kosmorrolib/assets/pdf/* +!/assets/pdf/*.tex # Translation files are taken care on https://poeditor.com/join/project/GXuhLpdaoh *.mo diff --git a/Pipfile b/Pipfile index e716458..f72dd1a 100644 --- a/Pipfile +++ b/Pipfile @@ -14,6 +14,7 @@ skyfield = ">=1.13.0,<2.0.0" tabulate = "*" numpy = ">=1.17.0,<2.0.0" termcolor = "*" +latex = "*" [requires] python_version = "3" diff --git a/Pipfile.lock b/Pipfile.lock index 906ca02..9e0a149 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "fb530146420b5768bc25165302d947d11615aac375e7a63a9076fdddd0372d53" + "sha256": "789ae3ae412a3b57df763f776aa8ce0497d680de59fa09ad22a9b91a4e3d6b4e" }, "pipfile-spec": 6, "requires": { @@ -16,12 +16,45 @@ ] }, "default": { + "data": { + "hashes": [ + "sha256:2726a65da1af31e2345b6bba81ae4cee87dddf17f7c62f5c63ba7327a8480667" + ], + "version": "==0.4" + }, + "decorator": { + "hashes": [ + "sha256:54c38050039232e1db4ad7375cfce6748d7b41c29e95a081c8a6d2c30364a2ce", + "sha256:5d19b92a3c8f7f101c8dd86afd86b0f061a8ce4540ab8cd401fa2542756bce6d" + ], + "version": "==4.4.1" + }, + "funcsigs": { + "hashes": [ + "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", + "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50" + ], + "version": "==1.0.2" + }, + "future": { + "hashes": [ + "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" + ], + "version": "==0.18.2" + }, "jplephem": { "hashes": [ "sha256:35a3b67444c7b03433e5ffff89fe10fd78d9bc88c12aafd001631227a1782023" ], "version": "==2.12" }, + "latex": { + "hashes": [ + "sha256:bf10c3fe27e9f3adccebc12e90ec239c86dcba101b89221f6775918211482a79" + ], + "index": "pypi", + "version": "==0.7.0" + }, "numpy": { "hashes": [ "sha256:1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6", @@ -94,6 +127,12 @@ "index": "pypi", "version": "==0.8.6" }, + "tempdir": { + "hashes": [ + "sha256:689680ed3ba4cc8347a70e67efc25086ce85b53b9d24a1420899c585bbf7ba8e" + ], + "version": "==0.7.1" + }, "termcolor": { "hashes": [ "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b" diff --git a/README.md b/README.md index adcd4b6..0fda4a2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ![Kosmorro](assets/png/kosmorro-logo.png) +# ![Kosmorro](kosmorrolib/assets/png/kosmorro-logo.png) [![codecov](https://codecov.io/gh/Deuchnord/kosmorro/branch/master/graph/badge.svg)](https://codecov.io/gh/Deuchnord/kosmorro) [![Version on PyPI](https://img.shields.io/pypi/v/kosmorro)](https://pypi.org/project/kosmorro) [![Discord](https://img.shields.io/discord/650237632533757965?logo=discord&label=%23kosmorro)](https://discord.gg/nyemBqE) ## About the project diff --git a/kosmorrolib/assets/moonphases/png/first-quarter.png b/kosmorrolib/assets/moonphases/png/first-quarter.png new file mode 100644 index 0000000..c9ec338 Binary files /dev/null and b/kosmorrolib/assets/moonphases/png/first-quarter.png differ diff --git a/kosmorrolib/assets/moonphases/png/full-moon.png b/kosmorrolib/assets/moonphases/png/full-moon.png new file mode 100644 index 0000000..54beb67 Binary files /dev/null and b/kosmorrolib/assets/moonphases/png/full-moon.png differ diff --git a/kosmorrolib/assets/moonphases/png/last-quarter.png b/kosmorrolib/assets/moonphases/png/last-quarter.png new file mode 100644 index 0000000..86ebc38 Binary files /dev/null and b/kosmorrolib/assets/moonphases/png/last-quarter.png differ diff --git a/kosmorrolib/assets/moonphases/png/new-moon.png b/kosmorrolib/assets/moonphases/png/new-moon.png new file mode 100644 index 0000000..ff0cdb4 Binary files /dev/null and b/kosmorrolib/assets/moonphases/png/new-moon.png differ diff --git a/kosmorrolib/assets/moonphases/png/waning-crescent.png b/kosmorrolib/assets/moonphases/png/waning-crescent.png new file mode 100644 index 0000000..697fab9 Binary files /dev/null and b/kosmorrolib/assets/moonphases/png/waning-crescent.png differ diff --git a/kosmorrolib/assets/moonphases/png/waning-gibbous.png b/kosmorrolib/assets/moonphases/png/waning-gibbous.png new file mode 100644 index 0000000..6303cc0 Binary files /dev/null and b/kosmorrolib/assets/moonphases/png/waning-gibbous.png differ diff --git a/kosmorrolib/assets/moonphases/png/waxing-crescent.png b/kosmorrolib/assets/moonphases/png/waxing-crescent.png new file mode 100644 index 0000000..2fb768d Binary files /dev/null and b/kosmorrolib/assets/moonphases/png/waxing-crescent.png differ diff --git a/kosmorrolib/assets/moonphases/png/waxing-gibbous.png b/kosmorrolib/assets/moonphases/png/waxing-gibbous.png new file mode 100644 index 0000000..093b674 Binary files /dev/null and b/kosmorrolib/assets/moonphases/png/waxing-gibbous.png differ diff --git a/kosmorrolib/assets/moonphases/svg/first-quarter.svg b/kosmorrolib/assets/moonphases/svg/first-quarter.svg new file mode 100644 index 0000000..c3b79d4 --- /dev/null +++ b/kosmorrolib/assets/moonphases/svg/first-quarter.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/kosmorrolib/assets/moonphases/svg/full-moon.svg b/kosmorrolib/assets/moonphases/svg/full-moon.svg new file mode 100644 index 0000000..0ad1088 --- /dev/null +++ b/kosmorrolib/assets/moonphases/svg/full-moon.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/kosmorrolib/assets/moonphases/svg/last-quarter.svg b/kosmorrolib/assets/moonphases/svg/last-quarter.svg new file mode 100644 index 0000000..62f0559 --- /dev/null +++ b/kosmorrolib/assets/moonphases/svg/last-quarter.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/kosmorrolib/assets/moonphases/svg/new-moon.svg b/kosmorrolib/assets/moonphases/svg/new-moon.svg new file mode 100644 index 0000000..3e962ef --- /dev/null +++ b/kosmorrolib/assets/moonphases/svg/new-moon.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/kosmorrolib/assets/moonphases/svg/waning-crescent.svg b/kosmorrolib/assets/moonphases/svg/waning-crescent.svg new file mode 100644 index 0000000..e5775b1 --- /dev/null +++ b/kosmorrolib/assets/moonphases/svg/waning-crescent.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/kosmorrolib/assets/moonphases/svg/waning-gibbous.svg b/kosmorrolib/assets/moonphases/svg/waning-gibbous.svg new file mode 100644 index 0000000..eda8eb7 --- /dev/null +++ b/kosmorrolib/assets/moonphases/svg/waning-gibbous.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/kosmorrolib/assets/moonphases/svg/waxing-crescent.svg b/kosmorrolib/assets/moonphases/svg/waxing-crescent.svg new file mode 100644 index 0000000..6c6775e --- /dev/null +++ b/kosmorrolib/assets/moonphases/svg/waxing-crescent.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/kosmorrolib/assets/moonphases/svg/waxing-gibbous.svg b/kosmorrolib/assets/moonphases/svg/waxing-gibbous.svg new file mode 100644 index 0000000..7450f15 --- /dev/null +++ b/kosmorrolib/assets/moonphases/svg/waxing-gibbous.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/kosmorrolib/assets/pdf/template.tex b/kosmorrolib/assets/pdf/template.tex new file mode 100644 index 0000000..6cb87ed --- /dev/null +++ b/kosmorrolib/assets/pdf/template.tex @@ -0,0 +1,90 @@ +\documentclass[a4paper,12pt]{article} + +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} +\usepackage[margin=25mm]{geometry} +\usepackage{graphicx} +\usepackage{hyperref} + +% Fix non-break spaces issues +\DeclareUnicodeCharacter{202F}{~} + +\hypersetup{pdfinfo={ + Title={+++DOCUMENT-TITLE+++}, + Creator={Kosmorro v+++KOSMORRO-VERSION+++} +}} + +\pagenumbering{gobble} +\setcounter{secnumdepth}{0} + +\title{\sffamily\href{http://kosmorro.space}{\includegraphics[width=5cm]{+++KOSMORRO-LOGO+++}}\\+++DOCUMENT-TITLE+++} +\date{\vspace{-11mm}\sffamily +++DOCUMENT-DATE+++} + +\begin{document} + + \newcommand{\object}[4]{ + \hline + \textbf{#1} & {#2} & {#3} & {#4}\\ + } + + \newcommand{\moonphase}[2]{ + \begin{center} + \begin{minipage}{2cm} + \includegraphics[width=\linewidth]{#1} + \end{minipage} + \hspace{5mm} + \begin{minipage}{7cm} + \textbf{+++CURRENT-MOON-PHASE-TITLE+++}\\#2 + \end{minipage} + \end{center} +} + + \newenvironment{ephemerides}{ + \begin{table}[h] + \centering + \begin{tabular}{lccc} + \textbf{+++EPHEMERIDES-OBJECT+++} & + \textbf{+++EPHEMERIDES-RISE-TIME+++} & + \textbf{+++EPHEMERIDES-CULMINATION-TIME+++} & + \textbf{+++EPHEMERIDES-SET-TIME+++}\\ + \hline + }{ + \end{tabular} + \end{table} + } + + \newcommand{\event}[2]{ + \textbf{#1} & {#2}\\ + } + + \newenvironment{events}{ + \begin{table}[h] + \begin{tabular}{ll} + }{ + \end{tabular} + \end{table} + } + + \maketitle + + +++INTRODUCTION+++ + + \moonphase{+++MOON-PHASE-GRAPHICS+++}{+++CURRENT-MOON-PHASE+++} + + %%% BEGIN-EPHEMERIDES-SECTION + \section{\sffamily +++SECTION-EPHEMERIDES+++} + + \begin{ephemerides} + +++EPHEMERIDES+++ + \end{ephemerides} + %%% END-EPHEMERIDES-SECTION + + %%% BEGIN-EVENTS-SECTION + \section{\sffamily +++SECTION-EVENTS+++} + + \begin{events} + +++EVENTS+++ + \end{events} + %%% END-EVENTS-SECTION + +\end{document} diff --git a/assets/png/kosmorro-icon-white.png b/kosmorrolib/assets/png/kosmorro-icon-white.png similarity index 100% rename from assets/png/kosmorro-icon-white.png rename to kosmorrolib/assets/png/kosmorro-icon-white.png diff --git a/assets/png/kosmorro-icon.png b/kosmorrolib/assets/png/kosmorro-icon.png similarity index 100% rename from assets/png/kosmorro-icon.png rename to kosmorrolib/assets/png/kosmorro-icon.png diff --git a/assets/png/kosmorro-logo-white.png b/kosmorrolib/assets/png/kosmorro-logo-white.png similarity index 100% rename from assets/png/kosmorro-logo-white.png rename to kosmorrolib/assets/png/kosmorro-logo-white.png diff --git a/assets/png/kosmorro-logo.png b/kosmorrolib/assets/png/kosmorro-logo.png similarity index 100% rename from assets/png/kosmorro-logo.png rename to kosmorrolib/assets/png/kosmorro-logo.png diff --git a/assets/svg/kosmorro-icon-white.svg b/kosmorrolib/assets/svg/kosmorro-icon-white.svg similarity index 100% rename from assets/svg/kosmorro-icon-white.svg rename to kosmorrolib/assets/svg/kosmorro-icon-white.svg diff --git a/assets/svg/kosmorro-icon.svg b/kosmorrolib/assets/svg/kosmorro-icon.svg similarity index 100% rename from assets/svg/kosmorro-icon.svg rename to kosmorrolib/assets/svg/kosmorro-icon.svg diff --git a/assets/svg/kosmorro-logo-white.svg b/kosmorrolib/assets/svg/kosmorro-logo-white.svg similarity index 100% rename from assets/svg/kosmorro-logo-white.svg rename to kosmorrolib/assets/svg/kosmorro-logo-white.svg diff --git a/assets/svg/kosmorro-logo.svg b/kosmorrolib/assets/svg/kosmorro-logo.svg similarity index 100% rename from assets/svg/kosmorro-logo.svg rename to kosmorrolib/assets/svg/kosmorro-logo.svg diff --git a/kosmorrolib/dumper.py b/kosmorrolib/dumper.py index 92b3649..b7fe4c2 100644 --- a/kosmorrolib/dumper.py +++ b/kosmorrolib/dumper.py @@ -19,12 +19,19 @@ from abc import ABC, abstractmethod import datetime import json +import os from tabulate import tabulate from skyfield.timelib import Time from numpy import int64 from termcolor import colored from .data import Object, AsterEphemerides, MoonPhase, Event from .i18n import _ +from .version import VERSION +from .exceptions import UnavailableFeatureError +try: + from latex import build_pdf +except ImportError: + build_pdf = None FULL_DATE_FORMAT = _('{day_of_week} {month} {day_number}, {year}').format(day_of_week='%A', month='%B', day_number='%d', year='%Y') @@ -39,10 +46,22 @@ class Dumper(ABC): self.date = date self.with_colors = with_colors + def get_date_as_string(self, capitalized: bool = False) -> str: + date = self.date.strftime(FULL_DATE_FORMAT) + + if capitalized: + return ''.join([date[0].upper(), date[1:]]) + + return date + @abstractmethod def to_string(self): pass + @staticmethod + def is_file_output_needed() -> bool: + return False + class JsonDumper(Dumper): def to_string(self): @@ -83,7 +102,7 @@ class JsonDumper(Dumper): class TextDumper(Dumper): def to_string(self): - text = [self.style(self.get_date(), 'h1')] + text = [self.style(self.get_date_as_string(capitalized=True), 'h1')] if len(self.ephemeris['details']) > 0: text.append(self.get_asters(self.ephemeris['details'])) @@ -112,11 +131,6 @@ class TextDumper(Dumper): return styles[tag](text) - def get_date(self) -> str: - date = self.date.strftime(FULL_DATE_FORMAT) - - return ''.join([date[0].upper(), date[1:]]) - def get_asters(self, asters: [Object]) -> str: data = [] @@ -164,3 +178,132 @@ class TextDumper(Dumper): ) return '\n'.join([current_moon_phase, new_moon_phase]) + + +class _LatexDumper(Dumper): + def to_string(self): + template_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), + 'assets', 'pdf', 'template.tex') + + with open(template_path, mode='r') as file: + template = file.read() + + return self._make_document(template) + + def _make_document(self, template: str) -> str: + kosmorro_logo_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), + 'assets', 'png', 'kosmorro-logo.png') + moon_phase_graphics = os.path.join(os.path.abspath(os.path.dirname(__file__)), + 'assets', 'moonphases', 'png', + '.'.join([self.ephemeris['moon_phase'].identifier.lower().replace('_', '-'), + 'png'])) + + document = template + + if len(self.ephemeris['details']) == 0: + document = self._remove_section(document, 'ephemerides') + + if len(self.events) == 0: + document = self._remove_section(document, 'events') + + document = document \ + .replace('+++KOSMORRO-VERSION+++', VERSION) \ + .replace('+++KOSMORRO-LOGO+++', kosmorro_logo_path) \ + .replace('+++DOCUMENT-TITLE+++', _('A Summary of your Sky')) \ + .replace('+++DOCUMENT-DATE+++', self.get_date_as_string(capitalized=True)) \ + .replace('+++INTRODUCTION+++', + '\n\n'.join([ + _("This document summarizes the ephemerides and the events of {date}. " + "It aims to help you to prepare your observation session.").format( + date=self.get_date_as_string()), + _("Don't forget to check the weather forecast before you go out with your material.") + ])) \ + .replace('+++SECTION-EPHEMERIDES+++', _('Ephemerides of the day')) \ + .replace('+++EPHEMERIDES-OBJECT+++', _('Object')) \ + .replace('+++EPHEMERIDES-RISE-TIME+++', _('Rise time')) \ + .replace('+++EPHEMERIDES-CULMINATION-TIME+++', _('Culmination time')) \ + .replace('+++EPHEMERIDES-SET-TIME+++', _('Set time')) \ + .replace('+++EPHEMERIDES+++', self._make_ephemerides()) \ + .replace('+++MOON-PHASE-GRAPHICS+++', moon_phase_graphics) \ + .replace('+++CURRENT-MOON-PHASE-TITLE+++', _('Moon phase:')) \ + .replace('+++CURRENT-MOON-PHASE+++', self.ephemeris['moon_phase'].get_phase()) \ + .replace('+++SECTION-EVENTS+++', _('Expected events')) \ + .replace('+++EVENTS+++', self._make_events()) + + return document + + def _make_ephemerides(self) -> str: + latex = [] + + for aster in self.ephemeris['details']: + if aster.ephemerides.rise_time is not None: + aster_rise = aster.ephemerides.rise_time.utc_strftime(TIME_FORMAT) + else: + aster_rise = '-' + + if aster.ephemerides.culmination_time is not None: + aster_culmination = aster.ephemerides.culmination_time.utc_strftime(TIME_FORMAT) + else: + aster_culmination = '-' + + if aster.ephemerides.set_time is not None: + aster_set = aster.ephemerides.set_time.utc_strftime(TIME_FORMAT) + else: + aster_set = '-' + + latex.append(r'\object{%s}{%s}{%s}{%s}' % (aster.name, + aster_rise, + aster_culmination, + aster_set)) + + return ''.join(latex) + + def _make_events(self) -> str: + latex = [] + + for event in self.events: + latex.append(r'\event{%s}{%s}' % (event.start_time.utc_strftime(TIME_FORMAT), + event.get_description())) + + return ''.join(latex) + + @staticmethod + def _remove_section(document: str, section: str): + begin_section_tag = '%%%%%% BEGIN-%s-SECTION' % section.upper() + end_section_tag = '%%%%%% END-%s-SECTION' % section.upper() + + document = document.split('\n') + new_document = [] + + ignore_line = False + for line in document: + if begin_section_tag in line or end_section_tag in line: + ignore_line = not ignore_line + continue + if ignore_line: + continue + new_document.append(line) + + return '\n'.join(new_document) + + +class PdfDumper(Dumper): + def to_string(self): + try: + latex_dumper = _LatexDumper(self.ephemeris, self.events, self.date, self.with_colors) + return self._compile(latex_dumper.to_string()) + except RuntimeError: + raise UnavailableFeatureError(_("Building PDFs was not possible, because some dependencies are not" + " installed.\nPlease look at the documentation at http://kosmorro.space " + "for more information.")) + + @staticmethod + def is_file_output_needed() -> bool: + return True + + @staticmethod + def _compile(latex_input) -> bytes: + if build_pdf is None: + raise RuntimeError('Python latex module not found') + + return bytes(build_pdf(latex_input)) diff --git a/kosmorrolib/exceptions.py b/kosmorrolib/exceptions.py new file mode 100644 index 0000000..d6a87d8 --- /dev/null +++ b/kosmorrolib/exceptions.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 + +# Kosmorro - Compute The Next Ephemerides +# Copyright (C) 2019 Jérôme Deuchnord +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +class UnavailableFeatureError(RuntimeError): + def __init__(self, msg: str): + super(UnavailableFeatureError, self).__init__() + self.msg = msg diff --git a/kosmorrolib/locales/messages.pot b/kosmorrolib/locales/messages.pot index 3893d2b..e67c57b 100644 --- a/kosmorrolib/locales/messages.pot +++ b/kosmorrolib/locales/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: kosmorro 0.5.1\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2020-02-03 13:19+0100\n" +"POT-Creation-Date: 2020-02-04 13:29+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -99,111 +99,170 @@ msgstr "" msgid "%s and %s are in conjunction" msgstr "" -#: kosmorrolib/dumper.py:29 +#: kosmorrolib/dumper.py:36 msgid "{day_of_week} {month} {day_number}, {year}" msgstr "" -#: kosmorrolib/dumper.py:31 +#: kosmorrolib/dumper.py:38 msgid "{hours}:{minutes}" msgstr "" -#: kosmorrolib/dumper.py:94 +#: kosmorrolib/dumper.py:113 msgid "Expected events:" msgstr "" -#: kosmorrolib/dumper.py:97 +#: kosmorrolib/dumper.py:116 msgid "Note: All the hours are given in UTC." msgstr "" -#: kosmorrolib/dumper.py:143 +#: kosmorrolib/dumper.py:157 kosmorrolib/dumper.py:222 msgid "Object" msgstr "" -#: kosmorrolib/dumper.py:144 +#: kosmorrolib/dumper.py:158 kosmorrolib/dumper.py:223 msgid "Rise time" msgstr "" -#: kosmorrolib/dumper.py:145 +#: kosmorrolib/dumper.py:159 kosmorrolib/dumper.py:224 msgid "Culmination time" msgstr "" -#: kosmorrolib/dumper.py:146 +#: kosmorrolib/dumper.py:160 kosmorrolib/dumper.py:225 msgid "Set time" msgstr "" -#: kosmorrolib/dumper.py:159 +#: kosmorrolib/dumper.py:173 kosmorrolib/dumper.py:228 msgid "Moon phase:" msgstr "" -#: kosmorrolib/dumper.py:160 +#: kosmorrolib/dumper.py:174 msgid "{next_moon_phase} on {next_moon_phase_date} at {next_moon_phase_time}" msgstr "" -#: kosmorrolib/main.py:76 -msgid "Running on Python {python_version}" +#: kosmorrolib/dumper.py:212 +msgid "A Summary of your Sky" +msgstr "" + +#: kosmorrolib/dumper.py:216 +msgid "" +"This document summarizes the ephemerides and the events of {date}. It " +"aims to help you to prepare your observation session." +msgstr "" + +#: kosmorrolib/dumper.py:219 +msgid "" +"Don't forget to check the weather forecast before you go out with your " +"material." +msgstr "" + +#: kosmorrolib/dumper.py:221 +msgid "Ephemerides of the day" +msgstr "" + +#: kosmorrolib/dumper.py:230 +msgid "Expected events" +msgstr "" + +#: kosmorrolib/dumper.py:296 +msgid "" +"Building PDFs was not possible, because some dependencies are not " +"installed.\n" +"Please look at the documentation at http://kosmorro.space for more " +"information." +msgstr "" + +#: kosmorrolib/main.py:58 +msgid "" +"Save the planet and paper!\n" +"Consider printing you PDF document only if really necessary, and use the " +"other side of the sheet." +msgstr "" + +#: kosmorrolib/main.py:62 +msgid "" +"PDF output will not contain the ephemerides, because you didn't provide " +"the observation coordinate." msgstr "" #: kosmorrolib/main.py:82 +msgid "Could not save the output in \"{path}\": {error}" +msgstr "" + +#: kosmorrolib/main.py:87 +msgid "Selected output format needs an output file (--output)." +msgstr "" + +#: kosmorrolib/main.py:104 +msgid "Running on Python {python_version}" +msgstr "" + +#: kosmorrolib/main.py:110 msgid "Do you really want to clear Kosmorro's cache? [yN] " msgstr "" -#: kosmorrolib/main.py:89 +#: kosmorrolib/main.py:117 msgid "Answer did not match expected options, cache not cleared." msgstr "" -#: kosmorrolib/main.py:98 +#: kosmorrolib/main.py:126 msgid "" "Compute the ephemerides and the events for a given date, at a given " "position on Earth." msgstr "" -#: kosmorrolib/main.py:100 +#: kosmorrolib/main.py:128 msgid "" "By default, only the events will be computed for today ({date}).\n" "To compute also the ephemerides, latitude and longitude arguments are " "needed." msgstr "" -#: kosmorrolib/main.py:105 +#: kosmorrolib/main.py:133 msgid "Show the program version" msgstr "" -#: kosmorrolib/main.py:107 +#: kosmorrolib/main.py:135 msgid "Delete all the files Kosmorro stored in the cache." msgstr "" -#: kosmorrolib/main.py:109 +#: kosmorrolib/main.py:137 msgid "The format under which the information have to be output" msgstr "" -#: kosmorrolib/main.py:111 +#: kosmorrolib/main.py:139 msgid "The observer's latitude on Earth" msgstr "" -#: kosmorrolib/main.py:113 +#: kosmorrolib/main.py:141 msgid "The observer's longitude on Earth" msgstr "" -#: kosmorrolib/main.py:115 +#: kosmorrolib/main.py:143 msgid "" "A number between 1 and 28, 29, 30 or 31 (depending on the month). The day" " you want to compute the ephemerides for. Defaults to {default_day} (the" " current day)." msgstr "" -#: kosmorrolib/main.py:119 +#: kosmorrolib/main.py:147 msgid "" "A number between 1 and 12. The month you want to compute the ephemerides " "for. Defaults to {default_month} (the current month)." msgstr "" -#: kosmorrolib/main.py:122 +#: kosmorrolib/main.py:150 msgid "" "The year you want to compute the ephemerides for. Defaults to " "{default_year} (the current year)." msgstr "" -#: kosmorrolib/main.py:125 +#: kosmorrolib/main.py:153 msgid "Disable the colors in the console." msgstr "" +#: kosmorrolib/main.py:155 +msgid "" +"A file to export the output to. If not given, the standard output is " +"used. This argument is needed for PDF format." +msgstr "" + diff --git a/kosmorrolib/main.py b/kosmorrolib/main.py index 6f27447..16788a0 100644 --- a/kosmorrolib/main.py +++ b/kosmorrolib/main.py @@ -22,6 +22,7 @@ import re import sys from datetime import date +from termcolor import colored from kosmorrolib.version import VERSION from kosmorrolib import dumper @@ -29,6 +30,7 @@ from kosmorrolib import core from kosmorrolib import events from kosmorrolib.i18n import _ from .ephemerides import EphemeridesComputer, Position +from .exceptions import UnavailableFeatureError def main(): @@ -52,13 +54,38 @@ def main(): else: position = Position(args.latitude, args.longitude) - ephemeris = EphemeridesComputer(position) - ephemerides = ephemeris.compute_ephemerides(year, month, day) + if args.format == 'pdf': + print(_('Save the planet and paper!\n' + 'Consider printing you PDF document only if really necessary, and use the other side of the sheet.')) + if position is None: + print() + print(colored(_("PDF output will not contain the ephemerides, because you didn't provide the observation " + "coordinate."), 'yellow')) - events_list = events.search_events(compute_date) + try: + ephemeris = EphemeridesComputer(position) + ephemerides = ephemeris.compute_ephemerides(year, month, day) - dump = output_formats[args.format](ephemerides, events_list, compute_date, args.colors) - print(dump.to_string()) + events_list = events.search_events(compute_date) + + selected_dumper = output_formats[args.format](ephemerides, events_list, compute_date, args.colors) + output = selected_dumper.to_string() + except UnavailableFeatureError as error: + print(colored(error.msg, 'red')) + return 2 + + if args.output is not None: + try: + with open(args.output, 'wb') as output_file: + output_file.write(output) + except OSError as error: + print(_('Could not save the output in "{path}": {error}').format(path=args.output, + error=error.strerror)) + elif not selected_dumper.is_file_output_needed(): + print(output) + else: + print(_('Selected output format needs an output file (--output).')) + return 1 return 0 @@ -66,7 +93,8 @@ def main(): def get_dumpers() -> {str: dumper.Dumper}: return { 'text': dumper.TextDumper, - 'json': dumper.JsonDumper + 'json': dumper.JsonDumper, + 'pdf': dumper.PdfDumper } @@ -123,5 +151,8 @@ def get_args(output_formats: [str]): ' Defaults to {default_year} (the current year).').format(default_year=today.year)) parser.add_argument('--no-colors', dest='colors', action='store_false', help=_('Disable the colors in the console.')) + parser.add_argument('--output', '-o', type=str, default=None, + help=_('A file to export the output to. If not given, the standard output is used. ' + 'This argument is needed for PDF format.')) return parser.parse_args() diff --git a/test/dumper.py b/test/dumper.py index 523d878..c1daeb7 100644 --- a/test/dumper.py +++ b/test/dumper.py @@ -2,7 +2,7 @@ import unittest from datetime import date from kosmorrolib.data import AsterEphemerides, Planet, MoonPhase, Event -from kosmorrolib.dumper import JsonDumper, TextDumper +from kosmorrolib.dumper import JsonDumper, TextDumper, _LatexDumper from kosmorrolib.core import get_timescale @@ -39,8 +39,7 @@ class DumperTestCase(unittest.TestCase): ' }\n' ' ]\n' '}', JsonDumper(data, - [Event('OPPOSITION', [Planet('Mars', 'MARS')], - get_timescale().utc(2018, 7, 27, 5, 12))] + self._get_events() ).to_string()) def test_text_dumper_without_events(self): @@ -65,10 +64,7 @@ class DumperTestCase(unittest.TestCase): 'Expected events:\n' '05:12 Mars is in opposition\n\n' 'Note: All the hours are given in UTC.', - TextDumper(ephemerides, [Event('OPPOSITION', - [Planet('Mars', 'MARS')], - get_timescale().utc(2018, 7, 27, 5, 12)) - ], date=date(2019, 10, 14), with_colors=False).to_string()) + TextDumper(ephemerides, self._get_events(), date=date(2019, 10, 14), with_colors=False).to_string()) def test_text_dumper_without_ephemerides_and_with_events(self): ephemerides = self._get_data(False) @@ -78,18 +74,59 @@ class DumperTestCase(unittest.TestCase): 'Expected events:\n' '05:12 Mars is in opposition\n\n' 'Note: All the hours are given in UTC.', - TextDumper(ephemerides, [Event('OPPOSITION', - [Planet('Mars', 'MARS')], - get_timescale().utc(2018, 7, 27, 5, 12)) - ], date=date(2019, 10, 14), with_colors=False).to_string()) + TextDumper(ephemerides, self._get_events(), date=date(2019, 10, 14), with_colors=False).to_string()) + + def test_latex_dumper(self): + latex = _LatexDumper(self._get_data(), self._get_events(), date=date(2019, 10, 14)).to_string() + self.assertRegex(latex, 'Monday October 14, 2019') + self.assertRegex(latex, 'Full Moon') + self.assertRegex(latex, r'\\section{\\sffamily Expected events}') + self.assertRegex(latex, r'\\section{\\sffamily Ephemerides of the day}') + self.assertRegex(latex, r'\\object\{Mars\}\{-\}\{-\}\{-\}') + self.assertRegex(latex, r'\\event\{05:12\}\{Mars is in opposition\}') + + latex = _LatexDumper(self._get_data(aster_rise_set=True), + self._get_events(), date=date(2019, 10, 14)).to_string() + self.assertRegex(latex, r'\\object\{Mars\}\{08:00\}\{13:00\}\{23:00\}') + + def test_latex_dumper_without_ephemerides(self): + latex = _LatexDumper(self._get_data(False), self._get_events(), date=date(2019, 10, 14)).to_string() + self.assertRegex(latex, 'Monday October 14, 2019') + self.assertRegex(latex, 'Full Moon') + self.assertRegex(latex, r'\\section{\\sffamily Expected events}') + self.assertRegex(latex, r'\\event\{05:12\}\{Mars is in opposition\}') + + self.assertNotRegex(latex, r'\\object\{Mars\}\{-\}\{-\}\{-\}') + self.assertNotRegex(latex, r'\\section{\\sffamily Ephemerides of the day}') + + def test_latex_dumper_without_events(self): + latex = _LatexDumper(self._get_data(), [], date=date(2019, 10, 14)).to_string() + self.assertRegex(latex, 'Monday October 14, 2019') + self.assertRegex(latex, 'Full Moon') + self.assertRegex(latex, r'\\object\{Mars\}\{-\}\{-\}\{-\}') + self.assertRegex(latex, r'\\section{\\sffamily Ephemerides of the day}') + + self.assertNotRegex(latex, r'\\section{\\sffamily Expected events}') @staticmethod - def _get_data(has_ephemerides: bool = True): + def _get_data(has_ephemerides: bool = True, aster_rise_set=False): + rise_time = get_timescale().utc(2019, 10, 14, 8) if aster_rise_set else None + culmination_time = get_timescale().utc(2019, 10, 14, 13) if aster_rise_set else None + set_time = get_timescale().utc(2019, 10, 14, 23) if aster_rise_set else None + return { 'moon_phase': MoonPhase('FULL_MOON', get_timescale().utc(2019, 10, 14), get_timescale().utc(2019, 10, 21)), - 'details': [Planet('Mars', 'MARS', AsterEphemerides(None, None, None))] if has_ephemerides else [] + 'details': [Planet('Mars', 'MARS', + AsterEphemerides(rise_time, culmination_time, set_time))] if has_ephemerides else [] } + @staticmethod + def _get_events(): + return [Event('OPPOSITION', + [Planet('Mars', 'MARS')], + get_timescale().utc(2018, 7, 27, 5, 12)) + ] + if __name__ == '__main__': unittest.main()