360 lines
11 KiB
Go
360 lines
11 KiB
Go
package main
|
||
|
||
import (
|
||
"bufio"
|
||
"flag"
|
||
"fmt"
|
||
"log"
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"regexp"
|
||
"sort"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"gopkg.in/ini.v1"
|
||
"gopkg.in/yaml.v2"
|
||
)
|
||
|
||
// Variables описывает работу с .ini файлами
|
||
type Variables struct {
|
||
iniFileName string
|
||
config *ini.File
|
||
}
|
||
|
||
// NewVariables конструктор для работы с .ini файлами
|
||
func NewVariables(iniFileName string) *Variables {
|
||
config, err := ini.LooseLoad(iniFileName)
|
||
if err != nil {
|
||
log.Fatalf("Ошибка загрузки ini-файла: %v", err)
|
||
}
|
||
|
||
return &Variables{
|
||
iniFileName: iniFileName,
|
||
config: config,
|
||
}
|
||
}
|
||
|
||
// GetVar читает или запрашивает переменную из .ini
|
||
func (v *Variables) GetVar(sectionName, varName string, interactive bool) string {
|
||
section := v.config.Section(sectionName)
|
||
value := section.Key(varName).String()
|
||
|
||
if interactive || value == "" {
|
||
reader := bufio.NewReader(os.Stdin)
|
||
fmt.Printf("Введите %s (%s): ", varName, value)
|
||
newValue, _ := reader.ReadString('\n')
|
||
newValue = strings.TrimSpace(newValue)
|
||
|
||
if newValue != "" {
|
||
section.Key(varName).SetValue(newValue)
|
||
v.config.SaveTo(v.iniFileName)
|
||
return newValue
|
||
}
|
||
}
|
||
if !interactive && value == "" {
|
||
fmt.Printf("Переменная %s не найдена", varName)
|
||
os.Exit(1)
|
||
}
|
||
|
||
return value
|
||
}
|
||
|
||
// MenuItem описывает структуру меню из YAML
|
||
type MenuItem struct {
|
||
Title string `yaml:"title"`
|
||
File string `yaml:"file,omitempty"`
|
||
Items map[int]MenuItem `yaml:"items,omitempty"`
|
||
Commands []string `yaml:"commands,omitempty"`
|
||
Catch []string `yaml:"catch,omitempty"`
|
||
Vars []string `yaml:"vars,omitempty"`
|
||
Env string `yaml:"env,omitempty"`
|
||
Hint string `yaml:"hint,omitempty"` // Добавляем hint в MenuItem
|
||
}
|
||
|
||
// Menu управляет отображением меню
|
||
type Menu struct {
|
||
hint string
|
||
items map[int]MenuItem
|
||
variables *Variables
|
||
baseDir string
|
||
}
|
||
|
||
// NewMenu инициализирует меню из YAML
|
||
func NewMenu(yamlFile string) *Menu {
|
||
currDir, _ := os.Getwd()
|
||
baseDir := currDir
|
||
yamlPath := resolvePath(baseDir, yamlFile)
|
||
|
||
// Читаем YAML файл
|
||
file, err := os.Open(yamlPath)
|
||
if err != nil {
|
||
log.Fatalf("Ошибка чтения YAML файла %s: %v", yamlPath, err)
|
||
}
|
||
defer file.Close()
|
||
|
||
decoder := yaml.NewDecoder(file)
|
||
var menuData struct {
|
||
Hint string `yaml:"hint,omitempty"` // Добавляем поле hint
|
||
Items map[int]MenuItem `yaml:"items"`
|
||
}
|
||
|
||
if err := decoder.Decode(&menuData); err != nil {
|
||
log.Fatalf("Ошибка парсинга YAML: %v", err)
|
||
}
|
||
|
||
return &Menu{
|
||
hint: menuData.Hint,
|
||
items: menuData.Items,
|
||
variables: NewVariables("env.ini"),
|
||
baseDir: baseDir,
|
||
}
|
||
}
|
||
|
||
// resolvePath корректно формирует путь к файлу
|
||
func resolvePath(baseDir, yamlFile string) string {
|
||
// Если путь абсолютный, используем его как есть
|
||
if filepath.IsAbs(yamlFile) {
|
||
return yamlFile
|
||
}
|
||
|
||
// Если путь уже содержит сегмент "menu", оставляем всё как есть
|
||
if strings.HasPrefix(baseDir, "menu"+string(os.PathSeparator)) {
|
||
return filepath.Join(baseDir, yamlFile)
|
||
}
|
||
|
||
// Обычно добавляем сегмент "menu" перед файлом
|
||
return filepath.Join(baseDir, string(os.PathSeparator)+"menu", yamlFile)
|
||
}
|
||
|
||
// ShowItems отображает главное меню
|
||
func (m *Menu) ShowItems() {
|
||
// Выводим подсказку, если она есть
|
||
if m.hint != "" {
|
||
fmt.Println(m.hint)
|
||
// fmt.Println() // Пустая строка после подсказки
|
||
} else {
|
||
fmt.Println()
|
||
}
|
||
|
||
fmt.Println("0. Выход")
|
||
|
||
// Извлекаем ключи из мапы
|
||
keys := make([]int, 0, len(m.items))
|
||
for key := range m.items {
|
||
keys = append(keys, key)
|
||
}
|
||
|
||
// Сортируем ключи
|
||
sort.Ints(keys)
|
||
|
||
for _, key := range keys {
|
||
fmt.Printf("%s\n", m.items[key].Title)
|
||
}
|
||
}
|
||
|
||
// Loop запускает интерактивный цикл меню
|
||
func (m *Menu) Loop() {
|
||
reader := bufio.NewReader(os.Stdin)
|
||
|
||
for {
|
||
m.ShowItems()
|
||
fmt.Print("\n")
|
||
fmt.Print("Выберите действие: ")
|
||
input, _ := reader.ReadString('\n')
|
||
input = strings.TrimSpace(input)
|
||
|
||
if input == "0" {
|
||
break
|
||
}
|
||
|
||
if itemNum, err := strconv.Atoi(input); err == nil {
|
||
m.ChooseItem(itemNum)
|
||
} else {
|
||
fmt.Println("Неверный ввод")
|
||
}
|
||
}
|
||
}
|
||
|
||
// ChooseItem обрабатывает выбор элемента меню
|
||
func (m *Menu) ChooseItem(itemNum int) {
|
||
item, found := m.items[itemNum]
|
||
if !found {
|
||
fmt.Println("Элемент не найден")
|
||
return
|
||
}
|
||
|
||
if len(item.File) > 0 {
|
||
// Подгружаем дочернее меню
|
||
f := strings.TrimPrefix(item.File, string(os.PathSeparator))
|
||
filePath := resolvePath(m.baseDir, f)
|
||
subMenu := NewMenu(filePath)
|
||
subMenu.Loop()
|
||
} else if len(item.Items) > 0 {
|
||
// Создаем подменю из вложенных items
|
||
subMenu := &Menu{
|
||
hint: item.Hint, // Используем hint из MenuItem
|
||
items: item.Items,
|
||
variables: m.variables,
|
||
baseDir: m.baseDir,
|
||
}
|
||
subMenu.Loop()
|
||
} else if len(item.Commands) > 0 {
|
||
vars := m.getVars(item, true)
|
||
if !m.runCommands(item, vars) {
|
||
m.catch(item, vars)
|
||
}
|
||
}
|
||
}
|
||
|
||
func (m *Menu) ExecItem(itemNum int) *Menu {
|
||
item, found := m.items[itemNum]
|
||
if !found {
|
||
fmt.Println("Элемент не найден")
|
||
return nil
|
||
}
|
||
|
||
if len(item.File) > 0 {
|
||
// Подгружаем дочернее меню
|
||
f := strings.TrimPrefix(item.File, string(os.PathSeparator))
|
||
filePath := resolvePath(m.baseDir, f)
|
||
subMenu := NewMenu(filePath)
|
||
return subMenu
|
||
} else if len(item.Items) > 0 {
|
||
subMenu := &Menu{items: item.Items, variables: m.variables, baseDir: m.baseDir}
|
||
return subMenu
|
||
} else if len(item.Commands) > 0 {
|
||
vars := m.getVars(item, false)
|
||
if !m.runCommands(item, vars) {
|
||
m.catch(item, vars)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// getVars получает переменные из .ini
|
||
func (m *Menu) getVars(item MenuItem, interactive bool) map[string]string {
|
||
vars := make(map[string]string)
|
||
vars["currentDir"], _ = os.Getwd()
|
||
|
||
for _, varName := range item.Vars {
|
||
vars[varName] = m.variables.GetVar(item.Env, varName, interactive)
|
||
}
|
||
|
||
return vars
|
||
}
|
||
|
||
// runCommands выполняет команды
|
||
func (m *Menu) runCommands(item MenuItem, vars map[string]string) bool {
|
||
for _, cmd := range item.Commands {
|
||
// Здесь можно вызвать реальное выполнение, используя os/exec
|
||
re := regexp.MustCompile(`%\(([a-zA-Z0-9\-]*)\)s`)
|
||
command := re.ReplaceAllStringFunc(cmd, func(match string) string {
|
||
// Извлекаем ключ внутри %( и )s
|
||
key := re.FindStringSubmatch(match)[1]
|
||
|
||
// Проверяем, есть ли значение для ключа в map
|
||
if val, exists := vars[key]; exists {
|
||
return val // Заменяем найденной строкой
|
||
}
|
||
// Если ключа нет в map, оставляем как есть
|
||
return match
|
||
})
|
||
fmt.Printf("Выполнение: %s\n", command)
|
||
|
||
// Разделяем на части
|
||
parts := strings.Split(command, " ")
|
||
|
||
// Проверяем, является ли первая часть установкой переменной окружения
|
||
envs := []string{} // Хранилище для переменных окружения
|
||
for len(parts) > 0 && strings.Contains(parts[0], "=") {
|
||
envs = append(envs, parts[0]) // Добавляем переменную в массив env
|
||
parts = parts[1:] // Убираем первую часть
|
||
}
|
||
|
||
cmd := exec.Command("sh", "-c", command)
|
||
|
||
// Добавляем переменные окружения к текущим
|
||
cmd.Env = append(os.Environ(), envs...)
|
||
// Передаём текущее окружение
|
||
// cmd.Env = os.Environ()
|
||
cmd.Stdin = os.Stdin // Ввод
|
||
cmd.Stdout = os.Stdout // Вывод
|
||
cmd.Stderr = os.Stderr // Вывод ошибок
|
||
|
||
// Запускаем команду
|
||
if err := cmd.Run(); err != nil {
|
||
os.Stderr.WriteString("Ошибка выполнения: " + err.Error() + "\n")
|
||
}
|
||
}
|
||
return true
|
||
}
|
||
|
||
// catch выполняет обработку ошибок команд
|
||
func (m *Menu) catch(item MenuItem, vars map[string]string) {
|
||
for _, cmd := range item.Catch {
|
||
fmt.Printf("Обработка ошибки: %s\n", cmd)
|
||
// Здесь можно вызвать реальное выполнение, используя os/exec
|
||
re := regexp.MustCompile(`%\(([a-zA-Z0-9]*)\)s`)
|
||
command := re.ReplaceAllStringFunc(cmd, func(match string) string {
|
||
// Извлекаем ключ внутри %( и )s
|
||
key := re.FindStringSubmatch(match)[1]
|
||
|
||
// Проверяем, есть ли значение для ключа в map
|
||
if val, exists := vars[key]; exists {
|
||
return val // Заменяем найденной строкой
|
||
}
|
||
// Если ключа нет в map, оставляем как есть
|
||
return match
|
||
})
|
||
fmt.Printf("Выполнение: %s\n", command)
|
||
|
||
// Разделяем на части
|
||
parts := strings.Split(command, " ")
|
||
|
||
// Проверяем, является ли первая часть установкой переменной окружения
|
||
envs := []string{} // Хранилище для переменных окружения
|
||
for len(parts) > 0 && strings.Contains(parts[0], "=") {
|
||
envs = append(envs, parts[0]) // Добавляем переменную в массив env
|
||
parts = parts[1:] // Убираем первую часть
|
||
}
|
||
|
||
// Теперь `parts` содержит только саму команду и её аргументы
|
||
cmd := exec.Command(parts[0], parts[1:]...)
|
||
|
||
// Добавляем переменные окружения к текущим
|
||
cmd.Env = append(os.Environ(), envs...)
|
||
// Передаём текущее окружение
|
||
// cmd.Env = os.Environ()
|
||
cmd.Stdin = os.Stdin // Ввод
|
||
cmd.Stdout = os.Stdout // Вывод
|
||
cmd.Stderr = os.Stderr // Вывод ошибок
|
||
|
||
// Запускаем команду
|
||
if err := cmd.Run(); err != nil {
|
||
os.Stderr.WriteString("Ошибка выполнения: " + err.Error() + "\n")
|
||
}
|
||
}
|
||
}
|
||
|
||
func main() {
|
||
// Инициализация главного меню
|
||
menu := NewMenu("menu.yml") // Файл главного меню (menu/menu.yml)
|
||
|
||
// Создаем флаг -e/--exec с необязательным аргументом
|
||
execFlag := flag.String("exec", "", "Execution parameter")
|
||
flag.Parse() // Парсим аргументы
|
||
|
||
if *execFlag != "" { // Проверяем, задан ли флаг --exec
|
||
items := strings.Split(*execFlag, "-") // Разделяем строку по "-"
|
||
for _, item := range items { // Итерируемся по элементам
|
||
num, _ := strconv.Atoi(item)
|
||
menu = menu.ExecItem(num)
|
||
}
|
||
os.Exit(0) // Завершаем выполнение программы
|
||
}
|
||
|
||
menu.Loop()
|
||
}
|