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 [](https://github.com/ua-parser/uap-ruby/actions/workflows/ci.yml) [](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
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
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 [](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.