feat(11-02): add [groups] TOML support and group-ordered PrintConfig
- Add Groups map[string]string field to rawConfig struct - Add applyGroupOverrides function for [groups] reassignment with stderr warning on unknown class - Add groupOrder canonical slice for section header ordering - Refactor PrintConfig to emit group headers (Infrastructure, Web, Mail, Remote Access, File Transfer, Database, Discovery, VoIP, Unknown) - Classes within each group sorted by ascending BaseHz - User-defined classes emitted under '# User-defined' section - Non-canonical group names (user-invented) emitted in sorted order after canonical groups
This commit is contained in:
+97
-11
@@ -38,6 +38,7 @@ type RawRule struct {
|
||||
type rawConfig struct {
|
||||
Sounds map[string]SoundOverride `toml:"sounds"`
|
||||
Rules []RawRule `toml:"rules"`
|
||||
Groups map[string]string `toml:"groups"`
|
||||
}
|
||||
|
||||
// LoadResult is the return type from Load(). It carries the merged FreqConfig map,
|
||||
@@ -99,6 +100,7 @@ func Load(configPath string) (LoadResult, error) {
|
||||
// Add auto-freq entries BEFORE merge so that [sounds.X] overrides for user classes apply.
|
||||
addAutoFreqEntries(freqCfgs, userRules, autoClasses)
|
||||
merge(freqCfgs, raw.Sounds)
|
||||
applyGroupOverrides(freqCfgs, raw.Groups)
|
||||
|
||||
return LoadResult{
|
||||
FreqCfgs: freqCfgs,
|
||||
@@ -262,6 +264,28 @@ func waveformString(wt synth.WaveformType) string {
|
||||
}
|
||||
}
|
||||
|
||||
// applyGroupOverrides overlays [groups] reassignments onto freqCfgs.Group in-place.
|
||||
// Unknown class names produce a warning to stderr (D-09).
|
||||
// Unknown group names are silently accepted -- users can invent custom groups (D-09).
|
||||
func applyGroupOverrides(cfgs map[classify.TrafficClass]synth.FreqConfig, groups map[string]string) {
|
||||
for className, groupName := range groups {
|
||||
cls := classify.TrafficClass(className)
|
||||
cfg, known := cfgs[cls]
|
||||
if !known {
|
||||
fmt.Fprintf(os.Stderr, "Warning: config: [groups]: unknown class %q (ignored)\n", className)
|
||||
continue
|
||||
}
|
||||
cfg.Group = groupName
|
||||
cfgs[cls] = cfg
|
||||
}
|
||||
}
|
||||
|
||||
// groupOrder defines the canonical display order for --print-config section headers (D-04).
|
||||
var groupOrder = []string{
|
||||
"Infrastructure", "Web", "Mail", "Remote Access",
|
||||
"File Transfer", "Database", "Discovery", "VoIP", "Unknown",
|
||||
}
|
||||
|
||||
// PrintConfig returns the effective configuration as commented TOML output.
|
||||
// The output includes a header with source path and generation date, an optional
|
||||
// [[rules]] section for user-defined rules, and a [sounds.*] section for all
|
||||
@@ -294,23 +318,34 @@ func PrintConfig(result LoadResult) string {
|
||||
}
|
||||
}
|
||||
|
||||
// [sounds.*] section — built-in classes first, then user-defined sorted alphabetically
|
||||
// [sounds.*] section — built-in classes grouped by family, then user-defined
|
||||
|
||||
// Build group -> []TrafficClass index from AllClasses()
|
||||
builtinByGroup := map[string][]classify.TrafficClass{}
|
||||
builtinSet := map[classify.TrafficClass]bool{}
|
||||
for _, cls := range classify.AllClasses() {
|
||||
builtinSet[cls] = true
|
||||
cfg := result.FreqCfgs[cls]
|
||||
grp := cfg.Group
|
||||
builtinByGroup[grp] = append(builtinByGroup[grp], cls)
|
||||
}
|
||||
|
||||
// Collect user-defined classes (in FreqCfgs but not in AllClasses)
|
||||
var userClasses []string
|
||||
for cls := range result.FreqCfgs {
|
||||
if !builtinSet[cls] {
|
||||
userClasses = append(userClasses, string(cls))
|
||||
// Sort each group by ascending BaseHz (D-05) using result.FreqCfgs (effective Hz, not defaults)
|
||||
for grp := range builtinByGroup {
|
||||
classes := builtinByGroup[grp]
|
||||
sort.Slice(classes, func(i, j int) bool {
|
||||
return result.FreqCfgs[classes[i]].BaseHz < result.FreqCfgs[classes[j]].BaseHz
|
||||
})
|
||||
}
|
||||
}
|
||||
sort.Strings(userClasses)
|
||||
|
||||
// Emit built-in classes first
|
||||
for _, cls := range classify.AllClasses() {
|
||||
// Emit built-in classes grouped with headers (D-04)
|
||||
for _, grp := range groupOrder {
|
||||
classes, ok := builtinByGroup[grp]
|
||||
if !ok || len(classes) == 0 {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(&sb, "# %s\n\n", grp)
|
||||
for _, cls := range classes {
|
||||
cfg := result.FreqCfgs[cls]
|
||||
annotation := classAnnotation(cls, cfg, result.AutoClasses)
|
||||
fmt.Fprintf(&sb, "# %s -- %.1f Hz (%s)\n", string(cls), cfg.BaseHz, annotation)
|
||||
@@ -319,8 +354,58 @@ func PrintConfig(result LoadResult) string {
|
||||
fmt.Fprintf(&sb, "waveform = %q\n", waveformString(cfg.WaveformType))
|
||||
fmt.Fprintf(&sb, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Emit user-defined classes sorted alphabetically
|
||||
// Check for custom groups (from [groups] reassignment) that are not in groupOrder.
|
||||
// For non-canonical group names (user-invented), collect and emit separately.
|
||||
customGroups := map[string][]classify.TrafficClass{}
|
||||
for _, cls := range classify.AllClasses() {
|
||||
cfg := result.FreqCfgs[cls]
|
||||
grp := cfg.Group
|
||||
isCanonical := false
|
||||
for _, cg := range groupOrder {
|
||||
if grp == cg {
|
||||
isCanonical = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isCanonical {
|
||||
customGroups[grp] = append(customGroups[grp], cls)
|
||||
}
|
||||
}
|
||||
// Sort and emit custom group sections
|
||||
var customGroupNames []string
|
||||
for grp := range customGroups {
|
||||
customGroupNames = append(customGroupNames, grp)
|
||||
}
|
||||
sort.Strings(customGroupNames)
|
||||
for _, grp := range customGroupNames {
|
||||
classes := customGroups[grp]
|
||||
sort.Slice(classes, func(i, j int) bool {
|
||||
return result.FreqCfgs[classes[i]].BaseHz < result.FreqCfgs[classes[j]].BaseHz
|
||||
})
|
||||
fmt.Fprintf(&sb, "# %s\n\n", grp)
|
||||
for _, cls := range classes {
|
||||
cfg := result.FreqCfgs[cls]
|
||||
annotation := classAnnotation(cls, cfg, result.AutoClasses)
|
||||
fmt.Fprintf(&sb, "# %s -- %.1f Hz (%s)\n", string(cls), cfg.BaseHz, annotation)
|
||||
fmt.Fprintf(&sb, "[sounds.%s]\n", string(cls))
|
||||
fmt.Fprintf(&sb, "frequency = %.1f\n", cfg.BaseHz)
|
||||
fmt.Fprintf(&sb, "waveform = %q\n", waveformString(cfg.WaveformType))
|
||||
fmt.Fprintf(&sb, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Emit user-defined classes (in FreqCfgs but not in AllClasses) under "# User-defined" (D-06)
|
||||
var userClasses []string
|
||||
for cls := range result.FreqCfgs {
|
||||
if !builtinSet[cls] {
|
||||
userClasses = append(userClasses, string(cls))
|
||||
}
|
||||
}
|
||||
sort.Strings(userClasses)
|
||||
if len(userClasses) > 0 {
|
||||
fmt.Fprintf(&sb, "# User-defined\n\n")
|
||||
for _, clsStr := range userClasses {
|
||||
cls := classify.TrafficClass(clsStr)
|
||||
cfg := result.FreqCfgs[cls]
|
||||
@@ -331,6 +416,7 @@ func PrintConfig(result LoadResult) string {
|
||||
fmt.Fprintf(&sb, "waveform = %q\n", waveformString(cfg.WaveformType))
|
||||
fmt.Fprintf(&sb, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user