moving project into here

This commit is contained in:
Omniscince 2025-03-23 02:03:55 +06:00
parent efc12fbff2
commit 48b29ef7e1
13 changed files with 514 additions and 0 deletions

1
src/config/__init__.py Normal file
View File

@ -0,0 +1 @@
from .config import Config

43
src/config/config.py Normal file
View File

@ -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']

42
src/console/__init__.py Normal file
View File

@ -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

41
src/console/console.py Normal file
View File

@ -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

1
src/core/__init__.py Normal file
View File

@ -0,0 +1 @@
from .core import Core

150
src/core/core.py Normal file
View File

@ -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()

34
src/core/localization.py Normal file
View File

@ -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)

29
src/core/user.py Normal file
View File

@ -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})"

58
src/core/users_storage.py Normal file
View File

@ -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()

36
src/languages/en.json Normal file
View File

@ -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"
}

36
src/languages/ky.json Normal file
View File

@ -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": "Справканы көрсөтүү"
}

36
src/languages/ru.json Normal file
View File

@ -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": "Показать справку"
}

View File

@ -0,0 +1,7 @@
from core import Core
core = Core()
if __name__ == '__main__':
core.load()
core.start()