Upload files to "internal/luabridge"
This commit is contained in:
344
internal/luabridge/luabridge.go
Normal file
344
internal/luabridge/luabridge.go
Normal file
@@ -0,0 +1,344 @@
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user