2026-01-20 17:40:21 +05:00
|
|
|
|
package utils
|
|
|
|
|
|
|
2026-01-21 17:56:21 +05:00
|
|
|
|
import (
|
2026-01-23 17:52:47 +05:00
|
|
|
|
"bubble/types"
|
|
|
|
|
|
"errors"
|
2026-01-21 17:56:21 +05:00
|
|
|
|
"fmt"
|
2026-01-23 17:52:47 +05:00
|
|
|
|
"github.com/expr-lang/expr"
|
2026-01-21 17:56:21 +05:00
|
|
|
|
"io"
|
2026-01-23 17:52:47 +05:00
|
|
|
|
"io/fs"
|
2026-01-21 17:56:21 +05:00
|
|
|
|
"net/http"
|
|
|
|
|
|
"os"
|
|
|
|
|
|
"os/exec"
|
2026-01-23 17:52:47 +05:00
|
|
|
|
"regexp"
|
|
|
|
|
|
"strconv"
|
2026-01-21 17:56:21 +05:00
|
|
|
|
"strings"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-01-20 17:40:21 +05:00
|
|
|
|
// CalculateSelectOptionsIndex рассчитывает индекс первой и последней опции для отображения в поле выбора
|
|
|
|
|
|
func CalculateSelectOptionsIndex(focus, visibleCount, optionsCount int) (int, int) {
|
|
|
|
|
|
//Если количество отображаемых больше, чем есть количество опций на самом деле,
|
|
|
|
|
|
//то меняем количество опций для отображения на количество опций.
|
|
|
|
|
|
if visibleCount > optionsCount {
|
|
|
|
|
|
visibleCount = optionsCount
|
|
|
|
|
|
}
|
|
|
|
|
|
//Количество объектов сверху и снизу
|
|
|
|
|
|
half := visibleCount / 2
|
|
|
|
|
|
//Индекс первой опции для показа в списке
|
|
|
|
|
|
start := focus - half
|
|
|
|
|
|
//Если индекс меньше 0, то фиксируем его на первой
|
|
|
|
|
|
if start < 0 {
|
|
|
|
|
|
start = 0
|
|
|
|
|
|
}
|
|
|
|
|
|
//Индекс последней опции для показа в списке
|
|
|
|
|
|
end := start + visibleCount
|
|
|
|
|
|
//Если индекс больше, чем количество опций для вывода,
|
|
|
|
|
|
//то фиксируем его на последней опции.
|
|
|
|
|
|
//Производим перерасчет индекса первой опции
|
|
|
|
|
|
if end > optionsCount {
|
|
|
|
|
|
end = optionsCount
|
|
|
|
|
|
start = end - visibleCount
|
|
|
|
|
|
if start < 0 {
|
|
|
|
|
|
start = 0
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return start, end
|
|
|
|
|
|
}
|
2026-01-21 17:56:21 +05:00
|
|
|
|
|
2026-01-23 17:52:47 +05:00
|
|
|
|
// CopyFile копирование файла в новое место
|
|
|
|
|
|
func CopyFile(src, dst string) error {
|
|
|
|
|
|
in, err := os.Open(src)
|
2026-01-21 17:56:21 +05:00
|
|
|
|
if err != nil {
|
2026-01-23 17:52:47 +05:00
|
|
|
|
return err
|
2026-01-21 17:56:21 +05:00
|
|
|
|
}
|
2026-01-23 17:52:47 +05:00
|
|
|
|
defer in.Close()
|
2026-01-22 17:54:42 +05:00
|
|
|
|
|
2026-01-23 17:52:47 +05:00
|
|
|
|
out, err := os.Create(dst)
|
2026-01-21 17:56:21 +05:00
|
|
|
|
if err != nil {
|
2026-01-23 17:52:47 +05:00
|
|
|
|
return err
|
2026-01-21 17:56:21 +05:00
|
|
|
|
}
|
2026-01-23 17:52:47 +05:00
|
|
|
|
defer out.Close()
|
|
|
|
|
|
_, err = io.Copy(out, in)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2026-01-22 17:54:42 +05:00
|
|
|
|
|
2026-01-23 17:52:47 +05:00
|
|
|
|
// ParamsForCompute получение словаря переменных без приведения типов, для вычисления
|
|
|
|
|
|
func ParamsForCompute(app *types.PackageApplication) map[string]interface{} {
|
|
|
|
|
|
params := make(map[string]interface{})
|
|
|
|
|
|
|
|
|
|
|
|
for i := range app.Variables {
|
|
|
|
|
|
v := &app.Variables[i]
|
|
|
|
|
|
value := v.Value
|
|
|
|
|
|
if value == nil {
|
|
|
|
|
|
value = v.Default
|
2026-01-21 17:56:21 +05:00
|
|
|
|
}
|
2026-01-23 17:52:47 +05:00
|
|
|
|
params[v.Name] = value
|
2026-01-21 17:56:21 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 17:52:47 +05:00
|
|
|
|
// Добавляем значения из активных полей
|
|
|
|
|
|
for _, section := range app.Sections {
|
|
|
|
|
|
for _, field := range section.Fields {
|
|
|
|
|
|
if field.Variable == nil || !field.Active {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
value := field.Variable.Value
|
|
|
|
|
|
if value == nil {
|
|
|
|
|
|
value = field.Variable.Default
|
|
|
|
|
|
}
|
|
|
|
|
|
params[field.VariableName] = value
|
|
|
|
|
|
}
|
2026-01-21 17:56:21 +05:00
|
|
|
|
}
|
2026-01-23 17:52:47 +05:00
|
|
|
|
|
|
|
|
|
|
return params
|
2026-01-21 17:56:21 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 17:52:47 +05:00
|
|
|
|
// ParamsForCondition получение словаря переменных с приведением типов для проверки условий
|
|
|
|
|
|
func ParamsForCondition(app *types.PackageApplication) map[string]interface{} {
|
|
|
|
|
|
params := make(map[string]interface{})
|
|
|
|
|
|
|
|
|
|
|
|
for _, variable := range app.Variables {
|
|
|
|
|
|
value := variable.Value
|
|
|
|
|
|
if value == nil {
|
|
|
|
|
|
value = variable.Default
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Приводим к нужному типу, как в твоём старом коде
|
|
|
|
|
|
switch variable.Type {
|
|
|
|
|
|
case "bool":
|
|
|
|
|
|
if s, ok := value.(string); ok {
|
|
|
|
|
|
value = strings.ToLower(s) == "true"
|
|
|
|
|
|
}
|
|
|
|
|
|
case "number", "int", "float":
|
|
|
|
|
|
if s, ok := value.(string); ok {
|
|
|
|
|
|
if n, err := strconv.ParseFloat(s, 64); err == nil {
|
|
|
|
|
|
value = n
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
params[variable.Name] = value
|
2026-01-21 17:56:21 +05:00
|
|
|
|
}
|
2026-01-23 17:52:47 +05:00
|
|
|
|
|
|
|
|
|
|
// Также добавляем значения из полей секций (на случай, если переменная используется только в поле)
|
|
|
|
|
|
for _, section := range app.Sections {
|
|
|
|
|
|
for _, field := range section.Fields {
|
|
|
|
|
|
if field.Variable == nil || !field.Active {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
value := field.Variable.Value
|
|
|
|
|
|
if value == nil {
|
|
|
|
|
|
value = field.Variable.Default
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch field.Variable.Type {
|
|
|
|
|
|
case "bool":
|
|
|
|
|
|
if s, ok := value.(string); ok {
|
|
|
|
|
|
value = strings.ToLower(s) == "true"
|
|
|
|
|
|
}
|
|
|
|
|
|
case "number", "int", "float":
|
|
|
|
|
|
if s, ok := value.(string); ok {
|
|
|
|
|
|
if n, err := strconv.ParseFloat(s, 64); err == nil {
|
|
|
|
|
|
value = n
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
params[field.VariableName] = value
|
|
|
|
|
|
}
|
2026-01-21 17:56:21 +05:00
|
|
|
|
}
|
2026-01-23 17:52:47 +05:00
|
|
|
|
|
|
|
|
|
|
return params
|
2026-01-21 17:56:21 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 17:52:47 +05:00
|
|
|
|
// DefineExprCustomFunctions возвращает собственные функции expr
|
|
|
|
|
|
func DefineExprCustomFunctions() []expr.Option {
|
|
|
|
|
|
return []expr.Option{
|
|
|
|
|
|
//вычисляет значение по заданному регулярному выражению
|
|
|
|
|
|
expr.Function("regex",
|
|
|
|
|
|
func(args ...interface{}) (interface{}, error) {
|
|
|
|
|
|
if len(args) != 3 {
|
|
|
|
|
|
return "", fmt.Errorf("regex() ожидает 3 аргумента")
|
|
|
|
|
|
}
|
|
|
|
|
|
input := fmt.Sprintf("%v", args[0])
|
|
|
|
|
|
pattern := fmt.Sprintf("%v", args[1])
|
|
|
|
|
|
repl := fmt.Sprintf("%v", args[2])
|
|
|
|
|
|
re, err := regexp.Compile(pattern)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return input, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
return re.ReplaceAllString(input, repl), nil
|
|
|
|
|
|
}, new(func(string, string, string) string),
|
|
|
|
|
|
),
|
|
|
|
|
|
//проверяет совпадение переданного значения и указанного регулярного выражения
|
|
|
|
|
|
expr.Function("regex_match",
|
|
|
|
|
|
func(args ...interface{}) (interface{}, error) {
|
|
|
|
|
|
if len(args) != 2 {
|
|
|
|
|
|
return false, fmt.Errorf("regex_match() ожидает 2 аргумента")
|
|
|
|
|
|
}
|
|
|
|
|
|
input := fmt.Sprintf("%v", args[0])
|
|
|
|
|
|
pattern := fmt.Sprintf("%v", args[1])
|
2026-01-21 17:56:21 +05:00
|
|
|
|
|
2026-01-23 17:52:47 +05:00
|
|
|
|
re, err := regexp.Compile(pattern)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, fmt.Errorf("некоректный regex %s, err:%v", pattern, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return re.MatchString(input), nil
|
|
|
|
|
|
|
|
|
|
|
|
}, new(func(string, string) bool),
|
|
|
|
|
|
),
|
|
|
|
|
|
//возвращает собственный текст ошибки
|
|
|
|
|
|
expr.Function("error",
|
|
|
|
|
|
func(args ...interface{}) (interface{}, error) {
|
|
|
|
|
|
if len(args) == 0 {
|
|
|
|
|
|
return nil, fmt.Errorf("error() требует сообщения")
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil, fmt.Errorf("%v", args[0])
|
|
|
|
|
|
},
|
|
|
|
|
|
new(func(string) error),
|
|
|
|
|
|
),
|
|
|
|
|
|
//возвращает тело ответа по указанному адресу
|
|
|
|
|
|
expr.Function("http_get",
|
|
|
|
|
|
func(args ...interface{}) (interface{}, error) {
|
|
|
|
|
|
if len(args) == 0 {
|
|
|
|
|
|
return "", nil
|
|
|
|
|
|
}
|
|
|
|
|
|
url := fmt.Sprintf("%v", args[0])
|
|
|
|
|
|
resp, err := http.Get(url)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
body, _ := io.ReadAll(resp.Body)
|
|
|
|
|
|
return string(body), nil
|
|
|
|
|
|
},
|
|
|
|
|
|
new(func(string) string),
|
|
|
|
|
|
),
|
|
|
|
|
|
//возвращает результат выполнения команды из оболочки
|
|
|
|
|
|
expr.Function("shell",
|
|
|
|
|
|
func(args ...interface{}) (interface{}, error) {
|
|
|
|
|
|
if len(args) == 0 {
|
|
|
|
|
|
return "", nil
|
|
|
|
|
|
}
|
|
|
|
|
|
cmd := fmt.Sprintf("%v", args[0])
|
|
|
|
|
|
parts := strings.Fields(cmd)
|
|
|
|
|
|
if len(parts) == 0 {
|
|
|
|
|
|
return "", nil
|
|
|
|
|
|
}
|
|
|
|
|
|
out, err := exec.Command(parts[0], parts[1:]...).CombinedOutput()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
return strings.TrimSpace(string(out)), nil
|
|
|
|
|
|
},
|
|
|
|
|
|
new(func(string) error),
|
|
|
|
|
|
),
|
|
|
|
|
|
//возвращает значение указанной переменной из env
|
|
|
|
|
|
expr.Function("env",
|
|
|
|
|
|
func(args ...interface{}) (interface{}, error) {
|
|
|
|
|
|
if len(args) == 0 {
|
|
|
|
|
|
return "", nil
|
|
|
|
|
|
}
|
|
|
|
|
|
return os.Getenv(fmt.Sprintf("%v", args[0])), nil
|
|
|
|
|
|
}, new(func(string) error),
|
|
|
|
|
|
),
|
|
|
|
|
|
//проверяет наличие файла в системе
|
|
|
|
|
|
expr.Function("file_exists",
|
|
|
|
|
|
func(args ...interface{}) (interface{}, error) {
|
|
|
|
|
|
if len(args) == 0 {
|
|
|
|
|
|
return false, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
path := fmt.Sprintf("%v", args[0])
|
|
|
|
|
|
fileInfo, err := os.Stat(path)
|
|
|
|
|
|
if err == nil && !fileInfo.IsDir() {
|
|
|
|
|
|
return true, nil
|
|
|
|
|
|
} else if err == nil && fileInfo.IsDir() {
|
|
|
|
|
|
return false, fmt.Errorf("file is dir")
|
|
|
|
|
|
}
|
|
|
|
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
|
|
|
|
return false, fmt.Errorf("no such directory or file")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
}, new(func(string) bool),
|
|
|
|
|
|
),
|
|
|
|
|
|
//проверяет наличие директории в системе
|
|
|
|
|
|
expr.Function("dir_exists",
|
|
|
|
|
|
func(args ...interface{}) (interface{}, error) {
|
|
|
|
|
|
if len(args) == 0 {
|
|
|
|
|
|
return false, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
path := fmt.Sprintf("%v", args[0])
|
|
|
|
|
|
fileInfo, err := os.Stat(path)
|
|
|
|
|
|
if err == nil && fileInfo.IsDir() {
|
|
|
|
|
|
return true, nil
|
|
|
|
|
|
} else if err == nil && !fileInfo.IsDir() {
|
|
|
|
|
|
return false, fmt.Errorf("file is not a dir")
|
|
|
|
|
|
}
|
|
|
|
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
|
|
|
|
return false, fmt.Errorf("no such directory or file")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
}, new(func(string) bool),
|
|
|
|
|
|
),
|
|
|
|
|
|
//проверяет доступность порта
|
|
|
|
|
|
expr.Function("port_free",
|
|
|
|
|
|
func(args ...interface{}) (interface{}, error) {
|
|
|
|
|
|
if len(args) == 0 {
|
|
|
|
|
|
return false, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
port := fmt.Sprintf("%v", args[0])
|
|
|
|
|
|
cmd := exec.Command("ss", "-ltn", "sport =: "+port)
|
|
|
|
|
|
return cmd.Run() != nil, nil
|
|
|
|
|
|
}, new(func(string) error),
|
|
|
|
|
|
),
|
|
|
|
|
|
//проверяет наличие пользователя в системе
|
|
|
|
|
|
expr.Function("user_exists",
|
|
|
|
|
|
func(args ...interface{}) (interface{}, error) {
|
|
|
|
|
|
if len(args) == 0 {
|
|
|
|
|
|
return false, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
name := fmt.Sprintf("%v", args[0])
|
|
|
|
|
|
cmd := exec.Command("id", "-u", name)
|
|
|
|
|
|
return cmd.Run() == nil, nil
|
|
|
|
|
|
}, new(func(string) bool),
|
|
|
|
|
|
),
|
2026-01-21 17:56:21 +05:00
|
|
|
|
}
|
|
|
|
|
|
}
|