diff --git a/internal/shell/shell.go b/internal/shell/shell.go deleted file mode 100644 index 6d29564..0000000 --- a/internal/shell/shell.go +++ /dev/null @@ -1,370 +0,0 @@ -// shell.go - основной цикл командного интерпретатора fush -// Обрабатывает ввод пользователя, историю команд и выполнение -// Поддерживает пайпы (|) и перенаправление вывода (>, >>) -// Интегрирует Lua мост для выполнения скриптов - -package shell - -import ( - "bufio" - "fmt" - "io" - "os" - "os/exec" - "path/filepath" - "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 := ansi.BrightCyan - - 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 { - // Проверяем перенаправление вывода ДО обработки пайпов - var outputFile string - var appendMode bool - - // Проверяем >> (добавление) - if idx := strings.Index(input, ">>"); idx != -1 { - outputFile = strings.TrimSpace(input[idx+2:]) - input = strings.TrimSpace(input[:idx]) - appendMode = true - } else if idx := strings.Index(input, ">"); idx != -1 { - outputFile = strings.TrimSpace(input[idx+1:]) - input = strings.TrimSpace(input[:idx]) - appendMode = false - } - - // Проверяем наличие пайпов (каналов) - if strings.Contains(input, "|") { - return s.executePipeline(input, outputFile, appendMode) - } - - // Разбиваем на команду и аргументы - parts := strings.Fields(input) - if len(parts) == 0 { - return nil - } - - cmd := parts[0] - args := parts[1:] - - var err error - var output []byte - - // Проверяем внутренние команды - switch cmd { - case "exit": - err = s.cmdExit(args) - case "help": - err = s.cmdHelp(args) - case "ls": - output, err = s.cmdLs(args) - case "cd": - err = s.cmdCd(args) - case "pwd": - output, err = s.cmdPwd(args) - case "mkdir": - err = s.cmdMkdir(args) - case "rm": - err = s.cmdRm(args) - case "touch": - err = s.cmdTouch(args) - case "cat": - output, err = s.cmdCat(args) - case "echo": - output, err = s.cmdEcho(args) - case "exec": - err = s.cmdExec(args) - default: - // Проверяем существование Lua скрипта ПО ПУТИ - scriptPath := s.findLuaScript(cmd) - if scriptPath != "" { - // Выполняем Lua скрипт - err = s.luaBridge.ExecuteScriptPath(scriptPath, args) - if err == nil { - return nil - } - } - // Пытаемся выполнить как внешнюю команду - err = s.executeExternal(cmd, args, nil, nil, nil) - } - - // Обрабатываем перенаправление вывода для внутренних команд - if err == nil && outputFile != "" && output != nil { - var file *os.File - var openErr error - - if appendMode { - file, openErr = os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) - } else { - file, openErr = os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) - } - - if openErr != nil { - return fmt.Errorf("ошибка открытия файла '%s': %v", outputFile, openErr) - } - defer file.Close() - - _, writeErr := file.Write(output) - if writeErr != nil { - return fmt.Errorf("ошибка записи в файл '%s': %v", outputFile, writeErr) - } - } - - return err -} - -// findLuaScript ищет Lua скрипт в директории скриптов -func (s *Shell) findLuaScript(name string) string { - // Проверяем с расширением .lua и без - possibleNames := []string{name, name + ".lua"} - - for _, n := range possibleNames { - fullPath := filepath.Join(s.config.LuaScriptsDir, n) - if _, err := os.Stat(fullPath); err == nil { - return fullPath - } - } - - return "" -} - -// executePipeline выполняет цепочку команд с пайпами -func (s *Shell) executePipeline(input string, outputFile string, appendMode bool) 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]) - 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 - } - - // Настройка вывода последней команды - if outputFile != "" { - var file *os.File - var err error - - if appendMode { - file, err = os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) - } else { - file, err = os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) - } - - if err != nil { - return fmt.Errorf("ошибка открытия файла '%s': %v", outputFile, err) - } - defer file.Close() - - commands[len(commands)-1].Stdout = file - } else { - commands[len(commands)-1].Stdout = os.Stdout - } - - commands[len(commands)-1].Stderr = os.Stderr - - 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:] - - 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) -}