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"` } // Menu управляет отображением меню type Menu struct { items map[int]MenuItem variables *Variables baseDir string } // NewMenu инициализирует меню из YAML func NewMenu(yamlFile string) *Menu { currDir, _ := os.Getwd() // baseDir := filepath.Join(currDir, "menu") // Базовая директория меню 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 menu struct { Items map[int]MenuItem `yaml:"items"` } if err := decoder.Decode(&menu); err != nil { log.Fatalf("Ошибка парсинга YAML: %v", err) } return &Menu{ items: menu.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() { 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("Выберите действие: ") 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 { subMenu := &Menu{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() }