Skip to content

Commit 8b723bf

Browse files
DeuchnordJérôme Deuchnord
authored andcommitted
refactor: use enum instead of dicts for the events and moon phase (#129)
BREAKING CHANGE: some methodes in Event and MoonPhase have been dropped in favor of `enum.Enum`'s `name` and `value` properties.
1 parent 331ab99 commit 8b723bf

File tree

11 files changed

+282
-267
lines changed

11 files changed

+282
-267
lines changed

.github/workflows/i18n.yml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,26 @@ jobs:
1919
- name: Check i18n
2020
run: |
2121
pipenv run python setup.py extract_messages --output-file=/tmp/kosmorro-messages.pot > /dev/null
22-
n=$(diff -y --suppress-common-lines kosmorrolib/locales/messages.pot /tmp/kosmorro-messages.pot | grep -v -E '^"POT-Creation-Date: ' | wc -l)
22+
diff=$(diff kosmorrolib/locales/messages.pot /tmp/kosmorro-messages.pot | grep '^>')
23+
n=$(echo "$diff" | grep -v '> "POT-Creation-Date: ' | wc -l)
24+
25+
if [ "$(echo "$diff" | grep -E '^"Generated-By: Babel' | wc -l)" -eq "1" ]; then
26+
echo "❌ You dependencies may be out of date!"
27+
echo " Please run the following command to fix this:"
28+
echo
29+
echo " pipenv sync --dev"
30+
echo
31+
echo " Then update the messages file:"
32+
echo
33+
echo " make messages"
34+
exit 2
35+
fi
2336
2437
if [ "$n" -ne "0" ]; then
2538
echo "❌ The messages file is not up-to-date!"
2639
echo " Please run the following command to fix this:"
2740
echo
28-
echo " pipenv run python setup.py extract_messages --output-file=kosmorrolib/locales/messages.pot"
41+
echo " make messages"
2942
exit 1
3043
fi
3144

Makefile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@ test:
66
unset KOSMORRO_TIMEZONE; \
77
LANG=C pipenv run python3 -m coverage run -m unittest test
88

9-
build: i18n
9+
build: i18n manpages
1010
python3 setup.py sdist bdist_wheel
1111

12-
i18n:
12+
messages:
13+
pipenv run python setup.py extract_messages --output-file=kosmorrolib/locales/messages.pot
14+
15+
manpages:
1316
ronn --roff manpage/kosmorro.1.md
1417
ronn --roff manpage/kosmorro.7.md
1518

19+
i18n:
1620
if [ "$$POEDITOR_API_ACCESS" != "" ]; then \
1721
python3 .scripts/build/getlangs.py; \
1822
python3 setup.py compile_catalog; \

kosmorrolib/data.py

Lines changed: 17 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -25,30 +25,10 @@
2525
from skyfield.api import Topos, Time
2626
from skyfield.vectorlib import VectorSum as SkfPlanet
2727

28-
from .core import get_skf_objects, get_timescale
28+
from .core import get_skf_objects
29+
from .enum import MoonPhaseType, EventType
2930
from .i18n import _
3031

31-
MOON_PHASES = {
32-
'NEW_MOON': _('New Moon'),
33-
'WAXING_CRESCENT': _('Waxing crescent'),
34-
'FIRST_QUARTER': _('First Quarter'),
35-
'WAXING_GIBBOUS': _('Waxing gibbous'),
36-
'FULL_MOON': _('Full Moon'),
37-
'WANING_GIBBOUS': _('Waning gibbous'),
38-
'LAST_QUARTER': _('Last Quarter'),
39-
'WANING_CRESCENT': _('Waning crescent'),
40-
'UNKNOWN': _('Unavailable')
41-
}
42-
43-
EVENTS = {
44-
'OPPOSITION': {'message': _('%s is in opposition')},
45-
'CONJUNCTION': {'message': _('%s and %s are in conjunction')},
46-
'OCCULTATION': {'message': _('%s occults %s')},
47-
'MAXIMAL_ELONGATION': {'message': _("%s's largest elongation")},
48-
'MOON_PERIGEE': {'message': _("%s is at its perigee")},
49-
'MOON_APOGEE': {'message': _("%s is at its apogee")},
50-
}
51-
5232

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

5838

5939
class MoonPhase(Serializable):
60-
def __init__(self, identifier: str, time: datetime = None, next_phase_date: datetime = None):
61-
if identifier not in MOON_PHASES.keys():
62-
raise ValueError('identifier parameter must be one of %s (got %s)' % (', '.join(MOON_PHASES.keys()),
63-
identifier))
64-
65-
self.identifier = identifier
40+
def __init__(self, phase_type: MoonPhaseType, time: datetime = None, next_phase_date: datetime = None):
41+
self.phase_type = phase_type
6642
self.time = time
6743
self.next_phase_date = next_phase_date
6844

69-
def get_phase(self):
70-
return MOON_PHASES[self.identifier]
71-
72-
def get_next_phase_name(self):
73-
next_identifier = self.get_next_phase()
74-
75-
return MOON_PHASES[next_identifier]
76-
7745
def get_next_phase(self):
78-
if self.identifier == 'NEW_MOON' or self.identifier == 'WAXING_CRESCENT':
79-
next_identifier = 'FIRST_QUARTER'
80-
elif self.identifier == 'FIRST_QUARTER' or self.identifier == 'WAXING_GIBBOUS':
81-
next_identifier = 'FULL_MOON'
82-
elif self.identifier == 'FULL_MOON' or self.identifier == 'WANING_GIBBOUS':
83-
next_identifier = 'LAST_QUARTER'
84-
else:
85-
next_identifier = 'NEW_MOON'
86-
return next_identifier
46+
if self.phase_type in [MoonPhaseType.NEW_MOON, MoonPhaseType.WAXING_CRESCENT]:
47+
return MoonPhaseType.FIRST_QUARTER
48+
if self.phase_type in [MoonPhaseType.FIRST_QUARTER, MoonPhaseType.WAXING_GIBBOUS]:
49+
return MoonPhaseType.FULL_MOON
50+
if self.phase_type in [MoonPhaseType.FULL_MOON, MoonPhaseType.WANING_GIBBOUS]:
51+
return MoonPhaseType.LAST_QUARTER
52+
53+
return MoonPhaseType.NEW_MOON
8754

8855
def serialize(self) -> dict:
8956
return {
90-
'phase': self.identifier,
57+
'phase': self.phase_type.name,
9158
'time': self.time.isoformat() if self.time is not None else None,
9259
'next': {
93-
'phase': self.get_next_phase(),
60+
'phase': self.get_next_phase().name,
9461
'time': self.next_phase_date.isoformat()
9562
}
9663
}
@@ -168,13 +135,8 @@ def get_type(self) -> str:
168135

169136

170137
class Event(Serializable):
171-
def __init__(self, event_type: str, objects: [Object], start_time: datetime,
138+
def __init__(self, event_type: EventType, objects: [Object], start_time: datetime,
172139
end_time: Union[datetime, None] = None, details: str = None):
173-
if event_type not in EVENTS.keys():
174-
accepted_types = ', '.join(EVENTS.keys())
175-
raise ValueError('event_type parameter must be one of the following: %s (got %s)' % (accepted_types,
176-
event_type))
177-
178140
self.event_type = event_type
179141
self.objects = objects
180142
self.start_time = start_time
@@ -189,7 +151,7 @@ def __repr__(self):
189151
self.details)
190152

191153
def get_description(self, show_details: bool = True) -> str:
192-
description = EVENTS[self.event_type]['message'] % self._get_objects_name()
154+
description = self.event_type.value % self._get_objects_name()
193155
if show_details and self.details is not None:
194156
description += ' ({:s})'.format(self.details)
195157
return description
@@ -203,50 +165,13 @@ def _get_objects_name(self):
203165
def serialize(self) -> dict:
204166
return {
205167
'objects': [object.serialize() for object in self.objects],
206-
'event': self.event_type,
168+
'event': self.event_type.name,
207169
'starts_at': self.start_time.isoformat(),
208170
'ends_at': self.end_time.isoformat() if self.end_time is not None else None,
209171
'details': self.details
210172
}
211173

212174

213-
def skyfield_to_moon_phase(times: [Time], vals: [int], now: Time) -> Union[MoonPhase, None]:
214-
tomorrow = get_timescale().utc(now.utc_datetime().year, now.utc_datetime().month, now.utc_datetime().day + 1)
215-
216-
phases = list(MOON_PHASES.keys())
217-
current_phase = None
218-
current_phase_time = None
219-
next_phase_time = None
220-
i = 0
221-
222-
if len(times) == 0:
223-
return None
224-
225-
for i, time in enumerate(times):
226-
if now.utc_iso() <= time.utc_iso():
227-
if vals[i] in [0, 2, 4, 6]:
228-
if time.utc_datetime() < tomorrow.utc_datetime():
229-
current_phase_time = time
230-
current_phase = phases[vals[i]]
231-
else:
232-
i -= 1
233-
current_phase_time = None
234-
current_phase = phases[vals[i]]
235-
else:
236-
current_phase = phases[vals[i]]
237-
238-
break
239-
240-
for j in range(i + 1, len(times)):
241-
if vals[j] in [0, 2, 4, 6]:
242-
next_phase_time = times[j]
243-
break
244-
245-
return MoonPhase(current_phase,
246-
current_phase_time.utc_datetime() if current_phase_time is not None else None,
247-
next_phase_time.utc_datetime() if next_phase_time is not None else None)
248-
249-
250175
class AsterEphemerides(Serializable):
251176
def __init__(self,
252177
rise_time: Union[datetime, None],
@@ -267,8 +192,6 @@ def serialize(self) -> dict:
267192
}
268193

269194

270-
MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']
271-
272195
EARTH = Planet('Earth', 'EARTH')
273196

274197
ASTERS = [Star(_('Sun'), 'SUN', radius=696342),

kosmorrolib/dumper.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from pathlib import Path
2424
from tabulate import tabulate
2525
from termcolor import colored
26+
2627
from .data import ASTERS, AsterEphemerides, MoonPhase, Event
2728
from .i18n import _, FULL_DATE_FORMAT, SHORT_DATETIME_FORMAT, TIME_FORMAT
2829
from .version import VERSION
@@ -159,9 +160,9 @@ def get_moon(self, moon_phase: MoonPhase) -> str:
159160
if moon_phase is None:
160161
return _('Moon phase is unavailable for this date.')
161162

162-
current_moon_phase = ' '.join([self.style(_('Moon phase:'), 'strong'), moon_phase.get_phase()])
163+
current_moon_phase = ' '.join([self.style(_('Moon phase:'), 'strong'), moon_phase.phase_type.value])
163164
new_moon_phase = _('{next_moon_phase} on {next_moon_phase_date} at {next_moon_phase_time}').format(
164-
next_moon_phase=moon_phase.get_next_phase_name(),
165+
next_moon_phase=moon_phase.get_next_phase().value,
165166
next_moon_phase_date=moon_phase.next_phase_date.strftime(FULL_DATE_FORMAT),
166167
next_moon_phase_time=moon_phase.next_phase_date.strftime(TIME_FORMAT)
167168
)
@@ -183,12 +184,9 @@ def _make_document(self, template: str) -> str:
183184
kosmorro_logo_path = os.path.join(os.path.abspath(os.path.dirname(__file__)),
184185
'assets', 'png', 'kosmorro-logo.png')
185186

186-
if self.moon_phase is None:
187-
self.moon_phase = MoonPhase('UNKNOWN')
188-
189187
moon_phase_graphics = os.path.join(os.path.abspath(os.path.dirname(__file__)),
190188
'assets', 'moonphases', 'png',
191-
'.'.join([self.moon_phase.identifier.lower().replace('_', '-'),
189+
'.'.join([self.moon_phase.phase_type.name.lower().replace('_', '-'),
192190
'png']))
193191

194192
document = template
@@ -234,7 +232,7 @@ def add_strings(self, document, kosmorro_logo_path, moon_phase_graphics) -> str:
234232
.replace('+++GRAPH_LABEL_HOURS+++', _('hours')) \
235233
.replace('+++MOON-PHASE-GRAPHICS+++', moon_phase_graphics) \
236234
.replace('+++CURRENT-MOON-PHASE-TITLE+++', _('Moon phase:')) \
237-
.replace('+++CURRENT-MOON-PHASE+++', self.moon_phase.get_phase()) \
235+
.replace('+++CURRENT-MOON-PHASE+++', self.moon_phase.phase_type.value) \
238236
.replace('+++SECTION-EVENTS+++', _('Expected events')) \
239237
.replace('+++EVENTS+++', self._make_events())
240238

kosmorrolib/enum.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/usr/bin/env python3
2+
3+
# Kosmorro - Compute The Next Ephemerides
4+
# Copyright (C) 2019 Jérôme Deuchnord <jerome@deuchnord.fr>
5+
#
6+
# This program is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU Affero General Public License as
8+
# published by the Free Software Foundation, either version 3 of the
9+
# License, or (at your option) any later version.
10+
#
11+
# This program is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU Affero General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU Affero General Public License
17+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
19+
from enum import Enum
20+
from .i18n import _
21+
22+
23+
class MoonPhaseType(Enum):
24+
NEW_MOON = _('New Moon')
25+
WAXING_CRESCENT = _('Waxing crescent')
26+
FIRST_QUARTER = _('First Quarter')
27+
WAXING_GIBBOUS = _('Waxing gibbous')
28+
FULL_MOON = _('Full Moon')
29+
WANING_GIBBOUS = _('Waning gibbous')
30+
LAST_QUARTER = _('Last Quarter')
31+
WANING_CRESCENT = _('Waning crescent')
32+
33+
34+
class EventType(Enum):
35+
OPPOSITION = _('%s is in opposition')
36+
CONJUNCTION = _('%s and %s are in conjunction')
37+
OCCULTATION = _('%s occults %s')
38+
MAXIMAL_ELONGATION = _("%s's largest elongation")
39+
MOON_PERIGEE = _("%s is at its perigee")
40+
MOON_APOGEE = _("%s is at its apogee")

kosmorrolib/ephemerides.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,59 @@
1717
# along with this program. If not, see <https://www.gnu.org/licenses/>.
1818

1919
import datetime
20+
from typing import Union
2021

2122
from skyfield.searchlib import find_discrete, find_maxima
2223
from skyfield.timelib import Time
2324
from skyfield.constants import tau
2425
from skyfield.errors import EphemerisRangeError
2526

26-
from .data import Position, AsterEphemerides, MoonPhase, Object, ASTERS, skyfield_to_moon_phase
27+
from .data import Position, AsterEphemerides, MoonPhase, Object, ASTERS
2728
from .dateutil import translate_to_timezone
2829
from .core import get_skf_objects, get_timescale, get_iau2000b
30+
from .enum import MoonPhaseType
2931
from .exceptions import OutOfRangeDateError
3032

3133
RISEN_ANGLE = -0.8333
3234

3335

36+
def _get_skyfield_to_moon_phase(times: [Time], vals: [int], now: Time) -> Union[MoonPhase, None]:
37+
tomorrow = get_timescale().utc(now.utc_datetime().year, now.utc_datetime().month, now.utc_datetime().day + 1)
38+
39+
phases = list(MoonPhaseType)
40+
current_phase = None
41+
current_phase_time = None
42+
next_phase_time = None
43+
i = 0
44+
45+
if len(times) == 0:
46+
return None
47+
48+
for i, time in enumerate(times):
49+
if now.utc_iso() <= time.utc_iso():
50+
if vals[i] in [0, 2, 4, 6]:
51+
if time.utc_datetime() < tomorrow.utc_datetime():
52+
current_phase_time = time
53+
current_phase = phases[vals[i]]
54+
else:
55+
i -= 1
56+
current_phase_time = None
57+
current_phase = phases[vals[i]]
58+
else:
59+
current_phase = phases[vals[i]]
60+
61+
break
62+
63+
for j in range(i + 1, len(times)):
64+
if vals[j] in [0, 2, 4, 6]:
65+
next_phase_time = times[j]
66+
break
67+
68+
return MoonPhase(current_phase,
69+
current_phase_time.utc_datetime() if current_phase_time is not None else None,
70+
next_phase_time.utc_datetime() if next_phase_time is not None else None)
71+
72+
3473
def get_moon_phase(compute_date: datetime.date, timezone: int = 0) -> MoonPhase:
3574
earth = get_skf_objects()['earth']
3675
moon = get_skf_objects()['moon']
@@ -60,7 +99,7 @@ def moon_phase_at(time: Time):
6099

61100
raise OutOfRangeDateError(start, end)
62101

63-
return skyfield_to_moon_phase(times, phase, today)
102+
return _get_skyfield_to_moon_phase(times, phase, today)
64103

65104

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

0 commit comments

Comments
 (0)