diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index f3e672e..c872f94 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -26,4 +26,17 @@ jobs: - name: Lint run: | pipenv run pylint kosmorro *.py kosmorrolib/*.py + - name: Check i18n + run: | + pipenv run python setup.py extract_messages --output-file=/tmp/kosmorro-messages.pot > /dev/null + n=$(diff -y --suppress-common-lines kosmorrolib/locales/messages.pot /tmp/kosmorro-messages.pot | grep -v -E '^"POT-Creation-Date: ' | wc -l) + + if [ "$n" -ne "0" ]; then + echo "❌ The messages file is not up-to-date!" + echo " Please run the following command to fix this:" + echo + echo " pipenv run python setup.py extract_messages --output-file=kosmorrolib/locales/messages.pot" + exit 1 + fi + echo "✔ Messages file up-to-date." diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5b636bb..994ac83 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,11 +16,14 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel twine skyfield numpy tabulate + pip install setuptools wheel twine skyfield numpy tabulate Babel requests - name: Build and publish env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + POEDITOR_API_ACCESS: ${{ secrets.POEDITOR_API_ACCESS }} + POEDITOR_PROJECT_ID: 306433 run: | + python .scripts/build/getlangs.py python setup.py sdist bdist_wheel twine upload dist/* diff --git a/.gitignore b/.gitignore index 2412569..6beac87 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ kosmorro.egg-info coverage.xml node_modules/ package-lock.json + +# Translation files are taken care on https://poeditor.com/join/project/GXuhLpdaoh +*.mo +*.po diff --git a/.scripts/build/getlangs.py b/.scripts/build/getlangs.py new file mode 100644 index 0000000..db6c11e --- /dev/null +++ b/.scripts/build/getlangs.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 + +# This script's purpose is to retrieve the translations from POEditor (https://poeditor.com). +# It is mainly used in the release process. +# (c) Jérôme Deuchnord - MIT License + +import os +import requests + +POEDITOR_URL = 'https://api.poeditor.com/v2' +API_TOKEN = os.environ['POEDITOR_API_ACCESS'] +PROJECT_ID = os.environ['POEDITOR_PROJECT_ID'] + +languages = requests.post('%s/languages/list' % POEDITOR_URL, + data={'api_token': API_TOKEN, + 'id': PROJECT_ID}) + +json = languages.json() + +if languages.status_code != 200: + raise AssertionError(json['response']['message']) + +for language in json['result']['languages']: + if language['percentage'] < 100: + # Ignore unfinished translations + continue + + print('Importing finished translation for %s... ' % language['name'], end='') + + translations = requests.post('%s/projects/export' % POEDITOR_URL, + data={'api_token': API_TOKEN, + 'id': PROJECT_ID, + 'language': language['code'], + 'type': 'po'}) + + if translations.status_code != 200: + print('Failed!') + raise AssertionError(translations.json()['response']['message']) + + translations = requests.get(translations.json()['result']['url']) + + if translations.status_code != 200: + print('Failed!') + raise AssertionError('URL given by the API returned a %d status code' % translations.status_code) + + os.makedirs('kosmorrolib/locales/%s/LC_MESSAGES' % language['code'], exist_ok=True) + + with open('kosmorrolib/locales/%s/LC_MESSAGES/messages.po' % language['code'], 'w') as file: + file.write(translations.text) + + print('OK') + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f73e598..c500c85 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,6 +15,10 @@ If it is not, [create a bug report](https://github.com/Deuchnord/kosmorro/issues Have an idea of feature you think would be nice on Kosmorro? Time to suggest it! First, please check someone didn't suggest your next revolution in the _Issues_ tab. If it's not, [create a feature request](https://github.com/Deuchnord/kosmorro/issues/new/choose) and fill in the templace that offers to you. +## Translating + +If you speak another language than English, another nice way to enhance Kosmorro is to translate its messages. The recommended way to begin translating Kosmorro is to [join the POEditor team](https://poeditor.com/join/project/GXuhLpdaoh). + ## Writing code 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**. @@ -22,6 +26,33 @@ First of all, if you are fixing an opened issue, check that nobody is already wo 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. +### 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. + +When you add a new string that will be displayed to the end user, please pass it to the `_()` function made available in the `kosmorrolib.i18n` package, for instance: + +```python +# Dont: +print('Note: All the hours are given in UTC.') + +# Do: +from kosmorrolib.i18n import _ +print(_('Note: All the hours are given in UTC.')) +``` + +This will allow Python's internationalization tool to translate the string in any available language. + +Once you have done your work, please remember to tell [Babel](http://babel.pocoo.org) to extract the new strings: + +```console +$ pipenv run python setup.py extract_messages --output-file=kosmorrolib/locales/messages.pot +``` + +> If the `setup.py` script tells you that the `extract_messages` command does not exist, then run `kosmorro sync` to ensure all the dev dependencies are installed and try again. + +Note that if you forget to update the messages file, the CI will fail. + ### Matching the coding standards Kosmorro's source code follows the major coding standards of Python (PEPs). Before marking your Pull Request as ready for review, don't forget to check that the code respects the coding standards with PyLint (it is run on the CI, but feel free to run it on your local machine too). Your PR must have a global note of 10/10 to be elligible to merge. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..9185a6c --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +recursive-include kosmorrolib/locales * diff --git a/Pipfile b/Pipfile index 17846f7..d6b8427 100644 --- a/Pipfile +++ b/Pipfile @@ -7,6 +7,7 @@ verify_ssl = true pylintfileheader = "*" pylint = "*" codecov = "*" +babel = "*" [packages] skyfield = ">=1.13.0,<2.0.0" diff --git a/Pipfile.lock b/Pipfile.lock index cf8e17d..162a440 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "127a13b5d3a3504bc6ccd84d14ed70b7d4561d1fc2a2c2dd5b2b55996a3a042d" + "sha256": "d5c2451a4f189a6d10a9205879f066fd9b595723f2e1077416aa3f48cdcbfb9f" }, "pipfile-spec": 6, "requires": { - "python_version": "3.8" + "python_version": "3" }, "sources": [ { @@ -78,6 +78,14 @@ ], "version": "==2.3.3" }, + "babel": { + "hashes": [ + "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38", + "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4" + ], + "index": "pypi", + "version": "==2.8.0" + }, "certifi": { "hashes": [ "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", @@ -199,6 +207,13 @@ "index": "pypi", "version": "==0.1.0" }, + "pytz": { + "hashes": [ + "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", + "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" + ], + "version": "==2019.3" + }, "requests": { "hashes": [ "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", diff --git a/kosmorro b/kosmorro index cd684f4..256126f 100644 --- a/kosmorro +++ b/kosmorro @@ -16,110 +16,11 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import argparse import sys -from datetime import date - -from kosmorrolib.version import VERSION -from kosmorrolib import dumper -from kosmorrolib import core -from kosmorrolib.ephemerides import EphemeridesComputer, Position -from kosmorrolib import events - - -def main(): - output_formats = get_dumpers() - args = get_args(list(output_formats.keys())) - - if args.special_action is not None: - return 0 if args.special_action() else 1 - - year = args.year - month = args.month - day = args.day - - compute_date = date(year, month, day) - - if day is not None and month is None: - month = date.today().month - - if args.latitude is None or args.longitude is None: - position = None - else: - position = Position(args.latitude, args.longitude) - - ephemeris = EphemeridesComputer(position) - ephemerides = ephemeris.compute_ephemerides(year, month, day) - - events_list = events.search_events(compute_date) - - dump = output_formats[args.format](ephemerides, events_list, compute_date) - print(dump.to_string()) - - return 0 - - -def get_dumpers() -> {str: dumper.Dumper}: - return { - 'text': dumper.TextDumper, - 'json': dumper.JsonDumper - } - - -def output_version() -> bool: - python_version = '%d.%d.%d' % (sys.version_info[0], sys.version_info[1], sys.version_info[2]) - print('Kosmorro %s' % VERSION) - print('Running on Python %s' % python_version) - - return True - - -def clear_cache() -> bool: - confirm = input("Do you really want to clear Kosmorro's cache? [yN] ").upper() - if confirm == 'Y': - try: - core.clear_cache() - except FileNotFoundError: - pass - elif confirm not in ('N', ''): - print('Answer did not match expected options, cache not cleared.') - return False - - return True - - -def get_args(output_formats: [str]): - today = date.today() - - parser = argparse.ArgumentParser(description='Compute the ephemerides and the events for a given date,' - ' at a given position on Earth.', - epilog='By default, only the events will be computed for today (%s).\n' - 'To compute also the ephemerides, latitude and longitude arguments' - ' are needed.' - % today.strftime('%a %b %d, %Y')) - - parser.add_argument('--version', '-v', dest='special_action', action='store_const', const=output_version, - default=None, help='Show the program version') - parser.add_argument('--clear-cache', dest='special_action', action='store_const', const=clear_cache, default=None, - help='Delete all the files Kosmorro stored in the cache.') - parser.add_argument('--format', '-f', type=str, default=output_formats[0], choices=output_formats, - help='The format under which the information have to be output') - parser.add_argument('--latitude', '-lat', type=float, default=None, - help="The observer's latitude on Earth") - parser.add_argument('--longitude', '-lon', type=float, default=None, - help="The observer's longitude on Earth") - parser.add_argument('--day', '-d', type=int, default=today.day, - help='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 %d (the current day).' % today.day) - parser.add_argument('--month', '-m', type=int, default=today.month, - help='A number between 1 and 12. The month you want to compute the ephemerides for. Defaults to' - ' %d (the current month).' % today.month) - parser.add_argument('--year', '-y', type=int, default=today.year, - help='The year you want to compute the ephemerides for.' - ' Defaults to %d (the current year).' % today.year) - - return parser.parse_args() +import locale +from kosmorrolib.main import main +locale.setlocale(locale.LC_ALL, '') if __name__ == '__main__': sys.exit(main()) diff --git a/kosmorrolib/core.py b/kosmorrolib/core.py index d98732f..4e52c58 100644 --- a/kosmorrolib/core.py +++ b/kosmorrolib/core.py @@ -25,21 +25,22 @@ from skyfield.timelib import Time from skyfield.nutationlib import iau2000b from .data import Star, Planet, Satellite, MOON_PHASES, MoonPhase +from .i18n import _ CACHE_FOLDER = str(Path.home()) + '/.kosmorro-cache' MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'] -ASTERS = [Star('Sun', 'SUN'), - Satellite('Moon', 'MOON'), - Planet('Mercury', 'MERCURY'), - Planet('Venus', 'VENUS'), - Planet('Mars', 'MARS'), - Planet('Jupiter', 'JUPITER BARYCENTER'), - Planet('Saturn', 'SATURN BARYCENTER'), - Planet('Uranus', 'URANUS BARYCENTER'), - Planet('Neptune', 'NEPTUNE BARYCENTER'), - Planet('Pluto', 'PLUTO BARYCENTER')] +ASTERS = [Star(_('Sun'), 'SUN'), + Satellite(_('Moon'), 'MOON'), + Planet(_('Mercury'), 'MERCURY'), + Planet(_('Venus'), 'VENUS'), + Planet(_('Mars'), 'MARS'), + Planet(_('Jupiter'), 'JUPITER BARYCENTER'), + Planet(_('Saturn'), 'SATURN BARYCENTER'), + Planet(_('Uranus'), 'URANUS BARYCENTER'), + Planet(_('Neptune'), 'NEPTUNE BARYCENTER'), + Planet(_('Pluto'), 'PLUTO BARYCENTER')] def get_loader(): diff --git a/kosmorrolib/data.py b/kosmorrolib/data.py index a62c74a..07b3a3f 100644 --- a/kosmorrolib/data.py +++ b/kosmorrolib/data.py @@ -22,20 +22,22 @@ from typing import Union from skyfield.api import Topos from skyfield.timelib import Time +from .i18n import _ + MOON_PHASES = { - 'NEW_MOON': 'New Moon', - 'WAXING_CRESCENT': 'Waxing crescent', - 'FIRST_QUARTER': 'First Quarter', - 'WAXING_GIBBOUS': 'Waxing gibbous', - 'FULL_MOON': 'Full Moon', - 'WANING_GIBBOUS': 'Waning gibbous', - 'LAST_QUARTER': 'Last Quarter', - 'WANING_CRESCENT': 'Waning crescent' + 'NEW_MOON': _('New Moon'), + 'WAXING_CRESCENT': _('Waxing crescent'), + 'FIRST_QUARTER': _('First Quarter'), + 'WAXING_GIBBOUS': _('Waxing gibbous'), + 'FULL_MOON': _('Full Moon'), + 'WANING_GIBBOUS': _('Waning gibbous'), + 'LAST_QUARTER': _('Last Quarter'), + 'WANING_CRESCENT': _('Waning crescent') } EVENTS = { - 'OPPOSITION': {'message': '%s is in opposition'}, - 'CONJUNCTION': {'message': '%s and %s are in conjunction'} + 'OPPOSITION': {'message': _('%s is in opposition')}, + 'CONJUNCTION': {'message': _('%s and %s are in conjunction')} } diff --git a/kosmorrolib/dumper.py b/kosmorrolib/dumper.py index db4622c..c631408 100644 --- a/kosmorrolib/dumper.py +++ b/kosmorrolib/dumper.py @@ -23,6 +23,11 @@ from tabulate import tabulate from skyfield.timelib import Time from numpy import int64 from .data import Object, AsterEphemerides, MoonPhase, Event +from .i18n import _ + +FULL_DATE_FORMAT = _('{day_of_week} {month} {day_number}, {year}').format(day_of_week='%A', month='%B', + day_number='%d', year='%Y') +TIME_FORMAT = _('{hours}:{minutes}').format(hours='%H', minutes='%M') class Dumper(ABC): @@ -75,7 +80,9 @@ class JsonDumper(Dumper): class TextDumper(Dumper): def to_string(self): - text = self.date.strftime('%A %B %d, %Y') + text = self.date.strftime(FULL_DATE_FORMAT) + # Always capitalize the first character + text = ''.join([text[0].upper(), text[1:]]) if len(self.ephemeris['details']) > 0: text = '\n\n'.join([text, @@ -88,11 +95,11 @@ class TextDumper(Dumper): if len(self.events) > 0: text = '\n\n'.join([text, - 'Expected events:', + _('Expected events:'), self.get_events(self.events) ]) - text = '\n\n'.join([text, 'Note: All the hours are given in UTC.']) + text = '\n\n'.join([text, _('Note: All the hours are given in UTC.')]) return text @@ -104,37 +111,43 @@ class TextDumper(Dumper): name = aster.name if aster.ephemerides.rise_time is not None: - planet_rise = aster.ephemerides.rise_time.utc_strftime('%H:%M') + planet_rise = aster.ephemerides.rise_time.utc_strftime(TIME_FORMAT) else: planet_rise = '-' if aster.ephemerides.culmination_time is not None: - planet_culmination = aster.ephemerides.culmination_time.utc_strftime('%H:%M') + planet_culmination = aster.ephemerides.culmination_time.utc_strftime(TIME_FORMAT) else: planet_culmination = '-' if aster.ephemerides.set_time is not None: - planet_set = aster.ephemerides.set_time.utc_strftime('%H:%M') + planet_set = aster.ephemerides.set_time.utc_strftime(TIME_FORMAT) else: planet_set = '-' data.append([name, planet_rise, planet_culmination, planet_set]) - return tabulate(data, headers=['Object', 'Rise time', 'Culmination time', 'Set time'], tablefmt='simple', - stralign='center', colalign=('left',)) + return tabulate(data, headers=[_('Object'), _('Rise time'), _('Culmination time'), _('Set time')], + tablefmt='simple', stralign='center', colalign=('left',)) @staticmethod def get_events(events: [Event]) -> str: data = [] for event in events: - data.append([event.start_time.utc_strftime('%H:%M'), event.get_description()]) + data.append([event.start_time.utc_strftime(TIME_FORMAT), event.get_description()]) return tabulate(data, tablefmt='plain', stralign='left') @staticmethod def get_moon(moon_phase: MoonPhase) -> str: - return 'Moon phase: %s\n' \ - '%s on %s' % (moon_phase.get_phase(), - moon_phase.get_next_phase(), - moon_phase.next_phase_date.utc_strftime('%a %b %-d, %Y %H:%M')) + current_moon_phase = _('Moon phase: {current_moon_phase}').format( + current_moon_phase=moon_phase.get_phase() + ) + new_moon_phase = _('{next_moon_phase} on {next_moon_phase_date} at {next_moon_phase_time}').format( + next_moon_phase=moon_phase.get_next_phase(), + next_moon_phase_date=moon_phase.next_phase_date.utc_strftime(FULL_DATE_FORMAT), + next_moon_phase_time=moon_phase.next_phase_date.utc_strftime(TIME_FORMAT) + ) + + return '\n'.join([current_moon_phase, new_moon_phase]) diff --git a/kosmorrolib/i18n.py b/kosmorrolib/i18n.py new file mode 100644 index 0000000..b5d09b4 --- /dev/null +++ b/kosmorrolib/i18n.py @@ -0,0 +1,30 @@ +#!/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 . + +import gettext +import os + +_LOCALE_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'locales') +_TRANSLATION = gettext.translation('messages', localedir=_LOCALE_DIR, fallback=True) + +_ = _TRANSLATION.gettext + + +def ngettext(msgid1, msgid2, number): + # Not using ngettext = _TRANSLATION.ngettext because the linter will give an invalid-name error otherwise + return _TRANSLATION.ngettext(msgid1, msgid2, number) diff --git a/kosmorrolib/locales/messages.pot b/kosmorrolib/locales/messages.pot new file mode 100644 index 0000000..ceb68b1 --- /dev/null +++ b/kosmorrolib/locales/messages.pot @@ -0,0 +1,205 @@ +# Translations template for kosmorro. +# Copyright (C) 2020 ORGANIZATION +# This file is distributed under the same license as the kosmorro project. +# FIRST AUTHOR , 2020. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: kosmorro 0.4.0\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2020-01-02 14:05+0100\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.8.0\n" + +#: kosmorrolib/core.py:34 +msgid "Sun" +msgstr "" + +#: kosmorrolib/core.py:35 +msgid "Moon" +msgstr "" + +#: kosmorrolib/core.py:36 +msgid "Mercury" +msgstr "" + +#: kosmorrolib/core.py:37 +msgid "Venus" +msgstr "" + +#: kosmorrolib/core.py:38 +msgid "Mars" +msgstr "" + +#: kosmorrolib/core.py:39 +msgid "Jupiter" +msgstr "" + +#: kosmorrolib/core.py:40 +msgid "Saturn" +msgstr "" + +#: kosmorrolib/core.py:41 +msgid "Uranus" +msgstr "" + +#: kosmorrolib/core.py:42 +msgid "Neptune" +msgstr "" + +#: kosmorrolib/core.py:43 +msgid "Pluto" +msgstr "" + +#: kosmorrolib/data.py:28 +msgid "New Moon" +msgstr "" + +#: kosmorrolib/data.py:29 +msgid "Waxing crescent" +msgstr "" + +#: kosmorrolib/data.py:30 +msgid "First Quarter" +msgstr "" + +#: kosmorrolib/data.py:31 +msgid "Waxing gibbous" +msgstr "" + +#: kosmorrolib/data.py:32 +msgid "Full Moon" +msgstr "" + +#: kosmorrolib/data.py:33 +msgid "Waning gibbous" +msgstr "" + +#: kosmorrolib/data.py:34 +msgid "Last Quarter" +msgstr "" + +#: kosmorrolib/data.py:35 +msgid "Waning crescent" +msgstr "" + +#: kosmorrolib/data.py:39 +#, python-format +msgid "%s is in opposition" +msgstr "" + +#: kosmorrolib/data.py:40 +#, python-format +msgid "%s and %s are in conjunction" +msgstr "" + +#: kosmorrolib/dumper.py:28 +msgid "{day_of_week} {month} {day_number}, {year}" +msgstr "" + +#: kosmorrolib/dumper.py:30 +msgid "{hours}:{minutes}" +msgstr "" + +#: kosmorrolib/dumper.py:98 +msgid "Expected events:" +msgstr "" + +#: kosmorrolib/dumper.py:102 +msgid "Note: All the hours are given in UTC." +msgstr "" + +#: kosmorrolib/dumper.py:130 +msgid "Object" +msgstr "" + +#: kosmorrolib/dumper.py:130 +msgid "Rise time" +msgstr "" + +#: kosmorrolib/dumper.py:130 +msgid "Culmination time" +msgstr "" + +#: kosmorrolib/dumper.py:130 +msgid "Set time" +msgstr "" + +#: kosmorrolib/dumper.py:144 +msgid "Moon phase: {current_moon_phase}" +msgstr "" + +#: kosmorrolib/dumper.py:147 +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}" +msgstr "" + +#: kosmorrolib/main.py:82 +msgid "Do you really want to clear Kosmorro's cache? [yN] " +msgstr "" + +#: kosmorrolib/main.py:89 +msgid "Answer did not match expected options, cache not cleared." +msgstr "" + +#: kosmorrolib/main.py:98 +msgid "" +"Compute the ephemerides and the events for a given date, at a given " +"position on Earth." +msgstr "" + +#: kosmorrolib/main.py:100 +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 +msgid "Show the program version" +msgstr "" + +#: kosmorrolib/main.py:107 +msgid "Delete all the files Kosmorro stored in the cache." +msgstr "" + +#: kosmorrolib/main.py:109 +msgid "The format under which the information have to be output" +msgstr "" + +#: kosmorrolib/main.py:111 +msgid "The observer's latitude on Earth" +msgstr "" + +#: kosmorrolib/main.py:113 +msgid "The observer's longitude on Earth" +msgstr "" + +#: kosmorrolib/main.py:115 +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 +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 +msgid "" +"The year you want to compute the ephemerides for. Defaults to " +"{default_year} (the current year)." +msgstr "" + diff --git a/kosmorrolib/main.py b/kosmorrolib/main.py new file mode 100644 index 0000000..2cd1d71 --- /dev/null +++ b/kosmorrolib/main.py @@ -0,0 +1,125 @@ +#!/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 . + +import argparse +import locale +import re +import sys + +from datetime import date + +from kosmorrolib.version import VERSION +from kosmorrolib import dumper +from kosmorrolib import core +from kosmorrolib import events +from kosmorrolib.i18n import _ +from .ephemerides import EphemeridesComputer, Position + + +def main(): + output_formats = get_dumpers() + args = get_args(list(output_formats.keys())) + + if args.special_action is not None: + return 0 if args.special_action() else 1 + + year = args.year + month = args.month + day = args.day + + compute_date = date(year, month, day) + + if day is not None and month is None: + month = date.today().month + + if args.latitude is None or args.longitude is None: + position = None + else: + position = Position(args.latitude, args.longitude) + + ephemeris = EphemeridesComputer(position) + ephemerides = ephemeris.compute_ephemerides(year, month, day) + + events_list = events.search_events(compute_date) + + dump = output_formats[args.format](ephemerides, events_list, compute_date) + print(dump.to_string()) + + return 0 + + +def get_dumpers() -> {str: dumper.Dumper}: + return { + 'text': dumper.TextDumper, + 'json': dumper.JsonDumper + } + + +def output_version() -> bool: + python_version = '%d.%d.%d' % (sys.version_info[0], sys.version_info[1], sys.version_info[2]) + print('Kosmorro %s' % VERSION) + print(_('Running on Python {python_version}').format(python_version=python_version)) + + return True + + +def clear_cache() -> bool: + confirm = input(_("Do you really want to clear Kosmorro's cache? [yN] ")).upper() + if re.match(locale.nl_langinfo(locale.YESEXPR), confirm) is not None: + try: + core.clear_cache() + except FileNotFoundError: + pass + elif confirm != '' and re.match(locale.nl_langinfo(locale.NOEXPR), confirm) is None: + print(_('Answer did not match expected options, cache not cleared.')) + return False + + return True + + +def get_args(output_formats: [str]): + today = date.today() + + parser = argparse.ArgumentParser(description=_('Compute the ephemerides and the events for a given date,' + ' at a given position on Earth.'), + epilog=_('By default, only the events will be computed for today ({date}).\n' + 'To compute also the ephemerides, latitude and longitude arguments' + ' are needed.').format(date=today.strftime(dumper.FULL_DATE_FORMAT))) + + parser.add_argument('--version', '-v', dest='special_action', action='store_const', const=output_version, + default=None, help=_('Show the program version')) + parser.add_argument('--clear-cache', dest='special_action', action='store_const', const=clear_cache, default=None, + help=_('Delete all the files Kosmorro stored in the cache.')) + parser.add_argument('--format', '-f', type=str, default=output_formats[0], choices=output_formats, + help=_('The format under which the information have to be output')) + parser.add_argument('--latitude', '-lat', type=float, default=None, + help=_("The observer's latitude on Earth")) + parser.add_argument('--longitude', '-lon', type=float, default=None, + help=_("The observer's longitude on Earth")) + parser.add_argument('--day', '-d', type=int, default=today.day, + help=_('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).').format( + default_day=today.day)) + parser.add_argument('--month', '-m', type=int, default=today.month, + help=_('A number between 1 and 12. The month you want to compute the ephemerides for.' + ' Defaults to {default_month} (the current month).').format(default_month=today.month)) + parser.add_argument('--year', '-y', type=int, default=today.year, + help=_('The year you want to compute the ephemerides for.' + ' Defaults to {default_year} (the current year).').format(default_year=today.year)) + + return parser.parse_args() diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..6c447bf --- /dev/null +++ b/setup.cfg @@ -0,0 +1,12 @@ +[extract_message] +charset = utf-8 +keywords = _ ngettext +width = 120 +output_file = kosmorrolib/locales/messages.pot +omit_header = true +copyright_holder = Jérôme Deuchnord +input_paths=kosmorro,kosmorrolib + +[compile_catalog] +domain = messages +directory = kosmorrolib/locales diff --git a/setup.py b/setup.py index 67ac66a..ffec58a 100644 --- a/setup.py +++ b/setup.py @@ -37,6 +37,7 @@ setup( keywords='kosmorro astronomy ephemerides ephemeris', packages=find_packages(), scripts=['kosmorro'], + include_package_data=True, install_requires=['skyfield>=1.13.0,<2.0.0', 'tabulate', 'numpy>=1.17.0,<2.0.0'], classifiers=[ 'Development Status :: 3 - Alpha', diff --git a/test/dumper.py b/test/dumper.py index 3cc10c7..5e5cfc4 100644 --- a/test/dumper.py +++ b/test/dumper.py @@ -50,7 +50,7 @@ class DumperTestCase(unittest.TestCase): '-------- ----------- ------------------ ----------\n' 'Mars - - -\n\n' 'Moon phase: Full Moon\n' - 'Last Quarter on Mon Oct 21, 2019 00:00\n\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)).to_string()) @@ -61,7 +61,7 @@ class DumperTestCase(unittest.TestCase): '-------- ----------- ------------------ ----------\n' 'Mars - - -\n\n' 'Moon phase: Full Moon\n' - 'Last Quarter on Mon Oct 21, 2019 00:00\n\n' + 'Last Quarter on Monday October 21, 2019 at 00:00\n\n' 'Expected events:\n\n' '05:12 Mars is in opposition\n\n' 'Note: All the hours are given in UTC.', @@ -74,7 +74,7 @@ class DumperTestCase(unittest.TestCase): ephemerides = self._get_data(False) self.assertEqual('Monday October 14, 2019\n\n' 'Moon phase: Full Moon\n' - 'Last Quarter on Mon Oct 21, 2019 00:00\n\n' + 'Last Quarter on Monday October 21, 2019 at 00:00\n\n' 'Expected events:\n\n' '05:12 Mars is in opposition\n\n' 'Note: All the hours are given in UTC.',