wurfl_device 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/Gemfile +3 -0
- data/LICENSE +0 -0
- data/README.md +0 -0
- data/Rakefile +30 -0
- data/bin/wurfldevice +17 -0
- data/lib/wurfl_device.rb +244 -0
- data/lib/wurfl_device/capability.rb +61 -0
- data/lib/wurfl_device/cli.rb +124 -0
- data/lib/wurfl_device/constants.rb +36 -0
- data/lib/wurfl_device/device.rb +82 -0
- data/lib/wurfl_device/ui.rb +73 -0
- data/lib/wurfl_device/user_agent_matcher.rb +156 -0
- data/lib/wurfl_device/version.rb +5 -0
- data/lib/wurfl_device/xml_loader.rb +49 -0
- data/spec/cache/device_spec.rb +21 -0
- data/spec/spec_helper.rb +20 -0
- data/wurfl_device.gemspec +35 -0
- metadata +189 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
File without changes
|
data/README.md
ADDED
File without changes
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'bundler'
|
5
|
+
Bundler::GemHelper.install_tasks
|
6
|
+
rescue LoadError
|
7
|
+
$stderr.puts "Bundler not installed. You should install it with: gem install bundler"
|
8
|
+
end
|
9
|
+
|
10
|
+
$LOAD_PATH << File.expand_path('./lib', File.dirname(__FILE__))
|
11
|
+
require 'wurfl_device/version'
|
12
|
+
|
13
|
+
begin
|
14
|
+
require 'rspec/core/rake_task'
|
15
|
+
|
16
|
+
RSpec::Core::RakeTask.new
|
17
|
+
|
18
|
+
if RUBY_VERSION >= '1.9'
|
19
|
+
RSpec::Core::RakeTask.new(:cov) do |t|
|
20
|
+
ENV['ENABLE_SIMPLECOV'] = '1'
|
21
|
+
t.ruby_opts = '-w'
|
22
|
+
t.rcov_opts = %q[-Ilib --exclude "spec/*,gems/*"]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
rescue LoadError
|
26
|
+
$stderr.puts "RSpec not available. Install it with: gem install rspec-core rspec-expectations rr faker"
|
27
|
+
end
|
28
|
+
|
29
|
+
task :default => :spec
|
30
|
+
|
data/bin/wurfldevice
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
|
4
|
+
|
5
|
+
require 'wurfl_device/cli'
|
6
|
+
|
7
|
+
begin
|
8
|
+
WurflDevice::CLI.start
|
9
|
+
rescue WurflDevice::WurflDeviceError => e
|
10
|
+
WurflDevice.ui.error e.message
|
11
|
+
WurflDevice.ui.debug e.backtrace.join("\n")
|
12
|
+
exit e.status_code
|
13
|
+
rescue Interrupt => e
|
14
|
+
WurflDevice.ui.error "\nQuitting..."
|
15
|
+
WurflDevice.ui.debug e.backtrace.join("\n")
|
16
|
+
exit 1
|
17
|
+
end
|
data/lib/wurfl_device.rb
ADDED
@@ -0,0 +1,244 @@
|
|
1
|
+
require 'etc'
|
2
|
+
require 'redis'
|
3
|
+
|
4
|
+
require 'wurfl_device/version'
|
5
|
+
|
6
|
+
module WurflDevice
|
7
|
+
autoload :Capability, 'wurfl_device/capability'
|
8
|
+
autoload :Constants, 'wurfl_device/constants'
|
9
|
+
autoload :Device, 'wurfl_device/device'
|
10
|
+
autoload :Handset, 'wurfl_device/handset'
|
11
|
+
autoload :UI, 'wurfl_device/ui'
|
12
|
+
autoload :CLI, 'wurfl_device/cli'
|
13
|
+
autoload :UserAgentMatcher, 'wurfl_device/user_agent_matcher'
|
14
|
+
autoload :XmlLoader, 'wurfl_device/xml_loader'
|
15
|
+
|
16
|
+
class WurflDeviceError < StandardError
|
17
|
+
def self.status_code(code = nil)
|
18
|
+
define_method(:status_code) { code }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class CacheError < WurflDeviceError; status_code(10); end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
attr_writer :ui, :db
|
26
|
+
|
27
|
+
def ui
|
28
|
+
@ui ||= UI.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def db
|
32
|
+
@db ||= Redis.new(:db => Constants::DB_INDEX)
|
33
|
+
end
|
34
|
+
|
35
|
+
def tmp_dir
|
36
|
+
File.join(Etc.systmpdir, 'wurfl_device')
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_actual_device(device_id)
|
40
|
+
capabilities = Capability.new
|
41
|
+
actual_device = get_actual_device_raw(device_id)
|
42
|
+
return nil if actual_device.nil?
|
43
|
+
actual_device.each_pair do |key, value|
|
44
|
+
if key =~ /^(.+)\:(.+)$/i
|
45
|
+
capabilities[$1] ||= Capability.new
|
46
|
+
capabilities[$1][$2] = parse_string_value(value)
|
47
|
+
else
|
48
|
+
capabilities[key] = parse_string_value(value)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
capabilities
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_device_from_id(device_id)
|
55
|
+
device = Device.new(device_id)
|
56
|
+
return device if device.is_valid?
|
57
|
+
return nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def get_device_from_ua(user_agent)
|
61
|
+
matcher = UserAgentMatcher.new.match(user_agent)
|
62
|
+
return matcher.device
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_device_from_ua_cache(user_agent)
|
66
|
+
cached_device = db.hget(Constants::WURFL_USER_AGENTS_CACHED, user_agent)
|
67
|
+
return Marshal::load(cached_device) unless cached_device.nil?
|
68
|
+
cached_device = db.hget(Constants::WURFL_USER_AGENTS, user_agent)
|
69
|
+
return Marshal::load(cached_device) unless cached_device.nil?
|
70
|
+
return nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def save_device_in_ua_cache(user_agent, device)
|
74
|
+
db.hset(Constants::WURFL_USER_AGENTS_CACHED, user_agent, Marshal::dump(device))
|
75
|
+
end
|
76
|
+
|
77
|
+
def get_info
|
78
|
+
db.hgetall(Constants::WURFL_INFO)
|
79
|
+
end
|
80
|
+
|
81
|
+
# cache related
|
82
|
+
def clear_user_agent_cache
|
83
|
+
db.del(Constants::WURFL_USER_AGENTS_CACHED)
|
84
|
+
end
|
85
|
+
|
86
|
+
def clear_cache
|
87
|
+
db.keys("#{Constants::WURFL}*").each { |k| db.del k }
|
88
|
+
end
|
89
|
+
|
90
|
+
def clear_devices
|
91
|
+
db.keys("#{Constants::WURFL_DEVICES}*").each { |k| db.del k }
|
92
|
+
db.del(Contants::WURFL_INITIALIZED)
|
93
|
+
db.del(Contants::WURFL_INFO)
|
94
|
+
end
|
95
|
+
|
96
|
+
def get_user_agents
|
97
|
+
db.hkeys(Constants::WURFL_USER_AGENTS)
|
98
|
+
end
|
99
|
+
|
100
|
+
def get_user_agents_in_cache
|
101
|
+
db.hkeys(Constants::WURFL_USER_AGENTS_CACHED)
|
102
|
+
end
|
103
|
+
|
104
|
+
def get_user_agents_in_index(matcher)
|
105
|
+
db.hgetall("#{Constants::WURFL_DEVICES_INDEX}#{matcher}")
|
106
|
+
end
|
107
|
+
|
108
|
+
def get_actual_device_raw(device_id)
|
109
|
+
db.hgetall("#{Constants::WURFL_DEVICES}#{device_id}")
|
110
|
+
end
|
111
|
+
|
112
|
+
def get_devices
|
113
|
+
db.keys("#{Constants::WURFL_DEVICES}*")
|
114
|
+
end
|
115
|
+
|
116
|
+
def get_indexes
|
117
|
+
db.keys("#{Constants::WURFL_DEVICES_INDEX}*")
|
118
|
+
end
|
119
|
+
|
120
|
+
def rebuild_user_agent_cache
|
121
|
+
# update the cache's
|
122
|
+
get_indexes.each { |k| db.del k }
|
123
|
+
db.del(Constants::WURFL_USER_AGENTS)
|
124
|
+
db.del(Constants::WURFL_DEVICES_INDEX)
|
125
|
+
|
126
|
+
get_devices.each do |device_id|
|
127
|
+
device_id.gsub!(Constants::WURFL_DEVICES, '')
|
128
|
+
actual_device = get_actual_device(device_id)
|
129
|
+
next if actual_device.nil?
|
130
|
+
user_agent = actual_device.user_agent
|
131
|
+
next if user_agent.nil? || user_agent.empty?
|
132
|
+
db.hset(Constants::WURFL_USER_AGENTS, user_agent, Marshal::dump(Device.new(device_id)))
|
133
|
+
|
134
|
+
matcher = UserAgentMatcher.get_index(user_agent)
|
135
|
+
db.hset("#{Constants::WURFL_DEVICES_INDEX}#{matcher}", user_agent, device_id)
|
136
|
+
end
|
137
|
+
|
138
|
+
get_user_agents_in_cache.each do |user_agent|
|
139
|
+
db.hdel(Constants::WURFL_USER_AGENTS_CACHED, user_agent)
|
140
|
+
UserAgentMatcher.new.match(user_agent)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def is_initialized?
|
145
|
+
status = parse_string_value(db.get(Constants::WURFL_INITIALIZED))
|
146
|
+
return false if status.nil?
|
147
|
+
return false unless status.is_a?(TrueClass)
|
148
|
+
true
|
149
|
+
end
|
150
|
+
|
151
|
+
def initialize_cache
|
152
|
+
# make sure only process can initialize at a time
|
153
|
+
# don't initialize if there is another initializing
|
154
|
+
lock_the_cache_for_initializing do
|
155
|
+
db.set(Constants::WURFL_INITIALIZED, false)
|
156
|
+
|
157
|
+
# download & parse the wurfl xml
|
158
|
+
(devices, version, last_updated) = XmlLoader.load_xml_file(download_wurfl_xml_file) do |capabilities|
|
159
|
+
device_id = capabilities.delete('id')
|
160
|
+
next if device_id.nil? || device_id.empty?
|
161
|
+
db.del("#{Constants::WURFL_DEVICES}#{device_id}")
|
162
|
+
user_agent = capabilities.delete('user_agent')
|
163
|
+
fall_back = capabilities.delete('fall_back')
|
164
|
+
|
165
|
+
device_id.strip! unless device_id.nil?
|
166
|
+
user_agent.strip! unless user_agent.nil?
|
167
|
+
fall_back.strip! unless fall_back.nil?
|
168
|
+
|
169
|
+
db.hset("#{Constants::WURFL_DEVICES}#{device_id}", "id", device_id)
|
170
|
+
db.hset("#{Constants::WURFL_DEVICES}#{device_id}", "user_agent", user_agent)
|
171
|
+
db.hset("#{Constants::WURFL_DEVICES}#{device_id}", "fall_back", fall_back)
|
172
|
+
|
173
|
+
capabilities.each_pair do |key, value|
|
174
|
+
if value.is_a?(Hash)
|
175
|
+
value.each_pair do |k, v|
|
176
|
+
db.hset("#{Constants::WURFL_DEVICES}#{device_id}", "#{key.to_s}:#{k.to_s}", v)
|
177
|
+
end
|
178
|
+
else
|
179
|
+
db.hset("#{Constants::WURFL_DEVICES}#{device_id}", "#{key.to_s}", value)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
db.set(Constants::WURFL_INITIALIZED, true)
|
185
|
+
db.hset(Constants::WURFL_INFO, "version", version)
|
186
|
+
db.hset(Constants::WURFL_INFO, "last_updated", Time.now)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def parse_string_value(value)
|
191
|
+
return value unless value.is_a?(String)
|
192
|
+
# convert to utf-8 (wurfl.xml encoding format)
|
193
|
+
value.force_encoding('UTF-8')
|
194
|
+
return false if value =~ /^false/i
|
195
|
+
return true if value =~ /^true/i
|
196
|
+
return value.to_i if (value == value.to_i.to_s)
|
197
|
+
return value.to_f if (value == value.to_f.to_s)
|
198
|
+
value
|
199
|
+
end
|
200
|
+
protected
|
201
|
+
def download_wurfl_xml_file
|
202
|
+
wurfl_xml_source = "http://sourceforge.net/projects/wurfl/files/WURFL/2.2/wurfl-2.2.xml.gz"
|
203
|
+
FileUtils.mkdir_p(WurflDevice.tmp_dir)
|
204
|
+
FileUtils.cd(WurflDevice.tmp_dir)
|
205
|
+
`wget --timeout=60 -qN -- #{wurfl_xml_source} > /dev/null`
|
206
|
+
raise "Failed to download wurfl-latest.xml.gz" unless $? == 0
|
207
|
+
|
208
|
+
wurfl_xml_filename = File.basename(wurfl_xml_source)
|
209
|
+
`gunzip -qc #{wurfl_xml_filename} > wurfl.xml`
|
210
|
+
raise 'Failed to unzip wurfl-latest.xml.gz' unless $? == 0
|
211
|
+
|
212
|
+
wurfl_xml_file_extracted = File.join(WurflDevice.tmp_dir, 'wurfl.xml')
|
213
|
+
raise "wurfl.xml does not exists!" unless File.exists?(wurfl_xml_file_extracted)
|
214
|
+
|
215
|
+
wurfl_xml_file_extracted
|
216
|
+
end
|
217
|
+
|
218
|
+
def lock_the_cache_for_initializing
|
219
|
+
start_at = Time.now
|
220
|
+
success = false
|
221
|
+
while Time.now - start_at < Constants::LOCK_TIMEOUT
|
222
|
+
success = true and break if try_lock
|
223
|
+
sleep Constants::LOCK_SLEEP
|
224
|
+
end
|
225
|
+
if block_given? and success
|
226
|
+
yield
|
227
|
+
unlock
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def try_lock
|
232
|
+
now = Time.now.to_f
|
233
|
+
@expires_at = now + Constants::LOCK_EXPIRE
|
234
|
+
return true if db.setnx(Constants::WURFL_INITIALIZING, @expires_at)
|
235
|
+
return false if db.get(Constants::WURFL_INITIALIZING).to_f > now
|
236
|
+
return true if db.getset(Constants::WURFL_INITIALIZING, @expires_at).to_f <= now
|
237
|
+
return false
|
238
|
+
end
|
239
|
+
|
240
|
+
def unlock(force=false)
|
241
|
+
db.del(Constants::WURFL_INITIALIZING) if db.get(Constants::WURFL_INITIALIZING).to_f == @expires_at or force
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module WurflDevice
|
2
|
+
class Capability < ::Hash
|
3
|
+
def initialize(hash={})
|
4
|
+
super()
|
5
|
+
hash.each do |key, value|
|
6
|
+
self[convert_key(key)] = value
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def [](key)
|
11
|
+
super(convert_key(key))
|
12
|
+
end
|
13
|
+
|
14
|
+
def []=(key, value)
|
15
|
+
super(convert_key(key), value)
|
16
|
+
end
|
17
|
+
|
18
|
+
def delete(key)
|
19
|
+
super(convert_key(key))
|
20
|
+
end
|
21
|
+
|
22
|
+
def values_at(*indices)
|
23
|
+
indices.collect { |key| self[convert_key(key)] }
|
24
|
+
end
|
25
|
+
|
26
|
+
def merge(other)
|
27
|
+
dup.merge!(other)
|
28
|
+
end
|
29
|
+
|
30
|
+
def merge!(other)
|
31
|
+
other.each do |key, value|
|
32
|
+
self[convert_key(key)] = value
|
33
|
+
end
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
def convert_key(key)
|
39
|
+
key.is_a?(Symbol) ? key.to_s : key
|
40
|
+
end
|
41
|
+
|
42
|
+
# Magic predicates. For instance:
|
43
|
+
#
|
44
|
+
# capability.force? # => !!options['force']
|
45
|
+
# capability.shebang # => "/usr/lib/local/ruby"
|
46
|
+
# capability.test_framework?(:rspec) # => options[:test_framework] == :rspec
|
47
|
+
#
|
48
|
+
def method_missing(method, *args, &block)
|
49
|
+
method = method.to_s
|
50
|
+
if method =~ /^(\w+)\?$/
|
51
|
+
if args.empty?
|
52
|
+
!!self[$1]
|
53
|
+
else
|
54
|
+
self[$1] == args.first
|
55
|
+
end
|
56
|
+
else
|
57
|
+
self[method]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'wurfl_device'
|
3
|
+
|
4
|
+
module WurflDevice
|
5
|
+
class CLI < Thor
|
6
|
+
include Thor::Actions
|
7
|
+
|
8
|
+
def initialize(*)
|
9
|
+
super
|
10
|
+
the_shell = (options["no-color"] ? Thor::Shell::Basic.new : shell)
|
11
|
+
WurflDevice.ui = UI::Shell.new(the_shell)
|
12
|
+
WurflDevice.ui.debug! if options["verbose"]
|
13
|
+
end
|
14
|
+
|
15
|
+
check_unknown_options!
|
16
|
+
|
17
|
+
default_task :help
|
18
|
+
|
19
|
+
desc "help", "Show this message"
|
20
|
+
def help(cli=nil)
|
21
|
+
version
|
22
|
+
WurflDevice.ui.info "http://github.com/aputs/wurfl_device for README"
|
23
|
+
WurflDevice.ui.info ""
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "dump DEVICE_ID|USER_AGENT", "display capabilities DEVICE_ID|USER_AGENT"
|
28
|
+
method_option :json, :type => :boolean, :banner => "show the dump in json format", :aliases => "-j"
|
29
|
+
method_option :yaml, :type => :boolean, :banner => "show the dump in yaml format", :aliases => "-y"
|
30
|
+
def dump(device_id)
|
31
|
+
device = WurflDevice.get_device_from_id(device_id)
|
32
|
+
device = WurflDevice.get_device_from_ua(device_id, use_cache) if device.nil?
|
33
|
+
|
34
|
+
if options.json?
|
35
|
+
WurflDevice.ui.info device.capabilities.to_json
|
36
|
+
else
|
37
|
+
WurflDevice.ui.info device.capabilities.to_yaml
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "list", "list user agent cache list"
|
42
|
+
def list
|
43
|
+
WurflDevice.get_user_agents_in_cache.each do |user_agent|
|
44
|
+
device = WurflDevice.get_device_from_ua_cache(user_agent)
|
45
|
+
device_id = ''
|
46
|
+
device_id = device.id unless device.nil?
|
47
|
+
WurflDevice.ui.info user_agent + ":" + device_id
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
desc "update", "update the wurfl cache"
|
52
|
+
method_option "clear-all", :type => :boolean, :banner => "remove all wurfl cache related"
|
53
|
+
method_option "clear-dev", :type => :boolean, :banner => "clear the device cache first before updating"
|
54
|
+
method_option "clear-ua", :type => :boolean, :banner => "remove all wurfl user agents cache"
|
55
|
+
def update
|
56
|
+
opts = options.dup
|
57
|
+
if opts['clear-all']
|
58
|
+
WurflDevice.ui.info "clearing all cache entries."
|
59
|
+
WurflDevice.clear_devices
|
60
|
+
WurflDevice.clear_cache
|
61
|
+
end
|
62
|
+
if opts['clear-ua']
|
63
|
+
WurflDevice.ui.info "clearing user agent cache."
|
64
|
+
WurflDevice.clear_user_agent_cache
|
65
|
+
end
|
66
|
+
if opts['clear-dev']
|
67
|
+
WurflDevice.ui.info "clearing device cache."
|
68
|
+
WurflDevice.clear_devices
|
69
|
+
end
|
70
|
+
WurflDevice.ui.info "updating wurfl devices cache."
|
71
|
+
WurflDevice.initialize_cache
|
72
|
+
WurflDevice.ui.info "rebuilding cache."
|
73
|
+
WurflDevice.rebuild_user_agent_cache
|
74
|
+
WurflDevice.ui.info "done."
|
75
|
+
WurflDevice.ui.info ""
|
76
|
+
status true
|
77
|
+
end
|
78
|
+
|
79
|
+
desc "rebuild", "rebuild the existing user_agents cache"
|
80
|
+
def rebuild
|
81
|
+
WurflDevice.ui.info "rebuilding the user_agents cache."
|
82
|
+
WurflDevice.rebuild_user_agent_cache
|
83
|
+
WurflDevice.ui.info "done."
|
84
|
+
end
|
85
|
+
|
86
|
+
desc "status", "show wurfl cache information"
|
87
|
+
def status(skip_version=false)
|
88
|
+
version unless skip_version
|
89
|
+
unless WurflDevice.is_initialized?
|
90
|
+
WurflDevice.ui.info "cache is not initialized"
|
91
|
+
return
|
92
|
+
end
|
93
|
+
info = WurflDevice.get_info
|
94
|
+
version = info['version'] || 'none'
|
95
|
+
last_update = info['last_updated'] || 'unknown'
|
96
|
+
WurflDevice.ui.info "cache info:"
|
97
|
+
WurflDevice.ui.info " wurfl-xml version: " + version
|
98
|
+
WurflDevice.ui.info " cache last updated: " + last_update
|
99
|
+
devices = WurflDevice.get_devices
|
100
|
+
user_agents = WurflDevice.get_user_agents
|
101
|
+
user_agents_message = ''
|
102
|
+
user_agents_message = " (warning count should be equal to devices count)" if devices.length != user_agents.length
|
103
|
+
WurflDevice.ui.info " " + commify(devices.length) + " device id's"
|
104
|
+
WurflDevice.ui.info " " + commify(user_agents.length) + " exact user agents" + user_agents_message
|
105
|
+
WurflDevice.ui.info " " + commify(WurflDevice.get_user_agents_in_cache.length) + " user agents found in cache"
|
106
|
+
WurflDevice.ui.info ""
|
107
|
+
end
|
108
|
+
map %w(stats stat info) => :status
|
109
|
+
|
110
|
+
desc "version", "show the wurfl_device version information"
|
111
|
+
def version
|
112
|
+
WurflDevice.ui.info "wurfl_device version #{WurflDevice::VERSION.freeze}"
|
113
|
+
end
|
114
|
+
map %w(-v --version) => :version
|
115
|
+
private
|
116
|
+
def commify(n)
|
117
|
+
n.to_s =~ /([^\.]*)(\..*)?/
|
118
|
+
int, dec = $1.reverse, $2 ? $2 : ""
|
119
|
+
while int.gsub!(/(,|\.|^)(\d{3})(\d)/, '\1\2,\3')
|
120
|
+
end
|
121
|
+
int.reverse + dec
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
module WurflDevice
|
3
|
+
class Constants
|
4
|
+
LOCK_TIMEOUT = 3
|
5
|
+
LOCK_EXPIRE = 10
|
6
|
+
LOCK_SLEEP = 0.1
|
7
|
+
|
8
|
+
DB_INDEX = "7".freeze
|
9
|
+
GENERIC = 'generic'
|
10
|
+
WURFL = "wurfl:"
|
11
|
+
WURFL_INFO = "wurfl:info"
|
12
|
+
WURFL_DEVICES = "wurfl:devices:"
|
13
|
+
WURFL_DEVICES_INDEX = "wurfl:index:"
|
14
|
+
WURFL_INITIALIZED = "wurfl:is_initialized"
|
15
|
+
WURFL_INITIALIZING = "wurfl:is_initializing"
|
16
|
+
WURFL_USER_AGENTS = "wurfl:user_agents"
|
17
|
+
WURFL_USER_AGENTS_CACHED = "wurfl:user_agents_cached"
|
18
|
+
|
19
|
+
USER_AGENT_MATCHERS =
|
20
|
+
[
|
21
|
+
"Alcatel", "Android", "AOL", "Apple", "BenQ", "BlackBerry", "Bot", "CatchAll", "Chrome", "DoCoMo",
|
22
|
+
"Firefox", "Grundig", "HTC", "Kddi", "Konqueror", "Kyocera", "LG", "Mitsubishi", "Motorola", "MSIE",
|
23
|
+
"Nec", "Nintendo", "Nokia", "Opera", "OperaMini", "Panasonic", "Pantech", "Philips", "Portalmmm", "Qtek",
|
24
|
+
"Safari", "Sagem", "Samsung", "Sanyo", "Sharp", "Siemens", "SonyEricsson", "SPV", "Toshiba", "Vodafone", "WindowsCE"
|
25
|
+
]
|
26
|
+
|
27
|
+
MOBILE_BROWSERS = [
|
28
|
+
'cldc', 'symbian', 'midp', 'j2me', 'mobile', 'wireless', 'palm', 'phone', 'pocket pc', 'pocketpc', 'netfront',
|
29
|
+
'bolt', 'iris', 'brew', 'openwave', 'windows ce', 'wap2.', 'android', 'opera mini', 'opera mobi', 'maemo', 'fennec',
|
30
|
+
'blazer', 'vodafone', 'wp7', 'armv'
|
31
|
+
]
|
32
|
+
|
33
|
+
ROBOTS = [ 'bot', 'crawler', 'spider', 'novarra', 'transcoder', 'yahoo! searchmonkey', 'yahoo! slurp', 'feedfetcher-google', 'toolbar', 'mowser' ]
|
34
|
+
DESKTOP_BROWSERS = [ 'slcc1', '.net clr', 'wow64', 'media center pc', 'funwebproducts', 'macintosh', 'aol 9.', 'america online browser', 'googletoolbar' ]
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'redis'
|
3
|
+
|
4
|
+
module WurflDevice
|
5
|
+
class Device
|
6
|
+
attr_accessor :capabilities
|
7
|
+
|
8
|
+
def initialize(device_id=nil)
|
9
|
+
raise WurflDevice::CacheError, "wurfl cache is not initialized" unless WurflDevice.is_initialized?
|
10
|
+
if device_id.nil?
|
11
|
+
@capabilities = WurflDevice.get_actual_device(WurflDevice::Constants::GENERIC)
|
12
|
+
else
|
13
|
+
@capabilities = build_device(device_id)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def is_valid?
|
18
|
+
return false if @capabilities.nil?
|
19
|
+
return false if @capabilities.id.nil?
|
20
|
+
return false if @capabilities.id.empty?
|
21
|
+
return true
|
22
|
+
end
|
23
|
+
|
24
|
+
def is_generic?
|
25
|
+
is_valid? && @capabilities.id !~ Regexp.new(WurflDevice::Constants::GENERIC, Regexp::IGNORECASE)
|
26
|
+
end
|
27
|
+
|
28
|
+
def build_device(device_id)
|
29
|
+
capabilities = Capability.new
|
30
|
+
device = WurflDevice.get_actual_device_raw(device_id)
|
31
|
+
return nil if device.nil?
|
32
|
+
|
33
|
+
capabilities['fall_back_tree'] ||= Array.new
|
34
|
+
|
35
|
+
if !device['fall_back'].nil? && !device['fall_back'].empty? && device['id'] != WurflDevice::Constants::GENERIC
|
36
|
+
fall_back = build_device(device['fall_back'])
|
37
|
+
unless fall_back.nil?
|
38
|
+
capabilities['fall_back_tree'].unshift(fall_back['id'])
|
39
|
+
fall_back.each_pair do |key, value|
|
40
|
+
if value.kind_of?(Hash)
|
41
|
+
capabilities[key] ||= Capability.new
|
42
|
+
value.each_pair do |k, v|
|
43
|
+
capabilities[key][k] = v
|
44
|
+
end
|
45
|
+
elsif value.is_a?(Array)
|
46
|
+
capabilities[key] ||= Array.new
|
47
|
+
capabilities[key] |= value
|
48
|
+
else
|
49
|
+
capabilities[key] = value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
device.each_pair do |key, value|
|
56
|
+
if key =~ /^(.+)\:(.+)$/i
|
57
|
+
capabilities[$1] ||= Capability.new
|
58
|
+
capabilities[$1][$2] = WurflDevice.parse_string_value(value)
|
59
|
+
else
|
60
|
+
capabilities[key] = WurflDevice.parse_string_value(value)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
capabilities
|
65
|
+
end
|
66
|
+
|
67
|
+
# Magic predicates
|
68
|
+
# slower than going directly to capabilities
|
69
|
+
def method_missing(method, *args, &block)
|
70
|
+
return nil if @capabilities.nil?
|
71
|
+
meth = method.to_s
|
72
|
+
meth.gsub!(/\?/, '')
|
73
|
+
return @capabilities.send(method, args, block) if @capabilities.has_key?(meth)
|
74
|
+
@capabilities.each_pair do |key, value|
|
75
|
+
if value.is_a?(Hash)
|
76
|
+
return value.send(method, args, block) if value.has_key?(meth)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
return nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'rubygems/user_interaction'
|
2
|
+
|
3
|
+
module WurflDevice
|
4
|
+
class UI
|
5
|
+
def warn(message)
|
6
|
+
end
|
7
|
+
|
8
|
+
def debug(message)
|
9
|
+
end
|
10
|
+
|
11
|
+
def error(message)
|
12
|
+
end
|
13
|
+
|
14
|
+
def info(message)
|
15
|
+
end
|
16
|
+
|
17
|
+
def confirm(message)
|
18
|
+
end
|
19
|
+
|
20
|
+
class Shell < UI
|
21
|
+
attr_writer :shell
|
22
|
+
|
23
|
+
def initialize(shell)
|
24
|
+
@shell = shell
|
25
|
+
@quiet = false
|
26
|
+
@debug = ENV['DEBUG']
|
27
|
+
end
|
28
|
+
|
29
|
+
def debug(msg)
|
30
|
+
@shell.say(msg) if @debug && !@quiet
|
31
|
+
end
|
32
|
+
|
33
|
+
def info(msg)
|
34
|
+
@shell.say(msg) if !@quiet
|
35
|
+
end
|
36
|
+
|
37
|
+
def confirm(msg)
|
38
|
+
@shell.say(msg, :green) if !@quiet
|
39
|
+
end
|
40
|
+
|
41
|
+
def warn(msg)
|
42
|
+
@shell.say(msg, :yellow)
|
43
|
+
end
|
44
|
+
|
45
|
+
def error(msg)
|
46
|
+
@shell.say(msg, :red)
|
47
|
+
end
|
48
|
+
|
49
|
+
def be_quiet!
|
50
|
+
@quiet = true
|
51
|
+
end
|
52
|
+
|
53
|
+
def debug!
|
54
|
+
@debug = true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class RGProxy < ::Gem::SilentUI
|
59
|
+
def initialize(ui)
|
60
|
+
@ui = ui
|
61
|
+
super()
|
62
|
+
end
|
63
|
+
|
64
|
+
def say(message)
|
65
|
+
if message =~ /native extensions/
|
66
|
+
@ui.info "with native extensions "
|
67
|
+
else
|
68
|
+
@ui.debug(message)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'text'
|
2
|
+
|
3
|
+
module WurflDevice
|
4
|
+
class UserAgentMatcher
|
5
|
+
attr_accessor :user_agent, :user_agent_cleaned, :device
|
6
|
+
|
7
|
+
def match(user_agent)
|
8
|
+
@user_agent = user_agent
|
9
|
+
@user_agent_cleaned = UserAgentMatcher.clean_user_agent(user_agent)
|
10
|
+
@device = nil
|
11
|
+
|
12
|
+
# exact match
|
13
|
+
@device = WurflDevice.get_device_from_ua_cache(@user_agent)
|
14
|
+
@device = WurflDevice.get_device_from_ua_cache(@user_agent_cleaned) if @device.nil?
|
15
|
+
|
16
|
+
# already in cache so return immediately
|
17
|
+
return self if !@device.nil? && @device.is_valid?
|
18
|
+
|
19
|
+
# ris match
|
20
|
+
if @device.nil?
|
21
|
+
user_agent = @user_agent
|
22
|
+
user_agent_list = WurflDevice.get_user_agents_in_index(UserAgentMatcher.get_index(user_agent)).sort { |a, b| a[0] <=> b[0] }
|
23
|
+
tolerance = UserAgentMatcher.first_slash(user_agent)-1
|
24
|
+
curlen = user_agent.length
|
25
|
+
while curlen >= tolerance
|
26
|
+
user_agent_list.map do |ua, device_id|
|
27
|
+
if ua.index(user_agent) == 0
|
28
|
+
@device = Device.new(device_id)
|
29
|
+
break
|
30
|
+
end
|
31
|
+
end
|
32
|
+
break unless @device.nil?
|
33
|
+
user_agent = user_agent.slice(0, curlen-1)
|
34
|
+
curlen = user_agent.length
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# last attempts
|
39
|
+
if @device.nil?
|
40
|
+
user_agent = @user_agent
|
41
|
+
device_id = WurflDevice::Constants::GENERIC
|
42
|
+
device_id = 'opwv_v7_generic' if user_agent.index('UP.Browser/7')
|
43
|
+
device_id = 'opwv_v6_generic' if user_agent.index('UP.Browser/6')
|
44
|
+
device_id = 'upgui_generic' if user_agent.index('UP.Browser/5')
|
45
|
+
device_id = 'uptext_generic' if user_agent.index('UP.Browser/4')
|
46
|
+
device_id = 'uptext_generic' if user_agent.index('UP.Browser/3')
|
47
|
+
device_id = 'nokia_generic_series60' if user_agent.index('Series60')
|
48
|
+
device_id = 'generic_web_browser' if user_agent.index('Mozilla/4.0')
|
49
|
+
device_id = 'generic_web_browser' if user_agent.index('Mozilla/5.0')
|
50
|
+
device_id = 'generic_web_browser' if user_agent.index('Mozilla/6.0')
|
51
|
+
@device = WurflDevice.get_device_from_id(device_id)
|
52
|
+
end
|
53
|
+
|
54
|
+
WurflDevice.save_device_in_ua_cache(@user_agent, @device)
|
55
|
+
|
56
|
+
return self
|
57
|
+
end
|
58
|
+
|
59
|
+
class << self
|
60
|
+
def get_index(user_agent)
|
61
|
+
# create device index
|
62
|
+
matcher = 'Generic'
|
63
|
+
WurflDevice::Constants::USER_AGENT_MATCHERS.each do |m|
|
64
|
+
if Regexp.new(m, Regexp::IGNORECASE) =~ user_agent
|
65
|
+
matcher = m
|
66
|
+
break
|
67
|
+
end
|
68
|
+
end
|
69
|
+
return matcher
|
70
|
+
end
|
71
|
+
|
72
|
+
def clean_user_agent(user_agent)
|
73
|
+
user_agent = remove_up_link_from_ua(user_agent)
|
74
|
+
|
75
|
+
# remove nokia-msisdn header
|
76
|
+
user_agent = remove_nokia_msisdn(user_agent)
|
77
|
+
|
78
|
+
# clean up myphone id's
|
79
|
+
user_agent = user_agent.sub("'M', 'Y' 'P', 'H', 'O', 'N', 'E'", "MyPhone")
|
80
|
+
|
81
|
+
# remove serial numbers
|
82
|
+
user_agent = user_agent.sub(/\/SN\d{15}/, '/SNXXXXXXXXXXXXXXX')
|
83
|
+
user_agent = user_agent.sub(/\[(ST|TF|NT)\d+\]/, '')
|
84
|
+
|
85
|
+
# remove locale identifiers
|
86
|
+
user_agent = user_agent.sub(/([ ;])[a-zA-Z]{2}-[a-zA-Z]{2}([ ;\)])/, '\1xx-xx\2')
|
87
|
+
|
88
|
+
user_agent = normalize_blackberry(user_agent)
|
89
|
+
user_agent = normalize_android(user_agent)
|
90
|
+
|
91
|
+
return user_agent.strip
|
92
|
+
end
|
93
|
+
|
94
|
+
def first_slash(user_agent)
|
95
|
+
pos = user_agent.index('/')
|
96
|
+
return user_agent.length if pos.nil?
|
97
|
+
return pos
|
98
|
+
end
|
99
|
+
|
100
|
+
def second_slash(user_agent)
|
101
|
+
first = user_agent.index('/')
|
102
|
+
return user_agent.length if first.nil?
|
103
|
+
first = first + 1
|
104
|
+
second = user_agent.index('/', first)
|
105
|
+
return user_agent.length if second.nil?
|
106
|
+
return second
|
107
|
+
end
|
108
|
+
|
109
|
+
def first_space(user_agent)
|
110
|
+
pos = user_agent.index(' ')
|
111
|
+
return user_agent.length if pos.nil?
|
112
|
+
return pos
|
113
|
+
end
|
114
|
+
|
115
|
+
def first_open_paren(user_agent)
|
116
|
+
pos = user_agent.index('(')
|
117
|
+
return user_agent.length if pos.nil?
|
118
|
+
return pos
|
119
|
+
end
|
120
|
+
|
121
|
+
def remove_nokia_msisdn(user_agent)
|
122
|
+
if user_agent =~ /^(.+)NOKIA-MSISDN\:\ (.+)$/i
|
123
|
+
user_agent = $1
|
124
|
+
end
|
125
|
+
return user_agent
|
126
|
+
end
|
127
|
+
|
128
|
+
def remove_up_link_from_ua(user_agent)
|
129
|
+
pos = user_agent.index('UP.Link')
|
130
|
+
return user_agent unless pos
|
131
|
+
return user_agent.slice(0..(pos-1))
|
132
|
+
end
|
133
|
+
|
134
|
+
def normalize_blackberry(user_agent)
|
135
|
+
pos = user_agent.index('BlackBerry')
|
136
|
+
return user_agent if pos.nil?
|
137
|
+
return user_agent.slice(pos, user_agent.length - pos)
|
138
|
+
end
|
139
|
+
|
140
|
+
def normalize_android(user_agent)
|
141
|
+
return user_agent.sub(/(Android \d\.\d)([^; \/\)]+)/, '\1')
|
142
|
+
end
|
143
|
+
|
144
|
+
def parse(user_agent)
|
145
|
+
# grab all Agent/version strings as 'agents'
|
146
|
+
agents = Array.new
|
147
|
+
user_agent.split(/\s+/).each do |string|
|
148
|
+
if string =~ /\//
|
149
|
+
agents << string
|
150
|
+
end
|
151
|
+
end
|
152
|
+
return agents
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'etc'
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
module WurflDevice
|
5
|
+
class XmlLoader
|
6
|
+
def self.load_xml_file(wurfl_file, &blk)
|
7
|
+
devices = Hash.new
|
8
|
+
|
9
|
+
xml = Nokogiri::XML File.open(wurfl_file)
|
10
|
+
|
11
|
+
version = xml.xpath('//version/ver')[0].children.to_s rescue nil
|
12
|
+
last_updated = DateTime.parse(xml.xpath('//version/last_updated')[0].children.to_s) rescue nil
|
13
|
+
|
14
|
+
xml.xpath('//devices/*').each do |element|
|
15
|
+
wurfl_id = 'generic'
|
16
|
+
user_agent = 'generic'
|
17
|
+
fall_back = nil
|
18
|
+
if element.attributes["id"].to_s != "generic"
|
19
|
+
wurfl_id = element.attributes["id"].to_s
|
20
|
+
user_agent = element.attributes["user_agent"].to_s
|
21
|
+
user_agent = 'generic' if user_agent.empty?
|
22
|
+
fall_back = element.attributes["fall_back"].to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
device = Hash.new
|
26
|
+
device['id'] = wurfl_id
|
27
|
+
device['user_agent'] = user_agent
|
28
|
+
device['fall_back'] = fall_back
|
29
|
+
|
30
|
+
element.xpath('.//*').each do |group|
|
31
|
+
group_id = group.attributes["id"].to_s
|
32
|
+
next if group_id.empty?
|
33
|
+
group_capa = Hash.new
|
34
|
+
group.xpath('.//*').each do |capability|
|
35
|
+
name = capability.attributes["name"].to_s
|
36
|
+
next if name.empty?
|
37
|
+
group_capa[name] = WurflDevice.parse_string_value(capability.attributes["value"].to_s)
|
38
|
+
end
|
39
|
+
device[group_id] = group_capa
|
40
|
+
end
|
41
|
+
|
42
|
+
devices[wurfl_id] = device
|
43
|
+
yield device if block_given?
|
44
|
+
end
|
45
|
+
|
46
|
+
[devices, version, last_updated]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
describe WurflDevice do
|
5
|
+
describe :devices do
|
6
|
+
it "cache should be available" do
|
7
|
+
WurflDevice.is_initialized?.should == true
|
8
|
+
end
|
9
|
+
|
10
|
+
it "generic device should exists" do
|
11
|
+
device = WurflDevice.get_actual_device(WurflDevice::Constants::GENERIC)
|
12
|
+
device.id.should == WurflDevice::Constants::GENERIC
|
13
|
+
end
|
14
|
+
|
15
|
+
it "check for user agent matcher" do
|
16
|
+
user_agent = 'Mozilla/5.0 (SymbianOS/9.2; U; Series60/3.1 Nokia6120c/6.01; Profile/MIDP-2.0 Configuration/CLDC-1.1 ) AppleWebKit/413 (KHTML, like Gecko) Safari/413'
|
17
|
+
device = WurflDevice.get_device_from_ua(user_agent)
|
18
|
+
device.id.should == 'nokia_6120c_ver1_sub601'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
if RUBY_VERSION >= '1.9'
|
2
|
+
if ENV['ENABLE_SIMPLECOV']
|
3
|
+
require 'simplecov'
|
4
|
+
SimpleCov.start
|
5
|
+
end
|
6
|
+
else
|
7
|
+
require 'rubygems'
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'faker'
|
11
|
+
require 'rspec/core'
|
12
|
+
|
13
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
|
14
|
+
|
15
|
+
RSpec.configure do |config|
|
16
|
+
config.mock_with :rr
|
17
|
+
config.color_enabled = true
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'wurfl_device'
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
$LOAD_PATH.push File.expand_path("../lib", __FILE__)
|
4
|
+
|
5
|
+
require "wurfl_device/version"
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "wurfl_device"
|
9
|
+
s.version = WurflDevice::VERSION.dup
|
10
|
+
s.platform = Gem::Platform::RUBY
|
11
|
+
s.authors = ["Allan Ralph Hutalla"]
|
12
|
+
s.email = ["ahutalla@gmail.com"]
|
13
|
+
s.homepage = "http://github.com/aputs/wurfl_device"
|
14
|
+
s.summary = %q{Ruby client library for mobile handset detection}
|
15
|
+
s.description = %q{Ruby client library for mobile handset detection}
|
16
|
+
|
17
|
+
s.add_dependency 'thor'
|
18
|
+
s.add_dependency 'nokogiri'
|
19
|
+
s.add_dependency 'redis'
|
20
|
+
s.add_dependency 'text'
|
21
|
+
|
22
|
+
s.add_development_dependency 'bundler', '>= 1.0.10'
|
23
|
+
s.add_development_dependency 'rake', '>= 0.9.2'
|
24
|
+
s.add_development_dependency 'rspec-core', '~> 2.0'
|
25
|
+
s.add_development_dependency 'rspec-expectations', '~> 2.0'
|
26
|
+
s.add_development_dependency 'rr', '~> 1.0'
|
27
|
+
s.add_development_dependency 'faker', '~> 0.9'
|
28
|
+
s.add_development_dependency 'simplecov', '~> 0.5.3'
|
29
|
+
|
30
|
+
s.files = `git ls-files`.split("\n")
|
31
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
32
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
33
|
+
s.require_paths = ["lib"]
|
34
|
+
s.extra_rdoc_files = ["LICENSE", "README.md"]
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wurfl_device
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.10
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Allan Ralph Hutalla
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-10-01 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: thor
|
16
|
+
requirement: &66803460 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *66803460
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: nokogiri
|
27
|
+
requirement: &66802960 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *66802960
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: redis
|
38
|
+
requirement: &66802500 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *66802500
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: text
|
49
|
+
requirement: &66802060 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *66802060
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: bundler
|
60
|
+
requirement: &66801540 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 1.0.10
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *66801540
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: &66801020 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 0.9.2
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *66801020
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: rspec-core
|
82
|
+
requirement: &66800540 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ~>
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '2.0'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *66800540
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: rspec-expectations
|
93
|
+
requirement: &66800060 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ~>
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '2.0'
|
99
|
+
type: :development
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: *66800060
|
102
|
+
- !ruby/object:Gem::Dependency
|
103
|
+
name: rr
|
104
|
+
requirement: &66799560 !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '1.0'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: *66799560
|
113
|
+
- !ruby/object:Gem::Dependency
|
114
|
+
name: faker
|
115
|
+
requirement: &66799000 !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ~>
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0.9'
|
121
|
+
type: :development
|
122
|
+
prerelease: false
|
123
|
+
version_requirements: *66799000
|
124
|
+
- !ruby/object:Gem::Dependency
|
125
|
+
name: simplecov
|
126
|
+
requirement: &66798340 !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ~>
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 0.5.3
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: *66798340
|
135
|
+
description: Ruby client library for mobile handset detection
|
136
|
+
email:
|
137
|
+
- ahutalla@gmail.com
|
138
|
+
executables:
|
139
|
+
- wurfldevice
|
140
|
+
extensions: []
|
141
|
+
extra_rdoc_files:
|
142
|
+
- LICENSE
|
143
|
+
- README.md
|
144
|
+
files:
|
145
|
+
- .gitignore
|
146
|
+
- Gemfile
|
147
|
+
- LICENSE
|
148
|
+
- README.md
|
149
|
+
- Rakefile
|
150
|
+
- bin/wurfldevice
|
151
|
+
- lib/wurfl_device.rb
|
152
|
+
- lib/wurfl_device/capability.rb
|
153
|
+
- lib/wurfl_device/cli.rb
|
154
|
+
- lib/wurfl_device/constants.rb
|
155
|
+
- lib/wurfl_device/device.rb
|
156
|
+
- lib/wurfl_device/ui.rb
|
157
|
+
- lib/wurfl_device/user_agent_matcher.rb
|
158
|
+
- lib/wurfl_device/version.rb
|
159
|
+
- lib/wurfl_device/xml_loader.rb
|
160
|
+
- spec/cache/device_spec.rb
|
161
|
+
- spec/spec_helper.rb
|
162
|
+
- wurfl_device.gemspec
|
163
|
+
homepage: http://github.com/aputs/wurfl_device
|
164
|
+
licenses: []
|
165
|
+
post_install_message:
|
166
|
+
rdoc_options: []
|
167
|
+
require_paths:
|
168
|
+
- lib
|
169
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
170
|
+
none: false
|
171
|
+
requirements:
|
172
|
+
- - ! '>='
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '0'
|
175
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
176
|
+
none: false
|
177
|
+
requirements:
|
178
|
+
- - ! '>='
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
requirements: []
|
182
|
+
rubyforge_project:
|
183
|
+
rubygems_version: 1.8.6
|
184
|
+
signing_key:
|
185
|
+
specification_version: 3
|
186
|
+
summary: Ruby client library for mobile handset detection
|
187
|
+
test_files:
|
188
|
+
- spec/cache/device_spec.rb
|
189
|
+
- spec/spec_helper.rb
|