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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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()