fast_browser 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/.rspec +1 -0
- data/.travis.yml +29 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +37 -0
- data/Rakefile +1 -0
- data/ext/extconf.rb +39 -0
- data/fast_browser.gemspec +26 -0
- data/lib/fast_browser/library_extensions.rb +15 -0
- data/lib/fast_browser/version.rb +3 -0
- data/lib/fast_browser.rb +69 -0
- data/rust/Cargo.toml +13 -0
- data/rust/build-debug.sh +17 -0
- data/rust/src/bot.rs +66 -0
- data/rust/src/browser.rs +181 -0
- data/rust/src/lib.rs +109 -0
- data/rust/src/user_agent.rs +32 -0
- data/rust/src/util.rs +10 -0
- data/spec/name_spec.rb +45 -0
- data/spec/safety_spec.rb +25 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/version_spec.rb +17 -0
- data/test/benchmark/compare.rb +72 -0
- data/test/test_helper.rb +2 -0
- metadata +125 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA1:
         | 
| 3 | 
            +
              metadata.gz: 5cf1f07c8cf5352d87676a1bf461dc53f7881120
         | 
| 4 | 
            +
              data.tar.gz: 9d3d924ef18bfac7948dd3acc197c56d5813d457
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: 2f8e15193b73056ad463a242d8ff063e01971e2097ea7bcc65d243a585d409654fae0f05f3a22e184699db05808720c4666f50461d33b43acebabd9c2773e6ad
         | 
| 7 | 
            +
              data.tar.gz: 13db7ca269f9f541af9bd18873d37a6627ad42b8b193648a4c7f1e3d2a42d931a250ca1ae4ae2772e8d654f1ea9307028ca572f5c8785145ab389e1db171000c
         | 
    
        data/.gitignore
    ADDED
    
    
    
        data/.rspec
    ADDED
    
    | @@ -0,0 +1 @@ | |
| 1 | 
            +
            --color
         | 
    
        data/.travis.yml
    ADDED
    
    | @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            language: rust
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            rust:
         | 
| 4 | 
            +
              - beta
         | 
| 5 | 
            +
              - nightly
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            matrix:
         | 
| 8 | 
            +
              allow_failures:
         | 
| 9 | 
            +
                - rust: nightly
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            before_install:
         | 
| 12 | 
            +
              - rvm install 2.3.0
         | 
| 13 | 
            +
              - gem install bundler
         | 
| 14 | 
            +
              - bundle install
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            install:
         | 
| 17 | 
            +
              - ruby ext/extconf.rb
         | 
| 18 | 
            +
              - make
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            script:
         | 
| 21 | 
            +
              # Rust tests
         | 
| 22 | 
            +
              - cd $TRAVIS_BUILD_DIR/rust
         | 
| 23 | 
            +
              - cargo test
         | 
| 24 | 
            +
              # Ruby specs
         | 
| 25 | 
            +
              - cd $TRAVIS_BUILD_DIR
         | 
| 26 | 
            +
              - bundle exec rspec
         | 
| 27 | 
            +
              # Check that the gem builds and installs
         | 
| 28 | 
            +
              - bundle exec rake install
         | 
| 29 | 
            +
              - ruby -e "require 'rubygems'; require 'fast_browser'"
         | 
    
        data/Gemfile
    ADDED
    
    
    
        data/Gemfile.lock
    ADDED
    
    | @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            PATH
         | 
| 2 | 
            +
              remote: .
         | 
| 3 | 
            +
              specs:
         | 
| 4 | 
            +
                fast_browser (0.1.0)
         | 
| 5 | 
            +
                  ffi (~> 1.9.10)
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            GEM
         | 
| 8 | 
            +
              remote: https://rubygems.org/
         | 
| 9 | 
            +
              specs:
         | 
| 10 | 
            +
                diff-lcs (1.2.5)
         | 
| 11 | 
            +
                ffi (1.9.10)
         | 
| 12 | 
            +
                rake (10.4.2)
         | 
| 13 | 
            +
                rspec (3.4.0)
         | 
| 14 | 
            +
                  rspec-core (~> 3.4.0)
         | 
| 15 | 
            +
                  rspec-expectations (~> 3.4.0)
         | 
| 16 | 
            +
                  rspec-mocks (~> 3.4.0)
         | 
| 17 | 
            +
                rspec-core (3.4.1)
         | 
| 18 | 
            +
                  rspec-support (~> 3.4.0)
         | 
| 19 | 
            +
                rspec-expectations (3.4.0)
         | 
| 20 | 
            +
                  diff-lcs (>= 1.2.0, < 2.0)
         | 
| 21 | 
            +
                  rspec-support (~> 3.4.0)
         | 
| 22 | 
            +
                rspec-mocks (3.4.1)
         | 
| 23 | 
            +
                  diff-lcs (>= 1.2.0, < 2.0)
         | 
| 24 | 
            +
                  rspec-support (~> 3.4.0)
         | 
| 25 | 
            +
                rspec-support (3.4.1)
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            PLATFORMS
         | 
| 28 | 
            +
              ruby
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            DEPENDENCIES
         | 
| 31 | 
            +
              bundler (~> 1.10)
         | 
| 32 | 
            +
              fast_browser!
         | 
| 33 | 
            +
              rake (~> 10.4.2)
         | 
| 34 | 
            +
              rspec (~> 3.4.0)
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            BUNDLED WITH
         | 
| 37 | 
            +
               1.11.2
         | 
    
        data/Rakefile
    ADDED
    
    | @@ -0,0 +1 @@ | |
| 1 | 
            +
            require 'bundler/gem_tasks'
         | 
    
        data/ext/extconf.rb
    ADDED
    
    | @@ -0,0 +1,39 @@ | |
| 1 | 
            +
            require 'mkmf'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            def sys(cmd, &block)
         | 
| 4 | 
            +
              block = ->(f) { f.gets } if block.nil?
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              ret = IO.popen(cmd, &block)
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              if $?.to_i != 0
         | 
| 9 | 
            +
                puts "=> Failed!"
         | 
| 10 | 
            +
                raise "Command failed: #{cmd}"
         | 
| 11 | 
            +
              else
         | 
| 12 | 
            +
                ret
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
            end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            ROOT      = File.expand_path '..', File.dirname(__FILE__)
         | 
| 17 | 
            +
            RUST_ROOT = File.join ROOT, 'rust'
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            puts ' - Checking Rust compiler'
         | 
| 20 | 
            +
            rustc = sys "cd #{RUST_ROOT}; rustc --version"
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            if !(rustc.include?('rustc 1.6.0') || rustc.include?('rustc 1.7.0'))
         | 
| 23 | 
            +
              puts "=> Bad Rust compiler version: #{rustc}"
         | 
| 24 | 
            +
              puts '   Version 1.6.0 or 1.7.0 is required.'
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              raise "Invalid Rust compiler version"
         | 
| 27 | 
            +
            end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            # Create an empty makefile with an empty `install` task
         | 
| 30 | 
            +
            puts ' - Creating Makefile'
         | 
| 31 | 
            +
            File.open('Makefile', 'w') do |f|
         | 
| 32 | 
            +
              body = [
         | 
| 33 | 
            +
                "install:",
         | 
| 34 | 
            +
                "\tcd #{RUST_ROOT}; cargo build --release",
         | 
| 35 | 
            +
                "\tmkdir -p #{ROOT}/ext/fast_browser",
         | 
| 36 | 
            +
                "\tcp #{RUST_ROOT}/target/release/libfast_browser.* #{ROOT}/ext/fast_browser"
         | 
| 37 | 
            +
              ]
         | 
| 38 | 
            +
              f.puts(body.join("\n") + "\n")
         | 
| 39 | 
            +
            end
         | 
| @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            # -*- encoding: utf-8 -*-
         | 
| 2 | 
            +
            $:.push File.expand_path('../lib', __FILE__)
         | 
| 3 | 
            +
            require 'fast_browser/version'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Gem::Specification.new do |s|
         | 
| 6 | 
            +
              s.name        = 'fast_browser'
         | 
| 7 | 
            +
              s.version     = FastBrowser::VERSION
         | 
| 8 | 
            +
              s.platform    = Gem::Platform::RUBY
         | 
| 9 | 
            +
              s.authors     = ['Dirk Gadsden']
         | 
| 10 | 
            +
              s.email       = ['dirk@esherido.com']
         | 
| 11 | 
            +
              s.homepage    = 'https://github.com/dirk/fast_browser'
         | 
| 12 | 
            +
              s.summary     = 'Blazing-fast, Rust-powered user agent detection library.'
         | 
| 13 | 
            +
              s.description = s.summary
         | 
| 14 | 
            +
              s.license     = ''
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              s.files         = `git ls-files`.split "\n"
         | 
| 17 | 
            +
              s.test_files    = `git ls-files -- {test}/*`.split "\n"
         | 
| 18 | 
            +
              s.require_paths = ['lib']
         | 
| 19 | 
            +
              s.extensions    = ['ext/extconf.rb']
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              s.add_dependency 'ffi', '~> 1.9.10'
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              s.add_development_dependency 'bundler', '~> 1.10'
         | 
| 24 | 
            +
              s.add_development_dependency 'rake', '~> 10.4.2'
         | 
| 25 | 
            +
              s.add_development_dependency 'rspec', '~> 3.4.0'
         | 
| 26 | 
            +
            end
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            class FastBrowser
         | 
| 2 | 
            +
              module LibraryExtensions
         | 
| 3 | 
            +
                def attach_string_returning_function(name, arg_types)
         | 
| 4 | 
            +
                  private_name = "_#{name}".to_sym
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  attach_function private_name, name, arg_types, :strptr
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  class_eval <<-BODY
         | 
| 9 | 
            +
                    def self.#{name}(*args)
         | 
| 10 | 
            +
                      call_and_free_string :#{private_name}, *args
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
                  BODY
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
    
        data/lib/fast_browser.rb
    ADDED
    
    | @@ -0,0 +1,69 @@ | |
| 1 | 
            +
            require 'ffi'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'fast_browser/version'
         | 
| 4 | 
            +
            require 'fast_browser/library_extensions'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            class FastBrowser
         | 
| 7 | 
            +
              module RustLib
         | 
| 8 | 
            +
                extend FFI::Library
         | 
| 9 | 
            +
                extend LibraryExtensions
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                lib_file = "libfast_browser.#{FFI::Platform::LIBSUFFIX}"
         | 
| 12 | 
            +
                ffi_lib File.expand_path("../../ext/fast_browser/#{lib_file}", __FILE__)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                %w(chrome edge firefox opera safari).each do |tester|
         | 
| 15 | 
            +
                  attach_function "is_#{tester}".to_sym, [:pointer], :bool
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                attach_function :get_browser_minor_version, [:pointer], :int8
         | 
| 19 | 
            +
                attach_function :get_browser_major_version, [:pointer], :int8
         | 
| 20 | 
            +
                attach_function :is_mobile, [:pointer], :bool
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                attach_string_returning_function :get_browser_family, [:pointer]
         | 
| 23 | 
            +
                attach_string_returning_function :get_user_agent, [:pointer]
         | 
| 24 | 
            +
                attach_string_returning_function :get_version, []
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                # Private Rust methods; don't call these directly!
         | 
| 27 | 
            +
                attach_function :_parse_user_agent, :parse_user_agent, [:string], :pointer
         | 
| 28 | 
            +
                attach_function :_free_user_agent,  :free_user_agent, [:pointer], :void
         | 
| 29 | 
            +
                attach_function :_free_string,      :free_string, [:pointer], :void
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                # Sends the given method name (`method`) to self, copies the returned
         | 
| 32 | 
            +
                # string into a Ruby string and then calls `.free_string` to deallocate
         | 
| 33 | 
            +
                # the original returned string.
         | 
| 34 | 
            +
                def self.call_and_free_string method, *args
         | 
| 35 | 
            +
                  string, ptr = send method, *args
         | 
| 36 | 
            +
                  _free_string ptr
         | 
| 37 | 
            +
                  string
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def self.parse_user_agent string
         | 
| 41 | 
            +
                  FFI::AutoPointer.new(
         | 
| 42 | 
            +
                    self._parse_user_agent(string),
         | 
| 43 | 
            +
                    self.method(:_free_user_agent)
         | 
| 44 | 
            +
                  )
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              def initialize(string)
         | 
| 49 | 
            +
                @pointer = RustLib.parse_user_agent(string)
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
              def chrome?;  RustLib.is_chrome(@pointer)  end
         | 
| 53 | 
            +
              def edge?;    RustLib.is_edge(@pointer)    end
         | 
| 54 | 
            +
              def firefox?; RustLib.is_firefox(@pointer) end
         | 
| 55 | 
            +
              def opera?;   RustLib.is_opera(@pointer)   end
         | 
| 56 | 
            +
              def safari?;  RustLib.is_safari(@pointer)  end
         | 
| 57 | 
            +
              def mobile?;  RustLib.is_mobile(@pointer)  end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
              def major_version; RustLib.get_browser_major_version(@pointer) end
         | 
| 60 | 
            +
              def minor_version; RustLib.get_browser_minor_version(@pointer) end
         | 
| 61 | 
            +
              def family;        RustLib.get_browser_family(@pointer) end
         | 
| 62 | 
            +
              def user_agent;    RustLib.get_user_agent(@pointer) end
         | 
| 63 | 
            +
            end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
            if FastBrowser::RustLib.get_version != FastBrowser::VERSION
         | 
| 66 | 
            +
              e = FastBrowser::VERSION
         | 
| 67 | 
            +
              g = FastBrowser::RustLib.get_version
         | 
| 68 | 
            +
              raise "Rust library version doesn't match Ruby gem version (expected #{e}, got #{g})"
         | 
| 69 | 
            +
            end
         | 
    
        data/rust/Cargo.toml
    ADDED
    
    
    
        data/rust/build-debug.sh
    ADDED
    
    | @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            #!/bin/sh
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # Call this like `TARGET=release ./build-debug.sh` to do a release build
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            # TODO: Make this set the right extension based on the current platform
         | 
| 6 | 
            +
            NATIVE_EXT=dylib
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            if [ "$TARGET" == "release" ]; then
         | 
| 9 | 
            +
              CARGO_ARGS=--release
         | 
| 10 | 
            +
              TARGET=release
         | 
| 11 | 
            +
            else
         | 
| 12 | 
            +
              CARGO_ARGS=
         | 
| 13 | 
            +
              TARGET=debug
         | 
| 14 | 
            +
            fi
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            set -x
         | 
| 17 | 
            +
            cargo build $CARGO_ARGS && cp target/$TARGET/libfast_browser.$NATIVE_EXT ../ext/fast_browser/libfast_browser.$NATIVE_EXT
         | 
    
        data/rust/src/bot.rs
    ADDED
    
    | @@ -0,0 +1,66 @@ | |
| 1 | 
            +
            use regex::{Regex};
         | 
| 2 | 
            +
            use util::map_first_captures;
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            #[derive(Debug, PartialEq)]
         | 
| 5 | 
            +
            pub enum BotName {
         | 
| 6 | 
            +
                Googlebot,
         | 
| 7 | 
            +
            }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            #[derive(Debug, PartialEq)]
         | 
| 10 | 
            +
            pub struct Bot {
         | 
| 11 | 
            +
                pub name: BotName,
         | 
| 12 | 
            +
            }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            impl Bot {
         | 
| 15 | 
            +
                pub fn new(name: BotName) -> Bot {
         | 
| 16 | 
            +
                    Bot { name: name, }
         | 
| 17 | 
            +
                }
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                pub fn parse(ua: &str) -> Option<Bot> {
         | 
| 20 | 
            +
                    for matcher in MATCH_SEQUENCE.iter() {
         | 
| 21 | 
            +
                        if let Some(bot) = matcher(ua) {
         | 
| 22 | 
            +
                            return Some(bot)
         | 
| 23 | 
            +
                        }
         | 
| 24 | 
            +
                    }
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    None
         | 
| 27 | 
            +
                }
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                fn match_googlebot(ua: &str) -> Option<Bot> {
         | 
| 30 | 
            +
                    let versions = GOOGLEBOT_REGEX
         | 
| 31 | 
            +
                        .captures(ua)
         | 
| 32 | 
            +
                        .map(map_first_captures);
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    if let Some(_) = versions {
         | 
| 35 | 
            +
                        Some(Bot::new(BotName::Googlebot))
         | 
| 36 | 
            +
                    } else {
         | 
| 37 | 
            +
                        None
         | 
| 38 | 
            +
                    }
         | 
| 39 | 
            +
                }
         | 
| 40 | 
            +
            }
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            type MatcherFn = Fn(&str) -> Option<Bot> + Sync;
         | 
| 43 | 
            +
            type Matcher = Box<MatcherFn>;
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            lazy_static! {
         | 
| 46 | 
            +
                static ref GOOGLEBOT_REGEX: Regex = Regex::new(r"Googlebot/(\d+)\.(\d+)").unwrap();
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                static ref MATCH_SEQUENCE: Vec<Matcher> = vec![
         | 
| 49 | 
            +
                    Box::new(Bot::match_googlebot),
         | 
| 50 | 
            +
                ];
         | 
| 51 | 
            +
            }
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            #[cfg(test)]
         | 
| 54 | 
            +
            mod tests {
         | 
| 55 | 
            +
                use super::{Bot, BotName};
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                const GOOGLEBOT: &'static str = "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)";
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                #[test]
         | 
| 60 | 
            +
                fn test_parse_googlebot() {
         | 
| 61 | 
            +
                    assert_eq!(
         | 
| 62 | 
            +
                        Bot::new(BotName::Googlebot),
         | 
| 63 | 
            +
                        Bot::parse(GOOGLEBOT).unwrap()
         | 
| 64 | 
            +
                    )
         | 
| 65 | 
            +
                }
         | 
| 66 | 
            +
            }
         | 
    
        data/rust/src/browser.rs
    ADDED
    
    | @@ -0,0 +1,181 @@ | |
| 1 | 
            +
            use regex::{Regex};
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            use util::map_first_captures;
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            #[derive(Clone, Debug, PartialEq)]
         | 
| 6 | 
            +
            pub enum BrowserFamily {
         | 
| 7 | 
            +
                Chrome,
         | 
| 8 | 
            +
                Edge,
         | 
| 9 | 
            +
                Firefox,
         | 
| 10 | 
            +
                Opera,
         | 
| 11 | 
            +
                Safari,
         | 
| 12 | 
            +
                MobileSafari,
         | 
| 13 | 
            +
            }
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            impl BrowserFamily {
         | 
| 16 | 
            +
                pub fn is_mobile(&self) -> bool {
         | 
| 17 | 
            +
                    match self {
         | 
| 18 | 
            +
                        &BrowserFamily::MobileSafari => true,
         | 
| 19 | 
            +
                        _ => false,
         | 
| 20 | 
            +
                    }
         | 
| 21 | 
            +
                }
         | 
| 22 | 
            +
            }
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            #[derive(Clone, Debug, PartialEq)]
         | 
| 25 | 
            +
            pub struct Browser {
         | 
| 26 | 
            +
                pub family: BrowserFamily,
         | 
| 27 | 
            +
                pub major_version: i8,
         | 
| 28 | 
            +
                pub minor_version: i8,
         | 
| 29 | 
            +
            }
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            type MatcherFn = Fn(&str) -> Option<(i8, i8)> + Sync;
         | 
| 32 | 
            +
            type Matcher = (BrowserFamily, Box<MatcherFn>);
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            lazy_static! {
         | 
| 35 | 
            +
                // NOTE: Order of tests is significant
         | 
| 36 | 
            +
                static ref MATCH_SEQUENCE: Vec<Matcher> = vec![
         | 
| 37 | 
            +
                    (BrowserFamily::Opera,        Box::new(Browser::match_opera)),
         | 
| 38 | 
            +
                    (BrowserFamily::Edge,         Box::new(Browser::match_edge)),
         | 
| 39 | 
            +
                    (BrowserFamily::Chrome,       Box::new(Browser::match_chrome)),
         | 
| 40 | 
            +
                    (BrowserFamily::Firefox,      Box::new(Browser::match_firefox)),
         | 
| 41 | 
            +
                    (BrowserFamily::MobileSafari, Box::new(Browser::match_mobile_safari)),
         | 
| 42 | 
            +
                    (BrowserFamily::Safari,       Box::new(Browser::match_safari)),
         | 
| 43 | 
            +
                ];
         | 
| 44 | 
            +
            }
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            impl Browser {
         | 
| 47 | 
            +
                fn new(family: BrowserFamily, versions: (i8, i8)) -> Browser {
         | 
| 48 | 
            +
                    Browser {
         | 
| 49 | 
            +
                        family:        family,
         | 
| 50 | 
            +
                        major_version: versions.0,
         | 
| 51 | 
            +
                        minor_version: versions.1,
         | 
| 52 | 
            +
                    }
         | 
| 53 | 
            +
                }
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                #[allow(unused_parens)]
         | 
| 56 | 
            +
                pub fn parse(ua: &str) -> Option<Browser> {
         | 
| 57 | 
            +
                    // NOTE: Order of these tests is significant because browser vendors are terrible
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    for tuple in MATCH_SEQUENCE.iter() {
         | 
| 60 | 
            +
                        let &(ref family, ref matcher) = tuple;
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                        if let Some(versions) = matcher(ua) {
         | 
| 63 | 
            +
                            let browser = Browser::new(family.clone(), versions);
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                            return Some(browser)
         | 
| 66 | 
            +
                        }
         | 
| 67 | 
            +
                    }
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                    return None
         | 
| 70 | 
            +
                }
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                /// Take a regex and attempt to match it to the browser. The regex must include two capture
         | 
| 73 | 
            +
                /// groups that capture the version of the matched browser.
         | 
| 74 | 
            +
                fn match_versions(ua: &str, regex: &Regex) -> Option<(i8, i8)> {
         | 
| 75 | 
            +
                    regex
         | 
| 76 | 
            +
                        .captures(ua)
         | 
| 77 | 
            +
                        .map(map_first_captures)
         | 
| 78 | 
            +
                }
         | 
| 79 | 
            +
            }
         | 
| 80 | 
            +
             | 
| 81 | 
            +
            lazy_static! {
         | 
| 82 | 
            +
                static ref CHROME_REGEX: Regex         = Regex::new(r"Chrom(?:ium|e)/(\d+)\.(\d+)").unwrap();
         | 
| 83 | 
            +
                static ref EDGE_REGEX: Regex           = Regex::new(r"Edge/(\d+)\.(\d+)").unwrap();
         | 
| 84 | 
            +
                static ref FIREFOX_REGEX: Regex        = Regex::new(r"Firefox/(\d+)\.(\d+)").unwrap();
         | 
| 85 | 
            +
                static ref OPERA_VERSION_REGEX: Regex  = Regex::new(r"Version/(\d+)\.(\d+)").unwrap();
         | 
| 86 | 
            +
                static ref SAFARI_VERSION_REGEX: Regex = Regex::new(r"Version/(\d+)\.(\d+)").unwrap();
         | 
| 87 | 
            +
            }
         | 
| 88 | 
            +
             | 
| 89 | 
            +
            impl Browser {
         | 
| 90 | 
            +
                pub fn match_edge(ua: &str) -> Option<(i8, i8)> {
         | 
| 91 | 
            +
                    Browser::match_versions(ua, &EDGE_REGEX)
         | 
| 92 | 
            +
                }
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                /// Search for the Firefox componenet in the user agent and parse out the version if present
         | 
| 95 | 
            +
                pub fn match_firefox(ua: &str) -> Option<(i8, i8)> {
         | 
| 96 | 
            +
                    Browser::match_versions(ua, &FIREFOX_REGEX)
         | 
| 97 | 
            +
                }
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                pub fn match_chrome(ua: &str) -> Option<(i8, i8)> {
         | 
| 100 | 
            +
                    Browser::match_versions(ua, &CHROME_REGEX)
         | 
| 101 | 
            +
                }
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                pub fn match_opera(ua: &str) -> Option<(i8, i8)> {
         | 
| 104 | 
            +
                    if !ua.contains("Opera") { return None }
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                    Browser::match_versions(ua, &OPERA_VERSION_REGEX)
         | 
| 107 | 
            +
                }
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                pub fn match_safari(ua: &str) -> Option<(i8, i8)> {
         | 
| 110 | 
            +
                    if !ua.contains("Safari") { return None }
         | 
| 111 | 
            +
                    if ua.contains("Mobile/") { return None }
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                    Browser::match_versions(ua, &SAFARI_VERSION_REGEX)
         | 
| 114 | 
            +
                }
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                pub fn match_mobile_safari(ua: &str) -> Option<(i8, i8)> {
         | 
| 117 | 
            +
                    if !ua.contains("Safari")  { return None }
         | 
| 118 | 
            +
                    if !ua.contains("Mobile/") { return None }
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                    Browser::match_versions(ua, &SAFARI_VERSION_REGEX)
         | 
| 121 | 
            +
                }
         | 
| 122 | 
            +
            }
         | 
| 123 | 
            +
             | 
| 124 | 
            +
            #[cfg(test)]
         | 
| 125 | 
            +
            mod tests {
         | 
| 126 | 
            +
                use super::{Browser, BrowserFamily};
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                type StaticStr = &'static str;
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                const OPERA_12: StaticStr        = "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16";
         | 
| 131 | 
            +
                const OPERA_11: StaticStr        = "Opera/9.80 (Windows NT 6.1; WOW64; U; pt) Presto/2.10.229 Version/11.62";
         | 
| 132 | 
            +
                const SAFARI_7: StaticStr        = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A";
         | 
| 133 | 
            +
                const SAFARI_5: StaticStr        = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-us) AppleWebKit/534.1+ (KHTML, like Gecko) Version/5.0 Safari/533.16";
         | 
| 134 | 
            +
                const MOBILE_SAFARI_6: StaticStr = "Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25";
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                #[test]
         | 
| 137 | 
            +
                fn test_parse_safari() {
         | 
| 138 | 
            +
                    assert_eq!(
         | 
| 139 | 
            +
                        Browser::new(BrowserFamily::Safari, (7, 0)),
         | 
| 140 | 
            +
                        Browser::parse(SAFARI_7).unwrap()
         | 
| 141 | 
            +
                    );
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                    assert_eq!(
         | 
| 144 | 
            +
                        Browser::new(BrowserFamily::MobileSafari, (6, 0)),
         | 
| 145 | 
            +
                        Browser::parse(MOBILE_SAFARI_6).unwrap()
         | 
| 146 | 
            +
                    )
         | 
| 147 | 
            +
                }
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                #[test]
         | 
| 150 | 
            +
                fn test_match_firefox() {
         | 
| 151 | 
            +
                    let did_match = Browser::match_firefox("Firefox/1.2");
         | 
| 152 | 
            +
                    assert_eq!(did_match, Some((1, 2)));
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                    let didnt_match = Browser::match_firefox("NotFirefox/x.y");
         | 
| 155 | 
            +
                    assert_eq!(didnt_match, None)
         | 
| 156 | 
            +
                }
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                #[test]
         | 
| 159 | 
            +
                fn test_match_safari() {
         | 
| 160 | 
            +
                    let version_7 = Browser::match_safari(SAFARI_7);
         | 
| 161 | 
            +
                    assert_eq!(version_7, Some((7, 0)));
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                    let version_5 = Browser::match_safari(SAFARI_5);
         | 
| 164 | 
            +
                    assert_eq!(version_5, Some((5, 0)));
         | 
| 165 | 
            +
                }
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                #[test]
         | 
| 168 | 
            +
                fn test_match_mobile_safari() {
         | 
| 169 | 
            +
                    let mobile_version_6 = Browser::match_mobile_safari(MOBILE_SAFARI_6);
         | 
| 170 | 
            +
                    assert_eq!(mobile_version_6, Some((6, 0)))
         | 
| 171 | 
            +
                }
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                #[test]
         | 
| 174 | 
            +
                fn test_match_opera() {
         | 
| 175 | 
            +
                    let opera_12 = Browser::match_opera(OPERA_12);
         | 
| 176 | 
            +
                    assert_eq!(opera_12, Some((12, 16)));
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                    let opera_11 = Browser::match_opera(OPERA_11);
         | 
| 179 | 
            +
                    assert_eq!(opera_11, Some((11, 62)))
         | 
| 180 | 
            +
                }
         | 
| 181 | 
            +
            }
         | 
    
        data/rust/src/lib.rs
    ADDED
    
    | @@ -0,0 +1,109 @@ | |
| 1 | 
            +
            #[macro_use]
         | 
| 2 | 
            +
            extern crate lazy_static;
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            extern crate libc;
         | 
| 5 | 
            +
            extern crate regex;
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            mod bot;
         | 
| 8 | 
            +
            mod browser;
         | 
| 9 | 
            +
            mod user_agent;
         | 
| 10 | 
            +
            mod util;
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            use libc::c_char;
         | 
| 13 | 
            +
            use std::ffi::{CStr, CString};
         | 
| 14 | 
            +
            use self::browser::BrowserFamily;
         | 
| 15 | 
            +
            use self::user_agent::UserAgent;
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            #[no_mangle]
         | 
| 18 | 
            +
            pub extern fn parse_user_agent(cstring: *const c_char) -> *const UserAgent {
         | 
| 19 | 
            +
                let string  = unsafe { CStr::from_ptr(cstring) }.to_str().unwrap();
         | 
| 20 | 
            +
                let browser = UserAgent::parse(string);
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                Box::into_raw(Box::new(browser))
         | 
| 23 | 
            +
            }
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            /// Take back ownership of an externally-owned `Browser` and destructively deallocate it.
         | 
| 26 | 
            +
            #[no_mangle]
         | 
| 27 | 
            +
            pub extern fn free_user_agent(ua: *mut UserAgent) {
         | 
| 28 | 
            +
                drop(unsafe { Box::from_raw(ua) })
         | 
| 29 | 
            +
            }
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            macro_rules! is_family {
         | 
| 32 | 
            +
                ($function:ident, $family:path) => {
         | 
| 33 | 
            +
                    #[no_mangle]
         | 
| 34 | 
            +
                    pub extern fn $function(ua: *const UserAgent) -> bool {
         | 
| 35 | 
            +
                        if let Some(ref browser) = UserAgent::borrow_from_c(ua).browser {
         | 
| 36 | 
            +
                            browser.family == $family
         | 
| 37 | 
            +
                        } else {
         | 
| 38 | 
            +
                            false
         | 
| 39 | 
            +
                        }
         | 
| 40 | 
            +
                    }
         | 
| 41 | 
            +
                };
         | 
| 42 | 
            +
            }
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            is_family!(is_chrome,  BrowserFamily::Chrome);
         | 
| 45 | 
            +
            is_family!(is_edge,    BrowserFamily::Edge);
         | 
| 46 | 
            +
            is_family!(is_firefox, BrowserFamily::Firefox);
         | 
| 47 | 
            +
            is_family!(is_opera,   BrowserFamily::Opera);
         | 
| 48 | 
            +
            is_family!(is_safari,  BrowserFamily::Safari);
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            #[no_mangle]
         | 
| 51 | 
            +
            pub extern fn is_mobile(ua: *const UserAgent) -> bool {
         | 
| 52 | 
            +
                let ua = UserAgent::borrow_from_c(ua);
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                match ua.browser {
         | 
| 55 | 
            +
                    Some(ref b) => b.family.is_mobile(),
         | 
| 56 | 
            +
                    _ => false
         | 
| 57 | 
            +
                }
         | 
| 58 | 
            +
            }
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            #[no_mangle]
         | 
| 61 | 
            +
            pub extern fn get_browser_major_version(ua: *const UserAgent) -> i8 {
         | 
| 62 | 
            +
                UserAgent::borrow_from_c(ua).browser.clone().map_or(0, |b| b.major_version)
         | 
| 63 | 
            +
            }
         | 
| 64 | 
            +
             | 
| 65 | 
            +
            #[no_mangle]
         | 
| 66 | 
            +
            pub extern fn get_browser_minor_version(ua: *const UserAgent) -> i8 {
         | 
| 67 | 
            +
                UserAgent::borrow_from_c(ua).browser.clone().map_or(0, |b| b.minor_version)
         | 
| 68 | 
            +
            }
         | 
| 69 | 
            +
             | 
| 70 | 
            +
            /// Returns the user agent's browser family name as a heap-allocated `CString`
         | 
| 71 | 
            +
            #[no_mangle]
         | 
| 72 | 
            +
            pub extern fn get_browser_family(ua: *const UserAgent) -> *mut c_char {
         | 
| 73 | 
            +
                let browser = UserAgent::borrow_from_c(ua).browser.clone();
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                let family =
         | 
| 76 | 
            +
                    browser.map_or("Other", |browser| {
         | 
| 77 | 
            +
                        match browser.family {
         | 
| 78 | 
            +
                            BrowserFamily::Chrome       => "Chrome",
         | 
| 79 | 
            +
                            BrowserFamily::Edge         => "Edge",
         | 
| 80 | 
            +
                            BrowserFamily::Firefox      => "Firefox",
         | 
| 81 | 
            +
                            BrowserFamily::Opera        => "Opera",
         | 
| 82 | 
            +
                            BrowserFamily::Safari       => "Safari",
         | 
| 83 | 
            +
                            BrowserFamily::MobileSafari => "Mobile Safari",
         | 
| 84 | 
            +
                        }
         | 
| 85 | 
            +
                    });
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                CString::new(family).unwrap().into_raw()
         | 
| 88 | 
            +
            }
         | 
| 89 | 
            +
             | 
| 90 | 
            +
            /// Returns the original user agent that was parsed as a `CString` (must free later)
         | 
| 91 | 
            +
            #[no_mangle]
         | 
| 92 | 
            +
            pub extern fn get_user_agent(ua: *const UserAgent) -> *mut c_char {
         | 
| 93 | 
            +
                let ref ua = UserAgent::borrow_from_c(ua);
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                CString::new(ua.source.clone()).unwrap().into_raw()
         | 
| 96 | 
            +
            }
         | 
| 97 | 
            +
             | 
| 98 | 
            +
            /// Free a `CString` pointer owned by Rust
         | 
| 99 | 
            +
            #[no_mangle]
         | 
| 100 | 
            +
            pub extern fn free_string(string: *mut c_char) {
         | 
| 101 | 
            +
                drop(unsafe { CString::from_raw(string) })
         | 
| 102 | 
            +
            }
         | 
| 103 | 
            +
             | 
| 104 | 
            +
            const VERSION: &'static str = "0.0.1";
         | 
| 105 | 
            +
             | 
| 106 | 
            +
            #[no_mangle]
         | 
| 107 | 
            +
            pub extern fn get_version() -> *const c_char {
         | 
| 108 | 
            +
                CString::new(VERSION).unwrap().into_raw()
         | 
| 109 | 
            +
            }
         | 
| @@ -0,0 +1,32 @@ | |
| 1 | 
            +
            use std::mem;
         | 
| 2 | 
            +
            use bot::Bot;
         | 
| 3 | 
            +
            use browser::Browser;
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            pub struct UserAgent {
         | 
| 6 | 
            +
                pub browser: Option<Browser>,
         | 
| 7 | 
            +
                pub bot: Option<Bot>,
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                /// The string that was parsed to determine the browser, bot, etc.
         | 
| 10 | 
            +
                pub source: String,
         | 
| 11 | 
            +
            }
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            impl UserAgent {
         | 
| 14 | 
            +
                pub fn parse(ua: &str) -> UserAgent {
         | 
| 15 | 
            +
                    let browser = Browser::parse(ua);
         | 
| 16 | 
            +
                    let bot     = Bot::parse(ua);
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    UserAgent {
         | 
| 19 | 
            +
                        browser: browser,
         | 
| 20 | 
            +
                        bot: bot,
         | 
| 21 | 
            +
                        source: ua.to_owned(),
         | 
| 22 | 
            +
                    }
         | 
| 23 | 
            +
                }
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                /// Take an externally-owned `Browser` and non-destructively borrow a reference to it.
         | 
| 26 | 
            +
                ///
         | 
| 27 | 
            +
                /// **Note**: This will *not* deallocate the instance passed in. So it is safe to call this
         | 
| 28 | 
            +
                /// over and over again.
         | 
| 29 | 
            +
                pub fn borrow_from_c<'a>(ua: *const UserAgent) -> &'a UserAgent {
         | 
| 30 | 
            +
                    unsafe { mem::transmute(ua) }
         | 
| 31 | 
            +
                }
         | 
| 32 | 
            +
            }
         | 
    
        data/rust/src/util.rs
    ADDED
    
    | @@ -0,0 +1,10 @@ | |
| 1 | 
            +
            use regex::{Captures};
         | 
| 2 | 
            +
            use std::str::FromStr;
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            /// Takes the first two capture groups from a regex result and turns them into a version
         | 
| 5 | 
            +
            /// integer 2-tuple
         | 
| 6 | 
            +
            pub fn map_first_captures(captures: Captures) -> (i8, i8) {
         | 
| 7 | 
            +
                let major_version = i8::from_str(&captures[1]).unwrap();
         | 
| 8 | 
            +
                let minor_version = i8::from_str(&captures[2]).unwrap();
         | 
| 9 | 
            +
                (major_version, minor_version)
         | 
| 10 | 
            +
            }
         | 
    
        data/spec/name_spec.rb
    ADDED
    
    | @@ -0,0 +1,45 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe FastBrowser do
         | 
| 4 | 
            +
              let(:firefox)       { 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1' }
         | 
| 5 | 
            +
              let(:mobile_safari) { 'Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25' }
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              it 'parses Firefox name' do
         | 
| 8 | 
            +
                browser = FastBrowser.new firefox
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                expect(browser.family).to eq 'Firefox'
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              it 'parses Chrome name' do
         | 
| 14 | 
            +
                browser = FastBrowser.new 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36'
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                expect(browser.family).to eq 'Chrome'
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              it 'returns Other if not parses matches' do
         | 
| 20 | 
            +
                browser = FastBrowser.new 'Mozilla/5.0 Unknwon/1.2'
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                expect(browser.family).to eq 'Other'
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              it 'returns true if it is a mobile browser' do
         | 
| 26 | 
            +
                browser = FastBrowser.new mobile_safari
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                expect(browser.mobile?).to eq true
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              it "returns false if it isn't a mobile browser" do
         | 
| 32 | 
            +
                browser = FastBrowser.new firefox
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                expect(browser.mobile?).to eq false
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              describe '#user_agent' do
         | 
| 38 | 
            +
                it 'returns the original user agent' do
         | 
| 39 | 
            +
                  nonsense = 'abc123'
         | 
| 40 | 
            +
                  browser  = FastBrowser.new nonsense
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  expect(browser.user_agent).to eq nonsense
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
            end
         | 
    
        data/spec/safety_spec.rb
    ADDED
    
    | @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe FastBrowser do
         | 
| 4 | 
            +
              let(:firefox) { 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1' }
         | 
| 5 | 
            +
              let(:chrome)  { 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36' }
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              it 'seems to be memory-safe' do
         | 
| 8 | 
            +
                b1 = FastBrowser.new firefox
         | 
| 9 | 
            +
                expect(b1.firefox?).to eq true
         | 
| 10 | 
            +
                expect(b1.chrome?).to eq false
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                b2 = FastBrowser.new chrome
         | 
| 13 | 
            +
                expect(b2.firefox?).to eq false
         | 
| 14 | 
            +
                expect(b2.chrome?).to eq true
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                # Then check each against to make sure they didn't overwrite each other
         | 
| 17 | 
            +
                # or something
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                expect(b1.firefox?).to eq true
         | 
| 20 | 
            +
                expect(b1.chrome?).to eq false
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                expect(b2.firefox?).to eq false
         | 
| 23 | 
            +
                expect(b2.chrome?).to eq true
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
            end
         | 
    
        data/spec/spec_helper.rb
    ADDED
    
    
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe FastBrowser do
         | 
| 4 | 
            +
              it 'parses Firefox versions' do
         | 
| 5 | 
            +
                browser = FastBrowser.new 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                expect(browser.major_version).to eq 40
         | 
| 8 | 
            +
                expect(browser.minor_version).to eq 1
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              it 'parses Chrome versions' do
         | 
| 12 | 
            +
                browser = FastBrowser.new 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36'
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                expect(browser.major_version).to eq 41
         | 
| 15 | 
            +
                expect(browser.minor_version).to eq 0
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
| @@ -0,0 +1,72 @@ | |
| 1 | 
            +
            require_relative '../test_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'benchmark'
         | 
| 4 | 
            +
            require 'minitest/autorun'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            require 'browser'
         | 
| 7 | 
            +
            require 'ruby-prof'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            class TestCompare < Minitest::Test
         | 
| 10 | 
            +
              TIMES = 50_000
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              FIREFOX_40 = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1'
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              def test_compare_with_browser
         | 
| 15 | 
            +
                ua = FIREFOX_40
         | 
| 16 | 
            +
                result = nil
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                # Make sure the Rust lib is hot
         | 
| 19 | 
            +
                _ = FastBrowser.new ua
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                test_browser = lambda do
         | 
| 22 | 
            +
                  TIMES.times do
         | 
| 23 | 
            +
                    b = Browser.new user_agent: ua
         | 
| 24 | 
            +
                    assert_equal b.firefox?, true
         | 
| 25 | 
            +
                    assert_equal "40", b.version
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                test_fast_browser = lambda do
         | 
| 30 | 
            +
                  TIMES.times do
         | 
| 31 | 
            +
                    b = FastBrowser.new ua
         | 
| 32 | 
            +
                    assert_equal b.firefox?, true
         | 
| 33 | 
            +
                    assert_equal 40, b.major_version
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                {
         | 
| 38 | 
            +
                  'browser' => test_browser,
         | 
| 39 | 
            +
                  'fast_browser' => test_fast_browser
         | 
| 40 | 
            +
                }.each do |(name, test_block)|
         | 
| 41 | 
            +
                  time    = benchmark_time &test_block
         | 
| 42 | 
            +
                  objects = benchmark_memory &test_block
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  print "#{name.ljust(15)}%10.5f seconds\t%d objects\n" % [time, objects]
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              private
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              def profile(&block)
         | 
| 51 | 
            +
                result = RubyProf.profile &block
         | 
| 52 | 
            +
                RubyProf::FlatPrinter.new(result).print(STDOUT)
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
              def benchmark_time &block
         | 
| 56 | 
            +
                Benchmark.realtime &block
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
              def benchmark_memory &block
         | 
| 60 | 
            +
                GC.start
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                before_allocated = GC.stat[:total_allocated_objects]
         | 
| 63 | 
            +
                GC.disable
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                block.call
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                after_allocated = GC.stat[:total_allocated_objects]
         | 
| 68 | 
            +
                GC.enable
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                return after_allocated - before_allocated
         | 
| 71 | 
            +
              end
         | 
| 72 | 
            +
            end
         | 
    
        data/test/test_helper.rb
    ADDED
    
    
    
        metadata
    ADDED
    
    | @@ -0,0 +1,125 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: fast_browser
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.1.0
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - Dirk Gadsden
         | 
| 8 | 
            +
            autorequire: 
         | 
| 9 | 
            +
            bindir: bin
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2016-01-19 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: ffi
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - "~>"
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: 1.9.10
         | 
| 20 | 
            +
              type: :runtime
         | 
| 21 | 
            +
              prerelease: false
         | 
| 22 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            +
                requirements:
         | 
| 24 | 
            +
                - - "~>"
         | 
| 25 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            +
                    version: 1.9.10
         | 
| 27 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            +
              name: bundler
         | 
| 29 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 | 
            +
                requirements:
         | 
| 31 | 
            +
                - - "~>"
         | 
| 32 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            +
                    version: '1.10'
         | 
| 34 | 
            +
              type: :development
         | 
| 35 | 
            +
              prerelease: false
         | 
| 36 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            +
                requirements:
         | 
| 38 | 
            +
                - - "~>"
         | 
| 39 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            +
                    version: '1.10'
         | 
| 41 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            +
              name: rake
         | 
| 43 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
                requirements:
         | 
| 45 | 
            +
                - - "~>"
         | 
| 46 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            +
                    version: 10.4.2
         | 
| 48 | 
            +
              type: :development
         | 
| 49 | 
            +
              prerelease: false
         | 
| 50 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - "~>"
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: 10.4.2
         | 
| 55 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 56 | 
            +
              name: rspec
         | 
| 57 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 58 | 
            +
                requirements:
         | 
| 59 | 
            +
                - - "~>"
         | 
| 60 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            +
                    version: 3.4.0
         | 
| 62 | 
            +
              type: :development
         | 
| 63 | 
            +
              prerelease: false
         | 
| 64 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 65 | 
            +
                requirements:
         | 
| 66 | 
            +
                - - "~>"
         | 
| 67 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 68 | 
            +
                    version: 3.4.0
         | 
| 69 | 
            +
            description: Blazing-fast, Rust-powered user agent detection library.
         | 
| 70 | 
            +
            email:
         | 
| 71 | 
            +
            - dirk@esherido.com
         | 
| 72 | 
            +
            executables: []
         | 
| 73 | 
            +
            extensions:
         | 
| 74 | 
            +
            - ext/extconf.rb
         | 
| 75 | 
            +
            extra_rdoc_files: []
         | 
| 76 | 
            +
            files:
         | 
| 77 | 
            +
            - ".gitignore"
         | 
| 78 | 
            +
            - ".rspec"
         | 
| 79 | 
            +
            - ".travis.yml"
         | 
| 80 | 
            +
            - Gemfile
         | 
| 81 | 
            +
            - Gemfile.lock
         | 
| 82 | 
            +
            - Rakefile
         | 
| 83 | 
            +
            - ext/extconf.rb
         | 
| 84 | 
            +
            - fast_browser.gemspec
         | 
| 85 | 
            +
            - lib/fast_browser.rb
         | 
| 86 | 
            +
            - lib/fast_browser/library_extensions.rb
         | 
| 87 | 
            +
            - lib/fast_browser/version.rb
         | 
| 88 | 
            +
            - rust/Cargo.toml
         | 
| 89 | 
            +
            - rust/build-debug.sh
         | 
| 90 | 
            +
            - rust/src/bot.rs
         | 
| 91 | 
            +
            - rust/src/browser.rs
         | 
| 92 | 
            +
            - rust/src/lib.rs
         | 
| 93 | 
            +
            - rust/src/user_agent.rs
         | 
| 94 | 
            +
            - rust/src/util.rs
         | 
| 95 | 
            +
            - spec/name_spec.rb
         | 
| 96 | 
            +
            - spec/safety_spec.rb
         | 
| 97 | 
            +
            - spec/spec_helper.rb
         | 
| 98 | 
            +
            - spec/version_spec.rb
         | 
| 99 | 
            +
            - test/benchmark/compare.rb
         | 
| 100 | 
            +
            - test/test_helper.rb
         | 
| 101 | 
            +
            homepage: https://github.com/dirk/fast_browser
         | 
| 102 | 
            +
            licenses:
         | 
| 103 | 
            +
            - ''
         | 
| 104 | 
            +
            metadata: {}
         | 
| 105 | 
            +
            post_install_message: 
         | 
| 106 | 
            +
            rdoc_options: []
         | 
| 107 | 
            +
            require_paths:
         | 
| 108 | 
            +
            - lib
         | 
| 109 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 110 | 
            +
              requirements:
         | 
| 111 | 
            +
              - - ">="
         | 
| 112 | 
            +
                - !ruby/object:Gem::Version
         | 
| 113 | 
            +
                  version: '0'
         | 
| 114 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 115 | 
            +
              requirements:
         | 
| 116 | 
            +
              - - ">="
         | 
| 117 | 
            +
                - !ruby/object:Gem::Version
         | 
| 118 | 
            +
                  version: '0'
         | 
| 119 | 
            +
            requirements: []
         | 
| 120 | 
            +
            rubyforge_project: 
         | 
| 121 | 
            +
            rubygems_version: 2.5.1
         | 
| 122 | 
            +
            signing_key: 
         | 
| 123 | 
            +
            specification_version: 4
         | 
| 124 | 
            +
            summary: Blazing-fast, Rust-powered user agent detection library.
         | 
| 125 | 
            +
            test_files: []
         |