package check import ( "sort" "git.vakhrushev.me/av/convy/internal/manifest" "git.vakhrushev.me/av/convy/internal/suite" ) // Suite прогоняет все проверки набора и возвращает отчёт. func Suite(s *suite.Suite) *Report { rep := &Report{} checkManifest(s, rep) checkBaseLayers(s, rep) for _, d := range s.Docs { checkForm(s, d, rep) checkLinks(s, d, rep) if d.Front.Topic != "" { checkSpread(s, d, rep) } } return rep } // checkManifest проверяет сам манифест: форму префиксов, непересечение живого // с выбывшим, наличие объявленных файлов и отсутствие незарегистрированных. func checkManifest(s *suite.Suite, rep *Report) { m := s.Manifest for _, key := range m.Undecoded { rep.Warnf(Manifest, manifest.Name, 0, "ключ %s инструменту неизвестен", key) } for _, name := range []string{m.Language.Description, m.Language.Reading} { if name != "" && !s.Exists(name) { rep.Errorf(Manifest, manifest.Name, 0, "секция [language] объявляет документ %q, а файла нет", name) } } byPath := make(map[string][]string) for _, prefix := range m.LivePrefixes() { if err := manifest.ValidPrefix(prefix); err != nil { rep.Errorf(Manifest, manifest.Name, 0, "%s", err) } if m.PrefixRetired(prefix) { rep.Errorf(Manifest, manifest.Name, 0, "префикс %s значится и среди живых, и среди выбывших: выбывший не выдаётся повторно", prefix) } path := m.Prefixes.Live[prefix] byPath[path] = append(byPath[path], prefix) } for _, path := range sortedKeys(byPath) { if prefixes := byPath[path]; len(prefixes) > 1 { sort.Strings(prefixes) rep.Errorf(Manifest, manifest.Name, 0, "на файл %q объявлено несколько префиксов (%v): префикс принадлежит файлу", path, prefixes) } } for prefix := range m.Prefixes.Retired { if err := manifest.ValidPrefix(prefix); err != nil { rep.Errorf(Manifest, manifest.Name, 0, "среди выбывших: %s", err) } } for _, prefix := range sortedKeys(s.Missing) { rep.Errorf(Manifest, manifest.Name, 0, "префикс %s объявлен за файлом %q, а файла в наборе нет", prefix, s.Missing[prefix]) } for _, path := range s.Unregistered { rep.Errorf(Manifest, path, 1, "файл записан языком конвенций, но в манифесте набора не объявлен: для набора его нет") } for _, err := range s.Broken { rep.Errorf(Manifest, manifest.Name, 0, "%s", err) } for _, topic := range m.LiveTopics() { if m.TopicRetired(topic) { rep.Errorf(Manifest, manifest.Name, 0, "тема %q значится и среди живых, и среди выбывших", topic) } if len(s.Layers(topic)) == 0 { rep.Errorf(Manifest, manifest.Name, 0, "тема %q объявлена живой, а слоёв у неё в наборе нет: тема живёт, пока есть хотя бы один слой", topic) } } } // checkBaseLayers проверяет, что у темы не больше одного слоя без ключей оси. // Базовый слой единственный: он попадает в копию всегда, и второй такой // означал бы два базовых текста в одном собранном файле. func checkBaseLayers(s *suite.Suite, rep *Report) { for _, topic := range s.Manifest.LiveTopics() { var base []string for _, d := range s.Layers(topic) { if !d.Front.Axis() { base = append(base, d.Path) } } if len(base) > 1 { sort.Strings(base) for _, path := range base[1:] { rep.Errorf(Spread, path, 1, "у темы %q больше одного слоя без ключей оси: базовый слой единственный, остальные — %v", topic, base) } } } } func sortedKeys[V any](m map[string]V) []string { keys := make([]string, 0, len(m)) for k := range m { keys = append(keys, k) } sort.Strings(keys) return keys }