// 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() } }