first commit

This commit is contained in:
2026-05-22 00:26:27 +03:00
commit c9d6d38491
17 changed files with 2311 additions and 0 deletions

205
internal/shell/commands.go Normal file
View File

@@ -0,0 +1,205 @@
// commands.go - встроенные команды fush shell
// Реализует базовые команды: exit, ls, cd, mkdir, rm, touch, exec, help
// Обеспечивает навигацию по файловой системе и управление файлами
// Служит основой для расширения функциональности shell
package shell
import (
"fmt"
"os"
"time"
"fush/pkg/ansi"
)
// cmdExit обрабатывает команду exit
func (s *Shell) cmdExit(args []string) error {
s.running.Store(false)
s.logger.Info("Выполнена команда exit")
return nil
}
// cmdHelp обрабатывает команду help - выводит список доступных команд
func (s *Shell) cmdHelp(args []string) error {
fmt.Println()
ansi.Println(ansi.Cyan, "╔══════════════════════════════════════════════════════════════╗")
ansi.Println(ansi.Cyan, "║ fush shell - Доступные команды ║")
ansi.Println(ansi.Cyan, "╚══════════════════════════════════════════════════════════════╝")
fmt.Println()
// Внутренние команды
ansi.Println(ansi.Yellow, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
ansi.Println(ansi.BrightGreen, "ВСТРОЕННЫЕ КОМАНДЫ:")
ansi.Println(ansi.Yellow, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
fmt.Printf(" %-15s %s\n", ansi.Colorize("exit", ansi.BrightWhite), "Выход из оболочки")
fmt.Printf(" %-15s %s\n", ansi.Colorize("help", ansi.BrightWhite), "Показать эту справку")
fmt.Printf(" %-15s %s\n", ansi.Colorize("ls [path]", ansi.BrightWhite), "Вывести список файлов в директории")
fmt.Printf(" %-15s %s\n", ansi.Colorize("cd [dir]", ansi.BrightWhite), "Сменить текущую директорию")
fmt.Printf(" %-15s %s\n", ansi.Colorize("mkdir <dir>", ansi.BrightWhite), "Создать новую директорию")
fmt.Printf(" %-15s %s\n", ansi.Colorize("rm <file>", ansi.BrightWhite), "Удалить файл или директорию")
fmt.Printf(" %-15s %s\n", ansi.Colorize("touch <file>", ansi.BrightWhite), "Создать файл или обновить время доступа")
fmt.Printf(" %-15s %s\n", ansi.Colorize("exec <cmd> [args...]", ansi.BrightWhite), "Выполнить внешнюю команду")
fmt.Println()
ansi.Println(ansi.Yellow, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
ansi.Println(ansi.BrightGreen, "ВНЕШНИЕ КОМАНДЫ:")
ansi.Println(ansi.Yellow, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
fmt.Printf(" %-15s %s\n", ansi.Colorize("<program>", ansi.BrightWhite), "Запуск любого внешнего приложения")
fmt.Printf(" %-15s %s\n", ansi.Colorize("<lua_script>", ansi.BrightWhite), "Выполнение Lua скрипта из директории ~/.local/share/fush/lua/")
fmt.Println()
ansi.Println(ansi.Yellow, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
ansi.Println(ansi.BrightGreen, "СПЕЦИАЛЬНЫЕ ВОЗМОЖНОСТИ:")
ansi.Println(ansi.Yellow, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
fmt.Printf(" %-20s %s\n", ansi.Colorize("cmd1 | cmd2", ansi.BrightWhite), "Пайплайн (канал) между командами")
fmt.Printf(" %-20s %s\n", ansi.Colorize("cmd > file", ansi.BrightWhite), "Перенаправление вывода в файл (перезапись)")
fmt.Printf(" %-20s %s\n", ansi.Colorize("cmd >> file", ansi.BrightWhite), "Перенаправление вывода в файл (добавление)")
fmt.Printf(" %-20s %s\n", ansi.Colorize("Lua API", ansi.BrightWhite), "Встроенный интерпретатор Lua с функциями: exec(), ls(), cd(), pwd()")
fmt.Println()
ansi.Println(ansi.Yellow, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
ansi.Println(ansi.BrightGreen, "ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:")
ansi.Println(ansi.Yellow, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
ansi.Println(ansi.BrightCyan, "ВСТРОЕННЫЕ КОМАНДЫ:")
fmt.Printf(" %-40s %s\n", ansi.Colorize("ls -la", ansi.BrightWhite), "Показать все файлы в текущей директории")
fmt.Printf(" %-40s %s\n", ansi.Colorize("cd /home/user", ansi.BrightWhite), "Перейти в директорию пользователя")
fmt.Printf(" %-40s %s\n", ansi.Colorize("mkdir mydir", ansi.BrightWhite), "Создать директорию 'mydir'")
fmt.Printf(" %-40s %s\n", ansi.Colorize("rm myfile.txt", ansi.BrightWhite), "Удалить файл 'myfile.txt'")
fmt.Printf(" %-40s %s\n", ansi.Colorize("touch newfile.txt", ansi.BrightWhite), "Создать файл 'newfile.txt'")
fmt.Printf(" %-40s %s\n", ansi.Colorize("exec go version", ansi.BrightWhite), "Выполнить команду 'go version'")
fmt.Println()
ansi.Println(ansi.BrightCyan, "ВНЕШНИЕ КОМАНДЫ (СИСТЕМНЫЕ УТИЛИТЫ):")
fmt.Printf(" %-40s %s\n", ansi.Colorize("go version", ansi.BrightWhite), "Показать версию Go (если установлен)")
fmt.Printf(" %-40s %s\n", ansi.Colorize("python --version", ansi.BrightWhite), "Показать версию Python (если установлен)")
fmt.Printf(" %-40s %s\n", ansi.Colorize("gcc --version", ansi.BrightWhite), "Показать версию GCC (если установлен)")
fmt.Printf(" %-40s %s\n", ansi.Colorize("date", ansi.BrightWhite), "Показать текущую дату и время")
fmt.Printf(" %-40s %s\n", ansi.Colorize("whoami", ansi.BrightWhite), "Показать имя текущего пользователя")
fmt.Printf(" %-40s %s\n", ansi.Colorize("pwd", ansi.BrightWhite), "Показать текущую рабочую директорию")
fmt.Printf(" %-40s %s\n", ansi.Colorize("echo Hello World", ansi.BrightWhite), "Вывести текст на экран")
fmt.Printf(" %-40s %s\n", ansi.Colorize("cat file.txt", ansi.BrightWhite), "Показать содержимое файла")
fmt.Printf(" %-40s %s\n", ansi.Colorize("grep pattern file.txt", ansi.BrightWhite), "Поиск строк по шаблону в файле")
fmt.Println()
ansi.Println(ansi.BrightCyan, "КОМАНДЫ ДЛЯ РАБОТЫ С ПАЙПЛАЙНАМИ (КАНАЛАМИ):")
fmt.Printf(" %-40s %s\n", ansi.Colorize("ls -la | grep .go", ansi.BrightWhite), "Показать только Go файлы в текущей директории")
fmt.Printf(" %-40s %s\n", ansi.Colorize("ps aux | grep python", ansi.BrightWhite), "Найти процессы Python")
fmt.Printf(" %-40s %s\n", ansi.Colorize("cat file.txt | wc -l", ansi.BrightWhite), "Подсчитать количество строк в файле")
fmt.Printf(" %-40s %s\n", ansi.Colorize("ls | sort | uniq", ansi.BrightWhite), "Отсортировать и убрать дубликаты")
fmt.Println()
ansi.Println(ansi.BrightCyan, "ПЕРЕНАПРАВЛЕНИЕ ВЫВОДА:")
fmt.Printf(" %-40s %s\n", ansi.Colorize("ls > files.txt", ansi.BrightWhite), "Сохранить список файлов в files.txt (перезапись)")
fmt.Printf(" %-40s %s\n", ansi.Colorize("echo Hello >> hello.txt", ansi.BrightWhite), "Добавить текст в конец файла")
fmt.Printf(" %-40s %s\n", ansi.Colorize("go version > version.txt", ansi.BrightWhite), "Сохранить версию Go в файл")
fmt.Println()
ansi.Println(ansi.BrightCyan, "LUA СКРИПТЫ:")
fmt.Printf(" %-40s %s\n", ansi.Colorize("example.lua", ansi.BrightWhite), "Выполнить Lua скрипт из директории ~/.local/share/fush/lua/")
fmt.Printf(" %-40s %s\n", ansi.Colorize("my_script.lua arg1 arg2", ansi.BrightWhite), "Выполнить Lua скрипт с аргументами")
fmt.Println()
ansi.Println(ansi.Yellow, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
ansi.Println(ansi.BrightCyan, "Для выхода из оболочки введите 'exit' или нажмите Ctrl+C")
fmt.Println()
return nil
}
// cmdLs обрабатывает команду ls
func (s *Shell) cmdLs(args []string) error {
// Определяем путь
path := "."
if len(args) > 0 {
path = args[0]
}
// Открываем директорию
dir, err := os.Open(path)
if err != nil {
return err
}
defer dir.Close()
// Читаем содержимое
files, err := dir.Readdir(-1)
if err != nil {
return err
}
// Выводим список
for _, file := range files {
name := file.Name()
if file.IsDir() {
name = name + "/"
}
fmt.Println(name)
}
return nil
}
// cmdCd обрабатывает команду cd
func (s *Shell) cmdCd(args []string) error {
// Определяем путь
path := s.GetEnv("HOME")
if len(args) > 0 {
path = args[0]
}
// Меняем директорию
if err := os.Chdir(path); err != nil {
return err
}
// Обновляем PWD
pwd, err := os.Getwd()
if err == nil {
s.SetEnv("PWD", pwd)
}
return nil
}
// cmdMkdir обрабатывает команду mkdir
func (s *Shell) cmdMkdir(args []string) error {
if len(args) == 0 {
return fmt.Errorf("требуется имя директории")
}
// Создаем директорию
return os.MkdirAll(args[0], 0755)
}
// cmdRm обрабатывает команду rm
func (s *Shell) cmdRm(args []string) error {
if len(args) == 0 {
return fmt.Errorf("требуется имя файла")
}
// Удаляем файл или директорию
return os.RemoveAll(args[0])
}
// cmdTouch обрабатывает команду touch
func (s *Shell) cmdTouch(args []string) error {
if len(args) == 0 {
return fmt.Errorf("требуется имя файла")
}
// Создаем файл или обновляем время модификации
file, err := os.OpenFile(args[0], os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer file.Close()
// Обновляем время модификации
now := time.Now()
return os.Chtimes(args[0], now, now)
}

343
internal/shell/shell.go Normal file
View File

@@ -0,0 +1,343 @@
// shell.go - основной цикл командного интерпретатора fush
// Обрабатывает ввод пользователя, историю команд и выполнение
// Поддерживает пайпы (|) и перенаправление вывода (>, >>)
// Интегрирует Lua мост для выполнения скриптов
package shell
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"strings"
"sync/atomic"
"fush/internal/config"
"fush/internal/history"
"fush/internal/logger"
"fush/internal/luabridge"
"fush/pkg/ansi"
)
// Shell представляет основную структуру командного интерпретатора
type Shell struct {
config *config.Config
logger *logger.Logger
history *history.History
luaBridge *luabridge.Bridge
running atomic.Bool
env map[string]string
}
// PipeCommand представляет команду в пайплайне
type PipeCommand struct {
Cmd string
Args []string
}
// New создает новый экземпляр shell
func New(cfg *config.Config, log *logger.Logger) *Shell {
sh := &Shell{
config: cfg,
logger: log,
env: make(map[string]string),
}
// Копируем переменные окружения из конфигурации
for k, v := range cfg.Environment {
sh.env[k] = v
}
// Инициализируем историю
sh.history = history.New(cfg.HistoryFile, cfg.HistorySize)
// Инициализируем Lua мост
sh.luaBridge = luabridge.New(cfg.LuaScriptsDir, sh)
return sh
}
// Run запускает основной цикл shell
func (s *Shell) Run() error {
s.running.Store(true)
s.logger.Info("Запуск основного цикла shell")
// Получаем цвет приглашения
//promptColor, err := ansi.HexToANSI(s.config.PromptColor)
//if err != nil {
// promptColor = ansi.Cyan
//}
promptColor := ansi.BrightCyan // Принудительно установить яркий циан
// Создаем читатель stdin
reader := bufio.NewReader(os.Stdin)
for s.running.Load() {
// Выводим приглашение
ansi.Printf(promptColor, s.config.Prompt)
// Читаем команду
input, err := reader.ReadString('\n')
if err != nil {
if err.Error() == "EOF" {
break
}
s.logger.Error("Ошибка чтения ввода", "error", err)
continue
}
// Обрабатываем команду
input = strings.TrimSpace(input)
if input == "" {
continue
}
// Добавляем в историю
s.history.Add(input)
// Обрабатываем команду
if err := s.executeCommand(input); err != nil {
ansi.Printf(ansi.Red, "Ошибка: %v\n", err)
s.logger.Error("Ошибка выполнения команды", "command", input, "error", err)
}
}
return nil
}
// executeCommand выполняет команду
func (s *Shell) executeCommand(input string) error {
// Проверяем наличие пайпов (каналов)
if strings.Contains(input, "|") {
return s.executePipeline(input)
}
// Проверяем перенаправление вывода
var outputFile string
var appendMode bool
if strings.Contains(input, ">>") {
parts := strings.SplitN(input, ">>", 2)
input = strings.TrimSpace(parts[0])
outputFile = strings.TrimSpace(parts[1])
appendMode = true
} else if strings.Contains(input, ">") {
parts := strings.SplitN(input, ">", 2)
input = strings.TrimSpace(parts[0])
outputFile = strings.TrimSpace(parts[1])
appendMode = false
}
// Разбиваем на команду и аргументы
parts := strings.Fields(input)
if len(parts) == 0 {
return nil
}
cmd := parts[0]
args := parts[1:]
var err error
// Проверяем внутренние команды
switch cmd {
case "exit":
err = s.cmdExit(args)
case "help":
err = s.cmdHelp(args)
case "ls":
err = s.cmdLs(args)
case "cd":
err = s.cmdCd(args)
case "mkdir":
err = s.cmdMkdir(args)
case "rm":
err = s.cmdRm(args)
case "touch":
err = s.cmdTouch(args)
case "exec":
err = s.cmdExec(args)
default:
// Пытаемся выполнить как Lua скрипт
err = s.luaBridge.ExecuteScript(cmd, args)
if err != nil {
// Пытаемся выполнить как внешнюю команду
err = s.executeExternal(cmd, args, nil, nil, nil)
}
}
// Обрабатываем перенаправление вывода если нужно
if err == nil && outputFile != "" {
// Для внутренних команд вывод уже был,
// для внешних перенаправление обрабатывается в executeExternal
_ = appendMode // Подавляем предупреждение о неиспользуемой переменной
}
return err
}
// executePipeline выполняет цепочку команд с пайпами
func (s *Shell) executePipeline(input string) error {
// Разбиваем на команды
cmdStrs := strings.Split(input, "|")
commands := make([]*exec.Cmd, 0, len(cmdStrs))
// Парсим каждую команду
for _, cmdStr := range cmdStrs {
cmdStr = strings.TrimSpace(cmdStr)
if cmdStr == "" {
return fmt.Errorf("пустая команда в пайплайне")
}
parts := strings.Fields(cmdStr)
if len(parts) == 0 {
return fmt.Errorf("пустая команда в пайплайне")
}
// Проверяем внутренние команды
switch parts[0] {
case "exit", "cd", "exec", "help":
return fmt.Errorf("команда '%s' не может использоваться в пайплайне", parts[0])
case "ls", "mkdir", "rm", "touch":
// Для внутренних команд используем внешний вызов
cmd := exec.Command(parts[0], parts[1:]...)
s.setCommandEnv(cmd)
commands = append(commands, cmd)
default:
// Внешняя команда
cmd := exec.Command(parts[0], parts[1:]...)
s.setCommandEnv(cmd)
commands = append(commands, cmd)
}
}
if len(commands) == 0 {
return fmt.Errorf("нет команд для выполнения")
}
// Создаем пайпы между командами
for i := 0; i < len(commands)-1; i++ {
stdout, err := commands[i].StdoutPipe()
if err != nil {
return fmt.Errorf("ошибка создания пайпа: %v", err)
}
commands[i+1].Stdin = stdout
}
// Последняя команда пишет в stdout
commands[len(commands)-1].Stdout = os.Stdout
commands[len(commands)-1].Stderr = os.Stderr
// Первая команда читает из stdin если нужно
if commands[0].Stdin == nil {
commands[0].Stdin = os.Stdin
}
// Запускаем все команды
for idx, cmd := range commands {
if err := cmd.Start(); err != nil {
return fmt.Errorf("ошибка запуска команды %d: %v", idx, err)
}
}
// Ожидаем завершения всех команд
for idx, cmd := range commands {
if err := cmd.Wait(); err != nil {
s.logger.Error("Ошибка выполнения команды в пайплайне", "index", idx, "error", err)
}
}
return nil
}
// cmdExec выполняет команду exec для запуска приложений
func (s *Shell) cmdExec(args []string) error {
if len(args) == 0 {
return fmt.Errorf("требуется команда для выполнения")
}
cmd := args[0]
cmdArgs := args[1:]
// Создаем exec.Command
externalCmd := exec.Command(cmd, cmdArgs...)
// Настраиваем окружение
s.setCommandEnv(externalCmd)
// Перенаправляем вывод
externalCmd.Stdout = os.Stdout
externalCmd.Stderr = os.Stderr
externalCmd.Stdin = os.Stdin
// Выполняем команду
err := externalCmd.Run()
if err != nil {
return fmt.Errorf("ошибка выполнения '%s': %v", cmd, err)
}
return nil
}
// executeExternal выполняет внешнюю команду с поддержкой перенаправления
func (s *Shell) executeExternal(cmd string, args []string, stdin io.Reader, stdout, stderr io.Writer) error {
// Создаем команду
externalCmd := exec.Command(cmd, args...)
// Настраиваем окружение
s.setCommandEnv(externalCmd)
// Перенаправляем вывод
if stdin != nil {
externalCmd.Stdin = stdin
} else {
externalCmd.Stdin = os.Stdin
}
if stdout != nil {
externalCmd.Stdout = stdout
} else {
externalCmd.Stdout = os.Stdout
}
if stderr != nil {
externalCmd.Stderr = stderr
} else {
externalCmd.Stderr = os.Stderr
}
// Выполняем
return externalCmd.Run()
}
// setCommandEnv устанавливает переменные окружения для команды
func (s *Shell) setCommandEnv(cmd *exec.Cmd) {
cmd.Env = os.Environ()
for k, v := range s.env {
cmd.Env = append(cmd.Env, k+"="+v)
}
}
// Shutdown завершает работу shell
func (s *Shell) Shutdown() {
s.logger.Info("Завершение работы shell")
s.running.Store(false)
s.luaBridge.Close()
}
// GetEnv возвращает переменную окружения
func (s *Shell) GetEnv(key string) string {
if val, ok := s.env[key]; ok {
return val
}
return os.Getenv(key)
}
// SetEnv устанавливает переменную окружения
func (s *Shell) SetEnv(key, value string) {
s.env[key] = value
os.Setenv(key, value)
s.logger.Debug("Установлена переменная окружения", "key", key, "value", value)
}