package classes import ( "fmt" "regexp" "slices" "strings" "git.golem15.com/golem15/sm-sitemap-plugin/models" "git.golem15.com/golem15/summercms/modules/compass" ) // TiersConfigKey is the config key of the priority tier rules. The plugin's // config/config.yaml ships the rules of the PHP getPriorityForUrl; a host // replaces the whole list in its own config. const TiersConfigKey = "golem15.sitemap.priority_tiers" // TierConfig is one configured tier rule: Setting names the priority setting // whose value applies (priority_homepage, priority_services, priority_blog, // priority_categories or priority_legal), and exactly one of Exact (paths // that match as they are) and Pattern (a regular expression, RE2 syntax) // selects the URLs. type TierConfig struct { Setting string `koanf:"setting"` Exact []string `koanf:"exact"` Pattern string `koanf:"pattern"` } // TierRule is a compiled tier rule. type TierRule struct { Setting string Exact []string Pattern *regexp.Regexp } // Tiers is the ordered tier rule list; the first matching rule wins. type Tiers []TierRule // CompileTiers checks and compiles tier rules. Every error names the rule: // an unknown setting, neither or both of exact and pattern, or a pattern // that does not compile. func CompileTiers(rules []TierConfig) (Tiers, error) { out := make(Tiers, 0, len(rules)) for i, r := range rules { if _, ok := models.PriorityDefaults[r.Setting]; !ok { return nil, fmt.Errorf("%s[%d]: setting %q is not one of priority_homepage, priority_services, priority_blog, priority_categories, priority_legal", TiersConfigKey, i, r.Setting) } hasPattern := strings.TrimSpace(r.Pattern) != "" if hasPattern == (len(r.Exact) > 0) { return nil, fmt.Errorf("%s[%d]: set exactly one of exact and pattern", TiersConfigKey, i) } rule := TierRule{Setting: r.Setting, Exact: append([]string(nil), r.Exact...)} if hasPattern { re, err := regexp.Compile(r.Pattern) if err != nil { return nil, fmt.Errorf("%s[%d]: pattern: %w", TiersConfigKey, i, err) } rule.Pattern = re } out = append(out, rule) } return out, nil } // LoadTiers reads and compiles the rules at TiersConfigKey; an absent key // is no rules. func LoadTiers(cfg *compass.Config) (Tiers, error) { if cfg == nil || !cfg.Has(TiersConfigKey) { return Tiers{}, nil } var rules []TierConfig if err := cfg.LoadSection(TiersConfigKey, &rules); err != nil { return nil, fmt.Errorf("%s must be a list of {setting, exact} or {setting, pattern} rules: %w", TiersConfigKey, err) } return CompileTiers(rules) } // Priority is PHP getPriorityForUrl with configured rules: when the // settings enable tiers, the URL's path (parse_url's, "/" when it has none) // right-trimmed of slashes is matched against the rules in order and the // first match's setting value is returned. ok is false when tiers are off or // no rule matches, so the item's own priority applies. func (t Tiers) Priority(url string, s models.Settings) (string, bool) { if !s.EnablePriorityTiers { return "", false } path := URLPath(url) if path == "" { path = "/" } path = strings.TrimRight(path, "/") for _, r := range t { if r.Pattern != nil && r.Pattern.MatchString(path) || r.Pattern == nil && slices.Contains(r.Exact, path) { return s.Priority(r.Setting), true } } return "", false }