#!/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 . from abc import ABC, abstractmethod import datetime import json from tabulate import tabulate from skyfield.timelib import Time from numpy import int64 from termcolor import colored 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): def __init__(self, ephemeris: dict, events: [Event], date: datetime.date = datetime.date.today(), with_colors: bool = True): self.ephemeris = ephemeris self.events = events self.date = date self.with_colors = with_colors @abstractmethod def to_string(self): pass class JsonDumper(Dumper): def to_string(self): self.ephemeris['events'] = self.events self.ephemeris['ephemerides'] = self.ephemeris.pop('details') return json.dumps(self.ephemeris, default=self._json_default, indent=4) @staticmethod def _json_default(obj): # Fixes the "TypeError: Object of type int64 is not JSON serializable" # See https://stackoverflow.com/a/50577730 if isinstance(obj, int64): return int(obj) if isinstance(obj, Time): return obj.utc_iso() if isinstance(obj, Object): obj = obj.__dict__ obj.pop('skyfield_name') obj['object'] = obj.pop('name') obj['details'] = obj.pop('ephemerides') return obj if isinstance(obj, AsterEphemerides): return obj.__dict__ if isinstance(obj, MoonPhase): moon_phase = obj.__dict__ moon_phase['phase'] = moon_phase.pop('identifier') moon_phase['date'] = moon_phase.pop('time') return moon_phase if isinstance(obj, Event): event = obj.__dict__ event['objects'] = [object.name for object in event['objects']] return event raise TypeError('Object of type "%s" could not be integrated in the JSON' % str(type(obj))) class TextDumper(Dumper): def to_string(self): text = [self.style(self.get_date(), 'h1')] if len(self.ephemeris['details']) > 0: text.append(self.get_asters(self.ephemeris['details'])) text.append(self.get_moon(self.ephemeris['moon_phase'])) if len(self.events) > 0: text.append('\n'.join([self.style(_('Expected events:'), 'h2'), self.get_events(self.events)])) text.append(self.style(_('Note: All the hours are given in UTC.'), 'em')) return '\n\n'.join(text) def style(self, text: str, tag: str) -> str: if not self.with_colors: return text styles = { 'h1': lambda t: colored(t, 'yellow', attrs=['bold']), 'h2': lambda t: colored(t, 'magenta', attrs=['bold']), 'th': lambda t: colored(t, 'white', attrs=['bold']), 'strong': lambda t: colored(t, attrs=['bold']), 'em': lambda t: colored(t, attrs=['dark']) } return styles[tag](text) def get_date(self) -> str: date = self.date.strftime(FULL_DATE_FORMAT) return ''.join([date[0].upper(), date[1:]]) def get_asters(self, asters: [Object]) -> str: data = [] for aster in asters: name = self.style(aster.name, 'th') if aster.ephemerides.rise_time is not None: 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(TIME_FORMAT) else: planet_culmination = '-' if aster.ephemerides.set_time is not None: 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=[self.style(_('Object'), 'th'), self.style(_('Rise time'), 'th'), self.style(_('Culmination time'), 'th'), self.style(_('Set time'), 'th')], tablefmt='simple', stralign='center', colalign=('left',)) def get_events(self, events: [Event]) -> str: data = [] for event in events: data.append([self.style(event.start_time.utc_strftime(TIME_FORMAT), 'th'), event.get_description()]) return tabulate(data, tablefmt='plain', stralign='left') def get_moon(self, moon_phase: MoonPhase) -> str: current_moon_phase = ' '.join([self.style(_('Moon phase:'), 'strong'), 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])