From 097ab05209b4741fffac95082bd599741acff564 Mon Sep 17 00:00:00 2001 From: Wing Date: Tue, 3 Feb 2026 07:27:10 -0800 Subject: [PATCH 1/2] Convert DeviceReg map to a sorted slice to emulate PHP associative arrays for stable parsing results. --- parser/device/device_parser.go | 38 +++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/parser/device/device_parser.go b/parser/device/device_parser.go index c5afd21..d0c7f40 100644 --- a/parser/device/device_parser.go +++ b/parser/device/device_parser.go @@ -1,7 +1,8 @@ package device import ( - "sort" + "cmp" + "slices" "strings" . "github.com/slipros/devicedetector/parser" @@ -29,29 +30,46 @@ type Model struct { type DeviceReg struct { Regular `yaml:",inline" json:",inline"` + Brand string `yaml:"brand" json:"brand"` Model string `yaml:"model" json:"model"` Device string `yaml:"device" json:"device"` Models []*Model `yaml:"models" json:"models"` } type DeviceParserAbstract struct { - Regexes map[string]*DeviceReg + Regexes []*DeviceReg overAllMatch Regular } func (d *DeviceParserAbstract) Load(file string) error { var v map[string]*DeviceReg + var unknownDeviceReg *DeviceReg err := ReadYamlFile(file, &v) if err != nil { return err } - for _, item := range v { + for brand, item := range v { + item.Brand = brand item.Compile() for _, m := range item.Models { m.Compile() } + if brand != UnknownBrand { + d.Regexes = append(d.Regexes, item) + } else { + unknownDeviceReg = item + } + } + // Sort regexes DeviceReg array + brandCmp := func(a, b *DeviceReg) int { + return cmp.Compare(a.Brand, b.Brand) + } + slices.SortFunc(d.Regexes, brandCmp) + + // Finally append the catch-all unknown device if it exists. + if unknownDeviceReg != nil { + d.Regexes = append(d.Regexes, unknownDeviceReg) } - d.Regexes = v return nil } @@ -61,16 +79,11 @@ func (d *DeviceParserAbstract) PreMatch(ua string) bool { if count == 0 { return false } - sortKeys := make([]string, 0, count) - for k, _ := range d.Regexes { - sortKeys = append(sortKeys, k) - } - sort.Strings(sortKeys) sb := strings.Builder{} - sb.WriteString(d.Regexes[sortKeys[count-1]].Regex) + sb.WriteString(d.Regexes[count-1].Regex) for i := count - 2; i >= 0; i-- { sb.WriteString("|") - sb.WriteString(d.Regexes[sortKeys[i]].Regex) + sb.WriteString(d.Regexes[i].Regex) } d.overAllMatch.Regex = sb.String() d.overAllMatch.Compile() @@ -83,7 +96,8 @@ func (d *DeviceParserAbstract) Parse(ua string) *DeviceMatchResult { var regex *DeviceReg var brand string var matches []string - for brand, regex = range d.Regexes { + for _, regex = range d.Regexes { + brand = regex.Brand matches = regex.MatchUserAgent(ua) if len(matches) > 0 { break From 5d7ca319a3b990dbc7d7e3483bf24b3f06f34016 Mon Sep 17 00:00:00 2001 From: Wing Date: Fri, 6 Feb 2026 10:21:31 -0800 Subject: [PATCH 2/2] Optimized device type and brand lookups. --- parser/device.go | 47 ++++++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/parser/device.go b/parser/device.go index f7079f6..5f23263 100644 --- a/parser/device.go +++ b/parser/device.go @@ -36,6 +36,17 @@ var deviceTypes = map[string]int{ `peripheral`: DEVICE_TYPE_PERIPHERAL, } +var deviceTypeIds = make(map[int]string, len(deviceTypes)) + +func init() { + for k, v := range deviceTypes { + deviceTypeIds[v] = k + } + if len(deviceTypeIds) != len(deviceTypes) { + panic("duplicate device type") + } +} + // Known device brands // Note: Before using a new brand in on of the regex files, it needs to be added here var deviceBrands = map[string]string{ @@ -2138,6 +2149,17 @@ var deviceBrands = map[string]string{ `XX`: `Unknown`, } +var deviceBrandIds = make(map[string]string, len(deviceBrands)) + +func init() { + for k, v := range deviceBrands { + deviceBrandIds[v] = k + } + if len(deviceBrandIds) != len(deviceBrandIds) { + panic("duplicate device brands") + } +} + // Returns names of all available device types func GetAvailableDeviceTypeNames() []string { keys := make([]string, 0, len(deviceTypes)) @@ -2149,12 +2171,8 @@ func GetAvailableDeviceTypeNames() []string { // Returns the name of the given device type func GetDeviceName(deviceType int) string { - for k, v := range deviceTypes { - if v == deviceType { - return k - } - } - return "" + deviceName := deviceTypeIds[deviceType] + return deviceName } func GetDeviceType(deviceName string) int { @@ -2171,19 +2189,14 @@ func GetFullName(brandId string) string { } func GetShortName(name string) string { - for k, v := range deviceBrands { - if v == name { - return k - } + shortName, ok := deviceBrandIds[name] + if !ok { + shortName = name } - return name + return shortName } func FindBrand(key string) string { - for k, v := range deviceBrands { - if v == key { - return k - } - } - return "" + brand := deviceBrandIds[key] + return brand }