diff --git a/.gitignore b/.gitignore index 06399ba..e89f60f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,10 @@ kosmorro.egg-info .coverage coverage.xml +/kosmorrolib/assets/pdf/* +!/kosmorrolib/assets/pdf/*.tex +!/kosmorrolib/assets/pdf/*.sty + /manpage/* !/manpage/*.md diff --git a/README.md b/README.md index 094b5d0..feb23fe 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,11 @@ By default, it will give you the current Moon phase and, if any, the events that Kosmorro has a lot of available options to get exactly what you want, including the possibility to get planets rise and set. To get a list of them, run `kosmorro --help`, or read its manual with `man kosmorro`. You can also find usage examples in [the `tldr` manual](https://tldr.sh) with [`tldr kosmorro`](https://tldr.inbrowser.app/pages/common/kosmorro). +### Exporting to PDF + +Kosmorro can export the computation results to PDF files, but this feature requires first that you install some additional dependencies. +You can find documentation about this on [Kosmorro's website](https://kosmorro.space/cli/generate-pdf/). + ## Help translating Kosmorro! Kosmorro is translated on [Weblate](https://hosted.weblate.org/engage/kosmorro/), a popular free platform for crowd-sourced internationalization. diff --git a/kosmorro/__main__.py b/kosmorro/__main__.py index 13c8eb0..d0f54f8 100644 --- a/kosmorro/__main__.py +++ b/kosmorro/__main__.py @@ -83,16 +83,23 @@ def run(): if output_format is None: output_format = "txt" - if output_format == "tex" and position is None: - print_stderr( - colored( - _( - "Output file will not contain the ephemerides, because you didn't provide the observation " - "coordinates." - ), - "yellow", + if output_format == "pdf": + print( + _( + "Save the planet and paper!\n" + "Consider printing your PDF document only if really necessary, and use the other side of the sheet." ) ) + if position is None: + print_stderr( + colored( + _( + "PDF output will not contain the ephemerides, because you didn't provide the observation " + "coordinates." + ), + "yellow", + ) + ) timezone = 0 @@ -233,11 +240,15 @@ def get_dumpers() -> {str: dumper.Dumper}: return { "txt": dumper.TextDumper, "json": dumper.JsonDumper, + "pdf": dumper.PdfDumper, "tex": dumper.LatexDumper, } def get_opening_mode(format: str) -> str: + if format == "pdf": + return "wb" + return "w" @@ -335,7 +346,8 @@ def get_args(output_formats: [str]): type=str, default=None, help=_( - "A file to export the output to. If not given, the standard output is used." + "A file to export the output to. If not given, the standard output is used. " + "This argument is needed for PDF format." ), ) parser.add_argument( @@ -343,7 +355,7 @@ def get_args(output_formats: [str]): dest="show_graph", action="store_false", help=_( - "Do not generate a graph to represent the rise and set times in the LaTeX file." + "Do not generate a graph to represent the rise and set times in the PDF format." ), ) parser.add_argument( diff --git a/kosmorro/assets/latex/template.tex b/kosmorro/assets/latex/template.tex index d1acc47..5daec4c 100644 --- a/kosmorro/assets/latex/template.tex +++ b/kosmorro/assets/latex/template.tex @@ -1,5 +1,13 @@ \documentclass[a4paper,12pt]{article} +% This file has been generated with Kosmorro version +++KOSMORRO-VERSION+++ (https://kosmorro.space) on +++CURRENT-DATE+++. +% Feel free to modify it at your needs. +% +% To compile this file, you will need to install LaTeX distribution like: +% +% - TeXLive (https://tug.org/texlive/) on Windows and Linux +% - MacTeX (https://www.tug.org/mactex/) on macOS + \usepackage[utf8]{inputenc} \usepackage[T1]{fontenc} \usepackage[margin=25mm]{geometry} diff --git a/kosmorro/assets/pdf/kosmorro.sty b/kosmorro/assets/pdf/kosmorro.sty new file mode 100644 index 0000000..e69de29 diff --git a/kosmorro/assets/pdf/template.tex b/kosmorro/assets/pdf/template.tex new file mode 100644 index 0000000..e69de29 diff --git a/kosmorro/dumper.py b/kosmorro/dumper.py index 7ee39a3..78f6c98 100644 --- a/kosmorro/dumper.py +++ b/kosmorro/dumper.py @@ -301,6 +301,9 @@ class LatexDumper(Dumper): def add_strings( self, document: str, kosmorro_logo_path: str, moon_phase_graphics: str ) -> str: + document = document.replace( + "+++CURRENT-DATE+++", datetime.datetime.now().isoformat() + ) document = document.replace("+++KOSMORRO-VERSION+++", KOSMORRO_VERSION) document = document.replace("+++KOSMORRO-LOGO+++", kosmorro_logo_path) document = document.replace("+++DOCUMENT-TITLE+++", _("Overview of your sky")) @@ -466,3 +469,76 @@ class LatexDumper(Dumper): new_document.append(line) return "\n".join(new_document) + + +class PdfDumper(Dumper): + def to_string(self): + try: + latex_dumper = LatexDumper( + self.ephemerides, + self.moon_phase, + self.events, + date=self.date, + timezone=self.timezone, + with_colors=self.with_colors, + show_graph=self.show_graph, + ) + + return self._compile(latex_dumper.to_string()) + except RuntimeError as error: + raise KosmorroUnavailableFeatureError( + _( + "Building PDF was not possible, because some dependencies are not" + " installed.\nPlease look at the documentation at https://kosmorro.space/cli/generate-pdf/ " + "for more information." + ) + ) from error + + @staticmethod + def is_file_output_needed() -> bool: + return True + + @staticmethod + def _compile(latex_input) -> bytes: + timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + current_dir = ( + os.getcwd() + ) # Keep the current directory to return to it after the PDFLaTeX execution + + try: + temp_dir = tempfile.mkdtemp() + temp_tex = "%s/%s.tex" % (temp_dir, timestamp) + + with open(temp_tex, "w") as tex_file: + tex_file.write(latex_input) + + os.chdir(temp_dir) + debug_print("LaTeX content:\n%s" % latex_input) + + subprocess.run( + ["pdflatex", "-interaction", "nonstopmode", "%s.tex" % timestamp], + capture_output=True, + check=True, + ) + + os.chdir(current_dir) + + with open("%s/%s.pdf" % (temp_dir, timestamp), "rb") as pdffile: + return bytes(pdffile.read()) + + except FileNotFoundError as error: + raise KosmorroUnavailableFeatureError( + "TeXLive is not installed." + ) from error + + except subprocess.CalledProcessError as error: + with open("/tmp/kosmorro-%s.log" % timestamp, "wb") as file: + file.write(error.stdout) + + raise CompileError( + _( + "An error occurred during the compilation of the PDF.\n" + "Please open an issue at https://github.com/Kosmorro/kosmorro/issues and share " + "the content of the log file at /tmp/kosmorro-%s.log" % timestamp + ) + ) from error diff --git a/kosmorro/locales/messages.pot b/kosmorro/locales/messages.pot index 4f56d1a..4e568c4 100644 --- a/kosmorro/locales/messages.pot +++ b/kosmorro/locales/messages.pot @@ -1,136 +1,118 @@ # Translations template for PROJECT. -# Copyright (C) 2025 ORGANIZATION +# Copyright (C) 2023 ORGANIZATION # This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR , 2025. +# FIRST AUTHOR , 2023. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2025-10-05 10:30+0200\n" +"POT-Creation-Date: 2023-05-24 13:41+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.17.0\n" +"Generated-By: Babel 2.12.1\n" -#: kosmorro/__main__.py:90 +#: kosmorro/__main__.py:82 msgid "" -"Output file will not contain the ephemerides, because you didn't provide " -"the observation coordinates." +"Save the planet and paper!\n" +"Consider printing your PDF document only if really necessary, and use the" +" other side of the sheet." msgstr "" -#: kosmorro/__main__.py:108 +#: kosmorro/__main__.py:90 msgid "" -"Environment variable KOSMORRO_TIMEZONE is deprecated. Use TZ instead, " -"which is more standard." -msgstr "" - -#: kosmorro/__main__.py:117 -#, python-brace-format -msgid "Unknown timezone: {timezone}" +"PDF output will not contain the ephemerides, because you didn't provide " +"the observation coordinates." msgstr "" -#: kosmorro/__main__.py:160 -#, python-brace-format +#: kosmorro/__main__.py:142 msgid "The file could not be saved in \"{path}\": {error}" msgstr "" -#: kosmorro/__main__.py:174 +#: kosmorro/__main__.py:156 msgid "Please provide a file path to export in this format (--output)." msgstr "" -#: kosmorro/__main__.py:206 -#, python-brace-format +#: kosmorro/__main__.py:187 msgid "Moon phase can only be computed between {min_date} and {max_date}" msgstr "" -#: kosmorro/__main__.py:253 -#, python-brace-format +#: kosmorro/__main__.py:238 msgid "Running on Python {python_version} with Kosmorrolib v{kosmorrolib_version}" msgstr "" -#: kosmorro/__main__.py:266 +#: kosmorro/__main__.py:251 msgid "" "Compute the ephemerides and the events for a given date and a given " "position on Earth." msgstr "" -#: kosmorro/__main__.py:269 +#: kosmorro/__main__.py:254 msgid "" "By default, only the events will be computed for today.\n" "To compute also the ephemerides, latitude and longitude arguments are " "needed." msgstr "" -#: kosmorro/__main__.py:281 +#: kosmorro/__main__.py:267 msgid "Show the program version" msgstr "" -#: kosmorro/__main__.py:290 +#: kosmorro/__main__.py:275 msgid "" "The format to output the information to. If not provided, the output " "format will be inferred from the file extension of the output file." msgstr "" -#: kosmorro/__main__.py:300 -#, python-brace-format +#: kosmorro/__main__.py:285 msgid "" "The observer's position on Earth, in the \"{latitude},{longitude}\" " "format. Can also be set in the KOSMORRO_POSITION environment variable." msgstr "" -#: kosmorro/__main__.py:310 +#: kosmorro/__main__.py:295 msgid "" "The date for which the ephemerides must be calculated. Can be in the " "YYYY-MM-DD format or an interval in the \"[+-]YyMmDd\" format (with Y, M," " and D numbers). Defaults to current date." msgstr "" -#: kosmorro/__main__.py:321 +#: kosmorro/__main__.py:306 msgid "" -"The timezone to use to display the hours. It can be either a number (e.g." -" 1 for UTC+1) or a timezone name (e.g. Europe/Paris). See " -"https://en.wikipedia.org/wiki/List_of_tz_database_time_zones to find your" -" timezone. Can also be set in the TZ environment variable." +"The timezone to display the hours in (e.g. 2 for UTC+2 or -3 for UTC-3). " +"Can also be set in the KOSMORRO_TIMEZONE environment variable." msgstr "" -#: kosmorro/__main__.py:330 +#: kosmorro/__main__.py:315 msgid "Disable the colors in the console." msgstr "" -#: kosmorro/__main__.py:338 -msgid "A file to export the output to. If not given, the standard output is used." +#: kosmorro/__main__.py:322 +msgid "" +"A file to export the output to. If not given, the standard output is " +"used. This argument is needed for PDF format." msgstr "" -#: kosmorro/__main__.py:346 +#: kosmorro/__main__.py:331 msgid "" -"Do not generate a graph to represent the rise and set times in the LaTeX " -"file." +"Do not generate a graph to represent the rise and set times in the PDF " +"format." msgstr "" -#: kosmorro/__main__.py:353 +#: kosmorro/__main__.py:339 msgid "Show debugging messages" msgstr "" -#: kosmorro/__main__.py:361 -msgid "Print a script allowing completion for your shell" -msgstr "" - -#: kosmorro/__main__.py:371 -msgid "No completion script available for this shell." -msgstr "" - #: kosmorro/date.py:17 -#, python-brace-format msgid "The date {date} is not valid: {error}" msgstr "" -#: kosmorro/date.py:40 -#, python-brace-format +#: kosmorro/date.py:39 msgid "" "The date {date} does not match the required YYYY-MM-DD format or the " "offset format." @@ -144,24 +126,23 @@ msgstr "" msgid "Note: All the hours are given in UTC." msgstr "" -#: kosmorro/dumper.py:152 -#, python-brace-format +#: kosmorro/dumper.py:151 msgid "Note: All the hours are given in the UTC{offset} timezone." msgstr "" -#: kosmorro/dumper.py:205 kosmorro/dumper.py:333 +#: kosmorro/dumper.py:205 kosmorro/dumper.py:339 msgid "Object" msgstr "" -#: kosmorro/dumper.py:206 kosmorro/dumper.py:334 +#: kosmorro/dumper.py:206 kosmorro/dumper.py:340 msgid "Rise time" msgstr "" -#: kosmorro/dumper.py:207 kosmorro/dumper.py:336 +#: kosmorro/dumper.py:207 kosmorro/dumper.py:342 msgid "Culmination time" msgstr "" -#: kosmorro/dumper.py:208 kosmorro/dumper.py:338 +#: kosmorro/dumper.py:208 kosmorro/dumper.py:344 msgid "Set time" msgstr "" @@ -169,52 +150,64 @@ msgstr "" msgid "Moon phase is unavailable for this date." msgstr "" -#: kosmorro/dumper.py:240 -#, python-brace-format +#: kosmorro/dumper.py:238 kosmorro/dumper.py:348 +msgid "Moon phase:" +msgstr "" + +#: kosmorro/dumper.py:242 msgid "{next_moon_phase} on {next_moon_phase_date} at {next_moon_phase_time}" msgstr "" -#: kosmorro/dumper.py:306 +#: kosmorro/dumper.py:312 msgid "Overview of your sky" msgstr "" -#: kosmorro/dumper.py:315 -#, python-brace-format +#: kosmorro/dumper.py:320 msgid "" "This document summarizes the ephemerides and the events of {date}. It " "aims to help you to prepare your observation session. All the hours are " "given in {timezone}." msgstr "" -#: kosmorro/dumper.py:325 +#: kosmorro/dumper.py:330 msgid "" "Don't forget to check the weather forecast before you go out with your " "equipment." msgstr "" -#: kosmorro/dumper.py:331 +#: kosmorro/dumper.py:337 msgid "Ephemerides of the day" msgstr "" -#: kosmorro/dumper.py:340 +#: kosmorro/dumper.py:346 msgid "hours" msgstr "" -#: kosmorro/dumper.py:342 -msgid "Moon phase:" +#: kosmorro/dumper.py:353 +msgid "Expected events" msgstr "" -#: kosmorro/dumper.py:347 -msgid "Expected events" +#: kosmorro/dumper.py:493 +msgid "" +"Building PDF was not possible, because some dependencies are not " +"installed.\n" +"Please look at the documentation at https://kosmorro.space/cli/generate-" +"pdf/ for more information." msgstr "" -#: kosmorro/exceptions.py:36 -#, python-brace-format +#: kosmorro/dumper.py:542 +#, python-format +msgid "" +"An error occurred during the compilation of the PDF.\n" +"Please open an issue at https://github.com/Kosmorro/kosmorro/issues and " +"share the content of the log file at /tmp/kosmorro-%s.log" +msgstr "" + +#: kosmorro/exceptions.py:35 msgid "The date must be between {minimum_date} and {maximum_date}" msgstr "" -#: kosmorro/exceptions.py:49 -#, python-brace-format +#: kosmorro/exceptions.py:48 msgid "" "Invalid output format: {output_format}. Output file must end with: " "{accepted_extensions}" @@ -225,164 +218,129 @@ msgstr "" msgid "The given position (%s) is not valid." msgstr "" -#: kosmorro/geolocation.py:30 +#: kosmorro/geolocation.py:29 msgid "The given Plus Code seems to be a short code, please provide a full code." msgstr "" -#: kosmorro/i18n/strings.py:21 +#: kosmorro/i18n/strings.py:11 #, python-format msgid "%s is in opposition" msgstr "" -#: kosmorro/i18n/strings.py:26 +#: kosmorro/i18n/strings.py:12 #, python-format msgid "%s and %s are in conjunction" msgstr "" -#: kosmorro/i18n/strings.py:35 +#: kosmorro/i18n/strings.py:13 #, python-format msgid "%s occults %s" msgstr "" -#: kosmorro/i18n/strings.py:44 +#: kosmorro/i18n/strings.py:15 #, python-format msgid "Elongation of %s is maximal" msgstr "" -#: kosmorro/i18n/strings.py:50 +#: kosmorro/i18n/strings.py:18 #, python-format msgid "%s is at its periapsis" msgstr "" -#: kosmorro/i18n/strings.py:55 +#: kosmorro/i18n/strings.py:19 #, python-format msgid "%s is at its apoapsis" msgstr "" -#: kosmorro/i18n/strings.py:61 -msgid "March equinox" -msgstr "" - -#: kosmorro/i18n/strings.py:63 -msgid "June solstice" -msgstr "" - -#: kosmorro/i18n/strings.py:65 -msgid "September equinox" -msgstr "" - -#: kosmorro/i18n/strings.py:67 -msgid "December solstice" -msgstr "" - -#: kosmorro/i18n/strings.py:73 -#, python-format -msgid "Total lunar eclipse until %(hour)s" -msgstr "" - -#: kosmorro/i18n/strings.py:78 -#, python-format -msgid "Penumbral lunar eclipse until %(hour)s" -msgstr "" - -#: kosmorro/i18n/strings.py:83 -#, python-format -msgid "Partial lunar eclipse until %(hour)s" -msgstr "" - -#: kosmorro/i18n/strings.py:99 +#: kosmorro/i18n/strings.py:35 msgid "New Moon" msgstr "" -#: kosmorro/i18n/strings.py:100 +#: kosmorro/i18n/strings.py:36 msgid "Waxing Crescent" msgstr "" -#: kosmorro/i18n/strings.py:101 +#: kosmorro/i18n/strings.py:37 msgid "First Quarter" msgstr "" -#: kosmorro/i18n/strings.py:102 +#: kosmorro/i18n/strings.py:38 msgid "Waxing Gibbous" msgstr "" -#: kosmorro/i18n/strings.py:103 +#: kosmorro/i18n/strings.py:39 msgid "Full Moon" msgstr "" -#: kosmorro/i18n/strings.py:104 +#: kosmorro/i18n/strings.py:40 msgid "Waning Gibbous" msgstr "" -#: kosmorro/i18n/strings.py:105 +#: kosmorro/i18n/strings.py:41 msgid "Last Quarter" msgstr "" -#: kosmorro/i18n/strings.py:106 +#: kosmorro/i18n/strings.py:42 msgid "Waning Crescent" msgstr "" -#: kosmorro/i18n/strings.py:117 +#: kosmorro/i18n/strings.py:53 msgid "Sun" msgstr "" -#: kosmorro/i18n/strings.py:118 +#: kosmorro/i18n/strings.py:54 msgid "Moon" msgstr "" -#: kosmorro/i18n/strings.py:119 +#: kosmorro/i18n/strings.py:55 msgid "Mercury" msgstr "" -#: kosmorro/i18n/strings.py:120 +#: kosmorro/i18n/strings.py:56 msgid "Venus" msgstr "" -#: kosmorro/i18n/strings.py:121 +#: kosmorro/i18n/strings.py:57 msgid "Earth" msgstr "" -#: kosmorro/i18n/strings.py:122 +#: kosmorro/i18n/strings.py:58 msgid "Mars" msgstr "" -#: kosmorro/i18n/strings.py:123 +#: kosmorro/i18n/strings.py:59 msgid "Jupiter" msgstr "" -#: kosmorro/i18n/strings.py:124 +#: kosmorro/i18n/strings.py:60 msgid "Saturn" msgstr "" -#: kosmorro/i18n/strings.py:125 +#: kosmorro/i18n/strings.py:61 msgid "Uranus" msgstr "" -#: kosmorro/i18n/strings.py:126 +#: kosmorro/i18n/strings.py:62 msgid "Neptune" msgstr "" -#: kosmorro/i18n/strings.py:127 +#: kosmorro/i18n/strings.py:63 msgid "Pluto" msgstr "" #: kosmorro/i18n/utils.py:27 -#, python-brace-format msgid "{day_of_week} {month} {day_number}, {year}" msgstr "" #: kosmorro/i18n/utils.py:30 -#, python-brace-format msgid "{month} {day_number}, {hours}:{minutes}" msgstr "" #: kosmorro/i18n/utils.py:33 -#, python-brace-format msgid "{month} {day_number}, {year}" msgstr "" #: kosmorro/i18n/utils.py:36 -#, python-brace-format msgid "{hours}:{minutes}" msgstr "" diff --git a/manpage/kosmorro.1.md b/manpage/kosmorro.1.md index 3a5db54..fbf0ddd 100644 --- a/manpage/kosmorro.1.md +++ b/manpage/kosmorro.1.md @@ -33,11 +33,11 @@ `--format=`_FORMAT_, `-f` _FORMAT_ (optional) the format under which the information have to be output; one of the following: - text (plain text, like normal console output), json, tex (LaTeX). + text (plain text, like normal console output), json, tex (LaTeX), pdf. If no format is provided, the output format will be inferred from the extension of the output file `--no-graph` - present the ephemerides in a table instead of a graph; LaTeX output format only + present the ephemerides in a table instead of a graph; PDF output format only `--completion [SHELL]` generate completion scripts for the specified shell (bash, zsh, fish, powershell) @@ -73,6 +73,12 @@ Compute the ephemerides for Lille, France, on April 1st, 2022: kosmorro --latitude=50.5876 --longitude=3.0624 --date=2022-04-01 ``` +Compute the ephemerides for Lille, France, on April 1st, 2022, and export them in a PDF document: + +``` +kosmorro --latitude=50.5876 --longitude=3.0624 -date=2022-04-01 --output=file.pdf +``` + ## AUTHOR Written by Jérôme Deuchnord. diff --git a/tests/general.py b/tests/general.py index 6716f7b..10d270d 100644 --- a/tests/general.py +++ b/tests/general.py @@ -39,7 +39,7 @@ def test_help_message(): if python_version.major == 3 and python_version.minor < 13: assert ( result.stdout - == """usage: kosmorro [-h] [--version] [--format {txt,json,tex}] + == """usage: kosmorro [-h] [--version] [--format {txt,json,pdf,tex}] [--position POSITION] [--date DATE] [--timezone TIMEZONE] [--no-colors] [--output OUTPUT] [--no-graph] [--debug] [--completion COMPLETION] @@ -50,7 +50,7 @@ on Earth. options: -h, --help show this help message and exit --version, -v Show the program version - --format {txt,json,tex}, -f {txt,json,tex} + --format {txt,json,pdf,tex}, -f {txt,json,pdf,tex} The format to output the information to. If not provided, the output format will be inferred from the file extension of the output file. @@ -71,9 +71,10 @@ options: --no-colors Disable the colors in the console. --output OUTPUT, -o OUTPUT A file to export the output to. If not given, the - standard output is used. + standard output is used. This argument is needed for + PDF format. --no-graph Do not generate a graph to represent the rise and set - times in the LaTeX file. + times in the PDF format. --debug Show debugging messages --completion COMPLETION Print a script allowing completion for your shell diff --git a/tests/output.py b/tests/output.py index 30c80cb..b239920 100644 --- a/tests/output.py +++ b/tests/output.py @@ -5,8 +5,9 @@ from .utils import ( execute, KOSMORRO, ) - -from os import path +import tempfile +from os import path, environ +from sys import platform def test_json_output():