Skip to content

Commit

Permalink
refactor: use enum instead of dicts for the events and moon phase (
Browse files Browse the repository at this point in the history
…#129)

BREAKING CHANGE: some methodes in Event and MoonPhase have been dropped
in favor of `enum.Enum`'s `name` and `value` properties.
  • Loading branch information
Deuchnord authored and Jérôme Deuchnord committed Jan 22, 2021
1 parent 331ab99 commit 8b723bf
Show file tree
Hide file tree
Showing 11 changed files with 282 additions and 267 deletions.
17 changes: 15 additions & 2 deletions .github/workflows/i18n.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,26 @@ jobs:
- 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)
diff=$(diff kosmorrolib/locales/messages.pot /tmp/kosmorro-messages.pot | grep '^>')
n=$(echo "$diff" | grep -v '> "POT-Creation-Date: ' | wc -l)
if [ "$(echo "$diff" | grep -E '^"Generated-By: Babel' | wc -l)" -eq "1" ]; then
echo "❌ You dependencies may be out of date!"
echo " Please run the following command to fix this:"
echo
echo " pipenv sync --dev"
echo
echo " Then update the messages file:"
echo
echo " make messages"
exit 2
fi
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"
echo " make messages"
exit 1
fi
Expand Down
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ test:
unset KOSMORRO_TIMEZONE; \
LANG=C pipenv run python3 -m coverage run -m unittest test

build: i18n
build: i18n manpages
python3 setup.py sdist bdist_wheel

i18n:
messages:
pipenv run python setup.py extract_messages --output-file=kosmorrolib/locales/messages.pot

manpages:
ronn --roff manpage/kosmorro.1.md
ronn --roff manpage/kosmorro.7.md

i18n:
if [ "$$POEDITOR_API_ACCESS" != "" ]; then \
python3 .scripts/build/getlangs.py; \
python3 setup.py compile_catalog; \
Expand Down
111 changes: 17 additions & 94 deletions kosmorrolib/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,30 +25,10 @@
from skyfield.api import Topos, Time
from skyfield.vectorlib import VectorSum as SkfPlanet

from .core import get_skf_objects, get_timescale
from .core import get_skf_objects
from .enum import MoonPhaseType, EventType
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'),
'UNKNOWN': _('Unavailable')
}

EVENTS = {
'OPPOSITION': {'message': _('%s is in opposition')},
'CONJUNCTION': {'message': _('%s and %s are in conjunction')},
'OCCULTATION': {'message': _('%s occults %s')},
'MAXIMAL_ELONGATION': {'message': _("%s's largest elongation")},
'MOON_PERIGEE': {'message': _("%s is at its perigee")},
'MOON_APOGEE': {'message': _("%s is at its apogee")},
}


class Serializable(ABC):
@abstractmethod
Expand All @@ -57,40 +37,27 @@ def serialize(self) -> dict:


class MoonPhase(Serializable):
def __init__(self, identifier: str, time: datetime = None, next_phase_date: datetime = None):
if identifier not in MOON_PHASES.keys():
raise ValueError('identifier parameter must be one of %s (got %s)' % (', '.join(MOON_PHASES.keys()),
identifier))

self.identifier = identifier
def __init__(self, phase_type: MoonPhaseType, time: datetime = None, next_phase_date: datetime = None):
self.phase_type = phase_type
self.time = time
self.next_phase_date = next_phase_date

def get_phase(self):
return MOON_PHASES[self.identifier]

def get_next_phase_name(self):
next_identifier = self.get_next_phase()

return MOON_PHASES[next_identifier]

def get_next_phase(self):
if self.identifier == 'NEW_MOON' or self.identifier == 'WAXING_CRESCENT':
next_identifier = 'FIRST_QUARTER'
elif self.identifier == 'FIRST_QUARTER' or self.identifier == 'WAXING_GIBBOUS':
next_identifier = 'FULL_MOON'
elif self.identifier == 'FULL_MOON' or self.identifier == 'WANING_GIBBOUS':
next_identifier = 'LAST_QUARTER'
else:
next_identifier = 'NEW_MOON'
return next_identifier
if self.phase_type in [MoonPhaseType.NEW_MOON, MoonPhaseType.WAXING_CRESCENT]:
return MoonPhaseType.FIRST_QUARTER
if self.phase_type in [MoonPhaseType.FIRST_QUARTER, MoonPhaseType.WAXING_GIBBOUS]:
return MoonPhaseType.FULL_MOON
if self.phase_type in [MoonPhaseType.FULL_MOON, MoonPhaseType.WANING_GIBBOUS]:
return MoonPhaseType.LAST_QUARTER

return MoonPhaseType.NEW_MOON

def serialize(self) -> dict:
return {
'phase': self.identifier,
'phase': self.phase_type.name,
'time': self.time.isoformat() if self.time is not None else None,
'next': {
'phase': self.get_next_phase(),
'phase': self.get_next_phase().name,
'time': self.next_phase_date.isoformat()
}
}
Expand Down Expand Up @@ -168,13 +135,8 @@ def get_type(self) -> str:


class Event(Serializable):
def __init__(self, event_type: str, objects: [Object], start_time: datetime,
def __init__(self, event_type: EventType, objects: [Object], start_time: datetime,
end_time: Union[datetime, None] = None, details: str = None):
if event_type not in EVENTS.keys():
accepted_types = ', '.join(EVENTS.keys())
raise ValueError('event_type parameter must be one of the following: %s (got %s)' % (accepted_types,
event_type))

self.event_type = event_type
self.objects = objects
self.start_time = start_time
Expand All @@ -189,7 +151,7 @@ def __repr__(self):
self.details)

def get_description(self, show_details: bool = True) -> str:
description = EVENTS[self.event_type]['message'] % self._get_objects_name()
description = self.event_type.value % self._get_objects_name()
if show_details and self.details is not None:
description += ' ({:s})'.format(self.details)
return description
Expand All @@ -203,50 +165,13 @@ def _get_objects_name(self):
def serialize(self) -> dict:
return {
'objects': [object.serialize() for object in self.objects],
'event': self.event_type,
'event': self.event_type.name,
'starts_at': self.start_time.isoformat(),
'ends_at': self.end_time.isoformat() if self.end_time is not None else None,
'details': self.details
}


def skyfield_to_moon_phase(times: [Time], vals: [int], now: Time) -> Union[MoonPhase, None]:
tomorrow = get_timescale().utc(now.utc_datetime().year, now.utc_datetime().month, now.utc_datetime().day + 1)

phases = list(MOON_PHASES.keys())
current_phase = None
current_phase_time = None
next_phase_time = None
i = 0

if len(times) == 0:
return None

for i, time in enumerate(times):
if now.utc_iso() <= time.utc_iso():
if vals[i] in [0, 2, 4, 6]:
if time.utc_datetime() < tomorrow.utc_datetime():
current_phase_time = time
current_phase = phases[vals[i]]
else:
i -= 1
current_phase_time = None
current_phase = phases[vals[i]]
else:
current_phase = phases[vals[i]]

break

for j in range(i + 1, len(times)):
if vals[j] in [0, 2, 4, 6]:
next_phase_time = times[j]
break

return MoonPhase(current_phase,
current_phase_time.utc_datetime() if current_phase_time is not None else None,
next_phase_time.utc_datetime() if next_phase_time is not None else None)


class AsterEphemerides(Serializable):
def __init__(self,
rise_time: Union[datetime, None],
Expand All @@ -267,8 +192,6 @@ def serialize(self) -> dict:
}


MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']

EARTH = Planet('Earth', 'EARTH')

ASTERS = [Star(_('Sun'), 'SUN', radius=696342),
Expand Down
12 changes: 5 additions & 7 deletions kosmorrolib/dumper.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from pathlib import Path
from tabulate import tabulate
from termcolor import colored

from .data import ASTERS, AsterEphemerides, MoonPhase, Event
from .i18n import _, FULL_DATE_FORMAT, SHORT_DATETIME_FORMAT, TIME_FORMAT
from .version import VERSION
Expand Down Expand Up @@ -159,9 +160,9 @@ def get_moon(self, moon_phase: MoonPhase) -> str:
if moon_phase is None:
return _('Moon phase is unavailable for this date.')

current_moon_phase = ' '.join([self.style(_('Moon phase:'), 'strong'), moon_phase.get_phase()])
current_moon_phase = ' '.join([self.style(_('Moon phase:'), 'strong'), moon_phase.phase_type.value])
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_name(),
next_moon_phase=moon_phase.get_next_phase().value,
next_moon_phase_date=moon_phase.next_phase_date.strftime(FULL_DATE_FORMAT),
next_moon_phase_time=moon_phase.next_phase_date.strftime(TIME_FORMAT)
)
Expand All @@ -183,12 +184,9 @@ 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')

if self.moon_phase is None:
self.moon_phase = MoonPhase('UNKNOWN')

moon_phase_graphics = os.path.join(os.path.abspath(os.path.dirname(__file__)),
'assets', 'moonphases', 'png',
'.'.join([self.moon_phase.identifier.lower().replace('_', '-'),
'.'.join([self.moon_phase.phase_type.name.lower().replace('_', '-'),
'png']))

document = template
Expand Down Expand Up @@ -234,7 +232,7 @@ def add_strings(self, document, kosmorro_logo_path, moon_phase_graphics) -> str:
.replace('+++GRAPH_LABEL_HOURS+++', _('hours')) \
.replace('+++MOON-PHASE-GRAPHICS+++', moon_phase_graphics) \
.replace('+++CURRENT-MOON-PHASE-TITLE+++', _('Moon phase:')) \
.replace('+++CURRENT-MOON-PHASE+++', self.moon_phase.get_phase()) \
.replace('+++CURRENT-MOON-PHASE+++', self.moon_phase.phase_type.value) \
.replace('+++SECTION-EVENTS+++', _('Expected events')) \
.replace('+++EVENTS+++', self._make_events())

Expand Down
40 changes: 40 additions & 0 deletions kosmorrolib/enum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env python3

# Kosmorro - Compute The Next Ephemerides
# Copyright (C) 2019 Jérôme Deuchnord <jerome@deuchnord.fr>
#
# 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 <https://www.gnu.org/licenses/>.

from enum import Enum
from .i18n import _


class MoonPhaseType(Enum):
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')


class EventType(Enum):
OPPOSITION = _('%s is in opposition')
CONJUNCTION = _('%s and %s are in conjunction')
OCCULTATION = _('%s occults %s')
MAXIMAL_ELONGATION = _("%s's largest elongation")
MOON_PERIGEE = _("%s is at its perigee")
MOON_APOGEE = _("%s is at its apogee")
43 changes: 41 additions & 2 deletions kosmorrolib/ephemerides.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,59 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.

import datetime
from typing import Union

from skyfield.searchlib import find_discrete, find_maxima
from skyfield.timelib import Time
from skyfield.constants import tau
from skyfield.errors import EphemerisRangeError

from .data import Position, AsterEphemerides, MoonPhase, Object, ASTERS, skyfield_to_moon_phase
from .data import Position, AsterEphemerides, MoonPhase, Object, ASTERS
from .dateutil import translate_to_timezone
from .core import get_skf_objects, get_timescale, get_iau2000b
from .enum import MoonPhaseType
from .exceptions import OutOfRangeDateError

RISEN_ANGLE = -0.8333


def _get_skyfield_to_moon_phase(times: [Time], vals: [int], now: Time) -> Union[MoonPhase, None]:
tomorrow = get_timescale().utc(now.utc_datetime().year, now.utc_datetime().month, now.utc_datetime().day + 1)

phases = list(MoonPhaseType)
current_phase = None
current_phase_time = None
next_phase_time = None
i = 0

if len(times) == 0:
return None

for i, time in enumerate(times):
if now.utc_iso() <= time.utc_iso():
if vals[i] in [0, 2, 4, 6]:
if time.utc_datetime() < tomorrow.utc_datetime():
current_phase_time = time
current_phase = phases[vals[i]]
else:
i -= 1
current_phase_time = None
current_phase = phases[vals[i]]
else:
current_phase = phases[vals[i]]

break

for j in range(i + 1, len(times)):
if vals[j] in [0, 2, 4, 6]:
next_phase_time = times[j]
break

return MoonPhase(current_phase,
current_phase_time.utc_datetime() if current_phase_time is not None else None,
next_phase_time.utc_datetime() if next_phase_time is not None else None)


def get_moon_phase(compute_date: datetime.date, timezone: int = 0) -> MoonPhase:
earth = get_skf_objects()['earth']
moon = get_skf_objects()['moon']
Expand Down Expand Up @@ -60,7 +99,7 @@ def moon_phase_at(time: Time):

raise OutOfRangeDateError(start, end)

return skyfield_to_moon_phase(times, phase, today)
return _get_skyfield_to_moon_phase(times, phase, today)


def get_ephemerides(date: datetime.date, position: Position, timezone: int = 0) -> [AsterEphemerides]:
Expand Down

0 comments on commit 8b723bf

Please sign in to comment.