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/CONTRIBUTING.md b/CONTRIBUTING.md index c500c85..4313cc1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,8 +24,19 @@ If you speak another language than English, another nice way to enhance Kosmorro First of all, if you are fixing an opened issue, check that nobody is already working on it — if someone seems to be but their Pull Request seems stuck, please ask them first if you can continue the development. If you retake the code they produced, **don't change the author of the commits**. Before writing the code, first create a fork of the repository and clone it. You may also want to add the original repository (`Deuchnord/kosmorro`), so you can update your fork with the last upstream commits. + Then create a new branch and start coding. Finally, commit and push, then open a PR on this project. If your project is not complete, feel free to open it as Draft (if you forgot to activate the Draft status, just edit the first comment to say it), then mark it as ready for review when you're done. +### Choosing the right target branch + +Whatever you are doing, always base your working branch on `master`. +When you create your PR, please consider selecting the right target branch: + +- If you are fixing a bug or optimizing something, then target the `master` branch. +- If you are doing anything else, then target the `feature` branch. + +This allows to make easier to publish patch releases, which have a higher priority than the minor releases. + ### Dealing with the translations The messages file contains all the messages Kosmorro can display, in order to make them translatable. When you change code, you may change also the messages displayed by the software. 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..791368e 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,38 @@ 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()) + + data = self._get_data(aster_rise_set=True) + self.assertEqual('{\n' + ' "moon_phase": {\n' + ' "next_phase_date": "2019-10-21T00:00:00Z",\n' + ' "phase": "FULL_MOON",\n' + ' "date": "2019-10-14T00:00:00Z"\n' + ' },\n' + ' "events": [\n' + ' {\n' + ' "event_type": "OPPOSITION",\n' + ' "objects": [\n' + ' "Mars"\n' + ' ],\n' + ' "start_time": "2018-07-27T05:12:00Z",\n' + ' "end_time": null\n' + ' }\n' + ' ],\n' + ' "ephemerides": [\n' + ' {\n' + ' "object": "Mars",\n' + ' "details": {\n' + ' "rise_time": "2019-10-14T08:00:00Z",\n' + ' "culmination_time": "2019-10-14T13:00:00Z",\n' + ' "set_time": "2019-10-14T23:00:00Z"\n' + ' }\n' + ' }\n' + ' ]\n' + '}', JsonDumper(data, + self._get_events() ).to_string()) def test_text_dumper_without_events(self): @@ -54,6 +84,16 @@ class DumperTestCase(unittest.TestCase): 'Note: All the hours are given in UTC.', TextDumper(ephemerides, [], date=date(2019, 10, 14), with_colors=False).to_string()) + ephemerides = self._get_data(aster_rise_set=True) + self.assertEqual('Monday October 14, 2019\n\n' + 'Object Rise time Culmination time Set time\n' + '-------- ----------- ------------------ ----------\n' + 'Mars 08:00 13:00 23:00\n\n' + 'Moon phase: Full Moon\n' + 'Last Quarter on Monday October 21, 2019 at 00:00\n\n' + 'Note: All the hours are given in UTC.', + TextDumper(ephemerides, [], date=date(2019, 10, 14), with_colors=False).to_string()) + def test_text_dumper_with_events(self): ephemerides = self._get_data() self.assertEqual('Monday October 14, 2019\n\n' @@ -65,10 +105,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 +115,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()