diff --git a/src/cli.rs b/src/cli.rs deleted file mode 100644 index d18377b..0000000 --- a/src/cli.rs +++ /dev/null @@ -1,1663 +0,0 @@ -// cli.rs - модуль реализующий REPL-интерфейс в проекте fluSQL -// Инициализирует два режима работв: 'lua' и 'sql' - -use std::collections::VecDeque; -use std::io::{self, Write, Read}; -use std::path::Path; - -use unicode_width::UnicodeWidthStr; - -use crate::cluster::ClusterManager; -use crate::history::CommandHistory; -use crate::lua::LuaInterpreter; -use crate::lua_mode::LuaModeContext; -use crate::plugins::{PluginManager, PluginConfig}; -use crate::utils::config::Config; -use std::sync::Arc; - -/// Проверка поддержки цветного вывода в терминале -fn supports_color() -> bool { - // Проверяем переменную окружения TERM - if let Ok(term) = std::env::var("TERM") { - // Поддерживаемые терминалы для всех дистрибутивов Linux - let supported_terms = [ - "xterm", "xterm-256color", "screen", "screen-256color", "tmux", "tmux-256color", - "rxvt", "rxvt-unicode", "linux", "vt100", "vt220", "ansi", "dumb" - ]; - - for supported in &supported_terms { - if term.contains(supported) { - return true; - } - } - } - - // Проверяем наличие переменной COLORTERM (используется в современных дистрибутивах) - if std::env::var("COLORTERM").is_ok() { - return true; - } - - // Проверяем, является ли stdout tty - if atty::is(atty::Stream::Stdout) { - // Дополнительные проверки для разных дистрибутивов - if cfg!(target_os = "linux") { - // Проверяем наличие известных окружений рабочего стола - let desktop_env = std::env::var("XDG_CURRENT_DESKTOP").unwrap_or_default(); - let session = std::env::var("DESKTOP_SESSION").unwrap_or_default(); - - // Поддерживаемые окружения - let supported_desktops = [ - "GNOME", "KDE", "XFCE", "LXDE", "LXQt", "MATE", "Cinnamon", "Pantheon", - "Deepin", "Budgie", "Unity", "i3", "awesome", "dwm", "bspwm", "herbstluftwm" - ]; - - for desktop in &supported_desktops { - if desktop_env.contains(desktop) || session.contains(desktop) { - return true; - } - } - } - } - - false -} - -/// Получение кодов цвета для текущего терминала -fn get_color_codes() -> ColorCodes { - if supports_color() { - ColorCodes { - reset: "\x1b[0m", - bold: "\x1b[1m", - faint: "\x1b[2m", - italic: "\x1b[3m", - underline: "\x1b[4m", - blink: "\x1b[5m", - - // Основные цвета - black: "\x1b[30m", - red: "\x1b[31m", - green: "\x1b[32m", - yellow: "\x1b[33m", - blue: "\x1b[34m", - magenta: "\x1b[35m", - cyan: "\x1b[36m", - white: "\x1b[37m", - - // Яркие цвета - bright_black: "\x1b[90m", - bright_red: "\x1b[91m", - bright_green: "\x1b[92m", - bright_yellow: "\x1b[93m", - bright_blue: "\x1b[94m", - bright_magenta: "\x1b[95m", - bright_cyan: "\x1b[96m", - bright_white: "\x1b[97m", - - // Фоны - bg_black: "\x1b[40m", - bg_red: "\x1b[41m", - bg_green: "\x1b[42m", - bg_yellow: "\x1b[43m", - bg_blue: "\x1b[44m", - bg_magenta: "\x1b[45m", - bg_cyan: "\x1b[46m", - bg_white: "\x1b[47m", - - // Яркие фоны - bg_bright_black: "\x1b[100m", - bg_bright_red: "\x1b[101m", - bg_bright_green: "\x1b[102m", - bg_bright_yellow: "\x1b[103m", - bg_bright_blue: "\x1b[104m", - bg_bright_magenta: "\x1b[105m", - bg_bright_cyan: "\x1b[106m", - bg_bright_white: "\x1b[107m", - - // ОСНОВНОЙ ЦВЕТ #00bfff для всего интерфейса flusql - // True color: RGB(0, 191, 255) - яркий голубой/синий - // 256-color: код 39 (яркий голубой) - ближайший к #00bfff - header_color: "\x1b[38;5;39m", // #00bfff для 256-цветных терминалов - header_color_true: "\x1b[38;2;0;191;255m", // #00bfff для true color терминалов - success_color: "\x1b[38;5;39m", // #00bfff для успешных операций - warning_color: "\x1b[38;5;214m", // Оранжевый - error_color: "\x1b[38;5;196m", // Красный - info_color: "\x1b[38;5;39m", // #00bfff для информационных сообщений - accent_color: "\x1b[38;5;39m", // #00bfff как акцентный цвет - - // Специальные коды для отладки - test_true_color: "\x1b[38;2;0;191;255m", - test_256_color_39: "\x1b[38;5;39m", - test_256_color_38: "\x1b[38;5;38m", - test_256_color_33: "\x1b[38;5;33m", - } - } else { - ColorCodes::no_color() - } -} - -/// Коды цветов для терминала -#[derive(Clone)] -struct ColorCodes { - reset: &'static str, - bold: &'static str, - faint: &'static str, - italic: &'static str, - underline: &'static str, - blink: &'static str, - - black: &'static str, - red: &'static str, - green: &'static str, - yellow: &'static str, - blue: &'static str, - magenta: &'static str, - cyan: &'static str, - white: &'static str, - - bright_black: &'static str, - bright_red: &'static str, - bright_green: &'static str, - bright_yellow: &'static str, - bright_blue: &'static str, - bright_magenta: &'static str, - bright_cyan: &'static str, - bright_white: &'static str, - - bg_black: &'static str, - bg_red: &'static str, - bg_green: &'static str, - bg_yellow: &'static str, - bg_blue: &'static str, - bg_magenta: &'static str, - bg_cyan: &'static str, - bg_white: &'static str, - - bg_bright_black: &'static str, - bg_bright_red: &'static str, - bg_bright_green: &'static str, - bg_bright_yellow: &'static str, - bg_bright_blue: &'static str, - bg_bright_magenta: &'static str, - bg_bright_cyan: &'static str, - bg_bright_white: &'static str, - - // Основной цвет проекта flusql - #00bfff (яркий голубой/синий) - header_color: &'static str, - header_color_true: &'static str, - success_color: &'static str, - warning_color: &'static str, - error_color: &'static str, - info_color: &'static str, - accent_color: &'static str, - - // Коды для тестирования - test_true_color: &'static str, - test_256_color_39: &'static str, - test_256_color_38: &'static str, - test_256_color_33: &'static str, -} - -impl ColorCodes { - fn no_color() -> Self { - Self { - reset: "", - bold: "", - faint: "", - italic: "", - underline: "", - blink: "", - - black: "", - red: "", - green: "", - yellow: "", - blue: "", - magenta: "", - cyan: "", - white: "", - - bright_black: "", - bright_red: "", - bright_green: "", - bright_yellow: "", - bright_blue: "", - bright_magenta: "", - bright_cyan: "", - bright_white: "", - - bg_black: "", - bg_red: "", - bg_green: "", - bg_yellow: "", - bg_blue: "", - bg_magenta: "", - bg_cyan: "", - bg_white: "", - - bg_bright_black: "", - bg_bright_red: "", - bg_bright_green: "", - bg_bright_yellow: "", - bg_bright_blue: "", - bg_bright_magenta: "", - bg_bright_cyan: "", - bg_bright_white: "", - - header_color: "", - header_color_true: "", - success_color: "", - warning_color: "", - error_color: "", - info_color: "", - accent_color: "", - - test_true_color: "", - test_256_color_39: "", - test_256_color_38: "", - test_256_color_33: "", - } - } - - /// Прямая раскраска текста указанным кодом - fn colorize(&self, text: &str, color: &str) -> String { - if color.is_empty() { - text.to_string() - } else { - format!("{}{}{}", color, text, self.reset) - } - } - - /// Основной цвет flusql - #00bfff для заголовков - fn header(&self, text: &str) -> String { - self.apply_primary_color(text, self.header_color_true, self.header_color) - } - - /// Успешные операций - цвет #00bfff - fn success(&self, text: &str) -> String { - self.apply_primary_color(text, self.header_color_true, self.success_color) - } - - /// Предупреждения - оранжевый - fn warning(&self, text: &str) -> String { - self.colorize(text, self.warning_color) - } - - /// Ошибки - красный - fn error(&self, text: &str) -> String { - self.colorize(text, self.error_color) - } - - /// Информационные сообщения - цвет #00bfff (заменяет cyan) - fn info(&self, text: &str) -> String { - self.apply_primary_color(text, self.header_color_true, self.info_color) - } - - /// Акцентный цвет - #00bfff (заменяет cyan) - fn accent(&self, text: &str) -> String { - self.apply_primary_color(text, self.header_color_true, self.accent_color) - } - - /// Жирный текст - fn bold(&self, text: &str) -> String { - self.colorize(text, self.bold) - } - - /// Подчеркнутый текст - fn underline(&self, text: &str) -> String { - self.colorize(text, self.underline) - } - - /// Основной метод применения цвета #00bfff - fn apply_primary_color(&self, text: &str, true_color: &str, fallback_256: &str) -> String { - if !supports_color() { - return text.to_string(); - } - - if self.supports_true_color() { - self.colorize(text, true_color) - } else if self.supports_256_colors() { - self.colorize(text, fallback_256) - } else { - // Для 8-цветных терминалов используем стандартный cyan (ближайший к #00bfff) - self.colorize(text, self.cyan) - } - } - - /// Проверка поддержки true color (24-bit цветов) - fn supports_true_color(&self) -> bool { - if !supports_color() { - return false; - } - - // Проверяем переменные окружения для поддержки true color - if let Ok(colorterm) = std::env::var("COLORTERM") { - if colorterm.contains("truecolor") || colorterm.contains("24bit") { - return true; - } - } - - // Проверяем переменную TERM для современных терминалов - if let Ok(term) = std::env::var("TERM") { - let truecolor_terms = [ - "xterm-truecolor", - "xterm-256color", // Многие современные терминалы с 256 цветами поддерживают true color - "screen-truecolor", - "screen-256color", - "tmux-truecolor", - "tmux-256color", - "rxvt-unicode-256color", - "alacritty", - "kitty", - "wezterm", - "foot", - "konsole", - "gnome-terminal", - "terminology", - ]; - - for truecolor_term in &truecolor_terms { - if term.contains(truecolor_term) { - return true; - } - } - } - - false - } - - /// Проверка поддержки 256 цветов - fn supports_256_colors(&self) -> bool { - if !supports_color() { - return false; - } - - if let Ok(term) = std::env::var("TERM") { - term.contains("256color") - } else { - false - } - } - - /// Тестирование цветов (для отладки) - fn test_colors(&self) -> String { - let mut result = String::new(); - result.push_str("\n=== ТЕСТИРОВАНИЕ ЦВЕТОВ flusql ===\n"); - result.push_str(&format!("True color #00bfff: {}Это цвет #00bfff (RGB: 0, 191, 255){}\n", - self.test_true_color, self.reset)); - result.push_str(&format!("256-color код 39: {}Это код 39 (яркий голубой){}\n", - self.test_256_color_39, self.reset)); - result.push_str(&format!("256-color код 38: {}Это код 38 (голубой){}\n", - self.test_256_color_38, self.reset)); - result.push_str(&format!("256-color код 33: {}Это код 33 (темно-голубой){}\n", - self.test_256_color_33, self.reset)); - result.push_str(&format!("Стандартный cyan: {}Это стандартный cyan (ближайший к #00bfff){}\n", - self.cyan, self.reset)); - result.push_str(&format!("Стандартный синий: {}Это стандартный синий{}\n", - self.blue, self.reset)); - result.push_str("===================================\n"); - result - } -} - -/// Получить ширину строки в терминале (учитывает Unicode символы) -fn display_width(s: &str) -> usize { - UnicodeWidthStr::width(s) -} - -/// Форматирование таблицы с использованием непрерывных линий -fn format_table(headers: &[String], rows: &[Vec]) -> String { - if rows.is_empty() { - return String::from("Нет данных"); - } - - let colors = get_color_codes(); - - // Рассчитываем ширину колонок с учетом отображения Unicode - let mut col_widths: Vec = headers.iter().map(|h| display_width(h)).collect(); - - for row in rows { - for (i, cell) in row.iter().enumerate() { - if i < col_widths.len() { - col_widths[i] = col_widths[i].max(display_width(cell)); - } - } - } - - // Добавляем отступы - for width in &mut col_widths { - *width += 2; // По 1 пробелу с каждой стороны - } - - let mut result = String::new(); - - // Верхняя граница - result.push('┌'); - for (i, &width) in col_widths.iter().enumerate() { - for _ in 0..width { - result.push('─'); - } - if i < col_widths.len() - 1 { - result.push('┬'); - } - } - result.push_str("┐\n"); - - // Заголовки - result.push('│'); - for (i, (header, &width)) in headers.iter().zip(col_widths.iter()).enumerate() { - let header_width = display_width(header); - let padding = width - header_width; - let left_pad = padding / 2; - let right_pad = padding - left_pad; - result.push_str(&format!("{}{}{}", - " ".repeat(left_pad), - colors.bold(header), - " ".repeat(right_pad))); - result.push('│'); - } - result.push('\n'); - - // Разделитель заголовков - result.push('├'); - for (i, &width) in col_widths.iter().enumerate() { - for _ in 0..width { - result.push('─'); - } - if i < col_widths.len() - 1 { - result.push('┼'); - } - } - result.push_str("┤\n"); - - // Данные - for (row_idx, row) in rows.iter().enumerate() { - result.push('│'); - for (i, (cell, &width)) in row.iter().zip(col_widths.iter()).enumerate() { - let cell_width = display_width(cell); - let padding = width - cell_width; - - // Для первой колонки - выравнивание по левому краю, для остальных - по центру - if i == 0 { - result.push_str(&format!(" {}{}{}", - cell, - " ".repeat(padding - 1), - "")); - } else { - let left_pad = padding / 2; - let right_pad = padding - left_pad; - result.push_str(&format!("{}{}{}", - " ".repeat(left_pad), - cell, - " ".repeat(right_pad))); - } - result.push('│'); - } - result.push('\n'); - - // Добавляем горизонтальные линии между строками (кроме последней) - if row_idx < rows.len() - 1 { - result.push('├'); - for (i, &width) in col_widths.iter().enumerate() { - for _ in 0..width { - result.push('─'); - } - if i < col_widths.len() - 1 { - result.push('┼'); - } - } - result.push_str("┤\n"); - } - } - - // Нижняя граница - result.push('└'); - for (i, &width) in col_widths.iter().enumerate() { - for _ in 0..width { - result.push('─'); - } - if i < col_widths.len() - 1 { - result.push('┴'); - } - } - result.push('┘'); - - result -} - -/// Форматирование таблицы с одной колонкой -fn format_single_column_table(items: &[String]) -> String { - if items.is_empty() { - return String::new(); - } - - let colors = get_color_codes(); - - // Находим максимальную ширину строки с учетом Unicode - let max_width = items.iter().map(|item| display_width(item)).max().unwrap_or(0); - - // Ширина колонки - let col_width = max_width + 2; // +2 для пробелов с каждой стороны - - let mut result = String::new(); - - // Верхняя граница - result.push('┌'); - for _ in 0..col_width { - result.push('─'); - } - result.push('┐'); - result.push('\n'); - - // Элементы - for (item_idx, item) in items.iter().enumerate() { - result.push('│'); - result.push(' '); - - // Разделяем на маркер и текст - let (marker, text) = if let Some(stripped) = item.strip_prefix("• ") { - ("• ", stripped) - } else { - ("", item.as_str()) - }; - - // Используем цвет #00bfff для всех информационных сообщений (заменяет cyan) - result.push_str(&colors.info(marker)); - result.push_str(text); - result.push_str(colors.reset); - - // Заполняем оставшееся пространство пробелами с учетом ширины в символы - let item_width = display_width(item); - let spaces_needed = col_width - item_width - 1; // -1 для пробела в начале - if spaces_needed > 0 { - result.push_str(&" ".repeat(spaces_needed)); - } - - result.push('│'); - result.push('\n'); - - // Добавляем горизонтальные линии между строками (кроме последней) - if item_idx < items.len() - 1 { - result.push('├'); - for _ in 0..col_width { - result.push('─'); - } - result.push('┤'); - result.push('\n'); - } - } - - // Нижняя граница - result.push('└'); - for _ in 0..col_width { - result.push('─'); - } - result.push('┘'); - result.push('\n'); - - result -} - -/// Показать справку в виде таблицы с горизонтальными линиями -fn show_help_table() -> String { - let colors = get_color_codes(); - let mut output = String::new(); - - // Таблица команд - let headers = vec!["Команда".to_string(), "Описание".to_string(), "Пример".to_string()]; - - let rows = vec![ - vec!["CREATE DATABASE".to_string(), "Создать базу данных".to_string(), "CREATE DATABASE mydb;".to_string()], - vec!["DROP DATABASE".to_string(), "Удалить базу данных".to_string(), "DROP DATABASE mydb;".to_string()], - vec!["SHOW DATABASES".to_string(), "Показать список баз данных".to_string(), "SHOW DATABASES;".to_string()], - vec!["\\dt".to_string(), "Показать таблицы в текущей БД".to_string(), "\\dt;".to_string()], - vec!["\\d таблица".to_string(), "Описание структуры таблицы".to_string(), "\\d users;".to_string()], - vec!["USE".to_string(), "Использовать базу данных".to_string(), "USE mydb;".to_string()], - vec!["CREATE TABLE".to_string(), "Создать таблицу".to_string(), "CREATE TABLE users (id INT, name VARCHAR(255));".to_string()], - vec!["ALTER TABLE".to_string(), "Изменить таблицу".to_string(), "ALTER TABLE users ADD COLUMN age INT;".to_string()], - vec!["DROP TABLE".to_string(), "Удалить таблицу".to_string(), "DROP TABLE users;".to_string()], - vec!["DROP TABLE CASCADE".to_string(), "Удалить таблицу с зависимостями".to_string(), "DROP TABLE users CASCADE;".to_string()], - vec!["SELECT".to_string(), "Выбрать данные".to_string(), "SELECT * FROM users;".to_string()], - vec!["INSERT".to_string(), "Вставить данные".to_string(), "INSERT INTO users (id, name) VALUES (1, 'Alice');".to_string()], - vec!["UPDATE".to_string(), "Обновить данные".to_string(), "UPDATE users SET age = 30 WHERE id = 1;".to_string()], - vec!["DELETE".to_string(), "Удалить данные".to_string(), "DELETE FROM users WHERE id = 1;".to_string()], - vec!["CREATE INDEX".to_string(), "Создать индекс".to_string(), "CREATE INDEX idx_users_id ON users (id);".to_string()], - vec!["DROP INDEX".to_string(), "Удалить индекс".to_string(), "DROP INDEX idx_users_id;".to_string()], - vec!["CREATE TRIGGER".to_string(), "Создать триггер".to_string(), "CREATE TRIGGER check_age BEFORE INSERT ON users FOR EACH ROW EXECUTE FUNCTION check_age();".to_string()], - vec!["DROP TRIGGER".to_string(), "Удалить триггер".to_string(), "DROP TRIGGER check_age ON users;".to_string()], - vec!["EXPLAIN".to_string(), "Объяснить план выполнения".to_string(), "EXPLAIN SELECT * FROM users;".to_string()], - vec!["EXPLAIN ANALYZE".to_string(), "Выполнить и объяснить запрос".to_string(), "EXPLAIN ANALYZE SELECT * FROM users;".to_string()], - vec!["BEGIN".to_string(), "Начать транзакцию".to_string(), "BEGIN;".to_string()], - vec!["COMMIT".to_string(), "Зафиксировать транзакцию".to_string(), "COMMIT;".to_string()], - vec!["ROLLBACK".to_string(), "Откатить транзакцию".to_string(), "ROLLBACK;".to_string()], - vec!["COPY TO".to_string(), "Экспорт в CSV".to_string(), "COPY users TO 'users.csv' WITH CSV HEADER;".to_string()], - vec!["COPY FROM".to_string(), "Импорт из CSV".to_string(), "COPY users FROM 'users.csv' WITH CSV HEADER;".to_string()], - vec!["CREATE SEQUENCE".to_string(), "Создать последовательность".to_string(), "CREATE SEQUENCE users_id_seq;".to_string()], - vec!["CREATE TYPE".to_string(), "Создать составной тип".to_string(), "CREATE TYPE address AS (street TEXT, city TEXT);".to_string()], - vec!["CREATE VIEW".to_string(), "Создать представление".to_string(), "CREATE VIEW user_emails AS SELECT id, email FROM users;".to_string()], - vec!["HISTORY".to_string(), "Показать историю команд".to_string(), "HISTORY;".to_string()], - vec!["HISTORY CLEAR".to_string(), "Очистить историю команд".to_string(), "HISTORY CLEAR;".to_string()], - vec!["HISTORY EXPORT".to_string(), "Экспорт истории в файл".to_string(), "HISTORY EXPORT 'history.txt';".to_string()], - vec!["!!".to_string(), "Повторить последнюю команду".to_string(), "!!".to_string()], - vec!["!n".to_string(), "Выполнить команду из истории по номеру".to_string(), "!3".to_string()], - vec!["HELP".to_string(), "Показать справку".to_string(), "HELP;".to_string()], - vec!["EXIT".to_string(), "Выйти из программы".to_string(), "EXIT;".to_string()], - vec!["QUIT".to_string(), "Выйти из программы".to_string(), "QUIT;".to_string()], - vec!["lua.mode".to_string(), "Войти в Lua режим".to_string(), "lua.mode".to_string()], - vec!["plugins list".to_string(), "Список загруженных плагинов".to_string(), "plugins list".to_string()], - vec!["plugin load".to_string(), "Загрузить плагин".to_string(), "plugin load './plugins/example.lua'".to_string()], - vec!["plugin unload".to_string(), "Выгрузить плагин".to_string(), "plugin unload 'plugin_id'".to_string()], - vec!["plugins reload".to_string(), "Перезагрузить все плагины".to_string(), "plugins reload".to_string()], - ]; - - // Команды управления кластером в Lua режиме - let cluster_headers = vec!["Функция Lua".to_string(), "Описание".to_string()]; - - let cluster_rows = vec![ - vec!["cluster.get_status()".to_string(), "Получить полный статус кластера".to_string()], - vec!["cluster.coord_status()".to_string(), "Показать IP-адрес текущего координатора".to_string()], - vec!["cluster.add_node(node_id, address)".to_string(), "Добавить узел".to_string()], - vec!["cluster.evict(node_id)".to_string(), "Исключить узел из кластера".to_string()], - vec!["cluster.elect_coordinator()".to_string(), "Выбрать нового координатора".to_string()], - vec!["cluster.rebalance()".to_string(), "Ребалансировать кластер".to_string()], - vec!["cluster.add_shard(shard_id, master, slaves)".to_string(), "Добавить шард".to_string()], - vec!["cluster.remove_shard(shard_id)".to_string(), "Удалить шард".to_string()], - vec!["cluster.start_replication(source, target)".to_string(), "Начать репликацию".to_string()], - ]; - - // Команды плагинов в Lua режиме - let plugin_headers = vec!["Функция Lua".to_string(), "Описание".to_string()]; - - let plugin_rows = vec![ - vec!["plugins.list()".to_string(), "Список загруженных плагинов".to_string()], - vec!["plugins.get(plugin_id)".to_string(), "Получить информацию о плагине".to_string()], - vec!["plugins.reload()".to_string(), "Перезагрузить все плагины".to_string()], - vec!["plugins.emit_event(event_name, data)".to_string(), "Отправить событие плагинам".to_string()], - ]; - - // Тестирование цветов (можно отключить) - // output.push_str(&colors.test_colors()); - - // Форматируем основную таблицу команд - output.push_str(&format!("\n{}\n", colors.header("ОСНОВНЫЕ КОМАНДЫ:"))); - output.push_str(&format_table(&headers, &rows)); - - // Команды управления кластером в Lua режиме - output.push_str(&format!("\n{}\n", colors.header("КОМАНДЫ УПРАВЛЕНИЯ КЛАСТЕРОМ (только в Lua режиме):"))); - output.push_str(&format!("{}\n", colors.info("1. Введите команду 'lua.mode' для входа в Lua режим"))); - output.push_str(&format!("{}\n", colors.info("2. В Lua режиме используйте функции:"))); - output.push_str(&format_table(&cluster_headers, &cluster_rows)); - - // Команды плагинов в Lua режиме - output.push_str(&format!("\n{}\n", colors.header("ПЛАГИНЫ (только в Lua режиме):"))); - output.push_str(&format!("{}\n", colors.info("1. Введите команду 'lua.mode' для входа в Lua режим"))); - output.push_str(&format!("{}\n", colors.info("2. В Lua режиме используйте функции:"))); - output.push_str(&format_table(&plugin_headers, &plugin_rows)); - - // Автоматические перевыборы координатора - output.push_str(&format!("\n{}\n", colors.header("АВТОМАТИЧЕСКИЕ ПЕРЕВЫБОРЫ КООРДИНАТОРА:"))); - output.push_str(&format_single_column_table(&[ - "• Координатор автоматически перевыбирается при его отказе".to_string(), - "• Новый координатор выбирается среди онлайн узлов".to_string(), - "• Критерий выбора: узел с наибольшим количеством шардов".to_string(), - "• Heartbeat проверки каждые 15 секунд".to_string(), - "• Timeout координатора: 30 секунд без heartbeat".to_string(), - ])); - - // История команд - output.push_str(&format!("\n{}\n", colors.header("ИСТОРИЯ КОМАНД:"))); - output.push_str(&format_single_column_table(&[ - "• История автоматически сохраняется между сессиями".to_string(), - "• Используйте стрелки вверх/вниз для навигации по истории".to_string(), - "• !! - повторить последнюю команду".to_string(), - "• !n - выполнить команду с номером n из истории".to_string(), - "• HISTORY - показать всю историю команд".to_string(), - "• HISTORY CLEAR - очистить историю".to_string(), - "• HISTORY EXPORT 'file' - экспортировать историю в файл".to_string(), - ])); - - // Система плагинов - output.push_str(&format!("\n{}\n", colors.header("СИСТЕМА ПЛАГИНОВ:"))); - output.push_str(&format_single_column_table(&[ - "• Плагины пишутся на Lua и загружаются из директории 'plugins'".to_string(), - "• Поддержка изоляции (sandbox) для безопасности".to_string(), - "• Система событий и триггеров".to_string(), - "• Зависимости между плагинами".to_string(), - "• plugins list - список плагинов".to_string(), - "• plugin load 'path' - загрузить плагин".to_string(), - "• plugin unload 'id' - выгрузить плагин".to_string(), - "• plugins reload - перезагрузить все плагины".to_string(), - ])); - - // Конфигурация - output.push_str(&format!("\n{}\n", colors.header("КОНФИГУРАЦИЯ:"))); - output.push_str(&format_single_column_table(&[ - "• Конфигурация загружается из config.toml".to_string(), - "• Настройки сервера: порт, хост, пул потоков".to_string(), - "• Настройки БД: размер страницы, кэш, WAL, MVCC".to_string(), - "• Настройки плагинов: директория, sandbox, таймауты".to_string(), - "• Настройки сети и безопасности".to_string(), - "• Переменная окружения FLUSQL_CONFIG для указания пути к конфигу".to_string(), - ])); - - // Информация о поддержке цветов - if supports_color() { - if get_color_codes().supports_true_color() { - output.push_str(&format!("\n{} {} {}\n", colors.header("ЦВЕТНОЙ ВЫВОД:"), colors.success("активирован (true color)"), colors.reset)); - } else if get_color_codes().supports_256_colors() { - output.push_str(&format!("\n{} {} {}\n", colors.header("ЦВЕТНОЙ ВЫВОД:"), colors.success("активирован (256 цветов)"), colors.reset)); - } else { - output.push_str(&format!("\n{} {} {}\n", colors.header("ЦВЕТНОЙ ВЫВОД:"), colors.success("активирован (8 цветов)"), colors.reset)); - } - } else { - output.push_str(&format!("\n{} {} {}\n", colors.header("ЦВЕТНОЙ ВЫВОД:"), colors.warning("недоступен"), colors.reset)); - } - - output -} - -/// Показать справку для Lua режима -fn show_lua_help_table() -> String { - let colors = get_color_codes(); - let mut output = String::new(); - - output.push_str(&format!("\n{}\n", colors.header("КОМАНДЫ LUA РЕЖИМА:"))); - - // Основные команды Lua режима - let main_headers = vec!["Команда".to_string(), "Описание".to_string()]; - let main_rows = vec![ - vec!["sql.mode".to_string(), "Вернуться в SQL режим".to_string()], - vec!["exit / quit".to_string(), "Выйти из Lua режима".to_string()], - vec!["help".to_string(), "Показать эту справку".to_string()], - vec!["history".to_string(), "Показать историю Lua команд".to_string()], - vec!["clear-history".to_string(), "Очистить историю Lua команд".to_string()], - ]; - output.push_str(&format_table(&main_headers, &main_rows)); - - // Функции кластера - output.push_str(&format!("\n{}\n", colors.header("ФУНКЦИИ КЛАСТЕРА:"))); - let cluster_headers = vec!["Функция Lua".to_string(), "Описание".to_string()]; - let cluster_rows = vec![ - vec!["cluster.get_status()".to_string(), "Получить полный статус кластера".to_string()], - vec!["cluster.coord_status()".to_string(), "Показать IP-адрес текущего координатора".to_string()], - vec!["cluster.add_node(node_id, address)".to_string(), "Добавить узел".to_string()], - vec!["cluster.evict(node_id)".to_string(), "Исключить узел из кластера".to_string()], - vec!["cluster.elect_coordinator()".to_string(), "Выбрать нового координатора".to_string()], - vec!["cluster.rebalance()".to_string(), "Ребалансировать кластер".to_string()], - vec!["cluster.add_shard(shard_id, master, slaves)".to_string(), "Добавить шард".to_string()], - vec!["cluster.remove_shard(shard_id)".to_string(), "Удалить шард".to_string()], - vec!["cluster.start_replication(source, target)".to_string(), "Начать репликации".to_string()], - ]; - output.push_str(&format_table(&cluster_headers, &cluster_rows)); - - // Функции плагинов - output.push_str(&format!("\n{}\n", colors.header("ФУНКЦИИ ПЛАГИНОВ:"))); - let plugin_headers = vec!["Функция Lua".to_string(), "Описание".to_string()]; - let plugin_rows = vec![ - vec!["plugins.list()".to_string(), "Список загруженных плагинов".to_string()], - vec!["plugins.get(plugin_id)".to_string(), "Получить информацию о плагине".to_string()], - vec!["plugins.reload()".to_string(), "Перезагрузить все плагины".to_string()], - vec!["plugins.emit_event(event_name, data)".to_string(), "Отправить событие плагинам".to_string()], - ]; - output.push_str(&format_table(&plugin_headers, &plugin_rows)); - - // Примеры Lua кода - output.push_str(&format!("\n{}\n", colors.header("ПРИМЕРЫ LUA КОДА:"))); - output.push_str(&format_single_column_table(&[ - "• print('Hello from Lua!')".to_string(), - "• for i=1,5 do print('Number: ' .. i) end".to_string(), - "• status = cluster.get_status()".to_string(), - "• print('Coordinator: ' .. status.coordinator_id)".to_string(), - "• plugins.list()".to_string(), - "• plugins.emit_event('custom.event', '{ \"data\": \"value\" }')".to_string(), - ])); - - // Встроенные функции Lua - output.push_str(&format!("\n{}\n", colors.header("ВСТРОЕННЫЕ ФУНКЦИИ LUA:"))); - output.push_str(&format_single_column_table(&[ - "• Все стандартные функции Lua доступны".to_string(), - "• math, string, table, os, io - стандартные библиотеки".to_string(), - "• print() - вывод в консоль".to_string(), - "• type() - определение типа переменной".to_string(), - "• pairs() и ipairs() - итерация по таблицам".to_string(), - ])); - - output -} - -/// Структура для управления историей команд в реальном времени -struct RealtimeHistory { - commands: VecDeque, - current_index: isize, - max_size: usize, -} - -impl RealtimeHistory { - fn new(max_size: usize) -> Self { - Self { - commands: VecDeque::with_capacity(max_size), - current_index: -1, - max_size, - } - } - - fn add(&mut self, command: String) { - // Удаляем дубликаты - if let Some(pos) = self.commands.iter().position(|c| c == &command) { - self.commands.remove(pos); - } - - self.commands.push_back(command); - while self.commands.len() > self.max_size { - self.commands.pop_front(); - } - - // Сбрасываем индекс - self.current_index = -1; - } - - fn get_previous(&mut self) -> Option { - if self.commands.is_empty() { - return None; - } - - if self.current_index < 0 { - // Начинаем с последней команды - self.current_index = self.commands.len() as isize - 1; - } else if self.current_index > 0 { - self.current_index -= 1; - } else { - // Уже на первой команде, возвращаем ее - return self.commands.get(0).cloned(); - } - - self.commands.get(self.current_index as usize).cloned() - } - - fn get_next(&mut self) -> Option { - if self.commands.is_empty() || self.current_index < 0 { - return None; - } - - if self.current_index < self.commands.len() as isize - 1 { - self.current_index += 1; - self.commands.get(self.current_index as usize).cloned() - } else { - // Достигли конца, сбрасываем индекс и возвращаем пустую строку - self.current_index = -1; - Some(String::new()) - } - } - - fn reset_index(&mut self) { - self.current_index = -1; - } - - fn get_all(&self) -> Vec { - self.commands.iter().cloned().collect() - } - - fn clear(&mut self) { - self.commands.clear(); - self.current_index = -1; - } -} - -/// Функция для чтения ввода с поддержкой истории (аналог readline) -fn read_line_with_history(prompt: &str, history: &mut RealtimeHistory) -> Result { - let colors = get_color_codes(); - print!("{}", colors.header(prompt)); - io::stdout().flush()?; - - let mut input = String::new(); - let mut cursor_pos = 0; - let mut saved_input = String::new(); - - // Устанавливаем raw режим для терминала - let mut termios = libc::termios { c_iflag: 0, c_oflag: 0, c_cflag: 0, c_lflag: 0, c_line: 0, c_cc: [0; 32], c_ispeed: 0, c_ospeed: 0 }; - - unsafe { - // Получаем текущие настройки терминала - if libc::tcgetattr(0, &mut termios) != 0 { - // Если не удалось получить настройки терминала, используем простой ввод - return read_line_simple(prompt); - } - - let mut raw_termios = termios; - // Отключаем канонический режим и эхо - raw_termios.c_lflag &= !(libc::ICANON | libc::ECHO); - // Устанавливаем минимальное количество символов для чтения и timeout - raw_termios.c_cc[libc::VMIN] = 1; - raw_termios.c_cc[libc::VTIME] = 0; - - // Применяем raw режим - if libc::tcsetattr(0, libc::TCSANOW, &raw_termios) != 0 { - // Если не удалось установить raw режим, используем простой ввод - return read_line_simple(prompt); - } - } - - // Функция восстановления настроек терминала - let restore_termios = move || { - unsafe { - libc::tcsetattr(0, libc::TCSANOW, &termios); - } - }; - - loop { - let mut buffer = [0; 1]; - - // Читаем один символ - let bytes_read = match io::stdin().read(&mut buffer) { - Ok(bytes) => bytes, - Err(e) => { - restore_termios(); - return Err(e); - } - }; - - if bytes_read == 0 { - restore_termios(); - return Ok(input); - } - - let c = buffer[0]; - - match c { - // Enter (13 или 10) - 13 | 10 => { - restore_termios(); - println!(); - let trimmed = input.trim().to_string(); - if !trimmed.is_empty() { - history.add(trimmed.clone()); - } - return Ok(trimmed); - } - // Backspace или Delete - 8 | 127 => { - if cursor_pos > 0 { - input.remove(cursor_pos - 1); - cursor_pos -= 1; - - // Очищаем строку и перерисовываем - print!("\r{}", " ".repeat(prompt.len() + input.len() + 2)); - print!("\r{}{}", colors.header(prompt), input); - io::stdout().flush()?; - } - } - // Escape последовательности (стрелки и т.д.) - 27 => { - // Читаем следующие 2 символа для определения escape последовательности - let mut next_buffer = [0; 2]; - if io::stdin().read(&mut next_buffer[0..1]).is_ok() && next_buffer[0] == 91 { - if io::stdin().read(&mut next_buffer[1..2]).is_ok() { - match next_buffer[1] { - // Стрелка вверх - 65 => { - if let Some(prev_command) = history.get_previous() { - // Сохраняем текущий ввод - if history.current_index == history.commands.len() as isize - 1 { - saved_input = input.clone(); - } - - input = prev_command.clone(); - cursor_pos = input.len(); - - print!("\r{}", " ".repeat(prompt.len() + input.len() + 2)); - print!("\r{}{}", colors.header(prompt), input); - io::stdout().flush()?; - } - } - // Стрелка вниз - 66 => { - if let Some(next_command) = history.get_next() { - input = next_command.clone(); - if input.is_empty() && !saved_input.is_empty() { - input = saved_input.clone(); - saved_input.clear(); - } - cursor_pos = input.len(); - - print!("\r{}", " ".repeat(prompt.len() + input.len() + 2)); - print!("\r{}{}", colors.header(prompt), input); - io::stdout().flush()?; - } - } - // Стрелка вправо - 67 => { - if cursor_pos < input.len() { - cursor_pos += 1; - // Показываем позицию курсора - print!("\r{}{}", colors.header(prompt), input); - // Позиционируем курсор - print!("\x1b[{}C", cursor_pos + prompt.len() - input.len()); - io::stdout().flush()?; - } - } - // Стрелка влево - 68 => { - if cursor_pos > 0 { - cursor_pos -= 1; - // Показываем позицию курсора - print!("\r{}{}", colors.header(prompt), input); - // Позиционируем курсор - print!("\x1b[{}C", cursor_pos + prompt.len() - input.len()); - io::stdout().flush()?; - } - } - // Home (часто 72 или 1~) - 72 | 49 => { - cursor_pos = 0; - print!("\r{}{}", colors.header(prompt), input); - print!("\x1b[{}C", prompt.len()); - io::stdout().flush()?; - } - // End (часто 70 или 4~) - 70 | 52 => { - cursor_pos = input.len(); - print!("\r{}{}", colors.header(prompt), input); - io::stdout().flush()?; - } - // Delete (51~) - 51 => { - if cursor_pos < input.len() { - let _ = input.remove(cursor_pos); - print!("\r{}", " ".repeat(prompt.len() + input.len() + 2)); - print!("\r{}{}", colors.header(prompt), input); - io::stdout().flush()?; - } - } - _ => {} - } - } - } - } - // Ctrl+C (3) - прерывание - 3 => { - restore_termios(); - println!("^C"); - return Err(io::Error::new(io::ErrorKind::Interrupted, "Ctrl+C pressed")); - } - // Ctrl+D (4) - EOF - 4 => { - restore_termios(); - return Ok(String::new()); - } - // Обычные символы - _ if c >= 32 => { - // Безопасное преобразование u8 в char - let ch = char::from_u32(c as u32).unwrap_or('?'); - input.insert(cursor_pos, ch); - cursor_pos += 1; - - // Очищаем строку и перерисовываем - print!("\r{}", " ".repeat(prompt.len() + input.len() + 2)); - print!("\r{}{}", colors.header(prompt), input); - io::stdout().flush()?; - } - _ => {} - } - } -} - -/// Простой ввод строки без поддержки стрелок (fallback) -fn read_line_simple(prompt: &str) -> Result { - let colors = get_color_codes(); - print!("{}", colors.header(prompt)); - io::stdout().flush()?; - - let mut input = String::new(); - io::stdin().read_line(&mut input)?; - - Ok(input.trim().to_string()) -} - -/// Запуск REPL интерфейса -pub async fn start_repl() -> Result<(), Box> { - // Инициализация истории команд - let history = CommandHistory::new(1000, ".flusql_history"); - let current_session = "default".to_string(); - - let colors = get_color_codes(); - - println!(); - println!("{}", colors.header("Distributed wide-column store")); - println!("{}", colors.header(&format!("version {}", crate::VERSION))); - - // Загрузка конфигурации - let config = Config::from_env_or_file("config.toml").unwrap_or_default(); - - // Инициализация менеджера кластера (из конфигурации) - let cluster_manager = Arc::new(ClusterManager::new( - &config.cluster.node_id, - &config.cluster.node_address - )); - - // Инициализация Lua интерпретатора - let mut lua_interpreter = LuaInterpreter::new(); - - // Регистрируем функции кластера в Lua - if let Err(e) = lua_interpreter.register_cluster_functions(Arc::clone(&cluster_manager)) { - println!("{}", colors.warning(&format!("Failed to register cluster functions: {}", e))); - } - - // Инициализация менеджера плагинов - let plugins_config = PluginConfig { - enabled: config.plugins.enabled, - plugins_dir: config.plugins.plugins_dir.clone(), - auto_load: config.plugins.auto_load, - }; - - let mut plugin_manager = PluginManager::new(plugins_config); - - if config.plugins.enabled { - match plugin_manager.initialize().await { - Ok(_) => { - println!("{}", colors.success("Plugin system initialized")); - - // Загружаем плагины если авто-загрузка включена - if config.plugins.auto_load { - match plugin_manager.load_all_plugins().await { - Ok(loaded_plugins) => { - let count: usize = loaded_plugins.len(); - println!("{}", colors.success(&format!("Loaded {} plugins", count))); - } - Err(e) => { - println!("{}", colors.warning(&format!("Failed to load plugins: {}", e))); - } - } - } - } - Err(e) => { - println!("{}", colors.warning(&format!("Failed to initialize plugin system: {}", e))); - } - } - } - - // Регистрируем функции плагинов в Lua - if let Err(e) = lua_interpreter.register_plugin_functions(Arc::new(plugin_manager.clone())) { - println!("{}", colors.warning(&format!("Failed to register plugin functions: {}", e))); - } - - // Инициализация Lua утилит - if let Err(e) = lua_interpreter.register_utilities() { - println!("{}", colors.warning(&format!("Failed to register Lua utilities: {}", e))); - } - - // Информация о системе - if cfg!(target_os = "linux") { - if let Ok(os_release) = std::fs::read_to_string("/etc/os-release") { - for line in os_release.lines() { - if line.starts_with("PRETTY_NAME=") { - let distro_name = line.trim_start_matches("PRETTY_NAME=").trim_matches('"'); - println!("{}", colors.info(&format!("Running on: {}", distro_name))); - break; - } - } - } - } - - // Информация о конфигурации - println!("{}", colors.info(&format!("Data directory: {}", config.database.data_dir))); - println!("{}", colors.info(&format!("Page size: {} bytes", config.database.page_size))); - println!("{}", colors.info(&format!("Server: {}:{}", config.server.host, config.server.port))); - println!("{}", colors.info(&format!("Plugins: {}", if config.plugins.enabled { "enabled" } else { "disabled" }))); - println!("{}", colors.info(&format!("Max connections: {}", config.server.max_connections))); - println!("{}", colors.info(&format!("Cache size: {} MB", config.database.cache_size_mb))); - - if supports_color() { - if colors.supports_true_color() { - println!("{}", colors.success("Цветной вывод активирован (true color)")); - } else if colors.supports_256_colors() { - println!("{}", colors.success("Цветной вывод активирован (256 цветов)")); - } else { - println!("{}", colors.success("Цветной вывод активирован (8 цветов)")); - } - } else { - println!("{}", colors.warning("Цветной вывод недоступен")); - } - - println!(); - - // Основной цикл REPL - let mut last_command = String::new(); - let mut command_history: VecDeque = VecDeque::with_capacity(1000); - let mut lua_mode = false; // Флаг режима Lua - let mut lua_mode_context = LuaModeContext::new(); // Контекст Lua режима - - // Создаем отдельные истории для SQL и Lua режимов - let mut sql_history = RealtimeHistory::new(1000); - let mut lua_history = RealtimeHistory::new(1000); - - loop { - // Выбор приглашения в зависимости от режима - let prompt = if lua_mode { "flusql(lua)€ " } else { "flusql> " }; - - // Используем соответствующую историю в зависимости от режима - let input_result = if lua_mode { - read_line_with_history(prompt, &mut lua_history) - } else { - read_line_with_history(prompt, &mut sql_history) - }; - - let input = match input_result { - Ok(input) => input, - Err(e) => { - if e.kind() == io::ErrorKind::Interrupted { - // Ctrl+C - очищаем строку и продолжаем - println!(); - continue; - } - println!("{}", colors.error(&format!("Ошибка ввода: {}", e))); - continue; - } - }; - - if input.is_empty() { - continue; - } - - // Обработка специальных команд истории - let processed_input = if input == "!!" { - if last_command.is_empty() { - println!("{}", colors.error("No previous command")); - continue; - } - last_command.clone() - } else if input.starts_with('!') && input.len() > 1 { - // Команда вида !n - выполнить команду из истории по номеру - if let Ok(n) = input[1..].parse::() { - if n > 0 && n <= command_history.len() { - command_history[n - 1].clone() - } else { - println!("{}", colors.error(&format!("Invalid history number: {}", n))); - continue; - } - } else { - // Поиск по префиксу - let prefix = &input[1..]; - let matches: Vec = command_history.iter() - .filter(|cmd| cmd.starts_with(prefix)) - .cloned() - .collect(); - - if matches.is_empty() { - println!("{}", colors.error(&format!("No matching command in history for: {}", prefix))); - continue; - } else if matches.len() == 1 { - matches[0].clone() - } else { - println!("{}", colors.error("Multiple matches found:")); - for (i, cmd) in matches.iter().enumerate() { - println!(" {}: {}", i + 1, cmd); - } - continue; - } - } - } else { - input.to_string() - }; - - // Сохраняем команду в истории (кроме переключения режимов) - if processed_input != "lua.mode" && processed_input != "sql.mode" { - history.add(¤t_session, &processed_input); - - // Добавляем в локальную историю для навигации стрелками - command_history.push_back(processed_input.clone()); - while command_history.len() > 1000 { - command_history.pop_front(); - } - - last_command = processed_input.clone(); - } - - // Сбрасываем индекс истории для реального времени - if lua_mode { - lua_history.reset_index(); - } else { - sql_history.reset_index(); - } - - // Обработка команд в зависимости от режима - if lua_mode { - // Lua режим - // Проверка и преобразование команд REPL в вызовы Lua функций - let lua_command = transform_repl_command_to_lua(&processed_input); - - match lua_command.to_lowercase().as_str() { - "sql.mode" | "exit" | "quit" => { - // Выход из Lua режима - println!("{}", colors.info("Exiting Lua mode, returning to SQL mode")); - lua_mode = false; - } - "help" => { - println!("{}", show_lua_help_table()); - } - "history" => { - // Показать историю команд Lua - println!("{}", colors.info("Lua Command History:")); - let lua_history_all = lua_mode_context.history.get_all_history(); - for (i, cmd) in lua_history_all.iter().enumerate() { - println!(" {}: {}", i + 1, cmd); - } - } - "clear-history" | "history clear" => { - // Очистить историю команд Lua - lua_mode_context.history.clear_history(); - lua_history.clear(); - println!("{}", colors.info("Lua history cleared")); - } - _ => { - // Добавляем команду в историю Lua - lua_mode_context.history.add_command(processed_input.clone()); - - // Выполнение Lua кода - match lua_interpreter.execute(&lua_command) { - Ok(result) => { - // Выводим результат только если он не пустой (не "nil") - if !result.is_empty() { - println!("{}", result); - } - } - Err(e) => { - println!("{}", colors.error(&format!("Lua Error: {}", e))); - } - } - } - } - } else { - // SQL режим - match processed_input.to_lowercase().as_str() { - "exit" | "quit" => { - // Сохраняем историю перед выходом - if let Err(e) = history.save_to_file().await { - println!("{}", colors.error(&format!("Warning: Failed to save command history: {}", e))); - } - - // Останавливаем систему плагинов - if config.plugins.enabled { - println!("{}", colors.info("Stopping plugin system...")); - // Здесь должна быть логика остановки плагинов - } - - println!("{}", colors.header("closed flusql...")); - break; - } - "help" => { - println!("{}", show_help_table()); - } - "history" => { - // Показать историю команд - println!("{}", colors.info("Command History:")); - println!("{}", colors.info(&format!("Session: {}", current_session))); - let history_list = history.get_history(¤t_session); - for (i, cmd) in history_list.iter().enumerate() { - println!(" {}: {}", i + 1, cmd); - } - } - "history clear" => { - // Очистить историю - history.clear(¤t_session); - command_history.clear(); - sql_history.clear(); - println!("{}", colors.info("History cleared")); - } - _ if processed_input.starts_with("history export ") => { - // Экспорт истории в файл - let export_path = processed_input.trim_start_matches("history export ").trim(); - if export_path.is_empty() { - println!("{}", colors.error("Usage: HISTORY EXPORT 'file_path';")); - } else { - let export_path = export_path.trim_matches(|c| c == '\'' || c == '"'); - match history.export_history(export_path).await { - Ok(_) => println!("{}", colors.success(&format!("History exported to {}", export_path))), - Err(e) => println!("{}", colors.error(&format!("Error exporting history: {}", e))), - } - } - } - "plugins list" => { - if !config.plugins.enabled { - println!("{}", colors.warning("Plugin system is disabled")); - continue; - } - - let plugins = plugin_manager.list_plugins(); - println!("{}", colors.info(&format!("Loaded plugins: {}", plugins.len()))); - - for plugin in plugins { - println!(" {} v{} - {}", - colors.accent(&plugin.name), - plugin.version, - plugin.description); - println!(" State: {:?}, Author: {}", plugin.state, plugin.author); - if !plugin.hooks.is_empty() { - println!(" Hooks: {}", plugin.hooks.len()); - } - } - } - _ if processed_input.starts_with("plugin load ") => { - if !config.plugins.enabled { - println!("{}", colors.warning("Plugin system is disabled")); - continue; - } - - let plugin_path = processed_input.trim_start_matches("plugin load ").trim(); - if plugin_path.is_empty() { - println!("{}", colors.error("Usage: PLUGIN LOAD 'path/to/plugin.lua';")); - continue; - } - - let plugin_path = plugin_path.trim_matches(|c| c == '\'' || c == '"'); - println!("{}", colors.info(&format!("Loading plugin from: {}", plugin_path))); - - match plugin_manager.load_plugin(Path::new(plugin_path)).await { - Ok(plugin_id) => { - println!("{}", colors.success(&format!("Plugin loaded with ID: {}", plugin_id))); - // Инициализируем плагин после загрузки - if let Err(e) = plugin_manager.initialize_plugin(&plugin_id).await { - println!("{}", colors.warning(&format!("Failed to initialize plugin: {}", e))); - } - } - Err(e) => { - println!("{}", colors.error(&format!("Failed to load plugin: {}", e))); - } - } - } - _ if processed_input.starts_with("plugin unload ") => { - if !config.plugins.enabled { - println!("{}", colors.warning("Plugin system is disabled")); - continue; - } - - let plugin_id = processed_input.trim_start_matches("plugin unload ").trim(); - if plugin_id.is_empty() { - println!("{}", colors.error("Usage: PLUGIN UNLOAD 'plugin_id';")); - continue; - } - - let plugin_id = plugin_id.trim_matches(|c| c == '\'' || c == '"'); - println!("{}", colors.info(&format!("Unloading plugin: {}", plugin_id))); - - match plugin_manager.unload_plugin(plugin_id).await { - Ok(_) => { - println!("{}", colors.success("Plugin unloaded successfully")); - } - Err(e) => { - println!("{}", colors.error(&format!("Failed to unload plugin: {}", e))); - } - } - } - "plugins reload" => { - if !config.plugins.enabled { - println!("{}", colors.warning("Plugin system is disabled")); - continue; - } - - println!("{}", colors.info("Reloading all plugins...")); - match plugin_manager.load_all_plugins().await { - Ok(loaded_plugins) => { - let count: usize = loaded_plugins.len(); - println!("{}", colors.success(&format!("Reloaded {} plugins", count))); - } - Err(e) => { - println!("{}", colors.error(&format!("Failed to reload plugins: {}", e))); - } - } - } - "config show" => { - println!("{}", colors.info("Current configuration:")); - println!(" Server: {}:{}", config.server.host, config.server.port); - println!(" Data directory: {}", config.database.data_dir); - println!(" Page size: {} bytes", config.database.page_size); - println!(" Cache size: {} MB", config.database.cache_size_mb); - println!(" Max connections: {}", config.server.max_connections); - println!(" Plugins enabled: {}", config.plugins.enabled); - println!(" Plugins directory: {}", config.plugins.plugins_dir); - } - "lua.mode" => { - println!(); - println!("{}", colors.info("Type 'help' to see available Lua functions")); - println!("{}", colors.info("Type 'sql.mode' to return to SQL mode")); - println!("{}", colors.info("Try: print('Hello from Lua!') or plugins.list()")); - println!(); // Добавленная пустая строка - lua_mode = true; - } - _ => { - // Парсинг и выполнение SQL команды - match crate::parser::sql::SqlParser::new().parse(&processed_input) { - Ok(sql_query) => { - println!("{}", colors.success("Parsed SQL query successfully")); - println!("{}", colors.info(&format!("Query: {:?}", sql_query))); - - // Обработка новых команд - match sql_query { - crate::parser::sql::SqlQuery::Explain { query, analyze, .. } => { - println!("{}", colors.info(&format!("Explain {}query:", if analyze { "Analyze " } else { "" }))); - println!(" {:?}", query); - println!("{}", colors.info("Explain functionality not fully implemented yet")); - } - crate::parser::sql::SqlQuery::CreateTrigger { name, table, .. } => { - println!("{}", colors.success(&format!("Creating trigger '{}' on table '{}'", name, table))); - println!("{}", colors.info("Trigger creation not fully implemented yet")); - } - crate::parser::sql::SqlQuery::DropTrigger { name, table, .. } => { - println!("{}", colors.success(&format!("Dropping trigger '{}' from table '{}'", name, table))); - println!("{}", colors.info("Trigger deletion not fully implemented yet")); - } - crate::parser::sql::SqlQuery::ShowTables => { - println!("{}", colors.info("Showing tables (functionality not implemented yet)")); - } - crate::parser::sql::SqlQuery::DescribeTable { table_name } => { - println!("{}", colors.info(&format!("Describing table '{}' (functionality not implemented yet)", table_name))); - } - _ => { - println!("{}", colors.warning(&format!("Command not fully implemented yet: {}", processed_input))); - } - } - } - Err(e) => { - println!("{}", colors.error(&format!("SQL Parse Error: {}", e))); - } - } - } - } - } - } - - Ok(()) -} - -/// Преобразование команд REPL в вызовы Lua функций -/// Это позволяет использовать знакомые команды в Lua режиме -fn transform_repl_command_to_lua(command: &str) -> String { - let trimmed = command.trim(); - - // Обрабатываем команды плагинов - if trimmed.starts_with("plugins ") { - let parts: Vec<&str> = trimmed.split_whitespace().collect(); - if parts.len() >= 2 { - match parts[1] { - "list" => return "plugins.list()".to_string(), - "reload" => return "plugins.reload()".to_string(), - _ => {} - } - } - } else if trimmed.starts_with("plugin ") { - let parts: Vec<&str> = trimmed.split_whitespace().collect(); - if parts.len() >= 3 { - match parts[1] { - "load" if parts.len() >= 3 => { - return format!("plugins.load_plugin(\"{}\")", parts[2]); - } - "unload" if parts.len() >= 3 => { - return format!("plugins.unload_plugin(\"{}\")", parts[2]); - } - _ => {} - } - } - } - - // Обрабатываем команды кластера - else if trimmed.starts_with("cluster ") { - let parts: Vec<&str> = trimmed.split_whitespace().collect(); - if parts.len() >= 2 { - match parts[1] { - "status" | "get_status" => return "cluster.get_status()".to_string(), - "coord_status" => return "cluster.coord_status()".to_string(), - "add_node" if parts.len() >= 4 => { - return format!("cluster.add_node(\"{}\", \"{}\")", parts[2], parts[3]); - } - "evict" if parts.len() >= 3 => { - return format!("cluster.evict(\"{}\")", parts[2]); - } - "elect_coordinator" | "elect" => return "cluster.elect_coordinator()".to_string(), - "rebalance" => return "cluster.rebalance()".to_string(), - "add_shard" if parts.len() >= 5 => { - // Упрощенный формат: cluster add_shard shard_id master_node slave1,slave2 - let shard_id = parts[2]; - let master = parts[3]; - let slaves_str = if parts.len() > 4 { parts[4] } else { "" }; - let slaves: Vec<&str> = slaves_str.split(',').filter(|s| !s.is_empty()).collect(); - - if slaves.is_empty() { - return format!("cluster.add_shard(\"{}\", \"{}\", {{}})", shard_id, master); - } else { - let slaves_lua = slaves.iter() - .enumerate() - .map(|(i, slave)| format!("[{}] = \"{}\"", i + 1, slave)) - .collect::>() - .join(", "); - return format!("cluster.add_shard(\"{}\", \"{}\", {{{}}})", shard_id, master, slaves_lua); - } - } - "remove_shard" if parts.len() >= 3 => { - return format!("cluster.remove_shard(\"{}\")", parts[2]); - } - "start_replication" if parts.len() >= 4 => { - return format!("cluster.start_replication(\"{}\", \"{}\")", parts[2], parts[3]); - } - _ => {} - } - } - } - - // Обработка команд конфигурации - else if trimmed.starts_with("config ") { - let parts: Vec<&str> = trimmed.split_whitespace().collect(); - if parts.len() >= 2 { - match parts[1] { - "show" => return "print('Use config.toml file for configuration')".to_string(), - _ => {} - } - } - } - - // Если команда уже выглядит как вызов Lua функции, оставляем как есть - // Проверяем на наличие скобок - if trimmed.contains('(') && trimmed.contains(')') { - return trimmed.to_string(); - } - - // Возвращаем как есть (будет обработано как Lua код) - trimmed.to_string() -}