From 4b1f06e221dba2efcfca68119df25eca28bc89d7 Mon Sep 17 00:00:00 2001 From: Nic Date: Wed, 25 Aug 2021 08:52:10 -0500 Subject: [PATCH] feat: guess output format from output file extension (#197) guess output format based on output file extension check for empty output file default to .txt if no file extension provided change default output format back to txt add tests update man page fix default output format directly return output dumper use keys from dumpers in key error message fix man pages prevent output format from being overridden use first dumper key as default format update messages and formatting update help message for format argument specify list type in InvalidOutputFormat exception make default output format more apparent update messages --- .scripts/tests-e2e.sh | 13 +++++++ _kosmorro/exceptions.py | 13 +++++++ _kosmorro/locales/messages.pot | 52 +++++++++++++++------------ _kosmorro/main.py | 65 +++++++++++++++++++++++++--------- manpage/kosmorro.1.md | 6 ++-- 5 files changed, 108 insertions(+), 41 deletions(-) diff --git a/.scripts/tests-e2e.sh b/.scripts/tests-e2e.sh index 725812e..4ed0bbe 100755 --- a/.scripts/tests-e2e.sh +++ b/.scripts/tests-e2e.sh @@ -128,6 +128,19 @@ assertSuccess "ls $HOME/kosmorro/export/document.pdf" assertSuccess "$KOSMORRO_COMMAND --position=\"50.5876;3.0624\" -d 2020-01-27 --format=pdf -o $HOME/kosmorro/export/document-no-graph.pdf --no-graph" assertSuccess "ls $HOME/kosmorro/export/document-no-graph.pdf" +# If format argument is given, use it even if it conflicts with file extension +assertSuccess "$KOSMORRO_COMMAND --position=\"50.5876;3.0624\" -d 2020-01-27 --format=json -o $HOME/kosmorro/export/txt-document.txt" +assertSuccess "ls $HOME/kosmorro/export/txt-document.txt" + +assertSuccess "$KOSMORRO_COMMAND --position=\"50.5876;3.0624\" -d 2020-01-27 -o $HOME/kosmorro/export/txt-document.txt" +assertSuccess "ls $HOME/kosmorro/export/txt-document.txt" + +assertSuccess "$KOSMORRO_COMMAND --position=\"50.5876;3.0624\" -d 2020-01-27 -o $HOME/kosmorro/export/json-document.json" +assertSuccess "ls $HOME/kosmorro/export/json-document.json" + +assertSuccess "$KOSMORRO_COMMAND --position=\"50.5876;3.0624\" -d 2020-01-27 -o $HOME/kosmorro/export/pdf-document.pdf" +assertSuccess "ls $HOME/kosmorro/export/pdf-document.pdf" + # man page assertSuccess "man --pager=cat kosmorro" diff --git a/_kosmorro/exceptions.py b/_kosmorro/exceptions.py index 88ae9b8..4c36c3e 100644 --- a/_kosmorro/exceptions.py +++ b/_kosmorro/exceptions.py @@ -39,6 +39,19 @@ class OutOfRangeDateError(RuntimeError): ) +class InvalidOutputFormatError(RuntimeError): + def __init__(self, output_format: str, accepted_extensions: [str]): + super().__init__() + self.output_format = output_format + self.accepted_extensions = accepted_extensions + self.msg = _( + "Invalid output format: {output_format}. Output file must end with: {accepted_extensions}" + ).format( + output_format=output_format, + accepted_extensions=", ".join(accepted_extensions), + ) + + class CompileError(RuntimeError): def __init__(self, msg): super().__init__() diff --git a/_kosmorro/locales/messages.pot b/_kosmorro/locales/messages.pot index 51395ea..2938de2 100644 --- a/_kosmorro/locales/messages.pot +++ b/_kosmorro/locales/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: kosmorro 0.10.1\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2021-08-10 20:23+0200\n" +"POT-Creation-Date: 2021-08-24 17:06-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -116,6 +116,12 @@ msgstr "" msgid "The date must be between {minimum_date} and {maximum_date}" msgstr "" +#: _kosmorro/exceptions.py:47 +msgid "" +"Invalid output format: {output_format}. Output file must end with: " +"{accepted_extensions}" +msgstr "" + #: _kosmorro/geolocation.py:14 #, python-format msgid "The given position (%s) is not valid." @@ -125,104 +131,106 @@ msgstr "" msgid "The given Plus Code seems to be a short code, please provide a full code." msgstr "" -#: _kosmorro/main.py:58 +#: _kosmorro/main.py:73 msgid "" "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:67 +#: _kosmorro/main.py:82 msgid "" "PDF output will not contain the ephemerides, because you didn't provide " "the observation coordinates." msgstr "" -#: _kosmorro/main.py:112 +#: _kosmorro/main.py:132 msgid "The file could not be saved in \"{path}\": {error}" msgstr "" -#: _kosmorro/main.py:126 +#: _kosmorro/main.py:146 msgid "Please provide a file path to export in this format (--output)." msgstr "" -#: _kosmorro/main.py:159 +#: _kosmorro/main.py:179 msgid "Moon phase can only be displayed between {min_date} and {max_date}" msgstr "" -#: _kosmorro/main.py:198 +#: _kosmorro/main.py:228 msgid "Running on Python {python_version} with Kosmorrolib v{kosmorrolib_version}" msgstr "" -#: _kosmorro/main.py:208 +#: _kosmorro/main.py:238 msgid "Do you really want to clear Kosmorro's cache? [yN] " msgstr "" -#: _kosmorro/main.py:216 +#: _kosmorro/main.py:246 msgid "Incorrect answer, cache not cleared." msgstr "" -#: _kosmorro/main.py:226 +#: _kosmorro/main.py:256 msgid "" "Compute the ephemerides and the events for a given date and a given " "position on Earth." msgstr "" -#: _kosmorro/main.py:229 +#: _kosmorro/main.py:259 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 "" -#: _kosmorro/main.py:243 +#: _kosmorro/main.py:273 msgid "Show the program version" msgstr "" -#: _kosmorro/main.py:251 +#: _kosmorro/main.py:281 msgid "Delete all the files from Kosmorro's cache." msgstr "" -#: _kosmorro/main.py:259 -msgid "The format to output the information to" +#: _kosmorro/main.py:289 +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:266 +#: _kosmorro/main.py:299 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:276 +#: _kosmorro/main.py:309 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 today ({default_date})." msgstr "" -#: _kosmorro/main.py:287 +#: _kosmorro/main.py:320 msgid "" "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:296 +#: _kosmorro/main.py:329 msgid "Disable the colors in the console." msgstr "" -#: _kosmorro/main.py:303 +#: _kosmorro/main.py:336 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:312 +#: _kosmorro/main.py:345 msgid "" "Do not generate a graph to represent the rise and set times in the PDF " "format." msgstr "" -#: _kosmorro/main.py:320 +#: _kosmorro/main.py:353 msgid "Show debugging messages" msgstr "" diff --git a/_kosmorro/main.py b/_kosmorro/main.py index 56c5c52..1716e2f 100644 --- a/_kosmorro/main.py +++ b/_kosmorro/main.py @@ -20,6 +20,7 @@ import argparse import locale import re import sys +import os.path from kosmorrolib import Position, get_ephemerides, get_events, get_moon_phase from kosmorrolib.__version__ import __version__ as kosmorrolib_version @@ -31,7 +32,11 @@ from . import dumper, environment, debug from .date import parse_date from .geolocation import get_position from .__version__ import __version__ as kosmorro_version -from .exceptions import UnavailableFeatureError, OutOfRangeDateError as DateRangeError +from .exceptions import ( + InvalidOutputFormatError, + UnavailableFeatureError, + OutOfRangeDateError as DateRangeError, +) from _kosmorro.i18n.utils import _, SHORT_DATE_FORMAT @@ -53,6 +58,16 @@ def main(): position = get_position(args.position) if args.position not in [None, ""] else None + # if output format is not specified, try to use output file extension as output format + if args.output is not None and output_format is None: + file_extension = os.path.splitext(args.output)[-1][1:].lower() + if file_extension: + output_format = file_extension + + # default to .txt if output format was not given and output file did not have file extension + if output_format is None: + output_format = "txt" + if output_format == "pdf": print( _( @@ -88,6 +103,10 @@ def main(): args.colors, args.show_graph, ) + except InvalidOutputFormatError as error: + print(colored(error.msg, "red")) + debug.debug_print(error) + return 3 except UnavailableFeatureError as error: print(colored(error.msg, "red")) debug.debug_print(error) @@ -99,9 +118,10 @@ def main(): if args.output is not None: try: - pdf_content = output.to_string() - with open(args.output, "wb") as output_file: - output_file.write(pdf_content) + file_content = output.to_string() + opening_mode = get_opening_mode(output_format) + with open(args.output, opening_mode) as output_file: + output_file.write(file_content) except UnavailableFeatureError as error: print(colored(error.msg, "red")) debug.debug_print(error) @@ -168,25 +188,35 @@ def get_information( events_list = get_events(compute_date, timezone) - return get_dumpers()[output_format]( - ephemerides=eph, - moon_phase=moon_phase, - events=events_list, - date=compute_date, - timezone=timezone, - with_colors=colors, - show_graph=show_graph, - ) + try: + return get_dumpers()[output_format]( + ephemerides=eph, + moon_phase=moon_phase, + events=events_list, + date=compute_date, + timezone=timezone, + with_colors=colors, + show_graph=show_graph, + ) + except KeyError as error: + raise InvalidOutputFormatError(output_format, list(get_dumpers().keys())) def get_dumpers() -> {str: dumper.Dumper}: return { - "text": dumper.TextDumper, + "txt": dumper.TextDumper, "json": dumper.JsonDumper, "pdf": dumper.PdfDumper, } +def get_opening_mode(format: str) -> str: + if format == "pdf": + return "wb" + + return "w" + + def output_version() -> bool: python_version = "%d.%d.%d" % ( sys.version_info[0], @@ -254,9 +284,12 @@ def get_args(output_formats: [str]): "--format", "-f", type=str, - default=output_formats[0], + default=None, choices=output_formats, - help=_("The format to output the information to"), + help=_( + "The format to output the information to. If not provided, the output format " + "will be inferred from the file extension of the output file." + ), ) parser.add_argument( "--position", diff --git a/manpage/kosmorro.1.md b/manpage/kosmorro.1.md index c27738f..445fca2 100644 --- a/manpage/kosmorro.1.md +++ b/manpage/kosmorro.1.md @@ -31,8 +31,8 @@ `--output=`_OUTPUT_, `-o` _OUTPUT_ a file to export the output to; if not given, the standard output is used -`--format=`_FORMAT_, `-f` _FORMAT_ - the format under which the information have to be output; one of the following: text, json, pdf +`--format=`_FORMAT_, `-f` _FORMAT_ (optional) + the format under which the information have to be output; one of the following: text, json, 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; PDF output format only @@ -68,7 +68,7 @@ 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 --format=pdf --output=file.pdf +kosmorro --latitude=50.5876 --longitude=3.0624 -date=2022-04-01 --output=file.pdf ``` ## AUTHOR