hello/main.go

360 lines
11 KiB
Go
Raw 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.

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()
}