diff --git a/README.md b/README.md index b19e29c..851a10a 100644 --- a/README.md +++ b/README.md @@ -42,14 +42,29 @@ package main import ( "fmt" "log" + "os" "github.com/ua-parser/uap-go/uaparser" + "gopkg.in/yaml.v3" ) func main() { uagent := "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-us; Silk/1.1.0-80) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16 Silk-Accelerated=true" - parser, err := uaparser.New("./regexes.yaml") + + regexes, err := os.ReadFile("./regexes.yaml") + if err != nil { + log.Fatal(err) + } + + var def *uaparser.RegexesDefinitions + + if err := yaml.Unmarshal(regexes, def); err != nil { + fmt.Printf("error parsing regexes definitions. Error: %s\n", err.Error()) + return + } + + parser, err := uaparser.New(uaparser.WithRegexesDefinitions(def)) if err != nil { log.Fatal(err) } diff --git a/test.go b/test.go index 0b06021..4183e36 100644 --- a/test.go +++ b/test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/ua-parser/uap-go/uaparser" + "gopkg.in/yaml.v3" ) func main() { @@ -16,12 +17,33 @@ func main() { fmt.Printf("Usage: %s [old|new|both] [concurrency level]\n", os.Args[0]) return } + + regexes, err := os.ReadFile("./uap-core/regexes.yaml") + if err != nil { + fmt.Printf("Failed to read regexes file. Error: %s\n", err.Error()) + return + } + + var def uaparser.RegexDefinitions + + if err := yaml.Unmarshal(regexes, &def); err != nil { + fmt.Printf("error parsing regexes definitions. Error: %s\n", err.Error()) + return + } + var wg sync.WaitGroup cLevel, _ := strconv.Atoi(os.Args[2]) switch os.Args[1] { case "new": fmt.Println("Running new version of uap...") - uaParser, _ := uaparser.NewWithOptions("./uap-core/regexes.yaml", (uaparser.EOsLookUpMode | uaparser.EUserAgentLookUpMode), 100, 20, true, true, 1024) + uaParser, _ := uaparser.New( + uaparser.WithRegexDefinitions(def), + uaparser.WithMode(uaparser.EOsLookUpMode|uaparser.EUserAgentLookUpMode), + uaparser.WithMatchIdxNotOk(20), + uaparser.WithSort(true, uaparser.WithMissesThreshold(100)), + uaparser.WithDebug(true), + uaparser.WithCacheSize(1024), + ) for i := 0; i < cLevel; i++ { wg.Add(1) go runTest(uaParser, i, &wg) @@ -30,7 +52,7 @@ func main() { return case "old": fmt.Println("Running old version of uap...") - uaParser, _ := uaparser.New("./uap-core/regexes.yaml") + uaParser, _ := uaparser.New(uaparser.WithRegexDefinitions(def)) for i := 0; i < cLevel; i++ { wg.Add(1) go runTest(uaParser, i, &wg) @@ -39,13 +61,20 @@ func main() { return case "both": fmt.Println("Running new version of uap...") - uaParser, _ := uaparser.NewWithOptions("./uap-core/regexes.yaml", (uaparser.EOsLookUpMode | uaparser.EUserAgentLookUpMode), 100, 20, true, true, 1024) + uaParser, _ := uaparser.New( + uaparser.WithRegexDefinitions(def), + uaparser.WithMode(uaparser.EOsLookUpMode|uaparser.EUserAgentLookUpMode), + uaparser.WithMatchIdxNotOk(20), + uaparser.WithSort(true, uaparser.WithMissesThreshold(100)), + uaparser.WithDebug(true), + uaparser.WithCacheSize(1024), + ) for i := 0; i < cLevel; i++ { wg.Add(1) runTest(uaParser, i, &wg) } fmt.Println("Running old version of uap...") - uaParser, _ = uaparser.New("./uap-core/regexes.yaml") + uaParser, _ = uaparser.New(uaparser.WithRegexDefinitions(def)) for i := 0; i < cLevel; i++ { wg.Add(1) runTest(uaParser, i, &wg) diff --git a/uaparser/benchmark_test.go b/uaparser/benchmark_test.go index 1fc99c1..953959d 100644 --- a/uaparser/benchmark_test.go +++ b/uaparser/benchmark_test.go @@ -7,6 +7,8 @@ import ( "os" "strings" "testing" + + "gopkg.in/yaml.v3" ) var benchedParser *Parser @@ -15,11 +17,30 @@ var largeUasSample []string func init() { var err error - benchedParser, err = New("../uap-core/regexes.yaml") + + regexes, err := os.ReadFile("../uap-core/regexes.yaml") if err != nil { log.Fatal(err) } - benchedParserWithOptions, err = NewWithOptions("../uap-core/regexes.yaml", (EOsLookUpMode | EUserAgentLookUpMode), 100, 20, true, true, cDefaultCacheSize) + + var def RegexDefinitions + + if err := yaml.Unmarshal(regexes, &def); err != nil { + log.Fatal(err) + } + + benchedParser, err = New(WithRegexDefinitions(def)) + if err != nil { + log.Fatal(err) + } + benchedParserWithOptions, err = New(WithRegexDefinitions(def), + WithMode(EOsLookUpMode|EUserAgentLookUpMode), + WithMatchIdxNotOk(20), + WithSort(true, WithMissesThreshold(100)), + WithDebug(true), + WithCacheSize(cDefaultCacheSize), + ) + if err != nil { log.Fatal(err) } @@ -48,17 +69,27 @@ func BenchmarkParserWithOptions(b *testing.B) { } func BenchmarkParserWithDifferentCacheSize(b *testing.B) { - sizes := []int{cDefaultCacheSize, cDefaultCacheSize*2, cDefaultCacheSize*3, cDefaultCacheSize*4} + sizes := []int{cDefaultCacheSize, cDefaultCacheSize * 2, cDefaultCacheSize * 3, cDefaultCacheSize * 4} + + regexes, err := os.ReadFile("../uap-core/regexes.yaml") + if err != nil { + log.Fatal(err) + } + + var def RegexDefinitions + + if err := yaml.Unmarshal(regexes, &def); err != nil { + log.Fatal(err) + } for _, size := range sizes { - parser, err := NewWithOptions( - "../uap-core/regexes.yaml", - EOsLookUpMode | EUserAgentLookUpMode | EDeviceLookUpMode, - cDefaultMissesTreshold, - cDefaultMatchIdxNotOk, - false, - false, - size) + parser, err := New(WithRegexDefinitions(def), + WithMode(EOsLookUpMode|EUserAgentLookUpMode|EDeviceLookUpMode), + WithMatchIdxNotOk(cDefaultMatchIdxNotOk), + WithSort(false, WithMissesThreshold(cDefaultMissesTreshold)), + WithDebug(false), + WithCacheSize(size), + ) if err != nil { log.Fatal(err) } diff --git a/uaparser/deprecated.go b/uaparser/deprecated.go new file mode 100644 index 0000000..886a9e2 --- /dev/null +++ b/uaparser/deprecated.go @@ -0,0 +1,56 @@ +package uaparser + +import ( + "os" + + "gopkg.in/yaml.v3" +) + +// NewWithOptions is deprecated. +// Deprecated: Use New and option functions instead. +func NewWithOptions(regexFile string, mode, treshold, topCnt int, useSort, debugMode bool, cacheSize int) (*Parser, error) { + uaRegexes, err := os.ReadFile(regexFile) + if err != nil { + return nil, err + } + + var def RegexDefinitions + + if err := yaml.Unmarshal(uaRegexes, &def); err != nil { + return nil, err + } + + return New( + WithRegexDefinitions(def), + WithMode(LookupMode(mode)), + WithMatchIdxNotOk(topCnt), + WithSort(useSort, WithMissesThreshold(uint64(treshold))), + WithDebug(debugMode), + WithCacheSize(cacheSize), + ) +} + +// NewFromSaved is deprecated. +// Deprecated: Use New() instead. +func NewFromSaved() *Parser { + parser, err := New() + if err != nil { + // if the YAML is malformed, it's a programmatic error inside what + // we've statically-compiled in our binary. Panic! + panic(err.Error()) + } + + return parser +} + +// NewFromBytes is deprecated. +// Deprecated: Use New(WithRegexDefinitions(...)) instead +func NewFromBytes(data []byte) (*Parser, error) { + var def RegexDefinitions + + if err := yaml.Unmarshal(data, &def); err != nil { + return nil, err + } + + return New(WithRegexDefinitions(def)) +} diff --git a/uaparser/device.go b/uaparser/device.go index 7a115e7..13d9ed4 100644 --- a/uaparser/device.go +++ b/uaparser/device.go @@ -8,20 +8,20 @@ type Device struct { Model string } -func (parser *deviceParser) Match(line string, dvc *Device) { - matches := parser.Reg.FindStringSubmatchIndex(line) +func (dp *deviceParser) Match(line string, dvc *Device) { + matches := dp.Reg.FindStringSubmatchIndex(line) if len(matches) == 0 { return } - dvc.Family = string(parser.Reg.ExpandString(nil, parser.DeviceReplacement, line, matches)) + dvc.Family = string(dp.Reg.ExpandString(nil, dp.DeviceReplacement, line, matches)) dvc.Family = strings.TrimSpace(dvc.Family) - dvc.Brand = string(parser.Reg.ExpandString(nil, parser.BrandReplacement, line, matches)) + dvc.Brand = string(dp.Reg.ExpandString(nil, dp.BrandReplacement, line, matches)) dvc.Brand = strings.TrimSpace(dvc.Brand) - dvc.Model = string(parser.Reg.ExpandString(nil, parser.ModelReplacement, line, matches)) + dvc.Model = string(dp.Reg.ExpandString(nil, dp.ModelReplacement, line, matches)) dvc.Model = strings.TrimSpace(dvc.Model) } diff --git a/uaparser/options.go b/uaparser/options.go new file mode 100644 index 0000000..696d4df --- /dev/null +++ b/uaparser/options.go @@ -0,0 +1,51 @@ +package uaparser + +type Option func(*Parser) + +func WithMode(mode LookupMode) Option { + return func(s *Parser) { + s.config.Mode = mode + } +} + +func WithSort(useSort bool, options ...SortOption) Option { + return func(s *Parser) { + s.config.UseSort = useSort + + for _, o := range options { + o(s) + } + } +} + +func WithDebug(debug bool) Option { + return func(s *Parser) { + s.config.DebugMode = debug + } +} + +func WithCacheSize(size int) Option { + return func(s *Parser) { + s.config.CacheSize = size + } +} + +func WithMatchIdxNotOk(idx int) Option { + return func(s *Parser) { + s.config.MatchIdxNotOk = idx + } +} + +func WithRegexDefinitions(def RegexDefinitions) Option { + return func(s *Parser) { + s.RegexDefinitions = &def + } +} + +type SortOption func(*Parser) + +func WithMissesThreshold(threshold uint64) SortOption { + return func(s *Parser) { + s.config.MissesThreshold = threshold + } +} diff --git a/uaparser/os.go b/uaparser/os.go index b30e8a1..2d33e94 100644 --- a/uaparser/os.go +++ b/uaparser/os.go @@ -8,14 +8,14 @@ type Os struct { PatchMinor string `yaml:"patch_minor"` } -func (parser *osParser) Match(line string, os *Os) { - matches := parser.Reg.FindStringSubmatchIndex(line) +func (osp *osParser) Match(line string, os *Os) { + matches := osp.Reg.FindStringSubmatchIndex(line) if len(matches) > 0 { - os.Family = string(parser.Reg.ExpandString(nil, parser.OSReplacement, line, matches)) - os.Major = string(parser.Reg.ExpandString(nil, parser.V1Replacement, line, matches)) - os.Minor = string(parser.Reg.ExpandString(nil, parser.V2Replacement, line, matches)) - os.Patch = string(parser.Reg.ExpandString(nil, parser.V3Replacement, line, matches)) - os.PatchMinor = string(parser.Reg.ExpandString(nil, parser.V4Replacement, line, matches)) + os.Family = string(osp.Reg.ExpandString(nil, osp.OSReplacement, line, matches)) + os.Major = string(osp.Reg.ExpandString(nil, osp.V1Replacement, line, matches)) + os.Minor = string(osp.Reg.ExpandString(nil, osp.V2Replacement, line, matches)) + os.Patch = string(osp.Reg.ExpandString(nil, osp.V3Replacement, line, matches)) + os.PatchMinor = string(osp.Reg.ExpandString(nil, osp.V4Replacement, line, matches)) } } diff --git a/uaparser/parser.go b/uaparser/parser.go index 9d617f6..7c28b82 100644 --- a/uaparser/parser.go +++ b/uaparser/parser.go @@ -2,7 +2,6 @@ package uaparser import ( "fmt" - "os" "regexp" "sort" "sync" @@ -12,12 +11,45 @@ import ( "gopkg.in/yaml.v3" ) -type RegexesDefinitions struct { +var defaultRegexesDefinitions = sync.OnceValue(func() RegexDefinitions { + var def RegexDefinitions + if err := yaml.Unmarshal(DefinitionYaml, &def); err != nil { + panic(fmt.Errorf("error parsing regexes definitions: %w", err)) + } + + return def +}) + +type RegexDefinitions struct { UA []*uaParser `yaml:"user_agent_parsers"` OS []*osParser `yaml:"os_parsers"` Device []*deviceParser `yaml:"device_parsers"` - _ [4]byte // padding for alignment - sync.RWMutex +} + +func (rd *RegexDefinitions) Clone() *RegexDefinitions { + if rd == nil { + return nil + } + + rd2 := &RegexDefinitions{ + UA: make([]*uaParser, len(rd.UA)), + OS: make([]*osParser, len(rd.OS)), + Device: make([]*deviceParser, len(rd.Device)), + } + + for i, v := range rd.UA { + rd2.UA[i] = v.Clone() + } + + for i, v := range rd.OS { + rd2.OS[i] = v.Clone() + } + + for i, v := range rd.Device { + rd2.Device[i] = v.Clone() + } + + return rd2 } type UserAgentSorter []*uaParser @@ -40,18 +72,29 @@ type uaParser struct { MatchesCount uint64 } -func (ua *uaParser) setDefaults() { - if ua.FamilyReplacement == "" { - ua.FamilyReplacement = "$1" +func (uap *uaParser) Clone() *uaParser { + if uap == nil { + return nil + } + + ua2 := *uap + ua2.MatchesCount = 0 + + return &ua2 +} + +func (uap *uaParser) setDefaults() { + if uap.FamilyReplacement == "" { + uap.FamilyReplacement = "$1" } - if ua.V1Replacement == "" { - ua.V1Replacement = "$2" + if uap.V1Replacement == "" { + uap.V1Replacement = "$2" } - if ua.V2Replacement == "" { - ua.V2Replacement = "$3" + if uap.V2Replacement == "" { + uap.V2Replacement = "$3" } - if ua.V3Replacement == "" { - ua.V3Replacement = "$4" + if uap.V3Replacement == "" { + uap.V3Replacement = "$4" } } @@ -76,21 +119,32 @@ type osParser struct { MatchesCount uint64 } -func (os *osParser) setDefaults() { - if os.OSReplacement == "" { - os.OSReplacement = "$1" +func (osp *osParser) Clone() *osParser { + if osp == nil { + return nil } - if os.V1Replacement == "" { - os.V1Replacement = "$2" + + os2 := *osp + os2.MatchesCount = 0 + + return &os2 +} + +func (osp *osParser) setDefaults() { + if osp.OSReplacement == "" { + osp.OSReplacement = "$1" + } + if osp.V1Replacement == "" { + osp.V1Replacement = "$2" } - if os.V2Replacement == "" { - os.V2Replacement = "$3" + if osp.V2Replacement == "" { + osp.V2Replacement = "$3" } - if os.V3Replacement == "" { - os.V3Replacement = "$4" + if osp.V3Replacement == "" { + osp.V3Replacement = "$4" } - if os.V4Replacement == "" { - os.V4Replacement = "$5" + if osp.V4Replacement == "" { + osp.V4Replacement = "$5" } } @@ -113,12 +167,23 @@ type deviceParser struct { MatchesCount uint64 } -func (device *deviceParser) setDefaults() { - if device.DeviceReplacement == "" { - device.DeviceReplacement = "$1" +func (dp *deviceParser) Clone() *deviceParser { + if dp == nil { + return nil + } + + device2 := *dp + device2.MatchesCount = 0 + + return &device2 +} + +func (dp *deviceParser) setDefaults() { + if dp.DeviceReplacement == "" { + dp.DeviceReplacement = "$1" } - if device.ModelReplacement == "" { - device.ModelReplacement = "$1" + if dp.ModelReplacement == "" { + dp.ModelReplacement = "$1" } } @@ -129,7 +194,7 @@ type Client struct { } type parserConfig struct { - Mode int + Mode LookupMode UseSort bool DebugMode bool CacheSize int @@ -146,119 +211,87 @@ type Parser struct { DeviceMisses uint64 config *parserConfig - cache *cache + cache *cache - RegexesDefinitions + *RegexDefinitions + + mu *sync.RWMutex } +type LookupMode int + const ( - EOsLookUpMode = 1 /* 00000001 */ - EUserAgentLookUpMode = 2 /* 00000010 */ - EDeviceLookUpMode = 4 /* 00000100 */ - cMinMissesTreshold = 100000 - cDefaultMissesTreshold = 500000 - cDefaultMatchIdxNotOk = 20 - cDefaultSortOption = false - cDefaultDebugMode = false - cDefaultCacheSize = 1024 + EOsLookUpMode LookupMode = 1 /* 00000001 */ + EUserAgentLookUpMode LookupMode = 2 /* 00000010 */ + EDeviceLookUpMode LookupMode = 4 /* 00000100 */ + cMinMissesTreshold = 100000 + cDefaultMissesTreshold = 500000 + cDefaultMatchIdxNotOk = 20 + cDefaultSortOption = false + cDefaultDebugMode = false + cDefaultCacheSize = 1024 ) -func (parser *Parser) mustCompile() { // until we can use yaml.UnmarshalYAML with embedded pointer struct - for _, p := range parser.UA { - p.Reg = compileRegex(p.Flags, p.Expr) - p.setDefaults() - } - for _, p := range parser.OS { - p.Reg = compileRegex(p.Flags, p.Expr) - p.setDefaults() - } - for _, p := range parser.Device { - p.Reg = compileRegex(p.Flags, p.Expr) - p.setDefaults() +func New(options ...Option) (*Parser, error) { + parser := &Parser{ + config: &parserConfig{ + Mode: EOsLookUpMode | EUserAgentLookUpMode | EDeviceLookUpMode, + UseSort: cDefaultSortOption, + DebugMode: cDefaultDebugMode, + CacheSize: cDefaultCacheSize, + MissesThreshold: cDefaultMissesTreshold, + MatchIdxNotOk: cDefaultMatchIdxNotOk, + }, + mu: &sync.RWMutex{}, } -} -func defaultParserConfig() *parserConfig { - return &parserConfig{ - Mode: EOsLookUpMode | EUserAgentLookUpMode | EDeviceLookUpMode, - UseSort: cDefaultSortOption, - DebugMode: cDefaultDebugMode, - CacheSize: cDefaultCacheSize, - MissesThreshold: cMinMissesTreshold, - MatchIdxNotOk: cDefaultMatchIdxNotOk, + for _, o := range options { + o(parser) } -} -func NewWithOptions(regexFile string, mode, treshold, topCnt int, useSort, debugMode bool, cacheSize int) (*Parser, error) { - data, err := os.ReadFile(regexFile) - if nil != err { - return nil, err + if parser.config.MatchIdxNotOk < 0 { + parser.config.MatchIdxNotOk = 0 } - cfg := &parserConfig{ - Mode: mode, - UseSort: useSort, - DebugMode: debugMode, - MatchIdxNotOk: cDefaultMatchIdxNotOk, - MissesThreshold: cDefaultMissesTreshold, - CacheSize: cDefaultCacheSize, + if parser.config.MissesThreshold <= cMinMissesTreshold { + parser.config.MissesThreshold = cMinMissesTreshold } - if topCnt >= 0 { - cfg.MatchIdxNotOk = topCnt - } - if treshold > cMinMissesTreshold { - cfg.MissesThreshold = uint64(treshold) - } - if cacheSize > 0 { - cfg.CacheSize = cacheSize + if parser.config.CacheSize < 0 { + parser.config.CacheSize = cDefaultCacheSize } - parser, err := newFromBytes(data, cfg) - if err != nil { - return nil, err + if parser.cache == nil { + parser.cache = newCache(parser.config.CacheSize) } - return parser, nil -} -func New(regexFile string) (*Parser, error) { - data, err := os.ReadFile(regexFile) - if nil != err { - return nil, err + if parser.RegexDefinitions == nil { + regexesDefinitions := defaultRegexesDefinitions() + parser.RegexDefinitions = ®exesDefinitions } - parser, err := newFromBytes(data, defaultParserConfig()) - if err != nil { - return nil, err - } - return parser, nil -} -func NewFromSaved() *Parser { - parser, err := newFromBytes(DefinitionYaml, defaultParserConfig()) - if err != nil { - // if the YAML is malformed, it's a programmatic error inside what - // we've statically-compiled in our binary. Panic! - panic(err.Error()) + if parser.config.UseSort { + parser.RegexDefinitions = parser.RegexDefinitions.Clone() } - return parser -} -func NewFromBytes(data []byte) (*Parser, error) { - return newFromBytes(data, defaultParserConfig()) + parser.mustCompile() + + return parser, nil } -func newFromBytes(data []byte, config *parserConfig) (*Parser, error) { - parser := &Parser{ - config: config, - cache: newCache(config.CacheSize), +func (parser *Parser) mustCompile() { // until we can use yaml.UnmarshalYAML with embedded pointer struct + for _, p := range parser.RegexDefinitions.UA { + p.Reg = compileRegex(p.Flags, p.Expr) + p.setDefaults() } - if err := yaml.Unmarshal(data, &parser.RegexesDefinitions); err != nil { - return nil, err + for _, p := range parser.RegexDefinitions.OS { + p.Reg = compileRegex(p.Flags, p.Expr) + p.setDefaults() + } + for _, p := range parser.RegexDefinitions.Device { + p.Reg = compileRegex(p.Flags, p.Expr) + p.setDefaults() } - - parser.mustCompile() - - return parser, nil } func (parser *Parser) Parse(line string) *Client { @@ -268,27 +301,27 @@ func (parser *Parser) Parse(line string) *Client { wg.Add(1) go func() { defer wg.Done() - parser.RLock() + parser.mu.RLock() cli.UserAgent = parser.ParseUserAgent(line) - parser.RUnlock() + parser.mu.RUnlock() }() } if EOsLookUpMode&parser.config.Mode == EOsLookUpMode { wg.Add(1) go func() { defer wg.Done() - parser.RLock() + parser.mu.RLock() cli.Os = parser.ParseOs(line) - parser.RUnlock() + parser.mu.RUnlock() }() } if EDeviceLookUpMode&parser.config.Mode == EDeviceLookUpMode { wg.Add(1) go func() { defer wg.Done() - parser.RLock() + parser.mu.RLock() cli.Device = parser.ParseDevice(line) - parser.RUnlock() + parser.mu.RUnlock() }() } wg.Wait() @@ -306,7 +339,7 @@ func (parser *Parser) ParseUserAgent(line string) *UserAgent { ua := new(UserAgent) foundIdx := -1 found := false - for i, uaPattern := range parser.UA { + for i, uaPattern := range parser.RegexDefinitions.UA { uaPattern.Match(line, ua) if len(ua.Family) > 0 { found = true @@ -334,7 +367,7 @@ func (parser *Parser) ParseOs(line string) *Os { os := new(Os) foundIdx := -1 found := false - for i, osPattern := range parser.OS { + for i, osPattern := range parser.RegexDefinitions.OS { osPattern.Match(line, os) if len(os.Family) > 0 { found = true @@ -363,7 +396,7 @@ func (parser *Parser) ParseDevice(line string) *Device { dvc := new(Device) foundIdx := -1 found := false - for i, dvcPattern := range parser.Device { + for i, dvcPattern := range parser.RegexDefinitions.Device { dvcPattern.Match(line, dvc) if len(dvc.Family) > 0 { found = true @@ -384,33 +417,36 @@ func (parser *Parser) ParseDevice(line string) *Device { } func checkAndSort(parser *Parser) { - parser.Lock() + parser.mu.Lock() if atomic.LoadUint64(&parser.UserAgentMisses) >= parser.config.MissesThreshold { if parser.config.DebugMode { fmt.Printf("%s\tSorting UserAgents slice\n", time.Now()) } parser.UserAgentMisses = 0 - sort.Sort(UserAgentSorter(parser.UA)) + + sort.Sort(UserAgentSorter(parser.RegexDefinitions.UA)) } - parser.Unlock() - parser.Lock() + parser.mu.Unlock() + parser.mu.Lock() if atomic.LoadUint64(&parser.OsMisses) >= parser.config.MissesThreshold { if parser.config.DebugMode { fmt.Printf("%s\tSorting OS slice\n", time.Now()) } parser.OsMisses = 0 - sort.Sort(OsSorter(parser.OS)) + + sort.Sort(OsSorter(parser.RegexDefinitions.OS)) } - parser.Unlock() - parser.Lock() + parser.mu.Unlock() + parser.mu.Lock() if atomic.LoadUint64(&parser.DeviceMisses) >= parser.config.MissesThreshold { if parser.config.DebugMode { fmt.Printf("%s\tSorting Device slice\n", time.Now()) } parser.DeviceMisses = 0 - sort.Sort(DeviceSorter(parser.Device)) + + sort.Sort(DeviceSorter(parser.RegexDefinitions.Device)) } - parser.Unlock() + parser.mu.Unlock() } func compileRegex(flags, expr string) *regexp.Regexp { diff --git a/uaparser/parsing_test.go b/uaparser/parsing_test.go index a7a303b..975c1a1 100644 --- a/uaparser/parsing_test.go +++ b/uaparser/parsing_test.go @@ -12,7 +12,19 @@ var testParser *Parser func init() { var err error - testParser, err = New("../uap-core/regexes.yaml") + + uaRegexes, err := os.ReadFile("../uap-core/regexes.yaml") + if err != nil { + log.Fatal(err) + } + + var def RegexDefinitions + + if err := yaml.Unmarshal(uaRegexes, &def); err != nil { + log.Fatal(err) + } + + testParser, err = New(WithRegexDefinitions(def)) if err != nil { log.Fatal(err) } @@ -38,8 +50,13 @@ func TestOSParsing(t *testing.T) { } } -func TestReadsInteralYAML(t *testing.T) { - _ = NewFromSaved() // should not panic +func TestReadsInternalYAML(t *testing.T) { + t.Parallel() + + _, err := New() + if err != nil { + t.Fatal(err) + } } func TestUAParsing(t *testing.T) { diff --git a/uaparser/user_agent.go b/uaparser/user_agent.go index ad4073a..569b56a 100644 --- a/uaparser/user_agent.go +++ b/uaparser/user_agent.go @@ -7,13 +7,13 @@ type UserAgent struct { Patch string } -func (parser *uaParser) Match(line string, ua *UserAgent) { - matches := parser.Reg.FindStringSubmatchIndex(line) +func (uap *uaParser) Match(line string, ua *UserAgent) { + matches := uap.Reg.FindStringSubmatchIndex(line) if len(matches) > 0 { - ua.Family = string(parser.Reg.ExpandString(nil, parser.FamilyReplacement, line, matches)) - ua.Major = string(parser.Reg.ExpandString(nil, parser.V1Replacement, line, matches)) - ua.Minor = string(parser.Reg.ExpandString(nil, parser.V2Replacement, line, matches)) - ua.Patch = string(parser.Reg.ExpandString(nil, parser.V3Replacement, line, matches)) + ua.Family = string(uap.Reg.ExpandString(nil, uap.FamilyReplacement, line, matches)) + ua.Major = string(uap.Reg.ExpandString(nil, uap.V1Replacement, line, matches)) + ua.Minor = string(uap.Reg.ExpandString(nil, uap.V2Replacement, line, matches)) + ua.Patch = string(uap.Reg.ExpandString(nil, uap.V3Replacement, line, matches)) } }