package gitlabcivalidator import ( "fmt" "gopkg.in/yaml.v3" ) // ParseGitLabCIConfig разбирает YAML и возвращает структуру GitLabCIConfig. func ParseGitLabCIConfig(data []byte) (*GitLabCIConfig, error) { var config GitLabCIConfig var root yaml.Node // Парсим YAML в узел root err := yaml.Unmarshal(data, &root) if err != nil { return nil, fmt.Errorf("ошибка синтаксического анализа YAML: %w", err) } // Если root пустой, значит YAML-файл был пуст или некорректен if len(root.Content) == 0 { return nil, fmt.Errorf("файл .gitlab-ci.yml пуст или не содержит допустимых данных") } // Ожидаем, что корневой узел — это YAML-документ if root.Kind != yaml.DocumentNode || len(root.Content) == 0 { return nil, fmt.Errorf("некорректный формат YAML: ожидался корневой объект") } // Основной объект — первый дочерний элемент mainNode := root.Content[0] // Декодируем root-объект в структуру err = mainNode.Decode(&config) if err != nil { return nil, fmt.Errorf("не удалось декодировать конфигурацию GitLab CI: %w", err) } // Ищем узел `jobs` вручную for i := 0; i < len(mainNode.Content)-1; i += 2 { keyNode := mainNode.Content[i] valNode := mainNode.Content[i+1] if keyNode.Value == "jobs" && valNode.Kind == yaml.MappingNode { // `jobs` найден, разбираем его вручную config.Jobs = make(map[string]*Job) for j := 0; j < len(valNode.Content)-1; j += 2 { jobKeyNode := valNode.Content[j] // Имя job'а jobValNode := valNode.Content[j+1] // Значение job'а (массив или объект) if jobValNode.Kind == yaml.MappingNode { job := &Job{ Name: jobKeyNode.Value, Line: jobKeyNode.Line, Column: jobKeyNode.Column, DependencyLines: make(map[string]struct{ Line, Column int }), } // Декодируем содержимое job err := jobValNode.Decode(job) if err != nil { return nil, fmt.Errorf("не удалось декодировать job '%s': %w", job.Name, err) } for d := 0; d < len(jobValNode.Content)-1; d += 2 { depKey := jobValNode.Content[d] depVal := jobValNode.Content[d+1] if depKey.Value == "dependencies" && depVal.Kind == yaml.SequenceNode { for k := 0; k < len(depVal.Content); k++ { depNode := depVal.Content[k] job.Dependencies = append(job.Dependencies, depNode.Value) job.DependencyLines[depNode.Value] = struct { Line int Column int }{depNode.Line, depNode.Column} } } } // Сохраняем job в конфигурации config.Jobs[job.Name] = job } } } } return &config, nil }