[
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: ci\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  specs:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        ruby:\n          - '3.4'\n          - '3.3'\n          - '3.2'\n          - '3.1'\n        include:\n          - ruby: '3.4'\n            coverage: '1'\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          submodules: true\n      - name: Setup Ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: ${{ matrix.ruby }}\n          bundler-cache: true # 'bundle install' and cache gems\n      - name: Run specs\n        env:\n          SIMPLECOV: ${{ matrix.coverage }}\n        run: |\n          bundle exec rake test\n"
  },
  {
    "path": ".gitignore",
    "content": ".bundle\ncoverage\nvendor/bundle\n.tool-versions\n\npkg\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"vendor/uap-core\"]\n\tpath = vendor/uap-core\n\turl = https://github.com/ua-parser/uap-core.git\n"
  },
  {
    "path": ".ruby-version",
    "content": "3.4.4\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# master\n\n# 2.21.0 (2026-02-12)\n  * Sync with https://github.com/ua-parser/uap-core/commit/383604dfd6c7518c152e3bd9b7eda67662b1b343\n\n# 2.20.0 (2025-07-15)\n  * Sync with https://github.com/ua-parser/uap-core/commit/432e95f6767cc8bab4c20c255784cd6f7e93bc15\n  * drop Ruby 3.0 support\n  * Add Ruby 3.4 support\n\n# 2.19.0 (2024-12-10)\n  * Sync with https://github.com/ua-parser/uap-core/commit/d4cde4c565a7e588472fbf6667f01fc4c23fa60b\n\n# 2.18.0 (2024-06-04)\n  * Sync with https://github.com/ua-parser/uap-core/commit/df56280c9e2b42dd64be2b750f803c58feb3f94a\n\n# 2.17.0 (2024-02-23)\n  * Sync with https://github.com/ua-parser/uap-core/commit/d3450bbe77fe49eb3a234ed6184065260e44d747\n\n# 2.16.0 (2023-06-07)\n  * Sync with https://github.com/ua-parser/uap-core/tree/v0.18.0\n\n# 2.15.0 (2023-04-12)\n * Expose `parse_os`, `parse_device` and `parse_ua` methods on `Parser`\n\n# 2.14.0 (2023-01-31)\n  * Sync with https://github.com/ua-parser/uap-core/commit/1ef0926f2b489cc929589c00f8b8a3efce25acc3\n\n# 2.13.0 (2022-10-21)\n  * Support loading multiple database files (via #70) (@misdoro)\n    * Support `patterns_path` argument but deprecate `pattern_path` attribute accessor\n      in `UserAgentParser::Parser`\n    * Add new `patterns_paths` array argument `UserAgentParser::Parser` to enable loading\n      multiple patterns files\n\n# 2.12.0 (2022-10-20)\n\n  * sync with https://github.com/ua-parser/uap-core/commit/dc85ab2628798538a2874dea4a9563f40a31f55a\n  * Memory optimization (via #104) (@casperisfine)\n\n# 2.11.0 (2022-04-18)\n  * Make user agent versions comparable (via #68) (@misdoro)\n\n# 2.10.0 (2022-04-18)\n  * sync with uap-core 09e9ccc\n\n# 2.9.0 (2022-01-27)\n  * sync with uap-core 0.15.0\n\n# 2.8.0 (2021-11-02)\n  * sync with uap-core 0.14.0\n  * drop support for ruby 2.4\n\n# 2.7.0 (2020-05-25)\n  * sync with uap-core 0.10.0\n"
  },
  {
    "path": "Gemfile",
    "content": "# frozen_string_literal: true\n\nsource 'https://rubygems.org/'\n\ngemspec\n\ngroup :development, :test do\n  gem 'coveralls_reborn'\n  gem 'minitest'\n  gem 'rake'\nend\n"
  },
  {
    "path": "MIT-LICENSE",
    "content": "Copyright (c) 2012 Tim Lucas\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# 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)\n\nUserAgentParser 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).\n\n## Supported Rubies\n\n* Ruby 3.4\n* Ruby 3.3\n* Ruby 3.2\n* Ruby 3.1\n* JRuby\n\n## Installation\n\n```bash\n$ gem install user_agent_parser\n```\n\n## Example usage\n\n```ruby\nrequire 'user_agent_parser'\n=> true\nuser_agent = UserAgentParser.parse 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0;)'\n=> #<UserAgentParser::UserAgent IE 9.0 (Windows Vista)>\nuser_agent.to_s\n=> \"IE 9.0\"\nuser_agent.family\n=> \"IE\"\nuser_agent.version.to_s\n=> \"9.0\"\nuser_agent.version.major\n=> \"9\"\nuser_agent.version.minor\n=> \"0\"\nuser_agent.family == \"IE\" && user_agent.version >= \"9\"\n=> true\noperating_system = user_agent.os\n=> #<UserAgentParser::OperatingSystem Windows Vista>\noperating_system.to_s\n=> \"Windows Vista\"\n\n# Device information can also be determined from some devices\nuser_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\"\n=> #<UserAgentParser::UserAgent Samsung Internet 5.0 (Android 7.0) (Samsung SM-G930T)>\nuser_agent.device.family\n=> \"Samsung SM-G930T\"\nuser_agent.device.brand\n=> \"Samsung\"\nuser_agent.device.model\n=> \"SM-G930T\"\n\nuser_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\"\n=> #<UserAgentParser::UserAgent Mobile Safari 10.2.1 (iOS 10.2.1) (iPad)>\nirb(main):026:0> user_agent.device.family\n=> \"iPad\"\nirb(main):027:0> user_agent.device.brand\n=> \"Apple\"\nirb(main):028:0> user_agent.device.model\n=> \"iPad\"\n\n\n# The parser database will be loaded and parsed on every call to\n# UserAgentParser.parse. To avoid this, instantiate your own Parser instance.\nparser = UserAgentParser::Parser.new\nparser.parse 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0;)'\n=> #<UserAgentParser::UserAgent IE 9.0 (Windows Vista)>\nparser.parse 'Opera/9.80 (Windows NT 5.1; U; ru) Presto/2.5.24 Version/10.53'\n=> #<UserAgentParser::UserAgent Opera 10.53 (Windows XP)>\n```\n\nIn a larger application, you could store a parser in a global to avoid repeat pattern loading:\n\n```ruby\nmodule MyApplication\n\n  # Instantiate the parser on load as it's quite expensive\n  USER_AGENT_PARSER = UserAgentParser::Parser.new\n\n  def self.user_agent_parser\n    USER_AGENT_PARSER\n  end\n\nend\n```\n\n## The pattern database\n\nThe [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!).\n\nYou can also specify the path to your own, updated and/or customised `regexes.yaml` file as a second argument to `UserAgentParser.parse`:\n\n```ruby\nUserAgentParser.parse(ua_string, patterns_path: '/some/path/to/regexes.yaml')\n```\n\nor when instantiating a `UserAgentParser::Parser`:\n\n```ruby\nUserAgentParser::Parser.new(patterns_path: '/some/path/to/regexes.yaml').parse(ua_string)\n```\n\nExtending the standard database is possible by providing multiple files in `patterns_paths` (plural) array argument:\n```ruby\nUserAgentParser::Parser.new(patterns_paths: [UserAgentParser::DefaultPatternsPath, '/some/path/to/regexes.yaml'])\n```\n\n## Command line tool\n\nThe gem incldes a `user_agent_parser` bin command which will read from\nstandard input, parse each line and print the result, for example:\n\n```bash\n$ cat > SOME-FILE-WITH-USER-AGENTS.txt\nUSER_AGENT_1\nUSER_AGENT_2\n...\n$ cat SOME-FILE-WITH-USER-AGENTS.txt | user_agent_parser --format '%f %M' | distribution\n```\n\nSee `user_agent_parser -h` for more information.\n\n## Contributing\n\n1. Fork\n2. Hack\n3. `rake test`\n4. Send a pull request\n\nAll accepted pull requests will earn you commit and release rights.\n\n## Releasing a new version\n\n1. Update the version in `user_agent_parser.gemspec`\n2. `git commit user_agent_parser.gemspec` with the following message format:\n\n        Version x.x.x\n\n        Changelog:\n        * Some new feature\n        * Some new bug fix\n3. `rake release`\n4. Create a [new Github release](https://github.com/ua-parser/uap-ruby/releases/new)\n\n## License\n\nMIT\n"
  },
  {
    "path": "Rakefile",
    "content": "# frozen_string_literal: true\n\nrequire 'rake/testtask'\nrequire 'bundler'\n\ntask default: :test\n\ndesc 'Run tests'\nRake::TestTask.new do |t|\n  t.warning = true\n  t.verbose = true\n  t.pattern = 'spec/*_spec.rb'\nend\n\nBundler::GemHelper.install_tasks\n\n# Does not actually get all families, as some are only listed in the regexes,\n# but gives you a pretty good idea of what will be returned.\ndesc 'Lists all unique family names for browsers and operating systems.'\ntask :families do\n  require 'pathname'\n  require 'pp'\n\n  root = Pathname(__FILE__).dirname\n  path = root.join('vendor', 'uap-core')\n\n  browser_families = paths_to_families(\n    [\n      # path.join('tests', 'test_ua.yaml'),\n      path.join('test_resources', 'firefox_user_agent_strings.yaml'),\n      path.join('test_resources', 'pgts_browser_list.yaml')\n    ]\n  )\n\n  os_families = paths_to_families(\n    [\n      # path.join('tests', 'test_os.yaml'),\n      path.join('test_resources', 'additional_os_tests.yaml')\n    ]\n  )\n\n  device_families = paths_to_families(\n    [\n      # path.join('tests', 'test_device.yaml'),\n    ]\n  )\n\n  puts \"\\n\\nBrowser Families\"\n  puts browser_families.inspect\n\n  puts \"\\n\\nOS Families\"\n  puts os_families.inspect\n\n  puts \"\\n\\nDevice Families\"\n  puts device_families.inspect\n\n  puts \"\\n\\n\"\n  puts \"Browser Family Count: #{browser_families.size}\"\n  puts \"OS Family Count: #{os_families.size}\"\n  puts \"Device Family Count: #{device_families.size}\"\nend\n\ndef paths_to_families(paths)\n  require 'yaml'\n\n  families = []\n\n  paths.each do |path|\n    data = YAML.load_file(path)\n    test_cases = data.fetch('test_cases')\n    families.concat test_cases.map { |row| row['family'] }\n  end\n\n  families.compact.uniq.sort\nend\n"
  },
  {
    "path": "bin/user_agent_parser",
    "content": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nrequire 'optparse'\n\nrequire 'user_agent_parser'\nrequire 'user_agent_parser/cli'\n\noptions = {}\n\noptparse = OptionParser.new do |opts|\n  opts.on('--family', 'Print family only') do\n    options[:family] = true\n  end\n\n  opts.on('--name', 'Print name (alias for family) only') do\n    options[:family] = true\n  end\n\n  opts.on('--version', 'Print version only') do\n    options[:version] = true\n  end\n\n  opts.on('--major', 'Print major version only') do\n    options[:major] = true\n  end\n\n  opts.on('--minor', 'Print minor version only') do\n    options[:minor] = true\n  end\n\n  opts.on('--os', 'Print operating system only') do\n    options[:os] = true\n  end\n\n  opts.on('--format format',\n          'Print output in specified format. The available formatters are:',\n          ' - %f: family',\n          ' - %n: name (alias for family)',\n          ' - %v: version',\n          ' - %M: major version',\n          ' - %m: minor version',\n          ' - %o: operating system'\n         ) do |format|\n    options[:format] = format\n  end\n\n  opts.on('-h', '--help', 'Display this screen') do\n    puts opts\n    exit\n  end\nend\n\noptparse.parse!\n\nparser = UserAgentParser::Parser.new\n\nARGF.each do |line|\n  puts UserAgentParser::Cli.new(parser.parse(line), options).run!\nend\n"
  },
  {
    "path": "lib/user_agent_parser/cli.rb",
    "content": "# frozen_string_literal: true\n\nmodule UserAgentParser\n  class Cli\n    def initialize(user_agent, options = {})\n      @user_agent = user_agent\n      @options = options\n    end\n\n    def run!\n      if @options[:family]\n        @user_agent.family\n      elsif @options[:name]\n        @user_agent.name\n      elsif @options[:version]\n        with_version(&:to_s)\n      elsif @options[:major]\n        major\n      elsif @options[:minor]\n        minor\n      elsif @options[:os]\n        @user_agent.os.to_s\n      elsif (format = @options[:format])\n        format\n          .gsub('%f', @user_agent.family)\n          .gsub('%n', @user_agent.name)\n          .gsub('%v', version.to_s)\n          .gsub('%M', major.to_s)\n          .gsub('%m', minor.to_s)\n          .gsub('%o', @user_agent.os.to_s)\n      else\n        @user_agent.to_s\n      end\n    end\n\n    private\n\n    def major\n      with_version(&:major)\n    end\n\n    def minor\n      with_version(&:minor)\n    end\n\n    def version\n      @version ||= @user_agent.version\n    end\n\n    def with_version\n      yield(version) if version\n    end\n  end\nend\n"
  },
  {
    "path": "lib/user_agent_parser/device.rb",
    "content": "# frozen_string_literal: true\n\nmodule UserAgentParser\n  class Device\n    DEFAULT_FAMILY = 'Other'\n\n    attr_reader :family, :model, :brand\n\n    alias name family\n\n    def initialize(family = nil, model = nil, brand = nil)\n      @family = family || DEFAULT_FAMILY\n      @model = model || @family\n      @brand = brand\n    end\n\n    def to_s\n      family\n    end\n\n    def inspect\n      \"#<#{self.class} #{self}>\"\n    end\n\n    def eql?(other)\n      self.class.eql?(other.class) && family == other.family\n    end\n\n    alias == eql?\n\n    def to_h\n      {\n        family: family,\n        model: model,\n        brand: brand\n      }\n    end\n  end\nend\n"
  },
  {
    "path": "lib/user_agent_parser/operating_system.rb",
    "content": "# frozen_string_literal: true\n\nmodule UserAgentParser\n  class OperatingSystem\n    DEFAULT_FAMILY = 'Other'\n\n    attr_reader :family, :version\n\n    alias name family\n\n    def initialize(family = DEFAULT_FAMILY, version = nil)\n      @family = family\n      @version = version\n    end\n\n    def to_s\n      string = family\n      string += \" #{version}\" unless version.nil?\n      string\n    end\n\n    def inspect\n      \"#<#{self.class} #{self}>\"\n    end\n\n    def eql?(other)\n      self.class.eql?(other.class) &&\n        family == other.family &&\n        version == other.version\n    end\n\n    alias == eql?\n\n    def to_h\n      {\n        version: version.to_h,\n        family: family\n      }\n    end\n  end\nend\n"
  },
  {
    "path": "lib/user_agent_parser/parser.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'yaml'\n\nmodule UserAgentParser\n  class Parser\n    extend Gem::Deprecate\n\n    FAMILY_REPLACEMENT_KEYS = %w[\n      family_replacement\n      v1_replacement\n      v2_replacement\n      v3_replacement\n      v4_replacement\n    ].freeze\n\n    OS_REPLACEMENT_KEYS = %w[\n      os_replacement\n      os_v1_replacement\n      os_v2_replacement\n      os_v3_replacement\n      os_v4_replacement\n    ].freeze\n\n    private_constant :FAMILY_REPLACEMENT_KEYS, :OS_REPLACEMENT_KEYS\n\n    attr_reader :patterns_paths\n\n    def initialize(patterns_path: nil, patterns_paths: [])\n      @patterns_paths = [patterns_path, *patterns_paths].compact\n      @patterns_paths = [UserAgentParser::DefaultPatternsPath] if @patterns_paths.empty?\n\n      @ua_patterns, @os_patterns, @device_patterns = load_patterns(@patterns_paths)\n    end\n\n    def parse(user_agent)\n      os = parse_os(user_agent)\n      device = parse_device(user_agent)\n      parse_ua(user_agent, os, device)\n    end\n\n    def parse_os(user_agent)\n      pattern, match = first_pattern_match(@os_patterns, user_agent)\n\n      if match\n        os_from_pattern_match(pattern, match)\n      else\n        OperatingSystem.new\n      end\n    end\n\n    def parse_device(user_agent)\n      pattern, match = first_pattern_match(@device_patterns, user_agent)\n\n      if match\n        device_from_pattern_match(pattern, match)\n      else\n        Device.new\n      end\n    end\n\n    def parse_ua(user_agent, os = nil, device = nil)\n      pattern, match = first_pattern_match(@ua_patterns, user_agent)\n\n      if match\n        user_agent_from_pattern_match(pattern, match, os, device)\n      else\n        UserAgent.new(nil, nil, os, device)\n      end\n    end\n\n    def patterns_path\n      patterns_paths.first\n    end\n    deprecate :patterns_path, :patterns_paths, 2022, 12\n\n    private\n\n    def load_patterns(patterns_paths)\n      patterns_paths.each_with_object([[], [], []]) do |path, patterns|\n        ua_patterns, os_patterns, device_patterns = load_patterns_file(path)\n        patterns[0] += ua_patterns\n        patterns[1] += os_patterns\n        patterns[2] += device_patterns\n      end\n    end\n\n    def load_patterns_file(path)\n      yml = begin\n        YAML.load_file(path, freeze: true)\n      rescue ArgumentError\n        YAML.load_file(path)\n      end\n      [\n        parse_pattern(yml['user_agent_parsers']),\n        parse_pattern(yml['os_parsers']),\n        parse_pattern(yml['device_parsers']),\n      ]\n    end\n\n    def parse_pattern(patterns)\n      patterns.map do |pattern|\n        pattern = pattern.dup\n        pattern[:regex] = Regexp.new(pattern.delete('regex'), pattern.delete('regex_flag') == 'i')\n        pattern\n      end\n    end\n\n    def first_pattern_match(patterns, value)\n      patterns.each do |pattern|\n        return [pattern, pattern[:regex].match(value)] if pattern[:regex].match?(value)\n      end\n      nil\n    end\n\n    def user_agent_from_pattern_match(pattern, match, os = nil, device = nil)\n      family, *versions = from_pattern_match(FAMILY_REPLACEMENT_KEYS, pattern, match)\n\n      UserAgent.new(family, version_from_segments(*versions), os, device)\n    end\n\n    def os_from_pattern_match(pattern, match)\n      os, *versions = from_pattern_match(OS_REPLACEMENT_KEYS, pattern, match)\n\n      OperatingSystem.new(os, version_from_segments(*versions))\n    end\n\n    def device_from_pattern_match(pattern, match)\n      match = match.to_a.map(&:to_s)\n      family = model = match[1]\n      brand = nil\n\n      if pattern['device_replacement']\n        family = pattern['device_replacement']\n        match.each_with_index { |m, i| family = family.sub(\"$#{i}\", m) }\n      end\n      if pattern['model_replacement']\n        model = pattern['model_replacement']\n        match.each_with_index { |m, i| model = model.sub(\"$#{i}\", m) }\n      end\n      if pattern['brand_replacement']\n        brand = pattern['brand_replacement']\n        match.each_with_index { |m, i| brand = brand.sub(\"$#{i}\", m) }\n        brand.strip!\n      end\n\n      model&.strip!\n\n      Device.new(family.strip, model, brand)\n    end\n\n    # Maps replacement keys to their values\n    def from_pattern_match(keys, pattern, match)\n      keys.each_with_index.map do |key, idx|\n        # Check if there is any replacement specified\n        if pattern[key]\n          interpolate(pattern[key], match)\n        else\n          # No replacement defined, just return correct match group\n          match[idx + 1]\n        end\n      end\n    end\n\n    # Interpolates a string with data from matches if specified\n    def interpolate(replacement, match)\n      group_idx = replacement.index('$')\n      return replacement if group_idx.nil?\n\n      group_nbr = replacement[group_idx + 1]\n      replacement.sub(\"$#{group_nbr}\", match[group_nbr.to_i])\n    end\n\n    def version_from_segments(*segments)\n      return if segments.all?(&:nil?)\n\n      Version.new(*segments)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/user_agent_parser/user_agent.rb",
    "content": "# frozen_string_literal: true\n\nmodule UserAgentParser\n  class UserAgent\n    DEFAULT_FAMILY = 'Other'\n\n    attr_reader :family, :version, :os, :device\n\n    alias name family\n\n    def initialize(family = nil, version = nil, os = nil, device = nil)\n      @family = family || DEFAULT_FAMILY\n      @version = version\n      @os = os\n      @device = device\n    end\n\n    def to_s\n      string = family\n      string += \" #{version}\" if version\n      string\n    end\n\n    def inspect\n      string = to_s\n      string += \" (#{os})\" if os\n      string += \" (#{device})\" if device\n      \"#<#{self.class} #{string}>\"\n    end\n\n    def eql?(other)\n      self.class.eql?(other.class) &&\n        family == other.family &&\n        version == other.version &&\n        os == other.os\n    end\n\n    alias == eql?\n\n    def to_h\n      {\n        device: device.to_h,\n        family: family,\n        os: os.to_h,\n        version: version.to_h\n      }\n    end\n  end\nend\n"
  },
  {
    "path": "lib/user_agent_parser/version.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'rubygems/version'\n\nmodule UserAgentParser\n  class Version\n    include Comparable\n\n    # Private: Regex used to split string version string into major, minor,\n    # patch, and patch_minor.\n    SEGMENTS_REGEX = /\\d+\\-\\d+|\\d+[a-zA-Z]+$|\\d+|[A-Za-z][0-9A-Za-z-]*$/.freeze\n\n    attr_reader :version\n    alias to_s version\n\n    def initialize(*args)\n      # If only one string argument is given, assume a complete version string\n      # and attempt to parse it\n      if args.length == 1 && args.first.is_a?(String)\n        @version = args.first.to_s.strip\n      else\n        @segments = args.compact.map(&:to_s).map(&:strip)\n        @version = segments.join('.')\n      end\n    end\n\n    def major\n      segments[0]\n    end\n\n    def minor\n      segments[1]\n    end\n\n    def patch\n      segments[2]\n    end\n\n    def patch_minor\n      segments[3]\n    end\n\n    def inspect\n      \"#<#{self.class} #{self}>\"\n    end\n\n    def eql?(other)\n      self.class.eql?(other.class) &&\n        version == other.version\n    end\n\n    def <=>(other)\n      Gem::Version.new(version).<=>(Gem::Version.new(other.to_s))\n    end\n\n    def segments\n      @segments ||= version.scan(SEGMENTS_REGEX)\n    end\n\n    def to_h\n      {\n        version: version,\n        major: major,\n        minor: minor,\n        patch: patch,\n        patch_minor: patch_minor\n      }\n    end\n  end\nend\n"
  },
  {
    "path": "lib/user_agent_parser.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'user_agent_parser/parser'\nrequire 'user_agent_parser/user_agent'\nrequire 'user_agent_parser/version'\nrequire 'user_agent_parser/operating_system'\nrequire 'user_agent_parser/device'\n\nmodule UserAgentParser\n  DefaultPatternsPath = File.join(File.dirname(__FILE__), '../vendor/uap-core/regexes.yaml')\n\n  # Parse the given +user_agent_string+, returning a +UserAgent+\n  def self.parse(user_agent_string, **args)\n    Parser.new(**args).parse(user_agent_string)\n  end\nend\n"
  },
  {
    "path": "spec/cli_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire File.expand_path(File.dirname(__FILE__) + '/spec_helper')\nrequire 'user_agent_parser/cli'\n\ndescribe UserAgentParser::Cli do\n  let(:cli) { UserAgentParser::Cli.new(user_agent, options) }\n  let(:options) { {} }\n  let(:parser) { UserAgentParser::Parser.new }\n  let(:user_agent) do\n    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')\n  end\n\n  it 'prints family and version when no options' do\n    _(cli.run!).must_equal('Mobile Safari 6.0')\n  end\n\n  describe 'invalid version' do\n    let(:user_agent) do\n      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')\n    end\n\n    describe '--version' do\n      let(:options) { { version: true } }\n\n      it 'returns nil' do\n        _(cli.run!).must_be_nil\n      end\n    end\n\n    describe '--major' do\n      let(:options) { { major: true } }\n\n      it 'returns nil' do\n        _(cli.run!).must_be_nil\n      end\n    end\n\n    describe '--minor' do\n      let(:options) { { minor: true } }\n\n      it 'returns nil' do\n        _(cli.run!).must_be_nil\n      end\n    end\n\n    describe '--format' do\n      let(:options) { { format: '%n|%f|%v|%M|%m|%o' } }\n\n      it 'returns string without versions' do\n        _(cli.run!).must_equal('Mobile Safari|Mobile Safari||||iOS')\n      end\n    end\n  end\n\n  describe '--name' do\n    let(:options) { { name: true } }\n\n    it 'returns name only' do\n      _(cli.run!).must_equal('Mobile Safari')\n    end\n  end\n\n  describe '--family' do\n    let(:options) { { family: true } }\n\n    it 'returns family only' do\n      _(cli.run!).must_equal('Mobile Safari')\n    end\n  end\n\n  describe '--version' do\n    let(:options) { { version: true } }\n\n    it 'returns version only' do\n      _(cli.run!).must_equal('6.0')\n    end\n  end\n\n  describe '--major' do\n    let(:options) { { major: true } }\n\n    it 'returns major version only' do\n      _(cli.run!).must_equal('6')\n    end\n  end\n\n  describe '--minor' do\n    let(:options) { { minor: true } }\n\n    it 'returns minor version only' do\n      _(cli.run!).must_equal('0')\n    end\n  end\n\n  describe '--os' do\n    let(:options) { { os: true } }\n\n    it 'returns operating system only' do\n      _(cli.run!).must_equal('iOS 6.0.1')\n    end\n  end\n\n  describe '--format' do\n    let(:options) { { format: '%n|%v|%M|%m|%o' } }\n\n    it 'return string with correct replacements' do\n      _(cli.run!).must_equal('Mobile Safari|6.0|6|0|iOS 6.0.1')\n    end\n  end\nend\n"
  },
  {
    "path": "spec/custom_regexes.yaml",
    "content": "user_agent_parsers:\n  - regex: 'Any.*'\n    family_replacement: 'Custom browser'\n    v1_replacement: '1'\n    v2_replacement: '2'\n    v3_replacement: '3'\n    v4_replacement: '4'\n\nos_parsers:\n  - regex: 'Any.*'\n    os_replacement: 'Custom OS'\n    os_v1_replacement: '1'\n    os_v2_replacement: '2'\n\ndevice_parsers:\n  - regex: 'Any.*'\n    device_replacement: 'Custom device'\n"
  },
  {
    "path": "spec/device_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire File.expand_path(File.dirname(__FILE__) + '/spec_helper')\n\ndescribe UserAgentParser::Device do\n  describe '#name' do\n    it 'returns family' do\n      os = UserAgentParser::Device.new('iPod')\n      _(os.name).must_equal os.family\n    end\n  end\n\n  describe '#to_s' do\n    it 'returns a string of just the family' do\n      os = UserAgentParser::Device.new('iPod')\n      _(os.to_s).must_equal 'iPod'\n    end\n  end\n\n  describe '#==' do\n    it 'returns true for same family' do\n      device1 = UserAgentParser::Device.new('iPod')\n      device2 = UserAgentParser::Device.new('iPod')\n      _(device1).must_equal device2\n    end\n\n    it 'returns false different family' do\n      device1 = UserAgentParser::Device.new('iPod')\n      device2 = UserAgentParser::Device.new('iPad')\n      _(device1).wont_equal device2\n    end\n  end\n\n  describe '#eql?' do\n    it 'returns true for same family' do\n      device1 = UserAgentParser::Device.new('iPod')\n      device2 = UserAgentParser::Device.new('iPod')\n      assert_equal true, device1.eql?(device2)\n    end\n\n    it 'returns false different family' do\n      device1 = UserAgentParser::Device.new('iPod')\n      device2 = UserAgentParser::Device.new('iPad')\n      assert_equal false, device1.eql?(device2)\n    end\n  end\n\n  describe '#inspect' do\n    it 'returns class family and instance to_s' do\n      device = UserAgentParser::Device.new('iPod')\n      _(device.inspect.to_s).must_equal '#<UserAgentParser::Device iPod>'\n    end\n  end\nend\n"
  },
  {
    "path": "spec/operating_system_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire File.expand_path(File.dirname(__FILE__) + '/spec_helper')\n\ndescribe UserAgentParser::OperatingSystem do\n  describe '#name' do\n    it 'returns family' do\n      os = UserAgentParser::OperatingSystem.new('Windows')\n      _(os.name).must_equal os.family\n    end\n  end\n\n  describe '#to_s' do\n    it 'returns a string of just the family' do\n      os = UserAgentParser::OperatingSystem.new('Windows')\n      _(os.to_s).must_equal 'Windows'\n    end\n\n    it 'returns a string of family and version' do\n      version = UserAgentParser::Version.new('7')\n      os = UserAgentParser::OperatingSystem.new('Windows', version)\n      _(os.to_s).must_equal 'Windows 7'\n    end\n  end\n\n  describe '#==' do\n    it \"returns true for same user agents across different O/S's\" do\n      version = UserAgentParser::Version.new('7')\n      os1 = UserAgentParser::OperatingSystem.new('Windows', version)\n      os2 = UserAgentParser::OperatingSystem.new('Windows', version)\n      _(os1).must_equal os2\n    end\n\n    it 'returns false for same family, different versions' do\n      seven = UserAgentParser::Version.new('7')\n      eight = UserAgentParser::Version.new('8')\n      os1 = UserAgentParser::OperatingSystem.new('Windows', seven)\n      os2 = UserAgentParser::OperatingSystem.new('Windows', eight)\n      _(os1).wont_equal os2\n    end\n\n    it 'returns false for different family, same version' do\n      version = UserAgentParser::Version.new('7')\n      os1 = UserAgentParser::OperatingSystem.new('Windows', version)\n      os2 = UserAgentParser::OperatingSystem.new('Blah', version)\n      _(os1).wont_equal os2\n    end\n  end\n\n  describe '#eql?' do\n    it \"returns true for same user agents across different O/S's\" do\n      version = UserAgentParser::Version.new('7')\n      os1 = UserAgentParser::OperatingSystem.new('Windows', version)\n      os2 = UserAgentParser::OperatingSystem.new('Windows', version)\n      assert_equal true, os1.eql?(os2)\n    end\n\n    it 'returns false for same family, different versions' do\n      seven = UserAgentParser::Version.new('7')\n      eight = UserAgentParser::Version.new('8')\n      os1 = UserAgentParser::OperatingSystem.new('Windows', seven)\n      os2 = UserAgentParser::OperatingSystem.new('Windows', eight)\n      assert_equal false, os1.eql?(os2)\n    end\n\n    it 'returns false for different family, same version' do\n      version = UserAgentParser::Version.new('7')\n      os1 = UserAgentParser::OperatingSystem.new('Windows', version)\n      os2 = UserAgentParser::OperatingSystem.new('Blah', version)\n      assert_equal false, os1.eql?(os2)\n    end\n  end\n\n  describe '#inspect' do\n    it 'returns class family and instance to_s' do\n      version = UserAgentParser::Version.new('10.7.4')\n      os = UserAgentParser::OperatingSystem.new('OS X', version)\n      _(os.inspect.to_s).must_equal '#<UserAgentParser::OperatingSystem OS X 10.7.4>'\n    end\n  end\nend\n"
  },
  {
    "path": "spec/other_regexes.yaml",
    "content": "user_agent_parsers:\n  - regex: 'Other.*'\n    family_replacement: 'Other browser'\n    v1_replacement: '1'\n    v2_replacement: '2'\n    v3_replacement: '3'\n    v4_replacement: '4'\n\nos_parsers:\n  - regex: 'Other.*'\n    os_replacement: 'Other OS'\n    os_v1_replacement: '1'\n    os_v2_replacement: '2'\n\ndevice_parsers:\n  - regex: 'Other.*'\n    device_replacement: 'Other device'\n"
  },
  {
    "path": "spec/parser_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire File.expand_path(File.dirname(__FILE__) + '/spec_helper')\nrequire 'yaml'\n\ndescribe UserAgentParser::Parser do\n  PARSER = UserAgentParser::Parser.new\n\n  # Some Ruby versions (JRuby) need sanitised test names, as some chars screw\n  # up the test method definitions\n  def self.test_case_to_test_name(test_case)\n    name = \"#{test_case['user_agent_string']}_#{test_case['family']}\"\n    name.gsub(/[^a-z0-9_.-]/i, '_').squeeze('_')\n  end\n\n  def self.file_to_test_cases(file)\n    file_to_yaml(file)['test_cases'].map do |test_case|\n      {\n        'user_agent_string' => test_case['user_agent_string'],\n        'family' => test_case['family'],\n        'major' => test_case['major'],\n        'minor' => test_case['minor'],\n        'patch' => test_case['patch'],\n        'patch_minor' => test_case['patch_minor'],\n        'brand' => test_case['brand'],\n        'model' => test_case['model']\n      }\n    end.reject do |test_case|\n      # We don't do the hacky javascript user agent overrides\n      test_case.key?('js_ua') ||\n        test_case['family'] == 'IE Platform Preview' ||\n        test_case['user_agent_string'].include?('chromeframe;')\n    end\n  end\n\n  def self.file_to_yaml(resource)\n    uap_path = File.expand_path('../../vendor/uap-core', __FILE__)\n    resource_path = File.join(uap_path, resource)\n    YAML.load_file(resource_path)\n  end\n\n  def self.user_agent_test_cases\n    file_to_test_cases('test_resources/firefox_user_agent_strings.yaml')\n    file_to_test_cases('tests/test_ua.yaml')\n  end\n\n  def self.operating_system_test_cases\n    file_to_test_cases('tests/test_os.yaml') +\n      file_to_test_cases('test_resources/additional_os_tests.yaml')\n  end\n\n  def self.device_test_cases\n    file_to_test_cases('tests/test_device.yaml')\n  end\n\n  def custom_patterns_path\n    File.join(File.dirname(__FILE__), 'custom_regexes.yaml')\n  end\n\n  def other_patterns_path\n    File.join(File.dirname(__FILE__), 'other_regexes.yaml')\n  end\n\n  describe '::parse' do\n    it 'parses a UA' do\n      ua = UserAgentParser.parse('Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/418.8 (KHTML, like Gecko) Safari/419.3')\n      _(ua.family).must_equal('Safari')\n    end\n    it 'accepts a custom patterns path' do\n      ua = UserAgentParser.parse('Any user agent string', patterns_path: custom_patterns_path)\n      _(ua.family).must_equal('Custom browser')\n    end\n  end\n\n  describe '#initialize with a custom patterns path' do\n    it 'accepts a single patterns_path string' do\n      parser = UserAgentParser::Parser.new(patterns_path: custom_patterns_path)\n      ua = parser.parse('Any user agent string')\n\n      _(parser.patterns_paths).must_equal([custom_patterns_path])\n      _(parser.patterns_path).must_equal(custom_patterns_path)\n\n      _(ua.family).must_equal('Custom browser')\n      _(ua.version.major).must_equal('1')\n      _(ua.version.minor).must_equal('2')\n      _(ua.version.patch).must_equal('3')\n      _(ua.version.patch_minor).must_equal('4')\n\n      _(ua.os.family).must_equal('Custom OS')\n      _(ua.os.version.major).must_equal('1')\n      _(ua.os.version.minor).must_equal('2')\n\n      _(ua.device.family).must_equal('Custom device')\n    end\n\n    it 'accepts patterns_paths array' do\n      patterns_paths = [custom_patterns_path, other_patterns_path]\n      parser = UserAgentParser::Parser.new(patterns_paths: patterns_paths)\n\n      _(parser.patterns_paths).must_equal(patterns_paths)\n      _(parser.patterns_path).must_equal(custom_patterns_path)\n\n      ua = parser.parse('Any user agent string')\n      oua = parser.parse('Other user agent string')\n\n      _(ua.family).must_equal('Custom browser')\n      _(oua.family).must_equal('Other browser')\n    end\n  end\n\n  describe '#parse' do\n    user_agent_test_cases.each do |test_case|\n      it \"parses UA for #{test_case_to_test_name(test_case)}\" do\n        user_agent = PARSER.parse(test_case['user_agent_string'])\n\n        if test_case['family']\n          _(user_agent.family).must_equal_test_case_property(test_case, 'family')\n        end\n\n        if test_case['major']\n          _(user_agent.version.major).must_equal_test_case_property(test_case, 'major')\n        end\n\n        if test_case['minor']\n          _(user_agent.version.minor).must_equal_test_case_property(test_case, 'minor')\n        end\n\n        if test_case['patch']\n          _(user_agent.version.patch).must_equal_test_case_property(test_case, 'patch')\n        end\n      end\n    end\n\n    operating_system_test_cases.each do |test_case|\n      it \"parses OS for #{test_case_to_test_name(test_case)}\" do\n        user_agent = PARSER.parse(test_case['user_agent_string'])\n        operating_system = user_agent.os\n\n        if test_case['family']\n          _(operating_system.family).must_equal_test_case_property(test_case, 'family')\n        end\n\n        if test_case['major']\n          _(operating_system.version.major).must_equal_test_case_property(test_case, 'major')\n        end\n\n        if test_case['minor']\n          _(operating_system.version.minor).must_equal_test_case_property(test_case, 'minor')\n        end\n\n        if test_case['patch']\n          _(operating_system.version.patch).must_equal_test_case_property(test_case, 'patch')\n        end\n\n        if test_case['patch_minor']\n          _(operating_system.version.patch_minor).must_equal_test_case_property(test_case, 'patch_minor')\n        end\n      end\n    end\n\n    device_test_cases.each do |test_case|\n      it \"parses device for #{test_case_to_test_name(test_case)}\" do\n        user_agent = PARSER.parse(test_case['user_agent_string'])\n        device = user_agent.device\n\n        if test_case['family']\n          _(device.family).must_equal_test_case_property(test_case, 'family')\n        end\n\n        if test_case['model']\n          _(device.model).must_equal_test_case_property(test_case, 'model')\n        end\n\n        if test_case['brand']\n          _(device.brand).must_equal_test_case_property(test_case, 'brand')\n        end\n      end\n    end\n  end\n\n  describe '#parse_os' do\n    operating_system_test_cases.each do |test_case|\n      it \"parses OS for #{test_case_to_test_name(test_case)}\" do\n        operating_system = PARSER.parse_os(test_case['user_agent_string'])\n\n        if test_case['family']\n          _(operating_system.family).must_equal_test_case_property(test_case, 'family')\n        end\n\n        if test_case['major']\n          _(operating_system.version.major).must_equal_test_case_property(test_case, 'major')\n        end\n\n        if test_case['minor']\n          _(operating_system.version.minor).must_equal_test_case_property(test_case, 'minor')\n        end\n\n        if test_case['patch']\n          _(operating_system.version.patch).must_equal_test_case_property(test_case, 'patch')\n        end\n\n        if test_case['patch_minor']\n          _(operating_system.version.patch_minor).must_equal_test_case_property(test_case, 'patch_minor')\n        end\n      end\n    end\n  end\n\n  describe '#parse_device' do\n    device_test_cases.each do |test_case|\n      it \"parses device for #{test_case_to_test_name(test_case)}\" do\n        device = PARSER.parse_device(test_case['user_agent_string'])\n\n        if test_case['family']\n          _(device.family).must_equal_test_case_property(test_case, 'family')\n        end\n\n        if test_case['model']\n          _(device.model).must_equal_test_case_property(test_case, 'model')\n        end\n\n        if test_case['brand']\n          _(device.brand).must_equal_test_case_property(test_case, 'brand')\n        end\n      end\n    end\n  end\n\n  describe '#parse_ua' do\n    user_agent_test_cases.each do |test_case|\n      it \"parses UA for #{test_case_to_test_name(test_case)}\" do\n        user_agent = PARSER.parse_ua(test_case['user_agent_string'])\n\n        assert_nil user_agent.os\n        assert_nil user_agent.device\n\n        if test_case['family']\n          _(user_agent.family).must_equal_test_case_property(test_case, 'family')\n        end\n\n        if test_case['major']\n          _(user_agent.version.major).must_equal_test_case_property(test_case, 'major')\n        end\n\n        if test_case['minor']\n          _(user_agent.version.minor).must_equal_test_case_property(test_case, 'minor')\n        end\n\n        if test_case['patch']\n          _(user_agent.version.patch).must_equal_test_case_property(test_case, 'patch')\n        end\n      end\n    end\n  end\n\nend\n"
  },
  {
    "path": "spec/spec_helper.rb",
    "content": "# frozen_string_literal: true\n\nif ENV['SIMPLECOV'] == '1'\n  require 'coveralls'\n  require 'simplecov'\n\n  SimpleCov.formatter = Coveralls::SimpleCov::Formatter\n  SimpleCov.start do\n    add_filter '/.bundle/'\n    add_filter '/doc/'\n    add_filter '/spec/'\n    add_filter '/config/'\n    merge_timeout 600\n  end\nend\n\nrequire 'minitest/autorun'\n\n$:.unshift File.expand_path('../../lib', __FILE__)\nrequire 'user_agent_parser'\n\nmodule Minitest\n  module Assertions\n    # Asserts the test case property is equal to the expected value. On failure\n    # the message includes the property and user_agent_string from the test\n    # case for easier debugging\n    def assert_test_case_property_equal(test_case, actual, test_case_property)\n      assert_equal test_case[test_case_property],\n                   actual,\n                   \"#{test_case_property} failed for user agent: #{test_case['user_agent_string']}\"\n    end\n\n    Object.infect_an_assertion :assert_test_case_property_equal, :must_equal_test_case_property\n  end\nend\n"
  },
  {
    "path": "spec/user_agent_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire File.expand_path(File.dirname(__FILE__) + '/spec_helper')\n\ndescribe UserAgentParser::UserAgent do\n  describe '#to_s' do\n    it 'returns a string of just the family' do\n      _(UserAgentParser::UserAgent.new('Chrome').to_s).must_equal 'Chrome'\n    end\n\n    it 'returns a string of family and version' do\n      version = UserAgentParser::Version.new('1.2.3pre')\n      agent = UserAgentParser::UserAgent.new('Chrome', version)\n      _(agent.to_s).must_equal 'Chrome 1.2.3pre'\n    end\n  end\n\n  describe '#initialize' do\n    describe 'with family' do\n      it 'sets family' do\n        agent = UserAgentParser::UserAgent.new('Chromium')\n        _(agent.family).must_equal 'Chromium'\n      end\n    end\n\n    describe 'with no family' do\n      it 'sets family to Other' do\n        agent = UserAgentParser::UserAgent.new\n        _(agent.family).must_equal 'Other'\n      end\n    end\n\n    describe 'with version' do\n      it 'sets version' do\n        version = UserAgentParser::Version.new('1.2.3')\n        agent = UserAgentParser::UserAgent.new(nil, version)\n        _(agent.version).must_equal version\n      end\n    end\n\n    describe 'with os' do\n      it 'sets os' do\n        os = UserAgentParser::OperatingSystem.new('Windows XP')\n        agent = UserAgentParser::UserAgent.new(nil, nil, os)\n        _(agent.os).must_equal os\n      end\n    end\n\n    describe 'with device' do\n      it 'sets device' do\n        device = UserAgentParser::Device.new('iPhone')\n        agent = UserAgentParser::UserAgent.new(nil, nil, nil, device)\n        _(agent.device).must_equal device\n      end\n    end\n  end\n\n  describe '#name' do\n    it 'returns family' do\n      agent = UserAgentParser::UserAgent.new('Safari')\n      _(agent.name).must_equal agent.family\n    end\n  end\n\n  describe '#==' do\n    it 'returns true for same agents with no OS' do\n      version = UserAgentParser::Version.new('1.0')\n      agent1 = UserAgentParser::UserAgent.new('Chrome', version)\n      agent2 = UserAgentParser::UserAgent.new('Chrome', version)\n      _(agent1).must_equal agent2\n    end\n\n    it 'returns true for same agents on same OS' do\n      version = UserAgentParser::Version.new('1.0')\n      os = UserAgentParser::OperatingSystem.new('Windows')\n      agent1 = UserAgentParser::UserAgent.new('Chrome', version, os)\n      agent2 = UserAgentParser::UserAgent.new('Chrome', version, os)\n      _(agent1).must_equal agent2\n    end\n\n    it 'returns false for same agent on different OS' do\n      version = UserAgentParser::Version.new('1.0')\n      windows = UserAgentParser::OperatingSystem.new('Windows')\n      mac = UserAgentParser::OperatingSystem.new('Mac')\n      agent1 = UserAgentParser::UserAgent.new('Chrome', version, windows)\n      agent2 = UserAgentParser::UserAgent.new('Chrome', version, mac)\n      _(agent1).wont_equal agent2\n    end\n\n    it 'returns false for same os, but different browser version' do\n      browser_version1 = UserAgentParser::Version.new('1.0')\n      browser_version2 = UserAgentParser::Version.new('2.0')\n      os = UserAgentParser::OperatingSystem.new('Windows')\n      agent1 = UserAgentParser::UserAgent.new('Chrome', browser_version1, os)\n      agent2 = UserAgentParser::UserAgent.new('Chrome', browser_version2, os)\n      _(agent1).wont_equal agent2\n    end\n  end\n\n  describe '#eql?' do\n    it 'returns true for same agents with no OS' do\n      version = UserAgentParser::Version.new('1.0')\n      agent1 = UserAgentParser::UserAgent.new('Chrome', version)\n      agent2 = UserAgentParser::UserAgent.new('Chrome', version)\n      assert_equal true, agent1.eql?(agent2)\n    end\n\n    it 'returns true for same agents on same OS' do\n      version = UserAgentParser::Version.new('1.0')\n      os = UserAgentParser::OperatingSystem.new('Windows')\n      agent1 = UserAgentParser::UserAgent.new('Chrome', version, os)\n      agent2 = UserAgentParser::UserAgent.new('Chrome', version, os)\n      assert_equal true, agent1.eql?(agent2)\n    end\n\n    it 'returns false for same agent on different OS' do\n      version = UserAgentParser::Version.new('1.0')\n      windows = UserAgentParser::OperatingSystem.new('Windows')\n      mac = UserAgentParser::OperatingSystem.new('Mac')\n      agent1 = UserAgentParser::UserAgent.new('Chrome', version, windows)\n      agent2 = UserAgentParser::UserAgent.new('Chrome', version, mac)\n      assert_equal false, agent1.eql?(agent2)\n    end\n\n    it 'returns false for same os, but different browser version' do\n      browser_version1 = UserAgentParser::Version.new('1.0')\n      browser_version2 = UserAgentParser::Version.new('2.0')\n      os = UserAgentParser::OperatingSystem.new('Windows')\n      agent1 = UserAgentParser::UserAgent.new('Chrome', browser_version1, os)\n      agent2 = UserAgentParser::UserAgent.new('Chrome', browser_version2, os)\n      assert_equal false, agent1.eql?(agent2)\n    end\n  end\n\n  describe '#inspect' do\n    it 'returns the family and version' do\n      browser_version = UserAgentParser::Version.new('1.0')\n      agent = UserAgentParser::UserAgent.new('Chrome', browser_version)\n      _(agent.inspect.to_s).must_equal '#<UserAgentParser::UserAgent Chrome 1.0>'\n    end\n\n    it 'returns the OS if present' do\n      browser_version = UserAgentParser::Version.new('1.0')\n      os_version = UserAgentParser::Version.new('10.7.4')\n      os = UserAgentParser::OperatingSystem.new('OS X', os_version)\n      agent = UserAgentParser::UserAgent.new('Chrome', browser_version, os)\n      _(agent.inspect).must_equal '#<UserAgentParser::UserAgent Chrome 1.0 (OS X 10.7.4)>'\n    end\n\n    it 'returns device if present' do\n      browser_version = UserAgentParser::Version.new('5.0.2')\n      os_version = UserAgentParser::Version.new('4.2.1')\n      os = UserAgentParser::OperatingSystem.new('iOS', os_version)\n      device = UserAgentParser::Device.new('iPhone')\n      agent = UserAgentParser::UserAgent.new('Mobile Safari', browser_version, os, device)\n      _(agent.inspect).must_equal '#<UserAgentParser::UserAgent Mobile Safari 5.0.2 (iOS 4.2.1) (iPhone)>'\n    end\n  end\n\n  describe '#to_h' do\n    let(:expected) do\n      {\n        device: { family: 'iPhone', model: 'iPhone', brand: nil },\n        family: 'Mobile Safari',\n        os: {\n          version: { version: '4.2.1', major: '4', minor: '2', patch: '1', patch_minor: nil},\n          family: 'iOS'\n        },\n        version: { version: '5.0.2', major: '5', minor: '0', patch: '2', patch_minor: nil }\n      }\n    end\n\n    it 'returns everything' do\n      browser_version = UserAgentParser::Version.new('5.0.2')\n      os_version = UserAgentParser::Version.new('4.2.1')\n      os = UserAgentParser::OperatingSystem.new('iOS', os_version)\n      device = UserAgentParser::Device.new('iPhone')\n      agent = UserAgentParser::UserAgent.new('Mobile Safari', browser_version, os, device)\n      assert_equal(expected, agent.to_h)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/version_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire File.expand_path(File.dirname(__FILE__) + '/spec_helper')\n\ndescribe UserAgentParser::Version do\n  it \"parses '1'\" do\n    version = UserAgentParser::Version.new('1')\n    _(version.major).must_equal '1'\n  end\n\n  it \"parses '1.2'\" do\n    version = UserAgentParser::Version.new('1.2')\n    _(version.major).must_equal '1'\n    _(version.minor).must_equal '2'\n  end\n\n  it \"parses '1.2.3'\" do\n    version = UserAgentParser::Version.new('1.2.3')\n    _(version.major).must_equal '1'\n    _(version.minor).must_equal '2'\n    _(version.patch).must_equal '3'\n  end\n\n  it \"parses '1.2.3b4'\" do\n    version = UserAgentParser::Version.new('1.2.3b4')\n    _(version.major).must_equal '1'\n    _(version.minor).must_equal '2'\n    _(version.patch).must_equal '3'\n    _(version.patch_minor).must_equal 'b4'\n  end\n\n  it \"parses '1.2.3-b4'\" do\n    version = UserAgentParser::Version.new('1.2.3-b4')\n    _(version.major).must_equal '1'\n    _(version.minor).must_equal '2'\n    _(version.patch).must_equal '3'\n    _(version.patch_minor).must_equal 'b4'\n  end\n\n  it \"parses '1.2.3pre'\" do\n    version = UserAgentParser::Version.new('1.2.3pre')\n    _(version.major).must_equal '1'\n    _(version.minor).must_equal '2'\n    _(version.patch).must_equal '3pre'\n  end\n\n  it \"parses '1.2.3-45'\" do\n    version = UserAgentParser::Version.new('1.2.3-45')\n    _(version.major).must_equal '1'\n    _(version.minor).must_equal '2'\n    _(version.patch).must_equal '3-45'\n  end\n\n  it 'accepts Fixnum and String arguments' do\n    version = UserAgentParser::Version.new(1, '2a', 3, '4b')\n    _(version.major).must_equal '1'\n    _(version.minor).must_equal '2a'\n    _(version.patch).must_equal '3'\n    _(version.patch_minor).must_equal '4b'\n  end\n\n  describe '#to_s' do\n    it 'returns the same string as initialized with' do\n      version = UserAgentParser::Version.new('1.2.3b4')\n      _(version.to_s).must_equal '1.2.3b4'\n    end\n  end\n\n  describe '#==' do\n    it 'returns true for same versions' do\n      version = UserAgentParser::Version.new('1.2.3')\n      _(version).must_equal UserAgentParser::Version.new('1.2.3')\n    end\n\n    it 'returns false for different versions' do\n      version = UserAgentParser::Version.new('1.2.3')\n      _(version).wont_equal UserAgentParser::Version.new('1.2.2')\n    end\n  end\n\n  describe '#<=>' do\n    it 'accepts string for comparison' do\n      version = UserAgentParser::Version.new('1.2.3')\n\n      assert_operator version, :<, '1.2.4'\n      assert_operator version, :==, '1.2.3'\n      assert_operator version, :>, '1.2.2'\n    end\n\n    it 'accepts another instance of Version for comparison' do\n      version = UserAgentParser::Version.new('1.2.3')\n\n      assert_operator version, :>, UserAgentParser::Version.new('1.2.2')\n      assert_operator version, :==, UserAgentParser::Version.new('1.2.3')\n      assert_operator version, :<, UserAgentParser::Version.new('1.2.4')\n    end\n\n    it 'is comparing major version' do\n      version = UserAgentParser::Version.new('1.2.3')\n\n      assert_operator version, :<, '2'\n      assert_operator version, :>=, '1'\n      assert_operator version, :>, '0'\n    end\n\n    it 'is comparing minor version' do\n      version = UserAgentParser::Version.new('1.2.3')\n\n      assert_operator version, :<, '2.0'\n      assert_operator version, :<, '1.3'\n      assert_operator version, :>=, '1.2'\n      assert_operator version, :>, '1.1'\n      assert_operator version, :>, '0.1'\n    end\n\n    it 'is comparing patch level' do\n      version = UserAgentParser::Version.new('1.2.3')\n\n      assert_operator version, :<, '1.2.4'\n      assert_operator version, :>=, '1.2.3'\n      assert_operator version, :<=, '1.2.3'\n      assert_operator version, :>, '1.2.2'\n    end\n\n    it 'is comparing patch_minor level correctly' do\n      version = UserAgentParser::Version.new('1.2.3.p1')\n\n      assert_operator version, :<, '1.2.4'\n      assert_operator version, :<, '1.2.3.p2'\n      assert_operator version, :>=, '1.2.3.p1'\n      assert_operator version, :<=, '1.2.3.p1'\n      assert_operator version, :>, '1.2.3.p0'\n      assert_operator version, :>, '1.2.2'\n      assert_operator version, :>, '1.1'\n    end\n\n    it 'is correctly comparing versions with different lengths' do\n      version = UserAgentParser::Version.new('1.42.3')\n\n      assert_operator version, :<, '1.142'\n      assert_operator version, :<, '1.42.4'\n      assert_operator version, :>=, '1.42'\n      assert_operator version, :>, '1.14'\n      assert_operator version, :>, '1.7'\n      assert_operator version, :>, '1.3'\n    end\n\n    it 'does its best to compare string versions' do\n      version = UserAgentParser::Version.new('1.2.3.a')\n\n      assert_operator version, :<, '1.2.4'\n      assert_operator version, :<, '1.2.3.b'\n      assert_operator version, :<, '1.2.3.p1'\n      assert_operator version, :<, '1.2.3.p0'\n      assert_operator version, :>, '1.2.2'\n    end\n  end\n\n  describe '#inspect' do\n    it 'returns the class and version' do\n      version = UserAgentParser::Version.new('1.2.3')\n      _(version.inspect).must_equal '#<UserAgentParser::Version 1.2.3>'\n    end\n  end\nend\n"
  },
  {
    "path": "user_agent_parser.gemspec",
    "content": "# frozen_string_literal: true\n\nGem::Specification.new do |gem|\n  gem.name    = 'user_agent_parser'\n  gem.version = '2.21.0'\n\n  gem.authors     = 'Tim Lucas'\n  gem.email       = 't@toolmantim.com'\n  gem.homepage    = 'https://github.com/ua-parser/uap-ruby'\n  gem.summary     = \"Parsing user agent strings with the help of BrowserScope's UA database\"\n  gem.description = <<~DESCRIPTION\n    A simple, comprehensive Ruby gem for parsing user agent strings\n    with the help of BrowserScope's UserAgent database\n  DESCRIPTION\n  gem.license     = 'MIT'\n  gem.executables = ['user_agent_parser']\n\n  gem.files = %x{git ls-files}.split(\"\\n\").select { |d| d =~ %r{^(MIT-LICENSE|Readme.md|lib|bin/)} } + ['vendor/uap-core/regexes.yaml']\n\n  gem.required_ruby_version = '>= 2.5'\nend\n"
  }
]