wurfl_device 0.0.10
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.
- 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
|