feat: added versionning & About window
This commit is contained in:
+53
-6
@@ -68,6 +68,12 @@ type Def struct {
|
||||
Dynamic bool `json:"dynamic,omitempty"` // references not predefined (any value counts)
|
||||
AddPrefixes []string `json:"add_prefixes,omitempty"` // possible reference additional prefixes
|
||||
|
||||
// OrRules are ADDITIONAL searches OR'd with the primary one above: a QSO
|
||||
// earns a reference if the primary match OR any of these match. Lets a
|
||||
// French department (DDFM) be found from "D74" in the note AND from a postal
|
||||
// code "74140" in the address (pattern captures "74", Prefix "D" → "D74").
|
||||
OrRules []OrRule `json:"or_rules,omitempty"`
|
||||
|
||||
// --- Scope ---
|
||||
DXCCFilter []int `json:"dxcc_filter"` // limit to these DXCC entities (nil = any)
|
||||
ValidBands []string `json:"valid_bands,omitempty"` // empty = all bands
|
||||
@@ -84,6 +90,20 @@ type Def struct {
|
||||
Builtin bool `json:"builtin"` // shipped default (informational)
|
||||
}
|
||||
|
||||
// OrRule is one additional search OR'd with the award's primary matching rule.
|
||||
// Same knobs as the primary (field + how to match), plus Prefix which is
|
||||
// prepended to each reference it finds so a captured value can be normalised to
|
||||
// the award's reference codes (e.g. postal "74" + Prefix "D" → "D74").
|
||||
type OrRule struct {
|
||||
Field string `json:"field"` // QSO field to scan
|
||||
MatchBy string `json:"match_by,omitempty"` // "code" | "description" | "pattern"
|
||||
ExactMatch bool `json:"exact_match,omitempty"` // match the whole field vs substring
|
||||
Pattern string `json:"pattern,omitempty"` // Go regexp; group 1 = reference
|
||||
LeadingStr string `json:"leading_str,omitempty"` // strip this prefix before matching
|
||||
TrailingStr string `json:"trailing_str,omitempty"` // strip this suffix before matching
|
||||
Prefix string `json:"prefix,omitempty"` // prepended to each found reference
|
||||
}
|
||||
|
||||
// Defaults are the built-in awards seeded on first run (then user-editable).
|
||||
func Defaults() []Def {
|
||||
// Confirmed = any confirmation (LoTW or paper QSL). Validated = the stricter
|
||||
@@ -511,13 +531,15 @@ func labelRef(rf *Ref, d *Def, code string, rl refList, hasList bool, nameOf Nam
|
||||
|
||||
// candidates extracts the reference(s) a QSO contributes to an award, enforcing
|
||||
// a predefined list when one applies.
|
||||
func candidates(d *Def, re *regexp.Regexp, q *qso.QSO, rl refList, hasList bool) []string {
|
||||
raw := strings.TrimSpace(stripAffix(fieldRaw(d.Field, q), d.LeadingStr, d.TrailingStr))
|
||||
// searchOne runs one matching rule (the primary or an OR rule) over a QSO and
|
||||
// returns the reference codes it finds, each prefixed with `prefix` (so a
|
||||
// captured "74" becomes "D74"). predefined enables list-aware matching.
|
||||
func searchOne(field, matchBy string, re *regexp.Regexp, exact bool, leading, trailing, prefix string, q *qso.QSO, rl refList, predefined bool) []string {
|
||||
raw := strings.TrimSpace(stripAffix(fieldRaw(field, q), leading, trailing))
|
||||
if raw == "" {
|
||||
return nil
|
||||
}
|
||||
predefined := hasList && !d.Dynamic
|
||||
byDesc := predefined && strings.EqualFold(strings.TrimSpace(d.MatchBy), "description")
|
||||
byDesc := predefined && strings.EqualFold(strings.TrimSpace(matchBy), "description")
|
||||
|
||||
var found []string
|
||||
switch {
|
||||
@@ -530,7 +552,7 @@ func candidates(d *Def, re *regexp.Regexp, q *qso.QSO, rl refList, hasList bool)
|
||||
// the field equals the name; otherwise the name is a substring of it.
|
||||
up := strings.ToUpper(raw)
|
||||
for _, nc := range rl.names {
|
||||
if d.ExactMatch {
|
||||
if exact {
|
||||
if up == nc.name {
|
||||
found = append(found, nc.code)
|
||||
}
|
||||
@@ -538,7 +560,7 @@ func candidates(d *Def, re *regexp.Regexp, q *qso.QSO, rl refList, hasList bool)
|
||||
found = append(found, nc.code)
|
||||
}
|
||||
}
|
||||
case predefined && !d.ExactMatch:
|
||||
case predefined && !exact:
|
||||
// "Search reference inside the field": look up each token of the field in
|
||||
// the list — O(tokens), not O(all references) — plus test the few
|
||||
// references that declare a regex.
|
||||
@@ -558,6 +580,31 @@ func candidates(d *Def, re *regexp.Regexp, q *qso.QSO, rl refList, hasList bool)
|
||||
// counts each reference separately.
|
||||
found = splitRefs(raw)
|
||||
}
|
||||
if prefix != "" {
|
||||
for i := range found {
|
||||
found[i] = prefix + found[i]
|
||||
}
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
func candidates(d *Def, re *regexp.Regexp, q *qso.QSO, rl refList, hasList bool) []string {
|
||||
predefined := hasList && !d.Dynamic
|
||||
|
||||
// Primary search, then each OR rule — a QSO earns a reference if any matches.
|
||||
found := searchOne(d.Field, d.MatchBy, re, d.ExactMatch, d.LeadingStr, d.TrailingStr, "", q, rl, predefined)
|
||||
for i := range d.OrRules {
|
||||
r := &d.OrRules[i]
|
||||
var rre *regexp.Regexp
|
||||
if p := strings.TrimSpace(r.Pattern); p != "" {
|
||||
c, err := regexp.Compile(p)
|
||||
if err != nil {
|
||||
continue // skip a rule with a bad regex rather than failing the award
|
||||
}
|
||||
rre = c
|
||||
}
|
||||
found = append(found, searchOne(r.Field, r.MatchBy, rre, r.ExactMatch, r.LeadingStr, r.TrailingStr, r.Prefix, q, rl, predefined)...)
|
||||
}
|
||||
|
||||
if !predefined {
|
||||
return dedupe(found)
|
||||
|
||||
Reference in New Issue
Block a user