hcacalendar/main.py

192 lines
6.8 KiB
Python
Executable File

#!/usr/bin/env python
"""
CHURCH CALENDAR CSV GENERATOR
"""
import calendar
from dataclasses import dataclass
from datetime import date, datetime
import sys
import click
from dataclass_csv import DataclassWriter
from events import EVENT_LIST
def initalize_events(event_list):
""" convert EVENT_LIST into monthly_events """
monthly_events = [[] for _ in range(7)]
for event in event_list:
monthly_events[event[0]].append(event[1:])
return monthly_events
def debug_print(data, condition=True, end="\n"):
"""Conditionally print data."""
if condition:
print(data, end=end)
def initalize_year(year: int) -> dict[int, list[list[int]]]:
months = dict(enumerate
([calendar.monthcalendar(year, month) for month in range(1, 13)],
start=1))
return months
@dataclass
class Event():
"""Event for CSV export"""
event_name: str
venue_name: str
organizer_name: str
start_date: str
start_time: str
end_date: str
end_time: str
all_day_event: str
categories: str
event_cost: str
event_phone: str
event_website: str
show_map_link: str
show_map: str
event_description: str
def suffix(day: int) -> str:
"""convert day to suffix"""
result: str = 'th'
if day in [1, 21, 31]:
result = 'st'
elif day in [2, 22]:
result = 'nd'
elif day in [3, 23]:
result = 'rd'
return result
def create_event(name: str,
location: str,
categories: str,
description: str,
start,
finish) -> Event:
"""create event object"""
event = Event(name,
location,
"",
start.strftime("%Y-%m-%d"),
start.strftime("%I:%M %p"),
finish.strftime("%Y-%m-%d"),
finish.strftime("%I:%M %p"),
"FALSE",
categories,
"",
"",
"",
"",
"",
description)
return event
def write_calendar(events: list[Event], filename) -> None:
"""write calendar to csv file"""
with open(filename, "w", encoding="utf-8", newline="") as handle:
writer = DataclassWriter(handle, events, Event)
writer.map("event_name").to("EVENT NAME")
writer.map("venue_name").to("VENUE NAME")
writer.map("organizer_name").to("ORGANIZER NAME")
writer.map("start_date").to("START DATE")
writer.map("start_time").to("START TIME")
writer.map("end_date").to("END DATE")
writer.map("end_time").to("END TIME")
writer.map("all_day_event").to("ALL DAY EVENT")
writer.map("categories").to("CATEGORIES")
writer.map("event_cost").to("EVENT COST")
writer.map("event_phone").to("EVENT PHONE")
writer.map("event_website").to("EVENT_WEBSITE")
writer.map("show_map_link").to("SHOW MAP LINK?")
writer.map("show_map").to("SHOW MAP?")
writer.map("event_description").to("EVENT DESCRIPTION")
writer.write()
def process_day(events, year, month, day, day_events):
"""process all events for one day"""
for event_item in day_events:
start = datetime(year, month, day, event_item[0], event_item[1])
finish = datetime(year, month, day, event_item[2], event_item[3])
debug_print(f" {start:%H:%M}-{finish:%H:%M} {event_item[6]:25} {event_item[4]:11} {event_item[5]:16} {event_item[7]:70.70}", DEBUG)
event = create_event(event_item[6], event_item[4], event_item[5], event_item[7].replace("\n","\\n"), start, finish)
events.append(event)
return events
def process_month(monthly_events, year, month, week, events, weekday_counters):
"""process one week of events using weekday occurrence counters"""
for day_of_week, day in enumerate(week):
if day > 0:
# Increment the occurrence count for this specific weekday (0=Mon, 6=Sun)
weekday_counters[day_of_week] += 1
current_occurrence = weekday_counters[day_of_week]
debug_print(f" {day_of_week}-{day:02d} (Occurrence: {current_occurrence})", DEBUG)
# Filter events matching this day of week and this specific occurrence (e.g., 2nd Monday)
day_events = [x[1:] for x in monthly_events[day_of_week] if current_occurrence in x[0]]
events = process_day(events, year, month, day, day_events)
return events
def process_months(months_dict, monthly_events, year, month_range):
"""process selected months and return list of events"""
events = []
for month in range(1, 13):
if month not in month_range:
continue
debug_print(f"MONTH: {month}", DEBUG)
# Initialize counters for the month to track 1st, 2nd, etc. occurrence of each weekday
weekday_counters = [0] * 7
for week_of_month, week in enumerate(months_dict[month]):
debug_print(f"{week_of_month} {week}", DEBUG)
events = process_month(monthly_events, year, month, week, events, weekday_counters)
return events
def validate_months(ctx, param, value):
"""Validates and processes the --months option."""
if value is None:
return list(range(1, 13))
parts = value.split('-')
if len(parts) == 1:
try:
month = int(parts[0])
if 1 <= month <= 12:
return [month]
raise click.BadParameter("Month must be between 1 and 12.")
except ValueError:
raise click.BadParameter("Invalid month format.")
elif len(parts) == 2:
try:
start, end = int(parts[0]), int(parts[1])
if 1 <= start <= 12 and 1 <= end <= 12 and start <= end:
return list(range(start, end + 1))
raise click.BadParameter("Invalid month range.")
except ValueError:
raise click.BadParameter("Invalid range format.")
raise click.BadParameter("Use a single number or a range (e.g., 1-3).")
@click.command()
@click.option("--debug", "-d", is_flag=True, default=False, help="Print debug output")
@click.option('--year', "-y", type=int, default=date.today().year, help='Specify the year.')
@click.option('--months', '-m', 'month_range', callback=validate_months, default=None,
help='A single month (1-12) or a range (e.g., 1-3).')
@click.argument('filename', default="calendar.csv", required=False)
def main(debug, year, month_range, filename):
"""Create a .csv file of church events for the year."""
global DEBUG
DEBUG = debug
months_dict = initalize_year(year)
monthly_events = initalize_events(EVENT_LIST)
events = process_months(months_dict, monthly_events, year, month_range)
write_calendar(events, filename)
sys.exit(0)
if __name__ == "__main__":
main()