Files
fush/internal/luabridge/luabridge.go

345 lines
8.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
}
// ExecuteScriptPath выполняет Lua скрипт по полному пути (НОВЫЙ МЕТОД)
func (b *Bridge) ExecuteScriptPath(scriptPath string, args []string) error {
if b.closed.Load() {
return fmt.Errorf("мост закрыт")
}
// Проверяем существование файла
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()
}
}