first commit
This commit is contained in:
131
internal/config/config.go
Normal file
131
internal/config/config.go
Normal file
@@ -0,0 +1,131 @@
|
||||
// config.go - управление конфигурацией fush shell
|
||||
// Загружает и сохраняет настройки в формате TOML
|
||||
// Предоставляет конфигурацию по умолчанию для новой установки
|
||||
// Управляет переменными окружения и путями к директориям
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
// Config представляет структуру конфигурации fush
|
||||
type Config struct {
|
||||
// Общие настройки
|
||||
Prompt string `toml:"prompt"`
|
||||
PromptColor string `toml:"prompt_color"`
|
||||
|
||||
// Директории
|
||||
LuaScriptsDir string `toml:"lua_scripts_dir"`
|
||||
HistoryFile string `toml:"history_file"`
|
||||
LogFile string `toml:"log_file"`
|
||||
|
||||
// Настройки истории
|
||||
HistorySize int `toml:"history_size"`
|
||||
|
||||
// Переменные окружения
|
||||
Environment map[string]string `toml:"environment"`
|
||||
|
||||
// ОС (определяется автоматически)
|
||||
OS string `toml:"-"`
|
||||
}
|
||||
|
||||
// DefaultConfig возвращает конфигурацию по умолчанию
|
||||
func DefaultConfig() *Config {
|
||||
home, _ := os.UserHomeDir()
|
||||
|
||||
cfg := &Config{
|
||||
Prompt: "fush:-> ",
|
||||
PromptColor: "#00bfff",
|
||||
LuaScriptsDir: filepath.Join(home, ".local", "share", "fush", "lua"),
|
||||
HistoryFile: filepath.Join(home, ".cache", "fush", "history"),
|
||||
LogFile: filepath.Join(home, ".cache", "fush", "fush.log"),
|
||||
HistorySize: 1000,
|
||||
Environment: make(map[string]string),
|
||||
}
|
||||
|
||||
// Определение ОС
|
||||
cfg.OS = runtime.GOOS
|
||||
if cfg.OS == "sunos" {
|
||||
cfg.OS = "OpenIndiana"
|
||||
}
|
||||
|
||||
// Установка переменных окружения по умолчанию
|
||||
cfg.Environment["PATH"] = os.Getenv("PATH")
|
||||
cfg.Environment["HOME"] = home
|
||||
cfg.Environment["SHELL"] = os.Getenv("SHELL")
|
||||
cfg.Environment["USER"] = os.Getenv("USER")
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
// Load загружает конфигурацию из файла
|
||||
func Load(path string) (*Config, error) {
|
||||
cfg := DefaultConfig()
|
||||
|
||||
// Проверяем существование файла
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
// Создаем директорию для конфигурации
|
||||
configDir := filepath.Dir(path)
|
||||
if err := os.MkdirAll(configDir, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Сохраняем конфигурацию по умолчанию
|
||||
if err := Save(cfg, path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// Загружаем конфигурацию
|
||||
if _, err := toml.DecodeFile(path, cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Создаем необходимые директории
|
||||
if err := os.MkdirAll(filepath.Dir(cfg.HistoryFile), 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(cfg.LogFile), 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(cfg.LuaScriptsDir, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// Save сохраняет конфигурацию в файл
|
||||
func Save(cfg *Config, path string) error {
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
encoder := toml.NewEncoder(file)
|
||||
return encoder.Encode(cfg)
|
||||
}
|
||||
|
||||
// GetEnv возвращает значение переменной окружения
|
||||
func (c *Config) GetEnv(key string) string {
|
||||
if val, ok := c.Environment[key]; ok {
|
||||
return val
|
||||
}
|
||||
return os.Getenv(key)
|
||||
}
|
||||
|
||||
// SetEnv устанавливает переменную окружения
|
||||
func (c *Config) SetEnv(key, value string) {
|
||||
c.Environment[key] = value
|
||||
os.Setenv(key, value)
|
||||
}
|
||||
175
internal/history/history.go
Normal file
175
internal/history/history.go
Normal file
@@ -0,0 +1,175 @@
|
||||
// history.go - управление историей команд fush shell
|
||||
// Хранит до 1000 последних команд в файле с атомарными операциями
|
||||
// Обеспечивает навигацию по истории (вверх/вниз)
|
||||
// Предотвращает дублирование последовательных одинаковых команд
|
||||
|
||||
package history
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// History управляет историей команд
|
||||
type History struct {
|
||||
commands []string
|
||||
position int
|
||||
maxSize int
|
||||
file string
|
||||
loaded atomic.Bool
|
||||
commandsPtr unsafe.Pointer // Атомарный указатель на слайс команд
|
||||
}
|
||||
|
||||
// New создает новый объект истории
|
||||
func New(historyFile string, maxSize int) *History {
|
||||
h := &History{
|
||||
commands: make([]string, 0, maxSize),
|
||||
position: -1,
|
||||
maxSize: maxSize,
|
||||
file: historyFile,
|
||||
}
|
||||
|
||||
// Инициализируем атомарный указатель
|
||||
atomic.StorePointer(&h.commandsPtr, unsafe.Pointer(&h.commands))
|
||||
|
||||
// Загружаем историю из файла
|
||||
h.load()
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// load загружает историю из файла
|
||||
func (h *History) load() {
|
||||
if h.loaded.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
file, err := os.Open(h.file)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
commands := make([]string, 0, h.maxSize)
|
||||
|
||||
for scanner.Scan() {
|
||||
cmd := strings.TrimSpace(scanner.Text())
|
||||
if cmd != "" {
|
||||
commands = append(commands, cmd)
|
||||
}
|
||||
}
|
||||
|
||||
// Ограничиваем размер
|
||||
if len(commands) > h.maxSize {
|
||||
commands = commands[len(commands)-h.maxSize:]
|
||||
}
|
||||
|
||||
// Атомарно обновляем команды
|
||||
h.commands = commands
|
||||
atomic.StorePointer(&h.commandsPtr, unsafe.Pointer(&h.commands))
|
||||
h.loaded.Store(true)
|
||||
}
|
||||
|
||||
// Add добавляет команду в историю
|
||||
func (h *History) Add(command string) {
|
||||
if command == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Получаем текущие команды
|
||||
oldCommands := h.getCommands()
|
||||
|
||||
// Не добавляем дубликаты с предыдущей командой
|
||||
if len(oldCommands) > 0 && oldCommands[len(oldCommands)-1] == command {
|
||||
return
|
||||
}
|
||||
|
||||
// Создаем новый слайс
|
||||
newCommands := make([]string, 0, h.maxSize)
|
||||
newCommands = append(newCommands, oldCommands...)
|
||||
newCommands = append(newCommands, command)
|
||||
|
||||
// Ограничиваем размер
|
||||
if len(newCommands) > h.maxSize {
|
||||
newCommands = newCommands[len(newCommands)-h.maxSize:]
|
||||
}
|
||||
|
||||
// Атомарно обновляем
|
||||
h.commands = newCommands
|
||||
atomic.StorePointer(&h.commandsPtr, unsafe.Pointer(&h.commands))
|
||||
|
||||
// Сохраняем в файл
|
||||
h.save()
|
||||
}
|
||||
|
||||
// save сохраняет историю в файл
|
||||
func (h *History) save() {
|
||||
file, err := os.OpenFile(h.file, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
writer := bufio.NewWriter(file)
|
||||
for _, cmd := range h.getCommands() {
|
||||
writer.WriteString(cmd + "\n")
|
||||
}
|
||||
writer.Flush()
|
||||
}
|
||||
|
||||
// getCommands атомарно возвращает текущий список команд
|
||||
func (h *History) getCommands() []string {
|
||||
ptr := atomic.LoadPointer(&h.commandsPtr)
|
||||
return *(*[]string)(ptr)
|
||||
}
|
||||
|
||||
// Previous возвращает предыдущую команду
|
||||
func (h *History) Previous() string {
|
||||
commands := h.getCommands()
|
||||
if len(commands) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if h.position == -1 {
|
||||
h.position = len(commands) - 1
|
||||
} else if h.position > 0 {
|
||||
h.position--
|
||||
}
|
||||
|
||||
if h.position >= 0 && h.position < len(commands) {
|
||||
return commands[h.position]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Next возвращает следующую команду
|
||||
func (h *History) Next() string {
|
||||
commands := h.getCommands()
|
||||
if len(commands) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if h.position < len(commands)-1 {
|
||||
h.position++
|
||||
return commands[h.position]
|
||||
} else if h.position == len(commands)-1 {
|
||||
h.position = -1
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// ResetPosition сбрасывает позицию в истории
|
||||
func (h *History) ResetPosition() {
|
||||
h.position = -1
|
||||
}
|
||||
|
||||
// GetSize возвращает размер истории
|
||||
func (h *History) GetSize() int {
|
||||
return len(h.getCommands())
|
||||
}
|
||||
110
internal/logger/logger.go
Normal file
110
internal/logger/logger.go
Normal file
@@ -0,0 +1,110 @@
|
||||
// logger.go - система логирования для fush shell
|
||||
// Записывает события четырех уровней: DEBUG, INFO, WARN, ERROR
|
||||
// Использует атомарные операции для потокобезопасности
|
||||
// Обеспечивает принудительную запись на диск после каждого сообщения
|
||||
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LogLevel определяет уровень логирования
|
||||
type LogLevel int
|
||||
|
||||
const (
|
||||
LevelDebug LogLevel = iota
|
||||
LevelInfo
|
||||
LevelWarn
|
||||
LevelError
|
||||
)
|
||||
|
||||
// Logger представляет структуру логгера
|
||||
type Logger struct {
|
||||
file *os.File
|
||||
level LogLevel
|
||||
closed atomic.Bool
|
||||
}
|
||||
|
||||
// New создает новый логгер
|
||||
func New(logPath string) (*Logger, error) {
|
||||
// Открываем файл для добавления записей
|
||||
file, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Logger{
|
||||
file: file,
|
||||
level: LevelInfo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close закрывает логгер
|
||||
func (l *Logger) Close() error {
|
||||
if l.closed.Load() {
|
||||
return nil
|
||||
}
|
||||
l.closed.Store(true)
|
||||
return l.file.Close()
|
||||
}
|
||||
|
||||
// log записывает сообщение в лог
|
||||
func (l *Logger) log(level LogLevel, format string, args ...interface{}) {
|
||||
if l.closed.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
if level < l.level {
|
||||
return
|
||||
}
|
||||
|
||||
levelStr := map[LogLevel]string{
|
||||
LevelDebug: "DEBUG",
|
||||
LevelInfo: "INFO",
|
||||
LevelWarn: "WARN",
|
||||
LevelError: "ERROR",
|
||||
}[level]
|
||||
|
||||
timestamp := time.Now().Format("2006-01-02 15:04:05.000")
|
||||
message := fmt.Sprintf(format, args...)
|
||||
|
||||
logLine := fmt.Sprintf("[%s] [%s] %s\n", timestamp, levelStr, message)
|
||||
|
||||
// Атомарная запись в файл
|
||||
_, err := l.file.WriteString(logLine)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Ошибка записи лога: %v\n", err)
|
||||
}
|
||||
|
||||
// Принудительная запись на диск
|
||||
l.file.Sync()
|
||||
}
|
||||
|
||||
// Debug записывает отладочное сообщение
|
||||
func (l *Logger) Debug(format string, args ...interface{}) {
|
||||
l.log(LevelDebug, format, args...)
|
||||
}
|
||||
|
||||
// Info записывает информационное сообщение
|
||||
func (l *Logger) Info(format string, args ...interface{}) {
|
||||
l.log(LevelInfo, format, args...)
|
||||
}
|
||||
|
||||
// Warn записывает предупреждение
|
||||
func (l *Logger) Warn(format string, args ...interface{}) {
|
||||
l.log(LevelWarn, format, args...)
|
||||
}
|
||||
|
||||
// Error записывает ошибку
|
||||
func (l *Logger) Error(format string, args ...interface{}) {
|
||||
l.log(LevelError, format, args...)
|
||||
}
|
||||
|
||||
// SetLevel устанавливает уровень логирования
|
||||
func (l *Logger) SetLevel(level LogLevel) {
|
||||
l.level = level
|
||||
}
|
||||
308
internal/luabridge/luabridge.go
Normal file
308
internal/luabridge/luabridge.go
Normal file
@@ -0,0 +1,308 @@
|
||||
// luabridge.go - интеграция Lua как скриптового языка в fush shell
|
||||
// Предоставляет API для вызова команд shell из Lua скриптов
|
||||
// Регистрирует функции exec, pipe, ls, cd, pwd в окружении Lua
|
||||
// Позволяет создавать сложные скрипты для автоматизации задач
|
||||
|
||||
package luabridge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
// ShellInterface определяет интерфейс для взаимодействия с shell
|
||||
type ShellInterface interface {
|
||||
GetEnv(key string) string
|
||||
SetEnv(key, value string)
|
||||
}
|
||||
|
||||
// Bridge управляет выполнением Lua скриптов
|
||||
type Bridge struct {
|
||||
scriptsDir string
|
||||
shell ShellInterface
|
||||
state *lua.LState
|
||||
closed atomic.Bool
|
||||
}
|
||||
|
||||
// New создает новый Lua мост
|
||||
func New(scriptsDir string, shell ShellInterface) *Bridge {
|
||||
b := &Bridge{
|
||||
scriptsDir: scriptsDir,
|
||||
shell: shell,
|
||||
}
|
||||
|
||||
// Создаем Lua состояние
|
||||
b.state = lua.NewState()
|
||||
|
||||
// Регистрируем функции для Lua
|
||||
b.registerFunctions()
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// registerFunctions регистрирует функции для Lua
|
||||
func (b *Bridge) registerFunctions() {
|
||||
// Регистрируем функцию print
|
||||
b.state.SetGlobal("print", b.state.NewFunction(func(L *lua.LState) int {
|
||||
args := make([]interface{}, L.GetTop())
|
||||
for i := 1; i <= L.GetTop(); i++ {
|
||||
args[i-1] = L.Get(i)
|
||||
}
|
||||
fmt.Println(args...)
|
||||
return 0
|
||||
}))
|
||||
|
||||
// Регистрируем функцию getenv
|
||||
b.state.SetGlobal("getenv", b.state.NewFunction(func(L *lua.LState) int {
|
||||
key := L.CheckString(1)
|
||||
value := b.shell.GetEnv(key)
|
||||
L.Push(lua.LString(value))
|
||||
return 1
|
||||
}))
|
||||
|
||||
// Регистрируем функцию setenv
|
||||
b.state.SetGlobal("setenv", b.state.NewFunction(func(L *lua.LState) int {
|
||||
key := L.CheckString(1)
|
||||
value := L.CheckString(2)
|
||||
b.shell.SetEnv(key, value)
|
||||
return 0
|
||||
}))
|
||||
|
||||
// Регистрируем функцию exec для выполнения команд из Lua
|
||||
b.state.SetGlobal("exec", b.state.NewFunction(func(L *lua.LState) int {
|
||||
cmd := L.CheckString(1)
|
||||
args := make([]string, 0)
|
||||
|
||||
// Собираем аргументы
|
||||
for i := 2; i <= L.GetTop(); i++ {
|
||||
args = append(args, L.CheckString(i))
|
||||
}
|
||||
|
||||
// Выполняем команду
|
||||
externalCmd := exec.Command(cmd, args...)
|
||||
externalCmd.Stdout = os.Stdout
|
||||
externalCmd.Stderr = os.Stderr
|
||||
|
||||
err := externalCmd.Run()
|
||||
if err != nil {
|
||||
L.Push(lua.LBool(false))
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
L.Push(lua.LBool(true))
|
||||
return 1
|
||||
}))
|
||||
|
||||
// Регистрируем функцию exec_output для получения вывода команды
|
||||
b.state.SetGlobal("exec_output", b.state.NewFunction(func(L *lua.LState) int {
|
||||
cmd := L.CheckString(1)
|
||||
args := make([]string, 0)
|
||||
|
||||
for i := 2; i <= L.GetTop(); i++ {
|
||||
args = append(args, L.CheckString(i))
|
||||
}
|
||||
|
||||
externalCmd := exec.Command(cmd, args...)
|
||||
output, err := externalCmd.Output()
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
L.Push(lua.LString(string(output)))
|
||||
return 1
|
||||
}))
|
||||
|
||||
// Регистрируем функцию pipe для выполнения команд с пайпом
|
||||
b.state.SetGlobal("pipe", b.state.NewFunction(func(L *lua.LState) int {
|
||||
cmd1 := L.CheckString(1)
|
||||
cmd2 := L.CheckString(2)
|
||||
|
||||
// Разбиваем команды на части
|
||||
parts1 := strings.Fields(cmd1)
|
||||
parts2 := strings.Fields(cmd2)
|
||||
|
||||
if len(parts1) == 0 || len(parts2) == 0 {
|
||||
L.Push(lua.LBool(false))
|
||||
L.Push(lua.LString("пустая команда"))
|
||||
return 2
|
||||
}
|
||||
|
||||
// Создаем команды
|
||||
cmd1Exec := exec.Command(parts1[0], parts1[1:]...)
|
||||
cmd2Exec := exec.Command(parts2[0], parts2[1:]...)
|
||||
|
||||
// Создаем пайп
|
||||
stdout, err := cmd1Exec.StdoutPipe()
|
||||
if err != nil {
|
||||
L.Push(lua.LBool(false))
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
cmd2Exec.Stdin = stdout
|
||||
cmd2Exec.Stdout = os.Stdout
|
||||
cmd2Exec.Stderr = os.Stderr
|
||||
|
||||
// Запускаем команды
|
||||
if err := cmd1Exec.Start(); err != nil {
|
||||
L.Push(lua.LBool(false))
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
if err := cmd2Exec.Start(); err != nil {
|
||||
L.Push(lua.LBool(false))
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
// Ожидаем завершения
|
||||
cmd1Exec.Wait()
|
||||
cmd2Exec.Wait()
|
||||
|
||||
L.Push(lua.LBool(true))
|
||||
return 1
|
||||
}))
|
||||
|
||||
// Регистрируем функцию ls
|
||||
b.state.SetGlobal("ls", b.state.NewFunction(func(L *lua.LState) int {
|
||||
path := "."
|
||||
if L.GetTop() > 0 {
|
||||
path = L.CheckString(1)
|
||||
}
|
||||
|
||||
dir, err := os.Open(path)
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
defer dir.Close()
|
||||
|
||||
files, err := dir.Readdir(-1)
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
// Создаем таблицу с результатами
|
||||
tbl := L.NewTable()
|
||||
for _, file := range files {
|
||||
tbl.Append(lua.LString(file.Name()))
|
||||
}
|
||||
|
||||
L.Push(tbl)
|
||||
return 1
|
||||
}))
|
||||
|
||||
// Регистрируем функцию cd
|
||||
b.state.SetGlobal("cd", b.state.NewFunction(func(L *lua.LState) int {
|
||||
path := L.GetTop() > 0
|
||||
if !path {
|
||||
path = true
|
||||
// Передаем true, но нужен путь
|
||||
_ = path
|
||||
}
|
||||
// Простая реализация cd через shell
|
||||
dir := b.shell.GetEnv("HOME")
|
||||
if L.GetTop() > 0 {
|
||||
dir = L.CheckString(1)
|
||||
}
|
||||
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
L.Push(lua.LBool(false))
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
L.Push(lua.LBool(true))
|
||||
return 1
|
||||
}))
|
||||
|
||||
// Регистрируем функцию pwd
|
||||
b.state.SetGlobal("pwd", b.state.NewFunction(func(L *lua.LState) int {
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
L.Push(lua.LString(dir))
|
||||
return 1
|
||||
}))
|
||||
}
|
||||
|
||||
// ExecuteScript выполняет Lua скрипт
|
||||
func (b *Bridge) ExecuteScript(name string, args []string) error {
|
||||
if b.closed.Load() {
|
||||
return fmt.Errorf("мост закрыт")
|
||||
}
|
||||
|
||||
// Формируем путь к скрипту
|
||||
scriptPath := filepath.Join(b.scriptsDir, name+".lua")
|
||||
|
||||
// Проверяем существование файла
|
||||
if _, err := os.Stat(scriptPath); os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Загружаем и выполняем скрипт
|
||||
if err := b.state.DoFile(scriptPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Вызываем функцию main если она существует
|
||||
if fn := b.state.GetGlobal("main"); fn.Type() == lua.LTFunction {
|
||||
// Преобразуем аргументы в Lua значения
|
||||
luaArgs := make([]lua.LValue, len(args))
|
||||
for i, arg := range args {
|
||||
luaArgs[i] = lua.LString(arg)
|
||||
}
|
||||
|
||||
if err := b.state.CallByParam(lua.P{
|
||||
Fn: fn,
|
||||
NRet: 0,
|
||||
Protect: true,
|
||||
}, luaArgs...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExecuteString выполняет Lua код из строки
|
||||
func (b *Bridge) ExecuteString(code string) error {
|
||||
if b.closed.Load() {
|
||||
return fmt.Errorf("мост закрыт")
|
||||
}
|
||||
|
||||
return b.state.DoString(code)
|
||||
}
|
||||
|
||||
// GetState возвращает Lua состояние
|
||||
func (b *Bridge) GetState() *lua.LState {
|
||||
return b.state
|
||||
}
|
||||
|
||||
// Close закрывает Lua состояние
|
||||
func (b *Bridge) Close() {
|
||||
if b.closed.Load() {
|
||||
return
|
||||
}
|
||||
b.closed.Store(true)
|
||||
if b.state != nil {
|
||||
b.state.Close()
|
||||
}
|
||||
}
|
||||
205
internal/shell/commands.go
Normal file
205
internal/shell/commands.go
Normal 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
343
internal/shell/shell.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user