Full Code of ua-parser/uap-ruby for AI

main 38dc9d19771e cached
28 files
50.4 KB
14.7k tokens
72 symbols
1 requests
Download .txt
Repository: ua-parser/uap-ruby
Branch: main
Commit: 38dc9d19771e
Files: 28
Total size: 50.4 KB

Directory structure:
gitextract_ikhkw8zc/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .gitmodules
├── .ruby-version
├── CHANGELOG.md
├── Gemfile
├── MIT-LICENSE
├── README.md
├── Rakefile
├── bin/
│   └── user_agent_parser
├── lib/
│   ├── user_agent_parser/
│   │   ├── cli.rb
│   │   ├── device.rb
│   │   ├── operating_system.rb
│   │   ├── parser.rb
│   │   ├── user_agent.rb
│   │   └── version.rb
│   └── user_agent_parser.rb
├── spec/
│   ├── cli_spec.rb
│   ├── custom_regexes.yaml
│   ├── device_spec.rb
│   ├── operating_system_spec.rb
│   ├── other_regexes.yaml
│   ├── parser_spec.rb
│   ├── spec_helper.rb
│   ├── user_agent_spec.rb
│   └── version_spec.rb
└── user_agent_parser.gemspec

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"


================================================
FILE: .github/workflows/ci.yml
================================================
name: ci

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  specs:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        ruby:
          - '3.4'
          - '3.3'
          - '3.2'
          - '3.1'
        include:
          - ruby: '3.4'
            coverage: '1'
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: true
      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: ${{ matrix.ruby }}
          bundler-cache: true # 'bundle install' and cache gems
      - name: Run specs
        env:
          SIMPLECOV: ${{ matrix.coverage }}
        run: |
          bundle exec rake test


================================================
FILE: .gitignore
================================================
.bundle
coverage
vendor/bundle
.tool-versions

pkg


================================================
FILE: .gitmodules
================================================
[submodule "vendor/uap-core"]
	path = vendor/uap-core
	url = https://github.com/ua-parser/uap-core.git


================================================
FILE: .ruby-version
================================================
3.4.4


================================================
FILE: CHANGELOG.md
================================================
# master

# 2.21.0 (2026-02-12)
  * Sync with https://github.com/ua-parser/uap-core/commit/383604dfd6c7518c152e3bd9b7eda67662b1b343

# 2.20.0 (2025-07-15)
  * Sync with https://github.com/ua-parser/uap-core/commit/432e95f6767cc8bab4c20c255784cd6f7e93bc15
  * drop Ruby 3.0 support
  * Add Ruby 3.4 support

# 2.19.0 (2024-12-10)
  * Sync with https://github.com/ua-parser/uap-core/commit/d4cde4c565a7e588472fbf6667f01fc4c23fa60b

# 2.18.0 (2024-06-04)
  * Sync with https://github.com/ua-parser/uap-core/commit/df56280c9e2b42dd64be2b750f803c58feb3f94a

# 2.17.0 (2024-02-23)
  * Sync with https://github.com/ua-parser/uap-core/commit/d3450bbe77fe49eb3a234ed6184065260e44d747

# 2.16.0 (2023-06-07)
  * Sync with https://github.com/ua-parser/uap-core/tree/v0.18.0

# 2.15.0 (2023-04-12)
 * Expose `parse_os`, `parse_device` and `parse_ua` methods on `Parser`

# 2.14.0 (2023-01-31)
  * Sync with https://github.com/ua-parser/uap-core/commit/1ef0926f2b489cc929589c00f8b8a3efce25acc3

# 2.13.0 (2022-10-21)
  * Support loading multiple database files (via #70) (@misdoro)
    * Support `patterns_path` argument but deprecate `pattern_path` attribute accessor
      in `UserAgentParser::Parser`
    * Add new `patterns_paths` array argument `UserAgentParser::Parser` to enable loading
      multiple patterns files

# 2.12.0 (2022-10-20)

  * sync with https://github.com/ua-parser/uap-core/commit/dc85ab2628798538a2874dea4a9563f40a31f55a
  * Memory optimization (via #104) (@casperisfine)

# 2.11.0 (2022-04-18)
  * Make user agent versions comparable (via #68) (@misdoro)

# 2.10.0 (2022-04-18)
  * sync with uap-core 09e9ccc

# 2.9.0 (2022-01-27)
  * sync with uap-core 0.15.0

# 2.8.0 (2021-11-02)
  * sync with uap-core 0.14.0
  * drop support for ruby 2.4

# 2.7.0 (2020-05-25)
  * sync with uap-core 0.10.0


================================================
FILE: Gemfile
================================================
# frozen_string_literal: true

source 'https://rubygems.org/'

gemspec

group :development, :test do
  gem 'coveralls_reborn'
  gem 'minitest'
  gem 'rake'
end


================================================
FILE: MIT-LICENSE
================================================
Copyright (c) 2012 Tim Lucas

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
# UserAgentParser [![Build Status](https://github.com/ua-parser/uap-ruby/actions/workflows/ci.yml/badge.svg)](https://github.com/ua-parser/uap-ruby/actions/workflows/ci.yml) [![Coverage Status](https://coveralls.io/repos/github/ua-parser/uap-ruby/badge.svg)](https://coveralls.io/github/ua-parser/uap-ruby)

UserAgentParser is a simple, comprehensive Ruby gem for parsing user agent strings. It uses [BrowserScope](http://www.browserscope.org/)'s [parsing patterns](https://github.com/ua-parser/uap-core).

## Supported Rubies

* Ruby 3.4
* Ruby 3.3
* Ruby 3.2
* Ruby 3.1
* JRuby

## Installation

```bash
$ gem install user_agent_parser
```

## Example usage

```ruby
require 'user_agent_parser'
=> true
user_agent = UserAgentParser.parse 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0;)'
=> #<UserAgentParser::UserAgent IE 9.0 (Windows Vista)>
user_agent.to_s
=> "IE 9.0"
user_agent.family
=> "IE"
user_agent.version.to_s
=> "9.0"
user_agent.version.major
=> "9"
user_agent.version.minor
=> "0"
user_agent.family == "IE" && user_agent.version >= "9"
=> true
operating_system = user_agent.os
=> #<UserAgentParser::OperatingSystem Windows Vista>
operating_system.to_s
=> "Windows Vista"

# Device information can also be determined from some devices
user_agent = UserAgentParser.parse "Mozilla/5.0 (Linux; Android 7.0; SAMSUNG SM-G930T Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/5.0 Chrome/51.0.2704.106 Mobile Safari/537.36"
=> #<UserAgentParser::UserAgent Samsung Internet 5.0 (Android 7.0) (Samsung SM-G930T)>
user_agent.device.family
=> "Samsung SM-G930T"
user_agent.device.brand
=> "Samsung"
user_agent.device.model
=> "SM-G930T"

user_agent = UserAgentParser.parse "Mozilla/5.0 (iPad; CPU OS 10_2_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) GSA/23.1.148956103 Mobile/14D27 Safari/600.1.4"
=> #<UserAgentParser::UserAgent Mobile Safari 10.2.1 (iOS 10.2.1) (iPad)>
irb(main):026:0> user_agent.device.family
=> "iPad"
irb(main):027:0> user_agent.device.brand
=> "Apple"
irb(main):028:0> user_agent.device.model
=> "iPad"


# The parser database will be loaded and parsed on every call to
# UserAgentParser.parse. To avoid this, instantiate your own Parser instance.
parser = UserAgentParser::Parser.new
parser.parse 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0;)'
=> #<UserAgentParser::UserAgent IE 9.0 (Windows Vista)>
parser.parse 'Opera/9.80 (Windows NT 5.1; U; ru) Presto/2.5.24 Version/10.53'
=> #<UserAgentParser::UserAgent Opera 10.53 (Windows XP)>
```

In a larger application, you could store a parser in a global to avoid repeat pattern loading:

```ruby
module MyApplication

  # Instantiate the parser on load as it's quite expensive
  USER_AGENT_PARSER = UserAgentParser::Parser.new

  def self.user_agent_parser
    USER_AGENT_PARSER
  end

end
```

## The pattern database

The [ua-parser database](https://github.com/ua-parser/uap-core/blob/master/regexes.yaml) is included via a [git submodule](http://help.github.com/submodules/). To update the database the submodule needs to be updated and the gem re-released (pull requests for this are very welcome!).

You can also specify the path to your own, updated and/or customised `regexes.yaml` file as a second argument to `UserAgentParser.parse`:

```ruby
UserAgentParser.parse(ua_string, patterns_path: '/some/path/to/regexes.yaml')
```

or when instantiating a `UserAgentParser::Parser`:

```ruby
UserAgentParser::Parser.new(patterns_path: '/some/path/to/regexes.yaml').parse(ua_string)
```

Extending the standard database is possible by providing multiple files in `patterns_paths` (plural) array argument:
```ruby
UserAgentParser::Parser.new(patterns_paths: [UserAgentParser::DefaultPatternsPath, '/some/path/to/regexes.yaml'])
```

## Command line tool

The gem incldes a `user_agent_parser` bin command which will read from
standard input, parse each line and print the result, for example:

```bash
$ cat > SOME-FILE-WITH-USER-AGENTS.txt
USER_AGENT_1
USER_AGENT_2
...
$ cat SOME-FILE-WITH-USER-AGENTS.txt | user_agent_parser --format '%f %M' | distribution
```

See `user_agent_parser -h` for more information.

## Contributing

1. Fork
2. Hack
3. `rake test`
4. Send a pull request

All accepted pull requests will earn you commit and release rights.

## Releasing a new version

1. Update the version in `user_agent_parser.gemspec`
2. `git commit user_agent_parser.gemspec` with the following message format:

        Version x.x.x

        Changelog:
        * Some new feature
        * Some new bug fix
3. `rake release`
4. Create a [new Github release](https://github.com/ua-parser/uap-ruby/releases/new)

## License

MIT


================================================
FILE: Rakefile
================================================
# frozen_string_literal: true

require 'rake/testtask'
require 'bundler'

task default: :test

desc 'Run tests'
Rake::TestTask.new do |t|
  t.warning = true
  t.verbose = true
  t.pattern = 'spec/*_spec.rb'
end

Bundler::GemHelper.install_tasks

# Does not actually get all families, as some are only listed in the regexes,
# but gives you a pretty good idea of what will be returned.
desc 'Lists all unique family names for browsers and operating systems.'
task :families do
  require 'pathname'
  require 'pp'

  root = Pathname(__FILE__).dirname
  path = root.join('vendor', 'uap-core')

  browser_families = paths_to_families(
    [
      # path.join('tests', 'test_ua.yaml'),
      path.join('test_resources', 'firefox_user_agent_strings.yaml'),
      path.join('test_resources', 'pgts_browser_list.yaml')
    ]
  )

  os_families = paths_to_families(
    [
      # path.join('tests', 'test_os.yaml'),
      path.join('test_resources', 'additional_os_tests.yaml')
    ]
  )

  device_families = paths_to_families(
    [
      # path.join('tests', 'test_device.yaml'),
    ]
  )

  puts "\n\nBrowser Families"
  puts browser_families.inspect

  puts "\n\nOS Families"
  puts os_families.inspect

  puts "\n\nDevice Families"
  puts device_families.inspect

  puts "\n\n"
  puts "Browser Family Count: #{browser_families.size}"
  puts "OS Family Count: #{os_families.size}"
  puts "Device Family Count: #{device_families.size}"
end

def paths_to_families(paths)
  require 'yaml'

  families = []

  paths.each do |path|
    data = YAML.load_file(path)
    test_cases = data.fetch('test_cases')
    families.concat test_cases.map { |row| row['family'] }
  end

  families.compact.uniq.sort
end


================================================
FILE: bin/user_agent_parser
================================================
#!/usr/bin/env ruby
# frozen_string_literal: true

require 'optparse'

require 'user_agent_parser'
require 'user_agent_parser/cli'

options = {}

optparse = OptionParser.new do |opts|
  opts.on('--family', 'Print family only') do
    options[:family] = true
  end

  opts.on('--name', 'Print name (alias for family) only') do
    options[:family] = true
  end

  opts.on('--version', 'Print version only') do
    options[:version] = true
  end

  opts.on('--major', 'Print major version only') do
    options[:major] = true
  end

  opts.on('--minor', 'Print minor version only') do
    options[:minor] = true
  end

  opts.on('--os', 'Print operating system only') do
    options[:os] = true
  end

  opts.on('--format format',
          'Print output in specified format. The available formatters are:',
          ' - %f: family',
          ' - %n: name (alias for family)',
          ' - %v: version',
          ' - %M: major version',
          ' - %m: minor version',
          ' - %o: operating system'
         ) do |format|
    options[:format] = format
  end

  opts.on('-h', '--help', 'Display this screen') do
    puts opts
    exit
  end
end

optparse.parse!

parser = UserAgentParser::Parser.new

ARGF.each do |line|
  puts UserAgentParser::Cli.new(parser.parse(line), options).run!
end


================================================
FILE: lib/user_agent_parser/cli.rb
================================================
# frozen_string_literal: true

module UserAgentParser
  class Cli
    def initialize(user_agent, options = {})
      @user_agent = user_agent
      @options = options
    end

    def run!
      if @options[:family]
        @user_agent.family
      elsif @options[:name]
        @user_agent.name
      elsif @options[:version]
        with_version(&:to_s)
      elsif @options[:major]
        major
      elsif @options[:minor]
        minor
      elsif @options[:os]
        @user_agent.os.to_s
      elsif (format = @options[:format])
        format
          .gsub('%f', @user_agent.family)
          .gsub('%n', @user_agent.name)
          .gsub('%v', version.to_s)
          .gsub('%M', major.to_s)
          .gsub('%m', minor.to_s)
          .gsub('%o', @user_agent.os.to_s)
      else
        @user_agent.to_s
      end
    end

    private

    def major
      with_version(&:major)
    end

    def minor
      with_version(&:minor)
    end

    def version
      @version ||= @user_agent.version
    end

    def with_version
      yield(version) if version
    end
  end
end


================================================
FILE: lib/user_agent_parser/device.rb
================================================
# frozen_string_literal: true

module UserAgentParser
  class Device
    DEFAULT_FAMILY = 'Other'

    attr_reader :family, :model, :brand

    alias name family

    def initialize(family = nil, model = nil, brand = nil)
      @family = family || DEFAULT_FAMILY
      @model = model || @family
      @brand = brand
    end

    def to_s
      family
    end

    def inspect
      "#<#{self.class} #{self}>"
    end

    def eql?(other)
      self.class.eql?(other.class) && family == other.family
    end

    alias == eql?

    def to_h
      {
        family: family,
        model: model,
        brand: brand
      }
    end
  end
end


================================================
FILE: lib/user_agent_parser/operating_system.rb
================================================
# frozen_string_literal: true

module UserAgentParser
  class OperatingSystem
    DEFAULT_FAMILY = 'Other'

    attr_reader :family, :version

    alias name family

    def initialize(family = DEFAULT_FAMILY, version = nil)
      @family = family
      @version = version
    end

    def to_s
      string = family
      string += " #{version}" unless version.nil?
      string
    end

    def inspect
      "#<#{self.class} #{self}>"
    end

    def eql?(other)
      self.class.eql?(other.class) &&
        family == other.family &&
        version == other.version
    end

    alias == eql?

    def to_h
      {
        version: version.to_h,
        family: family
      }
    end
  end
end


================================================
FILE: lib/user_agent_parser/parser.rb
================================================
# frozen_string_literal: true

require 'yaml'

module UserAgentParser
  class Parser
    extend Gem::Deprecate

    FAMILY_REPLACEMENT_KEYS = %w[
      family_replacement
      v1_replacement
      v2_replacement
      v3_replacement
      v4_replacement
    ].freeze

    OS_REPLACEMENT_KEYS = %w[
      os_replacement
      os_v1_replacement
      os_v2_replacement
      os_v3_replacement
      os_v4_replacement
    ].freeze

    private_constant :FAMILY_REPLACEMENT_KEYS, :OS_REPLACEMENT_KEYS

    attr_reader :patterns_paths

    def initialize(patterns_path: nil, patterns_paths: [])
      @patterns_paths = [patterns_path, *patterns_paths].compact
      @patterns_paths = [UserAgentParser::DefaultPatternsPath] if @patterns_paths.empty?

      @ua_patterns, @os_patterns, @device_patterns = load_patterns(@patterns_paths)
    end

    def parse(user_agent)
      os = parse_os(user_agent)
      device = parse_device(user_agent)
      parse_ua(user_agent, os, device)
    end

    def parse_os(user_agent)
      pattern, match = first_pattern_match(@os_patterns, user_agent)

      if match
        os_from_pattern_match(pattern, match)
      else
        OperatingSystem.new
      end
    end

    def parse_device(user_agent)
      pattern, match = first_pattern_match(@device_patterns, user_agent)

      if match
        device_from_pattern_match(pattern, match)
      else
        Device.new
      end
    end

    def parse_ua(user_agent, os = nil, device = nil)
      pattern, match = first_pattern_match(@ua_patterns, user_agent)

      if match
        user_agent_from_pattern_match(pattern, match, os, device)
      else
        UserAgent.new(nil, nil, os, device)
      end
    end

    def patterns_path
      patterns_paths.first
    end
    deprecate :patterns_path, :patterns_paths, 2022, 12

    private

    def load_patterns(patterns_paths)
      patterns_paths.each_with_object([[], [], []]) do |path, patterns|
        ua_patterns, os_patterns, device_patterns = load_patterns_file(path)
        patterns[0] += ua_patterns
        patterns[1] += os_patterns
        patterns[2] += device_patterns
      end
    end

    def load_patterns_file(path)
      yml = begin
        YAML.load_file(path, freeze: true)
      rescue ArgumentError
        YAML.load_file(path)
      end
      [
        parse_pattern(yml['user_agent_parsers']),
        parse_pattern(yml['os_parsers']),
        parse_pattern(yml['device_parsers']),
      ]
    end

    def parse_pattern(patterns)
      patterns.map do |pattern|
        pattern = pattern.dup
        pattern[:regex] = Regexp.new(pattern.delete('regex'), pattern.delete('regex_flag') == 'i')
        pattern
      end
    end

    def first_pattern_match(patterns, value)
      patterns.each do |pattern|
        return [pattern, pattern[:regex].match(value)] if pattern[:regex].match?(value)
      end
      nil
    end

    def user_agent_from_pattern_match(pattern, match, os = nil, device = nil)
      family, *versions = from_pattern_match(FAMILY_REPLACEMENT_KEYS, pattern, match)

      UserAgent.new(family, version_from_segments(*versions), os, device)
    end

    def os_from_pattern_match(pattern, match)
      os, *versions = from_pattern_match(OS_REPLACEMENT_KEYS, pattern, match)

      OperatingSystem.new(os, version_from_segments(*versions))
    end

    def device_from_pattern_match(pattern, match)
      match = match.to_a.map(&:to_s)
      family = model = match[1]
      brand = nil

      if pattern['device_replacement']
        family = pattern['device_replacement']
        match.each_with_index { |m, i| family = family.sub("$#{i}", m) }
      end
      if pattern['model_replacement']
        model = pattern['model_replacement']
        match.each_with_index { |m, i| model = model.sub("$#{i}", m) }
      end
      if pattern['brand_replacement']
        brand = pattern['brand_replacement']
        match.each_with_index { |m, i| brand = brand.sub("$#{i}", m) }
        brand.strip!
      end

      model&.strip!

      Device.new(family.strip, model, brand)
    end

    # Maps replacement keys to their values
    def from_pattern_match(keys, pattern, match)
      keys.each_with_index.map do |key, idx|
        # Check if there is any replacement specified
        if pattern[key]
          interpolate(pattern[key], match)
        else
          # No replacement defined, just return correct match group
          match[idx + 1]
        end
      end
    end

    # Interpolates a string with data from matches if specified
    def interpolate(replacement, match)
      group_idx = replacement.index('$')
      return replacement if group_idx.nil?

      group_nbr = replacement[group_idx + 1]
      replacement.sub("$#{group_nbr}", match[group_nbr.to_i])
    end

    def version_from_segments(*segments)
      return if segments.all?(&:nil?)

      Version.new(*segments)
    end
  end
end


================================================
FILE: lib/user_agent_parser/user_agent.rb
================================================
# frozen_string_literal: true

module UserAgentParser
  class UserAgent
    DEFAULT_FAMILY = 'Other'

    attr_reader :family, :version, :os, :device

    alias name family

    def initialize(family = nil, version = nil, os = nil, device = nil)
      @family = family || DEFAULT_FAMILY
      @version = version
      @os = os
      @device = device
    end

    def to_s
      string = family
      string += " #{version}" if version
      string
    end

    def inspect
      string = to_s
      string += " (#{os})" if os
      string += " (#{device})" if device
      "#<#{self.class} #{string}>"
    end

    def eql?(other)
      self.class.eql?(other.class) &&
        family == other.family &&
        version == other.version &&
        os == other.os
    end

    alias == eql?

    def to_h
      {
        device: device.to_h,
        family: family,
        os: os.to_h,
        version: version.to_h
      }
    end
  end
end


================================================
FILE: lib/user_agent_parser/version.rb
================================================
# frozen_string_literal: true

require 'rubygems/version'

module UserAgentParser
  class Version
    include Comparable

    # Private: Regex used to split string version string into major, minor,
    # patch, and patch_minor.
    SEGMENTS_REGEX = /\d+\-\d+|\d+[a-zA-Z]+$|\d+|[A-Za-z][0-9A-Za-z-]*$/.freeze

    attr_reader :version
    alias to_s version

    def initialize(*args)
      # If only one string argument is given, assume a complete version string
      # and attempt to parse it
      if args.length == 1 && args.first.is_a?(String)
        @version = args.first.to_s.strip
      else
        @segments = args.compact.map(&:to_s).map(&:strip)
        @version = segments.join('.')
      end
    end

    def major
      segments[0]
    end

    def minor
      segments[1]
    end

    def patch
      segments[2]
    end

    def patch_minor
      segments[3]
    end

    def inspect
      "#<#{self.class} #{self}>"
    end

    def eql?(other)
      self.class.eql?(other.class) &&
        version == other.version
    end

    def <=>(other)
      Gem::Version.new(version).<=>(Gem::Version.new(other.to_s))
    end

    def segments
      @segments ||= version.scan(SEGMENTS_REGEX)
    end

    def to_h
      {
        version: version,
        major: major,
        minor: minor,
        patch: patch,
        patch_minor: patch_minor
      }
    end
  end
end


================================================
FILE: lib/user_agent_parser.rb
================================================
# frozen_string_literal: true

require 'user_agent_parser/parser'
require 'user_agent_parser/user_agent'
require 'user_agent_parser/version'
require 'user_agent_parser/operating_system'
require 'user_agent_parser/device'

module UserAgentParser
  DefaultPatternsPath = File.join(File.dirname(__FILE__), '../vendor/uap-core/regexes.yaml')

  # Parse the given +user_agent_string+, returning a +UserAgent+
  def self.parse(user_agent_string, **args)
    Parser.new(**args).parse(user_agent_string)
  end
end


================================================
FILE: spec/cli_spec.rb
================================================
# frozen_string_literal: true

require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
require 'user_agent_parser/cli'

describe UserAgentParser::Cli do
  let(:cli) { UserAgentParser::Cli.new(user_agent, options) }
  let(:options) { {} }
  let(:parser) { UserAgentParser::Parser.new }
  let(:user_agent) do
    parser.parse('Mozilla/5.0 (iPad; CPU OS 6_0_1 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A523 Safari/8536.25')
  end

  it 'prints family and version when no options' do
    _(cli.run!).must_equal('Mobile Safari 6.0')
  end

  describe 'invalid version' do
    let(:user_agent) do
      parser.parse('Mozilla/5.0 (iPad; CPU OS like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/XYZ Mobile/10A523 Safari/8536.25')
    end

    describe '--version' do
      let(:options) { { version: true } }

      it 'returns nil' do
        _(cli.run!).must_be_nil
      end
    end

    describe '--major' do
      let(:options) { { major: true } }

      it 'returns nil' do
        _(cli.run!).must_be_nil
      end
    end

    describe '--minor' do
      let(:options) { { minor: true } }

      it 'returns nil' do
        _(cli.run!).must_be_nil
      end
    end

    describe '--format' do
      let(:options) { { format: '%n|%f|%v|%M|%m|%o' } }

      it 'returns string without versions' do
        _(cli.run!).must_equal('Mobile Safari|Mobile Safari||||iOS')
      end
    end
  end

  describe '--name' do
    let(:options) { { name: true } }

    it 'returns name only' do
      _(cli.run!).must_equal('Mobile Safari')
    end
  end

  describe '--family' do
    let(:options) { { family: true } }

    it 'returns family only' do
      _(cli.run!).must_equal('Mobile Safari')
    end
  end

  describe '--version' do
    let(:options) { { version: true } }

    it 'returns version only' do
      _(cli.run!).must_equal('6.0')
    end
  end

  describe '--major' do
    let(:options) { { major: true } }

    it 'returns major version only' do
      _(cli.run!).must_equal('6')
    end
  end

  describe '--minor' do
    let(:options) { { minor: true } }

    it 'returns minor version only' do
      _(cli.run!).must_equal('0')
    end
  end

  describe '--os' do
    let(:options) { { os: true } }

    it 'returns operating system only' do
      _(cli.run!).must_equal('iOS 6.0.1')
    end
  end

  describe '--format' do
    let(:options) { { format: '%n|%v|%M|%m|%o' } }

    it 'return string with correct replacements' do
      _(cli.run!).must_equal('Mobile Safari|6.0|6|0|iOS 6.0.1')
    end
  end
end


================================================
FILE: spec/custom_regexes.yaml
================================================
user_agent_parsers:
  - regex: 'Any.*'
    family_replacement: 'Custom browser'
    v1_replacement: '1'
    v2_replacement: '2'
    v3_replacement: '3'
    v4_replacement: '4'

os_parsers:
  - regex: 'Any.*'
    os_replacement: 'Custom OS'
    os_v1_replacement: '1'
    os_v2_replacement: '2'

device_parsers:
  - regex: 'Any.*'
    device_replacement: 'Custom device'


================================================
FILE: spec/device_spec.rb
================================================
# frozen_string_literal: true

require File.expand_path(File.dirname(__FILE__) + '/spec_helper')

describe UserAgentParser::Device do
  describe '#name' do
    it 'returns family' do
      os = UserAgentParser::Device.new('iPod')
      _(os.name).must_equal os.family
    end
  end

  describe '#to_s' do
    it 'returns a string of just the family' do
      os = UserAgentParser::Device.new('iPod')
      _(os.to_s).must_equal 'iPod'
    end
  end

  describe '#==' do
    it 'returns true for same family' do
      device1 = UserAgentParser::Device.new('iPod')
      device2 = UserAgentParser::Device.new('iPod')
      _(device1).must_equal device2
    end

    it 'returns false different family' do
      device1 = UserAgentParser::Device.new('iPod')
      device2 = UserAgentParser::Device.new('iPad')
      _(device1).wont_equal device2
    end
  end

  describe '#eql?' do
    it 'returns true for same family' do
      device1 = UserAgentParser::Device.new('iPod')
      device2 = UserAgentParser::Device.new('iPod')
      assert_equal true, device1.eql?(device2)
    end

    it 'returns false different family' do
      device1 = UserAgentParser::Device.new('iPod')
      device2 = UserAgentParser::Device.new('iPad')
      assert_equal false, device1.eql?(device2)
    end
  end

  describe '#inspect' do
    it 'returns class family and instance to_s' do
      device = UserAgentParser::Device.new('iPod')
      _(device.inspect.to_s).must_equal '#<UserAgentParser::Device iPod>'
    end
  end
end


================================================
FILE: spec/operating_system_spec.rb
================================================
# frozen_string_literal: true

require File.expand_path(File.dirname(__FILE__) + '/spec_helper')

describe UserAgentParser::OperatingSystem do
  describe '#name' do
    it 'returns family' do
      os = UserAgentParser::OperatingSystem.new('Windows')
      _(os.name).must_equal os.family
    end
  end

  describe '#to_s' do
    it 'returns a string of just the family' do
      os = UserAgentParser::OperatingSystem.new('Windows')
      _(os.to_s).must_equal 'Windows'
    end

    it 'returns a string of family and version' do
      version = UserAgentParser::Version.new('7')
      os = UserAgentParser::OperatingSystem.new('Windows', version)
      _(os.to_s).must_equal 'Windows 7'
    end
  end

  describe '#==' do
    it "returns true for same user agents across different O/S's" do
      version = UserAgentParser::Version.new('7')
      os1 = UserAgentParser::OperatingSystem.new('Windows', version)
      os2 = UserAgentParser::OperatingSystem.new('Windows', version)
      _(os1).must_equal os2
    end

    it 'returns false for same family, different versions' do
      seven = UserAgentParser::Version.new('7')
      eight = UserAgentParser::Version.new('8')
      os1 = UserAgentParser::OperatingSystem.new('Windows', seven)
      os2 = UserAgentParser::OperatingSystem.new('Windows', eight)
      _(os1).wont_equal os2
    end

    it 'returns false for different family, same version' do
      version = UserAgentParser::Version.new('7')
      os1 = UserAgentParser::OperatingSystem.new('Windows', version)
      os2 = UserAgentParser::OperatingSystem.new('Blah', version)
      _(os1).wont_equal os2
    end
  end

  describe '#eql?' do
    it "returns true for same user agents across different O/S's" do
      version = UserAgentParser::Version.new('7')
      os1 = UserAgentParser::OperatingSystem.new('Windows', version)
      os2 = UserAgentParser::OperatingSystem.new('Windows', version)
      assert_equal true, os1.eql?(os2)
    end

    it 'returns false for same family, different versions' do
      seven = UserAgentParser::Version.new('7')
      eight = UserAgentParser::Version.new('8')
      os1 = UserAgentParser::OperatingSystem.new('Windows', seven)
      os2 = UserAgentParser::OperatingSystem.new('Windows', eight)
      assert_equal false, os1.eql?(os2)
    end

    it 'returns false for different family, same version' do
      version = UserAgentParser::Version.new('7')
      os1 = UserAgentParser::OperatingSystem.new('Windows', version)
      os2 = UserAgentParser::OperatingSystem.new('Blah', version)
      assert_equal false, os1.eql?(os2)
    end
  end

  describe '#inspect' do
    it 'returns class family and instance to_s' do
      version = UserAgentParser::Version.new('10.7.4')
      os = UserAgentParser::OperatingSystem.new('OS X', version)
      _(os.inspect.to_s).must_equal '#<UserAgentParser::OperatingSystem OS X 10.7.4>'
    end
  end
end


================================================
FILE: spec/other_regexes.yaml
================================================
user_agent_parsers:
  - regex: 'Other.*'
    family_replacement: 'Other browser'
    v1_replacement: '1'
    v2_replacement: '2'
    v3_replacement: '3'
    v4_replacement: '4'

os_parsers:
  - regex: 'Other.*'
    os_replacement: 'Other OS'
    os_v1_replacement: '1'
    os_v2_replacement: '2'

device_parsers:
  - regex: 'Other.*'
    device_replacement: 'Other device'


================================================
FILE: spec/parser_spec.rb
================================================
# frozen_string_literal: true

require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
require 'yaml'

describe UserAgentParser::Parser do
  PARSER = UserAgentParser::Parser.new

  # Some Ruby versions (JRuby) need sanitised test names, as some chars screw
  # up the test method definitions
  def self.test_case_to_test_name(test_case)
    name = "#{test_case['user_agent_string']}_#{test_case['family']}"
    name.gsub(/[^a-z0-9_.-]/i, '_').squeeze('_')
  end

  def self.file_to_test_cases(file)
    file_to_yaml(file)['test_cases'].map do |test_case|
      {
        'user_agent_string' => test_case['user_agent_string'],
        'family' => test_case['family'],
        'major' => test_case['major'],
        'minor' => test_case['minor'],
        'patch' => test_case['patch'],
        'patch_minor' => test_case['patch_minor'],
        'brand' => test_case['brand'],
        'model' => test_case['model']
      }
    end.reject do |test_case|
      # We don't do the hacky javascript user agent overrides
      test_case.key?('js_ua') ||
        test_case['family'] == 'IE Platform Preview' ||
        test_case['user_agent_string'].include?('chromeframe;')
    end
  end

  def self.file_to_yaml(resource)
    uap_path = File.expand_path('../../vendor/uap-core', __FILE__)
    resource_path = File.join(uap_path, resource)
    YAML.load_file(resource_path)
  end

  def self.user_agent_test_cases
    file_to_test_cases('test_resources/firefox_user_agent_strings.yaml')
    file_to_test_cases('tests/test_ua.yaml')
  end

  def self.operating_system_test_cases
    file_to_test_cases('tests/test_os.yaml') +
      file_to_test_cases('test_resources/additional_os_tests.yaml')
  end

  def self.device_test_cases
    file_to_test_cases('tests/test_device.yaml')
  end

  def custom_patterns_path
    File.join(File.dirname(__FILE__), 'custom_regexes.yaml')
  end

  def other_patterns_path
    File.join(File.dirname(__FILE__), 'other_regexes.yaml')
  end

  describe '::parse' do
    it 'parses a UA' do
      ua = UserAgentParser.parse('Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/418.8 (KHTML, like Gecko) Safari/419.3')
      _(ua.family).must_equal('Safari')
    end
    it 'accepts a custom patterns path' do
      ua = UserAgentParser.parse('Any user agent string', patterns_path: custom_patterns_path)
      _(ua.family).must_equal('Custom browser')
    end
  end

  describe '#initialize with a custom patterns path' do
    it 'accepts a single patterns_path string' do
      parser = UserAgentParser::Parser.new(patterns_path: custom_patterns_path)
      ua = parser.parse('Any user agent string')

      _(parser.patterns_paths).must_equal([custom_patterns_path])
      _(parser.patterns_path).must_equal(custom_patterns_path)

      _(ua.family).must_equal('Custom browser')
      _(ua.version.major).must_equal('1')
      _(ua.version.minor).must_equal('2')
      _(ua.version.patch).must_equal('3')
      _(ua.version.patch_minor).must_equal('4')

      _(ua.os.family).must_equal('Custom OS')
      _(ua.os.version.major).must_equal('1')
      _(ua.os.version.minor).must_equal('2')

      _(ua.device.family).must_equal('Custom device')
    end

    it 'accepts patterns_paths array' do
      patterns_paths = [custom_patterns_path, other_patterns_path]
      parser = UserAgentParser::Parser.new(patterns_paths: patterns_paths)

      _(parser.patterns_paths).must_equal(patterns_paths)
      _(parser.patterns_path).must_equal(custom_patterns_path)

      ua = parser.parse('Any user agent string')
      oua = parser.parse('Other user agent string')

      _(ua.family).must_equal('Custom browser')
      _(oua.family).must_equal('Other browser')
    end
  end

  describe '#parse' do
    user_agent_test_cases.each do |test_case|
      it "parses UA for #{test_case_to_test_name(test_case)}" do
        user_agent = PARSER.parse(test_case['user_agent_string'])

        if test_case['family']
          _(user_agent.family).must_equal_test_case_property(test_case, 'family')
        end

        if test_case['major']
          _(user_agent.version.major).must_equal_test_case_property(test_case, 'major')
        end

        if test_case['minor']
          _(user_agent.version.minor).must_equal_test_case_property(test_case, 'minor')
        end

        if test_case['patch']
          _(user_agent.version.patch).must_equal_test_case_property(test_case, 'patch')
        end
      end
    end

    operating_system_test_cases.each do |test_case|
      it "parses OS for #{test_case_to_test_name(test_case)}" do
        user_agent = PARSER.parse(test_case['user_agent_string'])
        operating_system = user_agent.os

        if test_case['family']
          _(operating_system.family).must_equal_test_case_property(test_case, 'family')
        end

        if test_case['major']
          _(operating_system.version.major).must_equal_test_case_property(test_case, 'major')
        end

        if test_case['minor']
          _(operating_system.version.minor).must_equal_test_case_property(test_case, 'minor')
        end

        if test_case['patch']
          _(operating_system.version.patch).must_equal_test_case_property(test_case, 'patch')
        end

        if test_case['patch_minor']
          _(operating_system.version.patch_minor).must_equal_test_case_property(test_case, 'patch_minor')
        end
      end
    end

    device_test_cases.each do |test_case|
      it "parses device for #{test_case_to_test_name(test_case)}" do
        user_agent = PARSER.parse(test_case['user_agent_string'])
        device = user_agent.device

        if test_case['family']
          _(device.family).must_equal_test_case_property(test_case, 'family')
        end

        if test_case['model']
          _(device.model).must_equal_test_case_property(test_case, 'model')
        end

        if test_case['brand']
          _(device.brand).must_equal_test_case_property(test_case, 'brand')
        end
      end
    end
  end

  describe '#parse_os' do
    operating_system_test_cases.each do |test_case|
      it "parses OS for #{test_case_to_test_name(test_case)}" do
        operating_system = PARSER.parse_os(test_case['user_agent_string'])

        if test_case['family']
          _(operating_system.family).must_equal_test_case_property(test_case, 'family')
        end

        if test_case['major']
          _(operating_system.version.major).must_equal_test_case_property(test_case, 'major')
        end

        if test_case['minor']
          _(operating_system.version.minor).must_equal_test_case_property(test_case, 'minor')
        end

        if test_case['patch']
          _(operating_system.version.patch).must_equal_test_case_property(test_case, 'patch')
        end

        if test_case['patch_minor']
          _(operating_system.version.patch_minor).must_equal_test_case_property(test_case, 'patch_minor')
        end
      end
    end
  end

  describe '#parse_device' do
    device_test_cases.each do |test_case|
      it "parses device for #{test_case_to_test_name(test_case)}" do
        device = PARSER.parse_device(test_case['user_agent_string'])

        if test_case['family']
          _(device.family).must_equal_test_case_property(test_case, 'family')
        end

        if test_case['model']
          _(device.model).must_equal_test_case_property(test_case, 'model')
        end

        if test_case['brand']
          _(device.brand).must_equal_test_case_property(test_case, 'brand')
        end
      end
    end
  end

  describe '#parse_ua' do
    user_agent_test_cases.each do |test_case|
      it "parses UA for #{test_case_to_test_name(test_case)}" do
        user_agent = PARSER.parse_ua(test_case['user_agent_string'])

        assert_nil user_agent.os
        assert_nil user_agent.device

        if test_case['family']
          _(user_agent.family).must_equal_test_case_property(test_case, 'family')
        end

        if test_case['major']
          _(user_agent.version.major).must_equal_test_case_property(test_case, 'major')
        end

        if test_case['minor']
          _(user_agent.version.minor).must_equal_test_case_property(test_case, 'minor')
        end

        if test_case['patch']
          _(user_agent.version.patch).must_equal_test_case_property(test_case, 'patch')
        end
      end
    end
  end

end


================================================
FILE: spec/spec_helper.rb
================================================
# frozen_string_literal: true

if ENV['SIMPLECOV'] == '1'
  require 'coveralls'
  require 'simplecov'

  SimpleCov.formatter = Coveralls::SimpleCov::Formatter
  SimpleCov.start do
    add_filter '/.bundle/'
    add_filter '/doc/'
    add_filter '/spec/'
    add_filter '/config/'
    merge_timeout 600
  end
end

require 'minitest/autorun'

$:.unshift File.expand_path('../../lib', __FILE__)
require 'user_agent_parser'

module Minitest
  module Assertions
    # Asserts the test case property is equal to the expected value. On failure
    # the message includes the property and user_agent_string from the test
    # case for easier debugging
    def assert_test_case_property_equal(test_case, actual, test_case_property)
      assert_equal test_case[test_case_property],
                   actual,
                   "#{test_case_property} failed for user agent: #{test_case['user_agent_string']}"
    end

    Object.infect_an_assertion :assert_test_case_property_equal, :must_equal_test_case_property
  end
end


================================================
FILE: spec/user_agent_spec.rb
================================================
# frozen_string_literal: true

require File.expand_path(File.dirname(__FILE__) + '/spec_helper')

describe UserAgentParser::UserAgent do
  describe '#to_s' do
    it 'returns a string of just the family' do
      _(UserAgentParser::UserAgent.new('Chrome').to_s).must_equal 'Chrome'
    end

    it 'returns a string of family and version' do
      version = UserAgentParser::Version.new('1.2.3pre')
      agent = UserAgentParser::UserAgent.new('Chrome', version)
      _(agent.to_s).must_equal 'Chrome 1.2.3pre'
    end
  end

  describe '#initialize' do
    describe 'with family' do
      it 'sets family' do
        agent = UserAgentParser::UserAgent.new('Chromium')
        _(agent.family).must_equal 'Chromium'
      end
    end

    describe 'with no family' do
      it 'sets family to Other' do
        agent = UserAgentParser::UserAgent.new
        _(agent.family).must_equal 'Other'
      end
    end

    describe 'with version' do
      it 'sets version' do
        version = UserAgentParser::Version.new('1.2.3')
        agent = UserAgentParser::UserAgent.new(nil, version)
        _(agent.version).must_equal version
      end
    end

    describe 'with os' do
      it 'sets os' do
        os = UserAgentParser::OperatingSystem.new('Windows XP')
        agent = UserAgentParser::UserAgent.new(nil, nil, os)
        _(agent.os).must_equal os
      end
    end

    describe 'with device' do
      it 'sets device' do
        device = UserAgentParser::Device.new('iPhone')
        agent = UserAgentParser::UserAgent.new(nil, nil, nil, device)
        _(agent.device).must_equal device
      end
    end
  end

  describe '#name' do
    it 'returns family' do
      agent = UserAgentParser::UserAgent.new('Safari')
      _(agent.name).must_equal agent.family
    end
  end

  describe '#==' do
    it 'returns true for same agents with no OS' do
      version = UserAgentParser::Version.new('1.0')
      agent1 = UserAgentParser::UserAgent.new('Chrome', version)
      agent2 = UserAgentParser::UserAgent.new('Chrome', version)
      _(agent1).must_equal agent2
    end

    it 'returns true for same agents on same OS' do
      version = UserAgentParser::Version.new('1.0')
      os = UserAgentParser::OperatingSystem.new('Windows')
      agent1 = UserAgentParser::UserAgent.new('Chrome', version, os)
      agent2 = UserAgentParser::UserAgent.new('Chrome', version, os)
      _(agent1).must_equal agent2
    end

    it 'returns false for same agent on different OS' do
      version = UserAgentParser::Version.new('1.0')
      windows = UserAgentParser::OperatingSystem.new('Windows')
      mac = UserAgentParser::OperatingSystem.new('Mac')
      agent1 = UserAgentParser::UserAgent.new('Chrome', version, windows)
      agent2 = UserAgentParser::UserAgent.new('Chrome', version, mac)
      _(agent1).wont_equal agent2
    end

    it 'returns false for same os, but different browser version' do
      browser_version1 = UserAgentParser::Version.new('1.0')
      browser_version2 = UserAgentParser::Version.new('2.0')
      os = UserAgentParser::OperatingSystem.new('Windows')
      agent1 = UserAgentParser::UserAgent.new('Chrome', browser_version1, os)
      agent2 = UserAgentParser::UserAgent.new('Chrome', browser_version2, os)
      _(agent1).wont_equal agent2
    end
  end

  describe '#eql?' do
    it 'returns true for same agents with no OS' do
      version = UserAgentParser::Version.new('1.0')
      agent1 = UserAgentParser::UserAgent.new('Chrome', version)
      agent2 = UserAgentParser::UserAgent.new('Chrome', version)
      assert_equal true, agent1.eql?(agent2)
    end

    it 'returns true for same agents on same OS' do
      version = UserAgentParser::Version.new('1.0')
      os = UserAgentParser::OperatingSystem.new('Windows')
      agent1 = UserAgentParser::UserAgent.new('Chrome', version, os)
      agent2 = UserAgentParser::UserAgent.new('Chrome', version, os)
      assert_equal true, agent1.eql?(agent2)
    end

    it 'returns false for same agent on different OS' do
      version = UserAgentParser::Version.new('1.0')
      windows = UserAgentParser::OperatingSystem.new('Windows')
      mac = UserAgentParser::OperatingSystem.new('Mac')
      agent1 = UserAgentParser::UserAgent.new('Chrome', version, windows)
      agent2 = UserAgentParser::UserAgent.new('Chrome', version, mac)
      assert_equal false, agent1.eql?(agent2)
    end

    it 'returns false for same os, but different browser version' do
      browser_version1 = UserAgentParser::Version.new('1.0')
      browser_version2 = UserAgentParser::Version.new('2.0')
      os = UserAgentParser::OperatingSystem.new('Windows')
      agent1 = UserAgentParser::UserAgent.new('Chrome', browser_version1, os)
      agent2 = UserAgentParser::UserAgent.new('Chrome', browser_version2, os)
      assert_equal false, agent1.eql?(agent2)
    end
  end

  describe '#inspect' do
    it 'returns the family and version' do
      browser_version = UserAgentParser::Version.new('1.0')
      agent = UserAgentParser::UserAgent.new('Chrome', browser_version)
      _(agent.inspect.to_s).must_equal '#<UserAgentParser::UserAgent Chrome 1.0>'
    end

    it 'returns the OS if present' do
      browser_version = UserAgentParser::Version.new('1.0')
      os_version = UserAgentParser::Version.new('10.7.4')
      os = UserAgentParser::OperatingSystem.new('OS X', os_version)
      agent = UserAgentParser::UserAgent.new('Chrome', browser_version, os)
      _(agent.inspect).must_equal '#<UserAgentParser::UserAgent Chrome 1.0 (OS X 10.7.4)>'
    end

    it 'returns device if present' do
      browser_version = UserAgentParser::Version.new('5.0.2')
      os_version = UserAgentParser::Version.new('4.2.1')
      os = UserAgentParser::OperatingSystem.new('iOS', os_version)
      device = UserAgentParser::Device.new('iPhone')
      agent = UserAgentParser::UserAgent.new('Mobile Safari', browser_version, os, device)
      _(agent.inspect).must_equal '#<UserAgentParser::UserAgent Mobile Safari 5.0.2 (iOS 4.2.1) (iPhone)>'
    end
  end

  describe '#to_h' do
    let(:expected) do
      {
        device: { family: 'iPhone', model: 'iPhone', brand: nil },
        family: 'Mobile Safari',
        os: {
          version: { version: '4.2.1', major: '4', minor: '2', patch: '1', patch_minor: nil},
          family: 'iOS'
        },
        version: { version: '5.0.2', major: '5', minor: '0', patch: '2', patch_minor: nil }
      }
    end

    it 'returns everything' do
      browser_version = UserAgentParser::Version.new('5.0.2')
      os_version = UserAgentParser::Version.new('4.2.1')
      os = UserAgentParser::OperatingSystem.new('iOS', os_version)
      device = UserAgentParser::Device.new('iPhone')
      agent = UserAgentParser::UserAgent.new('Mobile Safari', browser_version, os, device)
      assert_equal(expected, agent.to_h)
    end
  end
end


================================================
FILE: spec/version_spec.rb
================================================
# frozen_string_literal: true

require File.expand_path(File.dirname(__FILE__) + '/spec_helper')

describe UserAgentParser::Version do
  it "parses '1'" do
    version = UserAgentParser::Version.new('1')
    _(version.major).must_equal '1'
  end

  it "parses '1.2'" do
    version = UserAgentParser::Version.new('1.2')
    _(version.major).must_equal '1'
    _(version.minor).must_equal '2'
  end

  it "parses '1.2.3'" do
    version = UserAgentParser::Version.new('1.2.3')
    _(version.major).must_equal '1'
    _(version.minor).must_equal '2'
    _(version.patch).must_equal '3'
  end

  it "parses '1.2.3b4'" do
    version = UserAgentParser::Version.new('1.2.3b4')
    _(version.major).must_equal '1'
    _(version.minor).must_equal '2'
    _(version.patch).must_equal '3'
    _(version.patch_minor).must_equal 'b4'
  end

  it "parses '1.2.3-b4'" do
    version = UserAgentParser::Version.new('1.2.3-b4')
    _(version.major).must_equal '1'
    _(version.minor).must_equal '2'
    _(version.patch).must_equal '3'
    _(version.patch_minor).must_equal 'b4'
  end

  it "parses '1.2.3pre'" do
    version = UserAgentParser::Version.new('1.2.3pre')
    _(version.major).must_equal '1'
    _(version.minor).must_equal '2'
    _(version.patch).must_equal '3pre'
  end

  it "parses '1.2.3-45'" do
    version = UserAgentParser::Version.new('1.2.3-45')
    _(version.major).must_equal '1'
    _(version.minor).must_equal '2'
    _(version.patch).must_equal '3-45'
  end

  it 'accepts Fixnum and String arguments' do
    version = UserAgentParser::Version.new(1, '2a', 3, '4b')
    _(version.major).must_equal '1'
    _(version.minor).must_equal '2a'
    _(version.patch).must_equal '3'
    _(version.patch_minor).must_equal '4b'
  end

  describe '#to_s' do
    it 'returns the same string as initialized with' do
      version = UserAgentParser::Version.new('1.2.3b4')
      _(version.to_s).must_equal '1.2.3b4'
    end
  end

  describe '#==' do
    it 'returns true for same versions' do
      version = UserAgentParser::Version.new('1.2.3')
      _(version).must_equal UserAgentParser::Version.new('1.2.3')
    end

    it 'returns false for different versions' do
      version = UserAgentParser::Version.new('1.2.3')
      _(version).wont_equal UserAgentParser::Version.new('1.2.2')
    end
  end

  describe '#<=>' do
    it 'accepts string for comparison' do
      version = UserAgentParser::Version.new('1.2.3')

      assert_operator version, :<, '1.2.4'
      assert_operator version, :==, '1.2.3'
      assert_operator version, :>, '1.2.2'
    end

    it 'accepts another instance of Version for comparison' do
      version = UserAgentParser::Version.new('1.2.3')

      assert_operator version, :>, UserAgentParser::Version.new('1.2.2')
      assert_operator version, :==, UserAgentParser::Version.new('1.2.3')
      assert_operator version, :<, UserAgentParser::Version.new('1.2.4')
    end

    it 'is comparing major version' do
      version = UserAgentParser::Version.new('1.2.3')

      assert_operator version, :<, '2'
      assert_operator version, :>=, '1'
      assert_operator version, :>, '0'
    end

    it 'is comparing minor version' do
      version = UserAgentParser::Version.new('1.2.3')

      assert_operator version, :<, '2.0'
      assert_operator version, :<, '1.3'
      assert_operator version, :>=, '1.2'
      assert_operator version, :>, '1.1'
      assert_operator version, :>, '0.1'
    end

    it 'is comparing patch level' do
      version = UserAgentParser::Version.new('1.2.3')

      assert_operator version, :<, '1.2.4'
      assert_operator version, :>=, '1.2.3'
      assert_operator version, :<=, '1.2.3'
      assert_operator version, :>, '1.2.2'
    end

    it 'is comparing patch_minor level correctly' do
      version = UserAgentParser::Version.new('1.2.3.p1')

      assert_operator version, :<, '1.2.4'
      assert_operator version, :<, '1.2.3.p2'
      assert_operator version, :>=, '1.2.3.p1'
      assert_operator version, :<=, '1.2.3.p1'
      assert_operator version, :>, '1.2.3.p0'
      assert_operator version, :>, '1.2.2'
      assert_operator version, :>, '1.1'
    end

    it 'is correctly comparing versions with different lengths' do
      version = UserAgentParser::Version.new('1.42.3')

      assert_operator version, :<, '1.142'
      assert_operator version, :<, '1.42.4'
      assert_operator version, :>=, '1.42'
      assert_operator version, :>, '1.14'
      assert_operator version, :>, '1.7'
      assert_operator version, :>, '1.3'
    end

    it 'does its best to compare string versions' do
      version = UserAgentParser::Version.new('1.2.3.a')

      assert_operator version, :<, '1.2.4'
      assert_operator version, :<, '1.2.3.b'
      assert_operator version, :<, '1.2.3.p1'
      assert_operator version, :<, '1.2.3.p0'
      assert_operator version, :>, '1.2.2'
    end
  end

  describe '#inspect' do
    it 'returns the class and version' do
      version = UserAgentParser::Version.new('1.2.3')
      _(version.inspect).must_equal '#<UserAgentParser::Version 1.2.3>'
    end
  end
end


================================================
FILE: user_agent_parser.gemspec
================================================
# frozen_string_literal: true

Gem::Specification.new do |gem|
  gem.name    = 'user_agent_parser'
  gem.version = '2.21.0'

  gem.authors     = 'Tim Lucas'
  gem.email       = 't@toolmantim.com'
  gem.homepage    = 'https://github.com/ua-parser/uap-ruby'
  gem.summary     = "Parsing user agent strings with the help of BrowserScope's UA database"
  gem.description = <<~DESCRIPTION
    A simple, comprehensive Ruby gem for parsing user agent strings
    with the help of BrowserScope's UserAgent database
  DESCRIPTION
  gem.license     = 'MIT'
  gem.executables = ['user_agent_parser']

  gem.files = %x{git ls-files}.split("\n").select { |d| d =~ %r{^(MIT-LICENSE|Readme.md|lib|bin/)} } + ['vendor/uap-core/regexes.yaml']

  gem.required_ruby_version = '>= 2.5'
end
Download .txt
gitextract_ikhkw8zc/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .gitmodules
├── .ruby-version
├── CHANGELOG.md
├── Gemfile
├── MIT-LICENSE
├── README.md
├── Rakefile
├── bin/
│   └── user_agent_parser
├── lib/
│   ├── user_agent_parser/
│   │   ├── cli.rb
│   │   ├── device.rb
│   │   ├── operating_system.rb
│   │   ├── parser.rb
│   │   ├── user_agent.rb
│   │   └── version.rb
│   └── user_agent_parser.rb
├── spec/
│   ├── cli_spec.rb
│   ├── custom_regexes.yaml
│   ├── device_spec.rb
│   ├── operating_system_spec.rb
│   ├── other_regexes.yaml
│   ├── parser_spec.rb
│   ├── spec_helper.rb
│   ├── user_agent_spec.rb
│   └── version_spec.rb
└── user_agent_parser.gemspec
Download .txt
SYMBOL INDEX (72 symbols across 9 files)

FILE: lib/user_agent_parser.rb
  type UserAgentParser (line 9) | module UserAgentParser
    function parse (line 13) | def self.parse(user_agent_string, **args)

FILE: lib/user_agent_parser/cli.rb
  type UserAgentParser (line 3) | module UserAgentParser
    class Cli (line 4) | class Cli
      method initialize (line 5) | def initialize(user_agent, options = {})
      method run! (line 10) | def run!
      method major (line 38) | def major
      method minor (line 42) | def minor
      method version (line 46) | def version
      method with_version (line 50) | def with_version

FILE: lib/user_agent_parser/device.rb
  type UserAgentParser (line 3) | module UserAgentParser
    class Device (line 4) | class Device
      method initialize (line 11) | def initialize(family = nil, model = nil, brand = nil)
      method to_s (line 17) | def to_s
      method inspect (line 21) | def inspect
      method eql? (line 25) | def eql?(other)
      method to_h (line 31) | def to_h

FILE: lib/user_agent_parser/operating_system.rb
  type UserAgentParser (line 3) | module UserAgentParser
    class OperatingSystem (line 4) | class OperatingSystem
      method initialize (line 11) | def initialize(family = DEFAULT_FAMILY, version = nil)
      method to_s (line 16) | def to_s
      method inspect (line 22) | def inspect
      method eql? (line 26) | def eql?(other)
      method to_h (line 34) | def to_h

FILE: lib/user_agent_parser/parser.rb
  type UserAgentParser (line 5) | module UserAgentParser
    class Parser (line 6) | class Parser
      method initialize (line 29) | def initialize(patterns_path: nil, patterns_paths: [])
      method parse (line 36) | def parse(user_agent)
      method parse_os (line 42) | def parse_os(user_agent)
      method parse_device (line 52) | def parse_device(user_agent)
      method parse_ua (line 62) | def parse_ua(user_agent, os = nil, device = nil)
      method patterns_path (line 72) | def patterns_path
      method load_patterns (line 79) | def load_patterns(patterns_paths)
      method load_patterns_file (line 88) | def load_patterns_file(path)
      method parse_pattern (line 101) | def parse_pattern(patterns)
      method first_pattern_match (line 109) | def first_pattern_match(patterns, value)
      method user_agent_from_pattern_match (line 116) | def user_agent_from_pattern_match(pattern, match, os = nil, device =...
      method os_from_pattern_match (line 122) | def os_from_pattern_match(pattern, match)
      method device_from_pattern_match (line 128) | def device_from_pattern_match(pattern, match)
      method from_pattern_match (line 153) | def from_pattern_match(keys, pattern, match)
      method interpolate (line 166) | def interpolate(replacement, match)
      method version_from_segments (line 174) | def version_from_segments(*segments)

FILE: lib/user_agent_parser/user_agent.rb
  type UserAgentParser (line 3) | module UserAgentParser
    class UserAgent (line 4) | class UserAgent
      method initialize (line 11) | def initialize(family = nil, version = nil, os = nil, device = nil)
      method to_s (line 18) | def to_s
      method inspect (line 24) | def inspect
      method eql? (line 31) | def eql?(other)
      method to_h (line 40) | def to_h

FILE: lib/user_agent_parser/version.rb
  type UserAgentParser (line 5) | module UserAgentParser
    class Version (line 6) | class Version
      method initialize (line 16) | def initialize(*args)
      method major (line 27) | def major
      method minor (line 31) | def minor
      method patch (line 35) | def patch
      method patch_minor (line 39) | def patch_minor
      method inspect (line 43) | def inspect
      method eql? (line 47) | def eql?(other)
      method <=> (line 52) | def <=>(other)
      method segments (line 56) | def segments
      method to_h (line 60) | def to_h

FILE: spec/parser_spec.rb
  function test_case_to_test_name (line 11) | def self.test_case_to_test_name(test_case)
  function file_to_test_cases (line 16) | def self.file_to_test_cases(file)
  function file_to_yaml (line 36) | def self.file_to_yaml(resource)
  function user_agent_test_cases (line 42) | def self.user_agent_test_cases
  function operating_system_test_cases (line 47) | def self.operating_system_test_cases
  function device_test_cases (line 52) | def self.device_test_cases
  function custom_patterns_path (line 56) | def custom_patterns_path
  function other_patterns_path (line 60) | def other_patterns_path

FILE: spec/spec_helper.rb
  type Minitest (line 22) | module Minitest
    type Assertions (line 23) | module Assertions
      function assert_test_case_property_equal (line 27) | def assert_test_case_property_equal(test_case, actual, test_case_pro...
Condensed preview — 28 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (55K chars).
[
  {
    "path": ".github/dependabot.yml",
    "chars": 118,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 720,
    "preview": "name: ci\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  specs:\n    runs-on: ubuntu-late"
  },
  {
    "path": ".gitignore",
    "chars": 51,
    "preview": ".bundle\ncoverage\nvendor/bundle\n.tool-versions\n\npkg\n"
  },
  {
    "path": ".gitmodules",
    "chars": 103,
    "preview": "[submodule \"vendor/uap-core\"]\n\tpath = vendor/uap-core\n\turl = https://github.com/ua-parser/uap-core.git\n"
  },
  {
    "path": ".ruby-version",
    "chars": 6,
    "preview": "3.4.4\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 1810,
    "preview": "# master\n\n# 2.21.0 (2026-02-12)\n  * Sync with https://github.com/ua-parser/uap-core/commit/383604dfd6c7518c152e3bd9b7eda"
  },
  {
    "path": "Gemfile",
    "chars": 160,
    "preview": "# frozen_string_literal: true\n\nsource 'https://rubygems.org/'\n\ngemspec\n\ngroup :development, :test do\n  gem 'coveralls_re"
  },
  {
    "path": "MIT-LICENSE",
    "chars": 1053,
    "preview": "Copyright (c) 2012 Tim Lucas\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this softw"
  },
  {
    "path": "README.md",
    "chars": 4653,
    "preview": "# UserAgentParser [![Build Status](https://github.com/ua-parser/uap-ruby/actions/workflows/ci.yml/badge.svg)](https://gi"
  },
  {
    "path": "Rakefile",
    "chars": 1696,
    "preview": "# frozen_string_literal: true\n\nrequire 'rake/testtask'\nrequire 'bundler'\n\ntask default: :test\n\ndesc 'Run tests'\nRake::Te"
  },
  {
    "path": "bin/user_agent_parser",
    "chars": 1300,
    "preview": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nrequire 'optparse'\n\nrequire 'user_agent_parser'\nrequire 'user_agent_p"
  },
  {
    "path": "lib/user_agent_parser/cli.rb",
    "chars": 1086,
    "preview": "# frozen_string_literal: true\n\nmodule UserAgentParser\n  class Cli\n    def initialize(user_agent, options = {})\n      @us"
  },
  {
    "path": "lib/user_agent_parser/device.rb",
    "chars": 641,
    "preview": "# frozen_string_literal: true\n\nmodule UserAgentParser\n  class Device\n    DEFAULT_FAMILY = 'Other'\n\n    attr_reader :fami"
  },
  {
    "path": "lib/user_agent_parser/operating_system.rb",
    "chars": 701,
    "preview": "# frozen_string_literal: true\n\nmodule UserAgentParser\n  class OperatingSystem\n    DEFAULT_FAMILY = 'Other'\n\n    attr_rea"
  },
  {
    "path": "lib/user_agent_parser/parser.rb",
    "chars": 4896,
    "preview": "# frozen_string_literal: true\n\nrequire 'yaml'\n\nmodule UserAgentParser\n  class Parser\n    extend Gem::Deprecate\n\n    FAMI"
  },
  {
    "path": "lib/user_agent_parser/user_agent.rb",
    "chars": 941,
    "preview": "# frozen_string_literal: true\n\nmodule UserAgentParser\n  class UserAgent\n    DEFAULT_FAMILY = 'Other'\n\n    attr_reader :f"
  },
  {
    "path": "lib/user_agent_parser/version.rb",
    "chars": 1385,
    "preview": "# frozen_string_literal: true\n\nrequire 'rubygems/version'\n\nmodule UserAgentParser\n  class Version\n    include Comparable"
  },
  {
    "path": "lib/user_agent_parser.rb",
    "chars": 506,
    "preview": "# frozen_string_literal: true\n\nrequire 'user_agent_parser/parser'\nrequire 'user_agent_parser/user_agent'\nrequire 'user_a"
  },
  {
    "path": "spec/cli_spec.rb",
    "chars": 2576,
    "preview": "# frozen_string_literal: true\n\nrequire File.expand_path(File.dirname(__FILE__) + '/spec_helper')\nrequire 'user_agent_par"
  },
  {
    "path": "spec/custom_regexes.yaml",
    "chars": 370,
    "preview": "user_agent_parsers:\n  - regex: 'Any.*'\n    family_replacement: 'Custom browser'\n    v1_replacement: '1'\n    v2_replaceme"
  },
  {
    "path": "spec/device_spec.rb",
    "chars": 1510,
    "preview": "# frozen_string_literal: true\n\nrequire File.expand_path(File.dirname(__FILE__) + '/spec_helper')\n\ndescribe UserAgentPars"
  },
  {
    "path": "spec/operating_system_spec.rb",
    "chars": 2905,
    "preview": "# frozen_string_literal: true\n\nrequire File.expand_path(File.dirname(__FILE__) + '/spec_helper')\n\ndescribe UserAgentPars"
  },
  {
    "path": "spec/other_regexes.yaml",
    "chars": 373,
    "preview": "user_agent_parsers:\n  - regex: 'Other.*'\n    family_replacement: 'Other browser'\n    v1_replacement: '1'\n    v2_replacem"
  },
  {
    "path": "spec/parser_spec.rb",
    "chars": 8351,
    "preview": "# frozen_string_literal: true\n\nrequire File.expand_path(File.dirname(__FILE__) + '/spec_helper')\nrequire 'yaml'\n\ndescrib"
  },
  {
    "path": "spec/spec_helper.rb",
    "chars": 1016,
    "preview": "# frozen_string_literal: true\n\nif ENV['SIMPLECOV'] == '1'\n  require 'coveralls'\n  require 'simplecov'\n\n  SimpleCov.forma"
  },
  {
    "path": "spec/user_agent_spec.rb",
    "chars": 6864,
    "preview": "# frozen_string_literal: true\n\nrequire File.expand_path(File.dirname(__FILE__) + '/spec_helper')\n\ndescribe UserAgentPars"
  },
  {
    "path": "spec/version_spec.rb",
    "chars": 5094,
    "preview": "# frozen_string_literal: true\n\nrequire File.expand_path(File.dirname(__FILE__) + '/spec_helper')\n\ndescribe UserAgentPars"
  },
  {
    "path": "user_agent_parser.gemspec",
    "chars": 770,
    "preview": "# frozen_string_literal: true\n\nGem::Specification.new do |gem|\n  gem.name    = 'user_agent_parser'\n  gem.version = '2.21"
  }
]

About this extraction

This page contains the full source code of the ua-parser/uap-ruby GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 28 files (50.4 KB), approximately 14.7k tokens, and a symbol index with 72 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!