From 48b29ef7e1a18c8e93db2353d81941c7ce0fd38a Mon Sep 17 00:00:00 2001 From: Omniscince Date: Sun, 23 Mar 2025 02:03:55 +0600 Subject: [PATCH] moving project into here --- src/config/__init__.py | 1 + src/config/config.py | 43 +++++++++++ src/console/__init__.py | 42 +++++++++++ src/console/console.py | 41 +++++++++++ src/core/__init__.py | 1 + src/core/core.py | 150 ++++++++++++++++++++++++++++++++++++++ src/core/localization.py | 34 +++++++++ src/core/user.py | 29 ++++++++ src/core/users_storage.py | 58 +++++++++++++++ src/languages/en.json | 36 +++++++++ src/languages/ky.json | 36 +++++++++ src/languages/ru.json | 36 +++++++++ src/main.py | 7 ++ 13 files changed, 514 insertions(+) create mode 100644 src/config/__init__.py create mode 100644 src/config/config.py create mode 100644 src/console/__init__.py create mode 100644 src/console/console.py create mode 100644 src/core/__init__.py create mode 100644 src/core/core.py create mode 100644 src/core/localization.py create mode 100644 src/core/user.py create mode 100644 src/core/users_storage.py create mode 100644 src/languages/en.json create mode 100644 src/languages/ky.json create mode 100644 src/languages/ru.json diff --git a/src/config/__init__.py b/src/config/__init__.py new file mode 100644 index 0000000..3558f42 --- /dev/null +++ b/src/config/__init__.py @@ -0,0 +1 @@ +from .config import Config \ No newline at end of file diff --git a/src/config/config.py b/src/config/config.py new file mode 100644 index 0000000..faf52bc --- /dev/null +++ b/src/config/config.py @@ -0,0 +1,43 @@ +import json +from pathlib import Path + +blank_config = { + "console": { + "prompt": ":> ", + }, + "database": { + "file": "users.json", + }, + "localization": { + "dir": "languages", + "default": "en", + } +} + +class Config: + file = Path("./config.json") + + def __init__(self): + self._raw = {} + + self.console = {} + self.database = {} + self.localization = {} + + self.load() + + def create(self): + with open(self.file, "w") as f: + # noinspection PyTypeChecker + json.dump(blank_config, f, indent=4) + + def load(self): + if not self.file.exists(): + self.create() + + with open(self.file, "r") as f: + self._raw = json.load(f) + + self.console = self._raw['console'] + self.database = self._raw['database'] + self.localization = self._raw['localization'] diff --git a/src/console/__init__.py b/src/console/__init__.py new file mode 100644 index 0000000..1862998 --- /dev/null +++ b/src/console/__init__.py @@ -0,0 +1,42 @@ +class Console: + + def __init__(self, prompt: str, unknown_text="Unknown Command. Try help to see all commands."): + self.prompt = prompt + self.unknown_text = unknown_text + self.run = True + self.help = {} + self.commands = {} + + def reg_command(self, name, help_message, callback): + self.help[name] = help_message + self.commands[name] = callback + + def update_help_messages(self, localization): + for name in self.commands: + # Перерегистрируем строку помощи для каждой команды + localized_help = localization.t(f"cmd_{name}") + self.help[name] = localized_help if localized_help != f"cmd_{name}" else name + + def stop(self): + self.run = False + + def print_help(self): + # Адаптация принта под новый код + max_len = max(len(name) for name in self.help.keys()) + print("Available commands:") + for name, text in self.help.items(): + print(f"{name:<{max_len}} -- {text}") + + def _start(self): + while self.run: + cmd = input(self.prompt).strip() + if cmd not in self.commands: + print(self.unknown_text) + continue + self.commands[cmd]() + + def start(self): + try: + self._start() + except KeyboardInterrupt: + pass diff --git a/src/console/console.py b/src/console/console.py new file mode 100644 index 0000000..6c146c8 --- /dev/null +++ b/src/console/console.py @@ -0,0 +1,41 @@ +class Console: + + def __init__(self, prompt: str, unknown_text="Unknown Command. Try help to see all commands."): + self.prompt = prompt + self.unknown_text = unknown_text + self.run = True + self.help = {} + self.commands = {} + + self.reg_command("help", "This help message", self._print_help) + + def reg_command(self, name, help_message, callback): + self.help[name] = help_message + self.commands[name] = callback + + def stop(self): + self.run = False + + def _print_help(self): + max_len = 0 + for i in self.help.keys(): + if len(i) > max_len: + max_len = len(i) + + print("Available commands:") + for name, text in self.help.items(): + print(f"{name:<{max_len}} -- {text}") + + def _start(self): + while self.run: + cmd = input(self.prompt).strip() + if cmd not in self.commands: + print(self.unknown_text) + continue + self.commands[cmd]() + + def start(self): + try: + self._start() + except KeyboardInterrupt: + pass diff --git a/src/core/__init__.py b/src/core/__init__.py new file mode 100644 index 0000000..3fdfaf5 --- /dev/null +++ b/src/core/__init__.py @@ -0,0 +1 @@ +from .core import Core diff --git a/src/core/core.py b/src/core/core.py new file mode 100644 index 0000000..c1368c4 --- /dev/null +++ b/src/core/core.py @@ -0,0 +1,150 @@ +from pathlib import Path +from config import Config +from console import Console +from .localization import Localization +from .user import User +from .users_storage import UsersStorage + + +class Core: + def __init__(self): + self.config = Config() + + self.users_file = Path(self.config.database['file']) + self.storage = UsersStorage(self.users_file) + self.console = Console(self.config.console['prompt']) + self.i18n = Localization(self.config.localization['dir']) + + def _add_user(self): + while True: + username = input(self.i18n.t("enter_username")).lower() + if username in self.storage.get_index(): + print(self.i18n.t("username_exists")) + continue + break + + name = input(self.i18n.t("enter_name")) + surname = input(self.i18n.t("enter_surname")) + + while True: + age_input = input(self.i18n.t("enter_age")) + if age_input.isdigit(): + age = int(age_input) + break + print(self.i18n.t("invalid_age")) + + while True: + gender = input(self.i18n.t("enter_gender")).strip().lower() + if gender in ("male", "female"): + break + print(self.i18n.t("invalid_gender")) + + # noinspection PyTypeChecker + user = User(username=username, name=name, surname=surname, age=age, gender=gender) + self.storage.add(user) + print(self.i18n.t("user_added")) + + def _del_user(self): + username = input(self.i18n.t("enter_username_delete")).lower() + user = self.storage.find(username) + if user: + self.storage.delete(user) + print(self.i18n.t("user_deleted")) + else: + print(self.i18n.t("user_not_found")) + + def _edit_user(self): + username = input(self.i18n.t("enter_username_edit")).lower() + user = self.storage.find(username) + if not user: + print(self.i18n.t("user_not_found")) + return + + while True: + new_username = input(self.i18n.t("enter_new_username", current=user.username)) or user.username + if new_username != user.username and new_username in self.storage.get_index(): + print(self.i18n.t("username_exists")) + else: + break + + new_name = input(self.i18n.t("enter_new_name", current=user.name)) or user.name + new_surname = input(self.i18n.t("enter_new_surname", current=user.surname)) or user.surname + + while True: + new_age_input = input(self.i18n.t("enter_new_age", current=user.age)) or str(user.age) + if new_age_input.isdigit(): + new_age = int(new_age_input) + break + print(self.i18n.t("invalid_age")) + + while True: + new_gender = input(self.i18n.t("enter_new_gender", current=user.gender)) or user.gender + if new_gender in ("male", "female"): + break + print(self.i18n.t("invalid_gender")) + + self.storage.delete(user) + # noinspection PyTypeChecker + updated_user = User(username=new_username, name=new_name, surname=new_surname, age=new_age, gender=new_gender) + self.storage.add(updated_user) + print(self.i18n.t("user_updated")) + + def _list_of_users(self): + users = self.storage.list() + if not users: + print(self.i18n.t("no_users_found")) + return + + headers = [ + self.i18n.t("header_username"), + self.i18n.t("header_name"), + self.i18n.t("header_surname"), + self.i18n.t("header_age"), + self.i18n.t("header_gender") + ] + attributes = ['username', 'name', 'surname', 'age', 'gender'] + + # Вычисляем максимальную длину для каждого столбца + max_lengths = [] + for i in range(len(headers)): + max_len = max(len(headers[i]), max(len(str(getattr(user, attributes[i]))) for user in users)) + max_lengths.append(max_len) + + # Формируем строку заголовков + header_line = " | ".join(headers[i].center(max_lengths[i]) for i in range(len(headers))) + print(header_line) + + # Печатаем разделитель + print("-" * len(header_line)) + + # Выводим строки с данными пользователей + for user in users: + user_line = " | ".join(str(getattr(user, attributes[i])).center(max_lengths[i]) + for i in range(len(attributes))) + print(user_line) + + def _change_language(self): + available_languages = self.i18n.available_languages() + print(self.i18n.t("available_languages"), ", ".join(available_languages)) + new_lang = input(self.i18n.t("choose_language")) + self.i18n.set_language(new_lang) + + self.console.update_help_messages(self.i18n) + + def load(self): + + self.i18n.set_language(self.config.localization['default']) + + self.console.reg_command("help", self.i18n["cmd_help"], self.console.print_help) + self.console.reg_command("add", self.i18n["cmd_add"], self._add_user) + self.console.reg_command("del", self.i18n["cmd_del"], self._del_user) + self.console.reg_command("edit", self.i18n["cmd_edit"], self._edit_user) + self.console.reg_command("list", self.i18n["cmd_list"], self._list_of_users) + self.console.reg_command("lang", self.i18n["cmd_lang"], self._change_language) + self.console.reg_command("exit", self.i18n["cmd_exit"], self.console.stop) + + self.storage.load() + + def start(self): + print("Welcome to UsersConsole v1.1!") + self.console.start() diff --git a/src/core/localization.py b/src/core/localization.py new file mode 100644 index 0000000..4baddd4 --- /dev/null +++ b/src/core/localization.py @@ -0,0 +1,34 @@ +import json +from pathlib import Path + +class Localization: + + def __init__(self, lang_dir, lang="en"): + self.lang_dir = Path(lang_dir) + self.lang = lang + self.translations = {} + + def available_languages(self): + # Возвращает список доступных языков, исключая config.json + return [file.stem for file in self.lang_dir.glob("*.json")] + + def set_language(self, lang): + # Устанавливает новый язык, ну если он доступен. + if lang in (langs:=self.available_languages()): + self.lang = lang + self._load_language() + else: + raise FileNotFoundError(f'Localization {lang} not found. Available: {', '.join(langs)}') + + def _load_language(self): + # Загружает JSON-файл перевода. + with open(self.lang_dir / f"{self.lang}.json", "r", encoding="utf-8") as file: + self.translations = json.load(file) + + def t(self, key, **kwargs): + # Возвращает перевод строки по ключу. + text = self.translations.get(key, key) + return text.format(**kwargs) if kwargs else text + + def __getitem__(self, item): # self.i18n[KEY] + return self.t(item) diff --git a/src/core/user.py b/src/core/user.py new file mode 100644 index 0000000..b823272 --- /dev/null +++ b/src/core/user.py @@ -0,0 +1,29 @@ +from typing import Literal + +class User: + + def __init__(self, username: str, name: str, surname: str, age: int, gender: Literal["male", "female"]): + self.username = username + self.name = name + self.surname = surname + self.age = age + self.gender = gender + + def as_dict(self): + return { + "username": self.username, + "name": self.name, + "surname": self.surname, + "age": self.age, + "gender": self.gender + } + + @classmethod + def from_dict(cls, data: dict): + return cls(data['username'], data['name'], data['surname'], data['age'], data['gender']) + + def __str__(self): + return f"{self!r}" + + def __repr__(self): + return f"User({self.username}, {self.name}, {self.surname}, {self.age}, {self.gender})" diff --git a/src/core/users_storage.py b/src/core/users_storage.py new file mode 100644 index 0000000..bde7748 --- /dev/null +++ b/src/core/users_storage.py @@ -0,0 +1,58 @@ +import json +import os.path +from .user import User + + +class UsersStorage: + def __init__(self, users_file): + self.users_file = users_file + self._database = { + 'index': set(), + 'data': [] + } + + def save(self): + data = [] + for user in self._database['data']: + data.append(user.as_dict()) + + with open(self.users_file, 'w') as f: + # noinspection PyTypeChecker + json.dump(data, f) + + def find(self, username) -> User | None: + for u in self.list(): + if u.username == username: + return u + + def load(self): + if not os.path.exists(self.users_file): + self.save() + with open(self.users_file, 'r') as f: + data = json.load(f) + for row in data: + user = User.from_dict(row) + self._database['index'].add(user.username) + self._database['data'].append(user) + + def add(self, user: User): + self._database['index'].add(user.username) + self._database['data'].append(user) + self.save() + + def delete(self, user: User): + if user.username not in self._database['index']: + return + for stored_user in self._database['data']: + if stored_user.username != user.username: + continue + self._database['index'].remove(user.username) + self._database['data'].remove(stored_user) + self.save() + break + + def get_index(self): + return self._database['index'].copy() + + def list(self): + return self._database['data'].copy() diff --git a/src/languages/en.json b/src/languages/en.json new file mode 100644 index 0000000..1c861ab --- /dev/null +++ b/src/languages/en.json @@ -0,0 +1,36 @@ +{ + "enter_username": "Enter username: ", + "username_exists": "Username already exists. Please choose another.", + "enter_name": "Enter user name: ", + "enter_surname": "Enter user surname: ", + "enter_age": "Enter user age: ", + "invalid_age": "Invalid age. Please enter a valid age.", + "enter_gender": "Enter user gender (male/female): ", + "invalid_gender": "Invalid gender. Please enter 'male' or 'female'.", + "user_added": "User added successfully.", + "enter_username_delete": "Enter username to delete: ", + "user_deleted": "User deleted successfully.", + "user_not_found": "User not found.", + "enter_username_edit": "Enter username to edit: ", + "enter_new_username": "Enter new username [{current}]: ", + "enter_new_name": "Enter new name [{current}]: ", + "enter_new_surname": "Enter new surname [{current}]: ", + "enter_new_age": "Enter new age [{current}]: ", + "enter_new_gender": "Enter new gender [{current}]: ", + "user_updated": "User updated successfully.", + "no_users_found": "No users found.", + "available_languages": "Available languages: ", + "choose_language": "Choose language: ", + "header_username": "Username", + "header_name": "Name", + "header_surname": "Surname", + "header_age": "Age", + "header_gender": "Gender", + "cmd_add": "Add a new user", + "cmd_del": "Delete a user", + "cmd_edit": "Edit an existing user", + "cmd_list": "List all users", + "cmd_lang": "Change language", + "cmd_exit": "Exit the program", + "cmd_help": "Show this message" +} diff --git a/src/languages/ky.json b/src/languages/ky.json new file mode 100644 index 0000000..588915a --- /dev/null +++ b/src/languages/ky.json @@ -0,0 +1,36 @@ +{ + "enter_username": "Колдонуучунун атын киргизиниз: ", + "username_exists": "Колдонуучунун аты бош эмес. Сураныч, башканы тандаңыз.", + "enter_name": "Колдонуучу атты жазыңыз: ", + "enter_surname": "Колдонуучу фамилияны жазыңыз: ", + "enter_age": "Колдонуучу жашты жазыныз: ", + "invalid_age": "Туура эмес жаш. Сураныч, туура атты жазыңыз.", + "enter_gender": "Колдонуучу жынысты тандаңыз (male/female): ", + "invalid_gender": "Туура эмес жыныс. Сураныч, туура жынысты тандаңыз 'male' же 'female'.", + "user_added": "Колдонуучу ийгиликтүү кошулду.", + "enter_username_delete": "Очүрүү үчүн колдонуучунун атын жазыңыз: ", + "user_deleted": "Колдунуучу ийгиликтүү өчүрүлдү.", + "user_not_found": "Колдонуучу табылган жок.", + "enter_username_edit": "Алмаштыруу үчүн колдонуучунун атын жазыңыз: ", + "enter_new_username": "Жаңы колдонуучунун атын жазыңыз [{current}]: ", + "enter_new_name": "Жаңы колдонуучу атты жазыныз [{current}]: ", + "enter_new_surname": "Жаңы колдлнуучу фамилияны жазыңыз [{current}]: ", + "enter_new_age": "Жаңы жашты жазыңыз [{current}]: ", + "enter_new_gender": "Жаңы жынысты жазыңыз [{current}]: ", + "user_updated": "Колдонуучу ийгиликтүү алмашты.", + "no_users_found": "Колдонуучулар табылган жок.", + "available_languages": "Жеткиликтүү тилдер: ", + "choose_language": "Тилди тандаңыз: ", + "header_username": "Колдонуучунун аты", + "header_name": "Аты", + "header_surname": "Фамилиясы", + "header_age": "Жашы", + "header_gender": "Жынысы", + "cmd_add": "Жаңы колдонуучуну кошуу", + "cmd_del": "Колдонуучуну жок кылуу", + "cmd_edit": "Учурдагы колдонуучуну түзөтүү", + "cmd_list": "Бардык колдонуучуларды тизмеги", + "cmd_lang": "Тилди өзгөртүү", + "cmd_exit": "Программадан чыгуу", + "cmd_help": "Справканы көрсөтүү" +} diff --git a/src/languages/ru.json b/src/languages/ru.json new file mode 100644 index 0000000..6f88b82 --- /dev/null +++ b/src/languages/ru.json @@ -0,0 +1,36 @@ +{ + "enter_username": "Введите имя пользователя: ", + "username_exists": "Имя пользователя уже существует. Выберите другое.", + "enter_name": "Введите имя: ", + "enter_surname": "Введите фамилию: ", + "enter_age": "Введите возраст: ", + "invalid_age": "Неверный возраст. Введите корректное число.", + "enter_gender": "Введите пол (male/female): ", + "invalid_gender": "Неверный пол. Введите 'male' или 'female'.", + "user_added": "Пользователь успешно добавлен.", + "enter_username_delete": "Введите имя пользователя для удаления: ", + "user_deleted": "Пользователь успешно удалён.", + "user_not_found": "Пользователь не найден.", + "enter_username_edit": "Введите имя пользователя для редактирования: ", + "enter_new_username": "Введите новое имя пользователя [{current}]: ", + "enter_new_name": "Введите новое имя [{current}]: ", + "enter_new_surname": "Введите новую фамилию [{current}]: ", + "enter_new_age": "Введите новый возраст [{current}]: ", + "enter_new_gender": "Введите новый пол [{current}]: ", + "user_updated": "Пользователь успешно обновлён.", + "no_users_found": "Пользователей не найдено.", + "available_languages": "Доступные языки: ", + "choose_language": "Выберите язык: ", + "header_username": "Имя пользователя", + "header_name": "Имя", + "header_surname": "Фамилия", + "header_age": "Возраст", + "header_gender": "Пол", + "cmd_add": "Добавить нового пользователя", + "cmd_del": "Удалить пользователя", + "cmd_edit": "Редактировать пользователя", + "cmd_list": "Список пользователей", + "cmd_lang": "Сменить язык", + "cmd_exit": "Выход из программы", + "cmd_help": "Показать справку" +} diff --git a/src/main.py b/src/main.py index e69de29..fec5fde 100644 --- a/src/main.py +++ b/src/main.py @@ -0,0 +1,7 @@ +from core import Core + +core = Core() + +if __name__ == '__main__': + core.load() + core.start()