From 2208e3da170a4c843d65b796c96a8ae58cd5bb15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D1=80=D0=B8=D0=B3=D0=BE=D1=80=D0=B8=D0=B9=20=D0=A1?= =?UTF-8?q?=D0=B0=D1=84=D1=80=D0=BE=D0=BD=D0=BE=D0=B2?= Date: Sun, 24 May 2026 18:17:24 +0000 Subject: [PATCH] Upload files to "internal/shell" --- internal/shell/commands.go | 357 +++++++++++++++++++++++++++++++++++++ 1 file changed, 357 insertions(+) create mode 100644 internal/shell/commands.go diff --git a/internal/shell/commands.go b/internal/shell/commands.go new file mode 100644 index 0000000..80b8651 --- /dev/null +++ b/internal/shell/commands.go @@ -0,0 +1,357 @@ +// commands.go - встроенные команды fush shell (стиль busybox) +// Реализует базовые команды: exit, ls, cd, mkdir, rm, touch, pwd, cat, echo +// Все команды работают независимо от ОС, используя только стандартную библиотеку Go + +package shell + +import ( + "bufio" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "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, "ВСТРОЕННЫЕ КОМАНДЫ (BUSYBOX-STYLE):") + ansi.Println(ansi.Yellow, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + + commands := []struct { + name string + desc string + }{ + {"exit", "Выход из оболочки"}, + {"help", "Показать эту справку"}, + {"ls [path]", "Вывести список файлов в директории"}, + {"cd [dir]", "Сменить текущую директорию"}, + {"pwd", "Показать текущую директорию"}, + {"mkdir [-p] ", "Создать новую директорию"}, + {"rm [-rf] ", "Удалить файл или директорию"}, + {"touch ", "Создать файл или обновить время доступа"}, + {"cat ", "Вывести содержимое файла"}, + {"echo [text...]", "Вывести текст на экран"}, + {"exec [args...]", "Выполнить внешнюю команду"}, + } + + for _, cmd := range commands { + fmt.Printf(" %-20s %s\n", ansi.Colorize(cmd.name, ansi.BrightWhite), cmd.desc) + } + + fmt.Println() + ansi.Println(ansi.Yellow, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + ansi.Println(ansi.BrightGreen, "ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:") + ansi.Println(ansi.Yellow, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + + fmt.Printf(" %-30s %s\n", ansi.Colorize("ls -la", ansi.BrightWhite), "Показать все файлы") + fmt.Printf(" %-30s %s\n", ansi.Colorize("cd /tmp", ansi.BrightWhite), "Перейти в /tmp") + fmt.Printf(" %-30s %s\n", ansi.Colorize("pwd", ansi.BrightWhite), "Показать текущий путь") + fmt.Printf(" %-30s %s\n", ansi.Colorize("mkdir -p a/b/c", ansi.BrightWhite), "Создать вложенные директории") + fmt.Printf(" %-30s %s\n", ansi.Colorize("rm -rf olddir", ansi.BrightWhite), "Удалить директорию рекурсивно") + fmt.Printf(" %-30s %s\n", ansi.Colorize("cat file.txt", ansi.BrightWhite), "Показать содержимое файла") + fmt.Printf(" %-30s %s\n", ansi.Colorize("echo Hello World", ansi.BrightWhite), "Вывести текст") + fmt.Printf(" %-30s %s\n", ansi.Colorize("exec go version", ansi.BrightWhite), "Выполнить внешнюю команду") + fmt.Printf(" %-30s %s\n", ansi.Colorize("ls | grep .go", ansi.BrightWhite), "Пайплайн") + fmt.Printf(" %-30s %s\n", ansi.Colorize("ls > files.txt", ansi.BrightWhite), "Перенаправление вывода") + + fmt.Println() + ansi.Println(ansi.Yellow, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + + return nil +} + +// cmdLs обрабатывает команду ls (стиль busybox) +func (s *Shell) cmdLs(args []string) ([]byte, error) { + path := "." + showAll := false + longFormat := false + + // Парсим аргументы + for _, arg := range args { + if arg == "-a" || arg == "--all" { + showAll = true + } else if arg == "-l" { + longFormat = true + } else if !strings.HasPrefix(arg, "-") { + path = arg + } + } + + dir, err := os.Open(path) + if err != nil { + return nil, err + } + defer dir.Close() + + entries, err := dir.Readdir(-1) + if err != nil { + return nil, err + } + + var output strings.Builder + + for _, entry := range entries { + name := entry.Name() + + // Пропускаем скрытые файлы если не указан -a + if !showAll && strings.HasPrefix(name, ".") { + continue + } + + if longFormat { + // Формат: права ссылки владелец группа размер дата имя + perms := entry.Mode().String() + nlink := 1 // В Go сложно получить количество жестких ссылок + uid := fmt.Sprintf("%d", entry.Sys() != nil) // Упрощённо + gid := "users" + size := entry.Size() + modTime := entry.ModTime().Format("Jan _2 15:04") + + if entry.IsDir() { + name = name + "/" + } else if entry.Mode()&os.ModeSymlink != 0 { + // Для симлинков пытаемся прочитать цель + if target, err := os.Readlink(filepath.Join(path, entry.Name())); err == nil { + name = name + " -> " + target + } + } + + fmt.Fprintf(&output, "%s %3d %-8s %-8s %8d %s %s\n", + perms, nlink, uid, gid, size, modTime, name) + } else { + if entry.IsDir() { + name = name + "/" + } + fmt.Fprint(&output, name, "\n") + } + } + + return []byte(output.String()), nil +} + +// cmdCd обрабатывает команду cd +func (s *Shell) cmdCd(args []string) error { + path := s.GetEnv("HOME") + if len(args) > 0 { + path = args[0] + } + + if path == "~" { + path = s.GetEnv("HOME") + } + + if err := os.Chdir(path); err != nil { + return err + } + + pwd, err := os.Getwd() + if err == nil { + s.SetEnv("PWD", pwd) + } + + return nil +} + +// cmdPwd обрабатывает команду pwd +func (s *Shell) cmdPwd(args []string) ([]byte, error) { + dir, err := os.Getwd() + if err != nil { + return nil, err + } + return []byte(dir + "\n"), nil +} + +// cmdMkdir обрабатывает команду mkdir (с поддержкой -p) +func (s *Shell) cmdMkdir(args []string) error { + if len(args) == 0 { + return fmt.Errorf("требуется имя директории") + } + + createParents := false + var dirs []string + + for _, arg := range args { + if arg == "-p" || arg == "--parents" { + createParents = true + } else if !strings.HasPrefix(arg, "-") { + dirs = append(dirs, arg) + } + } + + for _, dir := range dirs { + var err error + if createParents { + err = os.MkdirAll(dir, 0755) + } else { + err = os.Mkdir(dir, 0755) + } + if err != nil { + return err + } + } + + return nil +} + +// cmdRm обрабатывает команду rm (с поддержкой -r и -f) +func (s *Shell) cmdRm(args []string) error { + if len(args) == 0 { + return fmt.Errorf("требуется имя файла") + } + + recursive := false + force := false + var targets []string + + for _, arg := range args { + switch arg { + case "-r", "-R", "--recursive": + recursive = true + case "-f", "--force": + force = true + default: + if !strings.HasPrefix(arg, "-") { + targets = append(targets, arg) + } + } + } + + for _, target := range targets { + info, err := os.Stat(target) + if err != nil { + if !force { + return err + } + continue + } + + if info.IsDir() && !recursive { + return fmt.Errorf("'%s' является директорией, используйте -r для удаления", target) + } + + if err := os.RemoveAll(target); err != nil && !force { + return err + } + } + + return nil +} + +// cmdTouch обрабатывает команду touch +func (s *Shell) cmdTouch(args []string) error { + if len(args) == 0 { + return fmt.Errorf("требуется имя файла") + } + + now := time.Now() + + for _, filename := range args { + if strings.HasPrefix(filename, "-") { + continue + } + + file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return err + } + file.Close() + + if err := os.Chtimes(filename, now, now); err != nil { + return err + } + } + + return nil +} + +// cmdCat обрабатывает команду cat (конкатенация файлов) +func (s *Shell) cmdCat(args []string) ([]byte, error) { + if len(args) == 0 { + // Читаем из stdin + info, err := os.Stdin.Stat() + if err != nil { + return nil, err + } + + if info.Mode()&os.ModeCharDevice != 0 { + return nil, fmt.Errorf("ожидается файл или stdin") + } + + reader := bufio.NewReader(os.Stdin) + var output strings.Builder + for { + line, err := reader.ReadString('\n') + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + output.WriteString(line) + } + return []byte(output.String()), nil + } + + var output strings.Builder + + for _, filename := range args { + if strings.HasPrefix(filename, "-") { + continue + } + + data, err := os.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("ошибка чтения '%s': %v", filename, err) + } + + output.Write(data) + if len(data) > 0 && data[len(data)-1] != '\n' { + output.WriteByte('\n') + } + } + + return []byte(output.String()), nil +} + +// cmdEcho обрабатывает команду echo +func (s *Shell) cmdEcho(args []string) ([]byte, error) { + newline := true + startIdx := 0 + + if len(args) > 0 && args[0] == "-n" { + newline = false + startIdx = 1 + } + + var output strings.Builder + for i := startIdx; i < len(args); i++ { + if i > startIdx { + output.WriteByte(' ') + } + output.WriteString(args[i]) + } + + if newline { + output.WriteByte('\n') + } + + return []byte(output.String()), nil +}