309 lines
7.5 KiB
Go
309 lines
7.5 KiB
Go
// 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()
|
||
}
|
||
}
|