moving project into here
This commit is contained in:
parent
efc12fbff2
commit
48b29ef7e1
1
src/config/__init__.py
Normal file
1
src/config/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .config import Config
|
43
src/config/config.py
Normal file
43
src/config/config.py
Normal 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
42
src/console/__init__.py
Normal 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
41
src/console/console.py
Normal 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
1
src/core/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .core import Core
|
150
src/core/core.py
Normal file
150
src/core/core.py
Normal 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
34
src/core/localization.py
Normal 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
29
src/core/user.py
Normal 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
58
src/core/users_storage.py
Normal 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
36
src/languages/en.json
Normal 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
36
src/languages/ky.json
Normal 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
36
src/languages/ru.json
Normal 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": "Показать справку"
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
from core import Core
|
||||
|
||||
core = Core()
|
||||
|
||||
if __name__ == '__main__':
|
||||
core.load()
|
||||
core.start()
|
Loading…
x
Reference in New Issue
Block a user