Merge branch 'develop' into main
This commit is contained in:
BIN
__pycache__/models.cpython-312.pyc
Normal file
BIN
__pycache__/models.cpython-312.pyc
Normal file
Binary file not shown.
BIN
__pycache__/storage.cpython-312.pyc
Normal file
BIN
__pycache__/storage.cpython-312.pyc
Normal file
Binary file not shown.
141
calendar_cli.py
Normal file
141
calendar_cli.py
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Personal Calendar - CLI приложение для управления событиями
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
from models import Event
|
||||||
|
from storage import load_events, add_event, remove_event, get_events_by_date
|
||||||
|
|
||||||
|
try:
|
||||||
|
from prettytable import PrettyTable
|
||||||
|
except ImportError:
|
||||||
|
print("Ошибка: установите prettytable: pip install prettytable")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def show_menu():
|
||||||
|
"""Показывает главное меню"""
|
||||||
|
print("\n" + "=" * 40)
|
||||||
|
print(" 📅 PERSONAL CALENDAR")
|
||||||
|
print("=" * 40)
|
||||||
|
print("1. Добавить событие")
|
||||||
|
print("2. Показать все события")
|
||||||
|
print("3. Показать события на дату")
|
||||||
|
print("4. Удалить событие")
|
||||||
|
print("5. Выйти")
|
||||||
|
print("=" * 40)
|
||||||
|
|
||||||
|
|
||||||
|
def add_event_interactive():
|
||||||
|
"""Интерактивное добавление события"""
|
||||||
|
print("\n--- Добавление события ---")
|
||||||
|
title = input("Название: ").strip()
|
||||||
|
if not title:
|
||||||
|
print("Ошибка: название не может быть пустым")
|
||||||
|
return
|
||||||
|
|
||||||
|
date = input("Дата (ГГГГ-ММ-ДД): ").strip()
|
||||||
|
try:
|
||||||
|
datetime.strptime(date, "%Y-%m-%d")
|
||||||
|
except ValueError:
|
||||||
|
print("Ошибка: неверный формат даты")
|
||||||
|
return
|
||||||
|
|
||||||
|
time = input("Время (ЧЧ:ММ) - необязательно: ").strip()
|
||||||
|
description = input("Описание: ").strip()
|
||||||
|
|
||||||
|
event = Event(title, date, time, description)
|
||||||
|
add_event(event)
|
||||||
|
print(f"✅ Событие добавлено: {event}")
|
||||||
|
|
||||||
|
|
||||||
|
def show_all_events():
|
||||||
|
"""Показывает все события в виде таблицы"""
|
||||||
|
events = load_events()
|
||||||
|
|
||||||
|
if not events:
|
||||||
|
print("\n📭 Нет сохранённых событий")
|
||||||
|
return
|
||||||
|
|
||||||
|
table = PrettyTable()
|
||||||
|
table.field_names = ["№", "Дата", "Время", "Название", "Описание"]
|
||||||
|
table.align = "l"
|
||||||
|
|
||||||
|
for i, event in enumerate(events, 1):
|
||||||
|
table.add_row([
|
||||||
|
i,
|
||||||
|
event.date,
|
||||||
|
event.time if event.time else "-",
|
||||||
|
event.title[:30],
|
||||||
|
event.description[:40] if event.description else "-"
|
||||||
|
])
|
||||||
|
|
||||||
|
print("\n" + str(table))
|
||||||
|
|
||||||
|
|
||||||
|
def show_events_by_date():
|
||||||
|
"""Показывает события на конкретную дату"""
|
||||||
|
date = input("\nВведите дату (ГГГГ-ММ-ДД): ").strip()
|
||||||
|
|
||||||
|
try:
|
||||||
|
datetime.strptime(date, "%Y-%m-%d")
|
||||||
|
except ValueError:
|
||||||
|
print("Ошибка: неверный формат даты")
|
||||||
|
return
|
||||||
|
|
||||||
|
events = get_events_by_date(date)
|
||||||
|
|
||||||
|
if not events:
|
||||||
|
print(f"\n📭 Нет событий на {date}")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"\n📅 События на {date}:")
|
||||||
|
for i, event in enumerate(events, 1):
|
||||||
|
print(f" {i}. {event}")
|
||||||
|
|
||||||
|
|
||||||
|
def remove_event_interactive():
|
||||||
|
"""Интерактивное удаление события"""
|
||||||
|
show_all_events()
|
||||||
|
events = load_events()
|
||||||
|
|
||||||
|
if not events:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
index = int(input("\nВведите номер события для удаления: ")) - 1
|
||||||
|
if remove_event(index):
|
||||||
|
print("✅ Событие удалено")
|
||||||
|
else:
|
||||||
|
print("❌ Неверный номер")
|
||||||
|
except ValueError:
|
||||||
|
print("Ошибка: введите число")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Основная функция"""
|
||||||
|
print("Добро пожаловать в Personal Calendar!")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
show_menu()
|
||||||
|
choice = input("\nВыберите действие (1-5): ").strip()
|
||||||
|
|
||||||
|
if choice == "1":
|
||||||
|
add_event_interactive()
|
||||||
|
elif choice == "2":
|
||||||
|
show_all_events()
|
||||||
|
elif choice == "3":
|
||||||
|
show_events_by_date()
|
||||||
|
elif choice == "4":
|
||||||
|
remove_event_interactive()
|
||||||
|
elif choice == "5":
|
||||||
|
print("\n👋 До свидания!")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("❌ Неверный выбор. Попробуйте снова.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
8
calendar_data.json
Normal file
8
calendar_data.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"title": "Встреча",
|
||||||
|
"date": "2026-05-20",
|
||||||
|
"time": "15:00",
|
||||||
|
"description": "Созвон с командой"
|
||||||
|
}
|
||||||
|
]
|
||||||
35
models.py
Normal file
35
models.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
class Event:
|
||||||
|
"""Класс события календаря"""
|
||||||
|
|
||||||
|
def __init__(self, title: str, date: str, time: str = "", description: str = ""):
|
||||||
|
self.title = title
|
||||||
|
self.date = date # формат YYYY-MM-DD
|
||||||
|
self.time = time # формат HH:MM
|
||||||
|
self.description = description
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
"""Преобразует событие в словарь для JSON"""
|
||||||
|
return {
|
||||||
|
"title": self.title,
|
||||||
|
"date": self.date,
|
||||||
|
"time": self.time,
|
||||||
|
"description": self.description
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict) -> 'Event':
|
||||||
|
"""Создаёт событие из словаря"""
|
||||||
|
return cls(
|
||||||
|
title=data["title"],
|
||||||
|
date=data["date"],
|
||||||
|
time=data.get("time", ""),
|
||||||
|
description=data.get("description", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
time_str = f" в {self.time}" if self.time else ""
|
||||||
|
return f"{self.date}{time_str}: {self.title} - {self.description}"
|
||||||
44
storage.py
Normal file
44
storage.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
from typing import List
|
||||||
|
from models import Event
|
||||||
|
|
||||||
|
DATA_FILE = "calendar_data.json"
|
||||||
|
|
||||||
|
def load_events() -> List[Event]:
|
||||||
|
"""Загружает события из JSON файла"""
|
||||||
|
if not os.path.exists(DATA_FILE):
|
||||||
|
return []
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(DATA_FILE, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
return [Event.from_dict(item) for item in data]
|
||||||
|
except (json.JSONDecodeError, FileNotFoundError):
|
||||||
|
return []
|
||||||
|
|
||||||
|
def save_events(events: List[Event]) -> None:
|
||||||
|
"""Сохраняет события в JSON файл"""
|
||||||
|
with open(DATA_FILE, 'w', encoding='utf-8') as f:
|
||||||
|
data = [event.to_dict() for event in events]
|
||||||
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
def add_event(event: Event) -> None:
|
||||||
|
"""Добавляет новое событие"""
|
||||||
|
events = load_events()
|
||||||
|
events.append(event)
|
||||||
|
save_events(events)
|
||||||
|
|
||||||
|
def remove_event(index: int) -> bool:
|
||||||
|
"""Удаляет событие по индексу (начиная с 0)"""
|
||||||
|
events = load_events()
|
||||||
|
if 0 <= index < len(events):
|
||||||
|
del events[index]
|
||||||
|
save_events(events)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_events_by_date(date: str) -> List[Event]:
|
||||||
|
"""Возвращает события на конкретную дату"""
|
||||||
|
events = load_events()
|
||||||
|
return [e for e in events if e.date == date]
|
||||||
Reference in New Issue
Block a user