- PVSM.RU - https://www.pvsm.ru -
В этой статье я бы хотел рассказать о том, как начал писать свой "терминал" (хотя скорее это кастомный CLI). По умолчанию встроенный в винду терминал не является самым удобным инструментом. На текущий момент конечно есть некоторые эмуляторы терминала с дополнениями, но я решил сделать свое. И вот что из этого вышло.
Я не являюсь Senior программистом и не являюсь богом всея кода на планете. Я обычный 10-классник, пытающийся сделать что-то хоть немного интересное, а не только калькулятор. Это мой первый open-source проект, поэтому не ругайте сильно.
Для начала, чтобы понять, а что я хочу, я залез в интернет почитать об интерфейсе командной строки и как её можно изменять. В итоге получил информацию, что терминал - CLI, а различные её приложения (по типу Node.JS, Django и т.д.) - CLI-Apps. По определению CLI - это и есть интерфейс командной строки.
Дальше нужно было выбрать язык, на котором я хотел писать свою "оболочку". Выбор, как у великого новичка пал на такой язык как Python. Он легкий, удобный, по скорости его вполне хватает.
Начал свою разработку с изучения различных встроенных библиотек питона, по типу os, sys, typing и других. Изучив их, я понял, что они отлично взаимодействуют с командной строкой и они подойдут для её видоизменения. Поэтому начал писать свой код.
Для начала написания кода пришлось определить структуру приложения. Нужно было создать удобную структуру, чтобы вся логика приложения не хранилась в одном файле. Немного поразмыслив, я решил сделать такую структуру:
main.py
config.py
commands_controller.py
commands.py
sys_controller.py
modules/
│
├── example.py
В файле main.py основной класс обработчик, который перенаправляет команды пользователя в специальный контроллер команд.
class Terminode:
def __init__(self):
self.version = config.console_version
self.cur_dir = os.getcwd()
self.username = config.username
self.input_line = f"{self.username} | {self.cur_dir} | "
self.commands = commands_return()
def parse_input(self, input_str: str) -> List[str]:
return input_str.strip().split()
def run(self):
print(f"Terminode - {self.version}")
print("Enter 'help' for commands list / Enter 'exit' for exit app")
while True:
try:
user_input = input(self.input_line).strip()
if not user_input:
continue
parts = self.parse_input(user_input)
command_name = parts[0]
args = parts[1:] if len(parts) > 1 else None
if command_name in self.commands:
self.commands[command_name](args)
else:
execute_system_command(user_input)
self.update_prompt()
except KeyboardInterrupt:
print("nFor quit enter 'exit'")
except EOFError:
print()
self.exit_command()
except Exception as e:
print(f"Error: {e}")
def update_prompt(self):
self.input_line = f"{self.username} | {os.getcwd()} | "
Данный класс проверяет команду на существование её в списке "кастомных команд". Если её нет в списке, то терминал пытается выполнить команду, как системную - встроенную в стандартный терминал с помощью файла sys_controller.py. Также при каждом сообщении обновляется строка ввода, чтобы пользователь мог видеть текущую директорию, в которой он находится.
Все команды проверяются из файла commands.py . Каждая функция в нём начинается с декоратора @command, которая отвечает за регистрацию команды в терминале. Вот пример одной из команд:
@command(name='time')
def time_command(args: List[str] = None):
"""Show time now"""
now = datetime.now()
time_format = '%d-%m-%Y %H:%M:%S'
print(f"Time: {now:{time_format}}")
Каждая такая команда регистрируется с помощью контроллера, который я называл ранее. Это контроллер команд - он отвечает за регистрацию модулей (о них чуть позже) и встроенные команды в моем терминале. Вот таким образом выглядит код, который регистрирует каждую команду из файла commands.py
from typing import Dict, Callable, Optional
COMMANDS: Dict[str, Callable] = {}
def command(name: Optional[str] = None, category: Optional[str] = None):
def decorator(func):
cmd_name = name or func.__name__
cmd_category = category
register_command(cmd_name, cmd_category, func)
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
return decorator
def register_command(name: str, func: Callable):
COMMANDS[name] = func
#Здесь код будет дополняться, поэтому выделена целая функция
Каждая команда проходит через этот регистратор, если у команды есть декоратор. С помощью данной регистрации можно либо улучшать уже существующие команды во встроенном терминале, либо создавать свои.
Я также задумался: "А что, если пользователю не хватит функционала?". Самому мне не сделать всё то, что хочет каждый пользователь, ведь это индивидуальные желания. И мною было принято решение добавить систему модулей.
Модули - моды, которые автоматически подключаются к Terminode (так я назвал свой "терминал"). С помощью модулей каждый сможет обновить мои команды или добавить свои.
На текущий момент можно создавать простые модули, но в будущем я буду развивать направление моддинга в своем приложении.
Для добавления самой системы модов, я обратился к истокам всех программистов - интернет. Долго копаясь, я понял как можно реализовать эту систему.
В файл контроллера команд я добавил такую функцию автоматической загрузки модулей:
import inspect
import importlib
COMMANDS: Dict[str, Callable] = {}
MODULES: Dict[str, Callable] = {}
def load_modules(folder_path: str):
folder = Path(folder_path)
for file in folder.glob("*.py"):
module_name = file.stem
if module_name.startswith("_"):
continue
try:
module = importlib.import_module(f"{folder_path}.{module_name}")
for _, func in inspect.getmembers(module, inspect.isfunction):
if hasattr(func, "_is_command"):
cmd_name = getattr(func, "_command_name", func.__name__)
COMMANDS[cmd_name] = func
except ImportError as e:
print(f"Loading error {module_name}: {e}")
def module(name: Optional[str] = None):
MODULES[name] = name
Данный код автоматически ищет файлы в папке с модулями и подключает их к терминалу. Для активации своего модуля, нужно лишь добавить 2 строчки в свой файл:
from commands_controller import module
module('Simple Example Module')
Так, как я являюсь далеко не самым опытным программистом (а значит новичком), то этот код может показаться читателям странным. Не ругайтесь, я потихоньку совершенствую этот код. Данное приложение Terminode выложено в открытый доступ и является open-source. Внизу две ссылки - на репозиторий и на канал, где будут новости о данном терминале и другом.
GitHub Repo [1] / Telegram-канал [2]
Также хочу сказать, что возможно я добавлю также в будущем эмулятор терминала. Если получится разработать хорошее приложение в консоли, то почему бы не сделать полноценное ПО?
На текущий момент оно ничем не отличается почти от стандартной консоли. Но я буду развивать данный проект. Возможно даже напишу вторую часть статьи =)
Спасибо за прочтение данной статьи!
Автор: tyZie
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/420270
Ссылки в тексте:
[1] GitHub Repo: https://github.com/Tyzie/Terminode
[2] Telegram-канал: https://t.me/tyzie_dev
[3] Источник: https://habr.com/ru/articles/910996/?utm_source=habrahabr&utm_medium=rss&utm_campaign=910996
Нажмите здесь для печати.