wurfl 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,153 +1,167 @@
1
- = WURFL
2
-
3
- WURFL is a collection of libraries and command line tools for using and manipulating the WURFL. To start using it, simply install the gem:
4
-
5
- sudo gem install wurfl
6
-
7
- == Tools
8
-
9
- All tools are run via wurfltools.rb
10
-
11
- === loader
12
-
13
- Is used to parse and load a WURFL XML file into memory or save a
14
- PStore database that is used by most of the other tools. This
15
- application creates WURFL PStore databases that are essential for use
16
- with the other Ruby applications.
17
-
18
- === inspector
19
-
20
- Is a tool that will let you do various searches and queries on the
21
- WURFL. This is a very simple, yet powerful program for finding
22
- information about the handsets in the WURFL. See the wurflinspector
23
- examples section for usage.
24
-
25
- ==== Usage
26
-
27
- The command below will search through all handsets and return the ids
28
- of handsets that match the passed Ruby boolean evaluation
29
-
30
- This command will return all handsets that have more than 2 colors.
31
-
32
- wurfltools.rb inspector -d pstorehandsets.db -s '{ |hand| hand["colors"].to_i > 2 }'"
33
-
34
- The Ruby query must go in between the single quote marks and needs to
35
- declare the WurflHandset instance variable name.
36
-
37
- This command shows you how you can cheat with the current design of
38
- the wurflinspector and print more user friendly results. This example
39
- assumes you have the command line programs sort and uniq, but that is
40
- only to make the output look better. This example does the same as the
41
- above, except that it prints out the brand name and model name of the
42
- matching handsets instead of the WURFL id.
43
-
44
- Note: this should all go into one command line call
45
-
46
- wurfltools.rb inspector -d pstorehandsets.db -s '{|hand| puts
47
- "#{hand["brand_name"]} #{hand["model_name"]}" if hand["colors"].to_i >
48
- 2}' | sort | uniq"
49
-
50
-
51
- The following individual handset query commands will tell the value of
52
- capabilities and from where it obtained the setting.
53
-
54
- A command to query the handset with the id sonyericsson_t300_ver1
55
- for all of its' capabilities:
56
-
57
- wurfltools.rb inspector -d pstorehandsets.db -i sonyericsson_t300_ver1
58
-
59
- A command to query the handset with the id sonyericsson_t300_ver1 for
60
- backlight capability:
61
-
62
- wurfltools.rb inspector -d pstorehandsets.db -i sonyericsson_t300_ver1 -q
63
- backlight
64
-
65
- === sanitycheck
66
-
67
- Is a partial WURFL validating program. It does a few simple
68
- checks to make sure the XML structure is parse-able by the wurflloader.
69
- If you receive loading errors by the wurflloader, then you can run the
70
- wurflsanitycheck program to find the lines in the XML file that might
71
- be causing the problem.
72
-
73
- === comparator
74
-
75
- Is a another simple program that will find the differences from two
76
- WURFL Ruby PStore databases. This is another way of finding changes
77
- from the different versions of the WURFL without running a diff on the
78
- XML files.
79
-
80
- === uaproftowurfl
81
-
82
- Is a program that takes UAProfiles and creates an equivalent WURFL
83
- entry. It holds all of the mappings used to convert a UAProfile to a
84
- WURFL entry. This program alone makes using the Ruby tools worth it.
85
-
86
- The mappings are not fully complete and can certainly use your input
87
- in improving them.
88
-
89
- About the source code:
90
- The main piece of code to read are the methods under the
91
- "UAProfile Mappings" comment.
92
-
93
- For now you can ignore the details above this comment.
94
- Basically each method is the name of a UAProfifle component.
95
- When you parse a UAProfile file it will call each UAProfile
96
- component's method name.
97
-
98
- So you can simply look at UAProfile component's method to see
99
- how it maps to the WURFL.
100
- If a component is not found as a method it will be logged to
101
- Standard Error. For this log one can then add the method to
102
- the UAProfToWurfl class later.
103
-
104
- ==== Usage
105
-
106
- A simple usage is:
107
-
108
- wurfltools.rb uaproftowurfl sampleprofile.xml
109
-
110
- Example use from a bash shell with many profiles:
111
-
112
- for i in `ls profiles`; do
113
- wurfltools.rb uaproftowurfl profiles/$i >output/$i.wurfl
114
- 2> output/$i.parser.err;
115
- done
116
-
117
- This assumes that you have a profiles directory with all of the UAProf
118
- file you wish to parse and a output directory to place the results and
119
- errors.
120
-
121
- === uaprofwurflcomparator
122
-
123
- Is a program that compares UAProfiles against the equivalent WURFL
124
- entries. It takes a file that contains an UAProfile URL and an
125
- User-Agent per line, in addition to a Ruby Wurfl PStore database. It
126
- then compares the UAProfile against the Wurfl that matches the same
127
- User Agent. The output is a list of the differences and a WURFL
128
- formatted entry showing the differences.
129
-
130
- ==== Usage
131
-
132
- You pass the program a directory to save the UAProfile files taken
133
- from the given URL, a file that holds the UAProfil URL and User-Agent
134
- mappings, and the Ruby PStore database that holds the WURFL.
135
-
136
- The following is a simple example of execution
137
-
138
- wurfltools.rb uaprofwurflcomparator -d ./profiles -f all-profile.2003-08.log -c -w wurfl.db
139
-
140
- == Authors
141
-
142
- * Zev Blut (Original Author)
143
- * Paul McMahon (gem installer, refactorings)
144
-
145
- == Copyright
146
-
147
- Copyright (c) 2009, mobalean (http://www.mobalean.com/)
148
-
149
- Copyright (c) 2003, Ubiquitous Business Technology (http://ubit.com)
150
- All rights reserved.
151
-
152
- Send enquiries to: mailto:info@mobalean.com
153
-
1
+ = WURFL
2
+
3
+ WURFL is a collection of libraries and command line tools for using and manipulating the WURFL. To start using it, simply install the gem:
4
+
5
+ sudo gem install wurfl
6
+
7
+ == Tools
8
+
9
+ All tools are run via wurfltools.rb
10
+
11
+ === loader
12
+
13
+ Is used to parse and load a WURFL XML file into memory or save a
14
+ PStore database that is used by most of the other tools. This
15
+ application creates WURFL PStore databases that are essential for use
16
+ with the other Ruby applications.
17
+
18
+ === inspector
19
+
20
+ Is a tool that will let you do various searches and queries on the
21
+ WURFL. This is a very simple, yet powerful program for finding
22
+ information about the handsets in the WURFL. See the wurflinspector
23
+ examples section for usage.
24
+
25
+ ==== Usage
26
+
27
+ The command below will search through all handsets and return the ids
28
+ of handsets that match the passed Ruby boolean evaluation
29
+
30
+ This command will return all handsets that have more than 2 colors.
31
+
32
+ wurfltools.rb inspector -d pstorehandsets.db -s '{ |hand| hand["colors"].to_i > 2 }'"
33
+
34
+ The Ruby query must go in between the single quote marks and needs to
35
+ declare the WurflHandset instance variable name.
36
+
37
+ This command shows you how you can cheat with the current design of
38
+ the wurflinspector and print more user friendly results. This example
39
+ assumes you have the command line programs sort and uniq, but that is
40
+ only to make the output look better. This example does the same as the
41
+ above, except that it prints out the brand name and model name of the
42
+ matching handsets instead of the WURFL id.
43
+
44
+ Note: this should all go into one command line call
45
+
46
+ wurfltools.rb inspector -d pstorehandsets.db -s '{|hand| puts
47
+ "#{hand["brand_name"]} #{hand["model_name"]}" if hand["colors"].to_i >
48
+ 2}' | sort | uniq"
49
+
50
+
51
+ The following individual handset query commands will tell the value of
52
+ capabilities and from where it obtained the setting.
53
+
54
+ A command to query the handset with the id sonyericsson_t300_ver1
55
+ for all of its' capabilities:
56
+
57
+ wurfltools.rb inspector -d pstorehandsets.db -i sonyericsson_t300_ver1
58
+
59
+ A command to query the handset with the id sonyericsson_t300_ver1 for
60
+ backlight capability:
61
+
62
+ wurfltools.rb inspector -d pstorehandsets.db -i sonyericsson_t300_ver1 -q
63
+ backlight
64
+
65
+ === uamatch
66
+
67
+ Is a tool that will let you do various searches and queries on the
68
+ WURFL based on user-agent strings. This is a very simple, yet powerful
69
+ program for finding information about the handsets in the WURFL.
70
+ See the uamatch examples section for usage.
71
+
72
+ The uamatcher will retrieve the handset descriptions with the closest
73
+ Levenshtein distance (also called the edit distance) to the user-agent
74
+ string you pass as argument.
75
+
76
+ For details see: http://en.wikipedia.org/wiki/Levenshtein_distance
77
+
78
+ When more than one handset has the same distance to the user agent string queried,
79
+ the command will iterate over the result set and print the result for each handset.
80
+
81
+ The found wurfl_ids can serve as input to the inspector command.
82
+ You can also specifically query for a certain capability.
83
+
84
+ ==== Usage
85
+
86
+ The following individual handset query commands will tell the value of
87
+ capabilities and from where it obtained the setting.
88
+
89
+ A command to query the handset with the user-agent SL55 for all of its capabilities:
90
+
91
+ wurfltools.rb uamatch -d pstorehandsets.db -u SL55
92
+
93
+ A command to query the handset with the id SL55 for the wallpaper_jpg capability:
94
+
95
+ wurfltools.rb uamatch -d pstorehandsets.db -u SL55 -q wallpaper_jpg
96
+
97
+ === sanitycheck
98
+
99
+ Is a partial WURFL validating program. It does a few simple
100
+ checks to make sure the XML structure is parse-able by the wurflloader.
101
+ If you receive loading errors by the wurflloader, then you can run the
102
+ wurflsanitycheck program to find the lines in the XML file that might
103
+ be causing the problem.
104
+
105
+ === comparator
106
+
107
+ Is a another simple program that will find the differences from two
108
+ WURFL Ruby PStore databases. This is another way of finding changes
109
+ from the different versions of the WURFL without running a diff on the
110
+ XML files.
111
+
112
+ === uaproftowurfl
113
+
114
+ Is a program that takes UAProfiles and creates an equivalent WURFL
115
+ entry. It holds all of the mappings used to convert a UAProfile to a
116
+ WURFL entry. This program alone makes using the Ruby tools worth it.
117
+
118
+ The mappings are not fully complete and can certainly use your input
119
+ in improving them.
120
+
121
+ About the source code:
122
+ The main piece of code to read are the methods under the
123
+ "UAProfile Mappings" comment.
124
+
125
+ For now you can ignore the details above this comment.
126
+ Basically each method is the name of a UAProfifle component.
127
+ When you parse a UAProfile file it will call each UAProfile
128
+ component's method name.
129
+
130
+ So you can simply look at UAProfile component's method to see
131
+ how it maps to the WURFL.
132
+ If a component is not found as a method it will be logged to
133
+ Standard Error. For this log one can then add the method to
134
+ the UAProfToWurfl class later.
135
+
136
+ ==== Usage
137
+
138
+ A simple usage is:
139
+
140
+ wurfltools.rb uaproftowurfl sampleprofile.xml
141
+
142
+ Example use from a bash shell with many profiles:
143
+
144
+ for i in `ls profiles`; do
145
+ wurfltools.rb uaproftowurfl profiles/$i >output/$i.wurfl
146
+ 2> output/$i.parser.err;
147
+ done
148
+
149
+ This assumes that you have a profiles directory with all of the UAProf
150
+ file you wish to parse and a output directory to place the results and
151
+ errors.
152
+
153
+ == Authors
154
+
155
+ * Zev Blut (Original Author)
156
+ * Paul McMahon (gem installer, refactorings)
157
+ * Kai W. Zimmermann (uamatch)
158
+
159
+ == Copyright
160
+
161
+ Copyright (c) 2009, mobalean (http://www.mobalean.com/)
162
+
163
+ Copyright (c) 2003, Ubiquitous Business Technology (http://ubit.com)
164
+ All rights reserved.
165
+
166
+ Send enquiries to: mailto:info@mobalean.com
167
+
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.2
1
+ 1.2.0
@@ -13,10 +13,11 @@ class Wurfl::Command::Comparator < Wurfl::Command
13
13
 
14
14
  def display_differences(hand1, hand2)
15
15
  puts "-------------------------------------"
16
- puts "Handset: #{hand1.user_agent} :ID: #{hand1.wurfl_id}"
17
- hand1.keys.each do |key|
16
+ puts "WURFL_ID: #{hand1.wurfl_id}"
17
+ puts "Handset 1: #{hand1.user_agent}"
18
+ puts "Handset 2: #{hand2.user_agent}"
19
+ hand1.differences(hand2).each do |key|
18
20
  v1, v2 = hand1[key], hand2[key]
19
- next if v1.nil? || v2.nil? || v1 == v2
20
21
  puts "Key:#{key}"
21
22
  puts "h1>:#{hand1[key]}"
22
23
  puts "h2<:#{hand2[key]}"
@@ -49,40 +50,22 @@ class Wurfl::Command::Comparator < Wurfl::Command
49
50
  puts "Comparing files: #{ARGV[0]} and #{ARGV[1]}"
50
51
  puts "-------------------------------------"
51
52
 
52
- if wurfl1.size > wurfl2.size
53
- mwurfl = wurfl1
54
- lwurfl = wurfl2
55
- else
56
- mwurfl = wurfl2
57
- lwurfl = wurfl1
58
- end
59
-
60
- notfound = Array.new
61
- different = Array.new
62
- mwurfl.each do |key,handset|
63
- if lwurfl.key?(key)
64
- if handset != lwurfl[key]
65
- different << [handset,lwurfl[key]]
66
- display_differences(handset,lwurfl[key])
67
- end
68
- else
69
- notfound<< handset
53
+ wurfl1_unknown, wurfl2_unknown, different = [],[],[]
54
+ (wurfl1.keys | wurfl2.keys).each do |key|
55
+ handset1, handset2 = wurfl1[key], wurfl2[key]
56
+ if !handset1
57
+ wurfl1_unknown << key
58
+ elsif !handset2
59
+ wurfl2_unknown << key
60
+ elsif handset1 != handset2
61
+ display_differences(handset1,handset2)
70
62
  end
71
63
  end
72
64
 
73
65
 
74
66
  puts "Comparision complete."
75
67
 
76
- puts "Not Found Handsets: #{notfound.size}"
77
- puts "||||||||||||||||||||||||||||||||||||"
78
- notfound = notfound.sort { |x,y| y.wurfl_id <=> x.wurfl_id }
79
- notfound.each { |hand| puts hand.wurfl_id }
80
- puts "||||||||||||||||||||||||||||||||||||"
81
-
82
- puts "Different handsets: #{different.size}"
83
- puts "||||||||||||||||||||||||||||||||||||"
84
- different = different.sort { |x,y| y.first.wurfl_id <=> x.first.wurfl_id }
85
-
86
- puts "||||||||||||||||||||||||||||||||||||"
68
+ puts "Handsets not found in wurfl1: #{wurfl1_unknown.inspect}"
69
+ puts "Handsets not found in wurfl2: #{wurfl2_unknown.inspect}"
87
70
  end
88
71
  end
@@ -0,0 +1,82 @@
1
+ require "wurfl/command"
2
+ require "wurfl/utils"
3
+ require "wurfl/handset"
4
+ require "wurfl/user_agent_matcher"
5
+ require "getoptlong"
6
+
7
+ =begin
8
+ The uamatch command itself is based on the inspector command found in this
9
+ library.
10
+
11
+ Author: Kai W. Zimmermann (kwz@kai-zimmermann.de)
12
+ =end
13
+ class Wurfl::Command::Uamatch < Wurfl::Command
14
+ include Wurfl::Utils
15
+
16
+ def usage
17
+ puts "Usage: wurfltools.rb uamatch [-u user_agent [-q attributename]] -d pstorefile"
18
+ puts "Examples:"
19
+ puts "wurfltools.rb uamatch -d pstorehandsets.db -u SL55"
20
+ puts "wurfltools.rb uamatch -d pstorehandsets.db -u SL55 -q wallpaper_jpg"
21
+ exit 1
22
+ end
23
+
24
+ def execute
25
+ pstorefile = nil
26
+ useragent = nil
27
+ query = nil
28
+ begin
29
+ opt = GetoptLong.new(
30
+ ["-d","--database", GetoptLong::REQUIRED_ARGUMENT],
31
+ ["-u","--useragent", GetoptLong::REQUIRED_ARGUMENT],
32
+ ["-q","--query", GetoptLong::REQUIRED_ARGUMENT]
33
+ )
34
+
35
+ opt.each do |arg,val|
36
+ case arg
37
+ when "-d"
38
+ pstorefile = val
39
+ when "-u"
40
+ useragent = val
41
+ when "-q"
42
+ query = val
43
+ else
44
+ usage
45
+ end
46
+ end
47
+
48
+ rescue => err
49
+ usage
50
+ end
51
+
52
+ if !pstorefile
53
+ puts "You must specify a Wurfl PStore db"
54
+ usage
55
+ end
56
+
57
+ begin
58
+ handsets, = load_wurfl_pstore(pstorefile)
59
+ puts "Number of Handsets: #{handsets.length}"
60
+ uamatch = Wurfl::UserAgentMatcher.new(handsets)
61
+ rescue => err
62
+ STDERR.puts "Error with file #{pstorefile}"
63
+ STDERR.puts err.message
64
+ exit 1
65
+ end
66
+
67
+ if useragent
68
+ matches, distance = uamatch.match_handsets(useragent)
69
+ puts "Shortest distance #{distance}, #{matches.length} match#{'es' if (matches.length!=1)}"
70
+ matches.each do |handset|
71
+ puts "Handset wurfl id: #{handset.wurfl_id}"
72
+ puts "User_agent found: #{handset.user_agent}"
73
+ if query
74
+ puts "Result of handset query: #{query}"
75
+ rez = handset.get_value_and_owner(query)
76
+ puts "#{rez[0]} from #{rez[1]}"
77
+ end
78
+ end
79
+ exit 0
80
+ end
81
+ end
82
+ end
data/lib/wurfl/handset.rb CHANGED
@@ -86,24 +86,9 @@ class Wurfl::Handset
86
86
  other.keys.all? {|key| other[key] == self[key] }
87
87
  end
88
88
 
89
- # A method to compare a handset's values against another handset.
90
- # Parameters:
91
- # other: is the another WurflHandset to compare against
92
- # Returns:
93
- # An array of the different values.
94
- # Each entry in the Array is an Array of three values.
95
- # The first value is the key in which both handsets have different values.
96
- # The second is the other handset's value for the key.
97
- # The third is the handset id from where the other handset got it's value.
98
- def compare(other)
99
- differences = Array.new
100
- self.keys.each do |key|
101
- oval,oid = other.get_value_and_owner(key)
102
- if @capabilityhash[key].to_s != oval.to_s
103
- differences << [key,oval,oid]
104
- end
105
- end
106
- differences
89
+ def differences(other)
90
+ keys = (self.keys | other.keys)
91
+ keys.find_all {|k| self[k] != other[k]}
107
92
  end
108
93
 
109
94
  class NullHandset
@@ -0,0 +1,142 @@
1
+ module Wurfl; end
2
+
3
+ =begin
4
+ A class that lists wurfl handsets that match user_agents using the shortest
5
+ Levenshtein distance, also sometimes called the edit distance,
6
+ see http://en.wikipedia.org/wiki/Levenshtein_distance
7
+
8
+ The implementation of the Levenshtein distance used here is based on the
9
+ algorithm found in the Text gem originally written by Paul Battley
10
+ (pbattley@gmail.com)
11
+
12
+ The implementation given here makes heavy use of optimizations based on
13
+ the estimation of the lower bound that can be achieved. Depending on the
14
+ length of the user agent this brought an over all increase by a factor of
15
+ about 40, although it renders the code slightly unreadable. In general the
16
+ longer the user agent string and the greater the result distance, the longer
17
+ the computation takes.
18
+
19
+ Author: Kai W. Zimmermann (kwz@kai-zimmermann.de)
20
+ =end
21
+ class Wurfl::UserAgentMatcher
22
+ # Constructor
23
+ # Parameters:
24
+ # handsets: A hashtable of wurfl handsets indexed by wurfl_id.
25
+ def initialize(handsets)
26
+ @handsets = handsets
27
+ @longest_user_agent_length = @handsets.values.inject(0) do |m,hand|
28
+ hand.user_agent.length > m ? hand.user_agent.length : m
29
+ end
30
+ @d=(0..@longest_user_agent_length).to_a
31
+ end
32
+
33
+ # A method to retrieve a list of the uamatcher's handsets that match
34
+ # the passed user_agent closest using the Levenshtein distance.
35
+ # Parameters:
36
+ # user_agent: is a user_agent string to be matched
37
+ # Returns:
38
+ # An Array of all WurflHandsets that match the user_agent closest with the
39
+ # same distance
40
+ # The Levenshtein distance for these matches
41
+ def match_handsets(user_agent)
42
+ rez = []
43
+ shortest_distance = [user_agent.length, @longest_user_agent_length].max
44
+ s = user_agent.unpack(unpack_rule)
45
+
46
+ @handsets.values.each do |hand|
47
+ distance = levenshtein_distance(user_agent, hand.user_agent, shortest_distance, s)
48
+ # found a shorter distance match, flush old results
49
+ rez.clear if shortest_distance > distance
50
+
51
+ if shortest_distance >= distance
52
+ # always add the first handset matched and each that has the same
53
+ # distance as the shortest distance so far
54
+ rez << hand
55
+ shortest_distance = distance
56
+ end
57
+
58
+ break if shortest_distance == 0
59
+ end
60
+
61
+ return rez, shortest_distance
62
+ end
63
+
64
+ private
65
+
66
+ # A method to estimate and compute the Levenshtein distance (LD) based on the
67
+ # implementation from the Text gem. The implementation given here applies
68
+ # known upper and lower bounds as found at:
69
+ # http://en.wikipedia.org/wiki/Levenshtein_distance
70
+ # LD is always at least the difference of the sizes of the two strings.
71
+ # -> We can safely discard the handset if the least distance is longer than
72
+ # the current shortest distance
73
+ # LD is zero if and only if the strings are identical.
74
+ # -> We can optimize the test for equality and stop searching after an exact
75
+ # match
76
+ # Parameters:
77
+ # str1: is the user-agent to look up
78
+ # str2: is the user-agent to compare against
79
+ # min: is the minimum distance found so far
80
+ # s: the unpacked version of the user-agent string we look up
81
+ # Returns:
82
+ # It returns the least bound estimation if the least bound is already greater
83
+ # than the current minimum distance
84
+ # Otherwise it will compute the Levenshtein distance of str1 and str2
85
+ # It optimizes the check for equality
86
+ def levenshtein_distance(str1, str2, min, s)
87
+ diff = (str1.length - str2.length).abs
88
+ return 0 if diff == 0 && str1 == str2
89
+ return diff if diff > min
90
+ t = str2.unpack(unpack_rule)
91
+ distance(s, t, min)
92
+ end
93
+
94
+ # Compute the Levenshtein distance or stop if the minimum found so far will
95
+ # be exceeded.
96
+ # Optimizations: Avoid GC, where possible reuse array
97
+ # The distance computation in the outer loop is monotonously decreasing thus
98
+ # we can safely stop if the estimated possible minimum exceeds the current
99
+ # minimum
100
+ # Parameters:
101
+ # s: is the first string, already unpacked
102
+ # t: is the second string, already unpacked
103
+ # min: is the minimum distance found so far
104
+ # Returns:
105
+ # The routine returns the computed distance or the minimum found so far if the # distance
106
+ # computed so far exceeds the minimum
107
+ def distance(s, t, min)
108
+ n = s.length
109
+ m = t.length
110
+ return m if 0 == n
111
+ return n if 0 == m
112
+
113
+ d = @d # Optimization: Avoid GC by reusing array
114
+ (0...m).each do |j|
115
+ d[j] = j
116
+ end
117
+
118
+ x = 0
119
+ (0...n).each do |i|
120
+ e = i+1
121
+ (0...m).each do |j|
122
+ cost = (s[i] == t[j]) ? 0 : 1
123
+ x = d[j+1] + 1 # insertion
124
+ y = e+1
125
+ x = y if y < x # deletion
126
+ z = d[j] + cost
127
+ x = z if z < x # substitution
128
+ d[j] = e
129
+ e = x
130
+ end
131
+ d[m] = x
132
+ # estimate the minimum LD that still can be achieved, this will be
133
+ # increasing monotonously stop once we exceed the current minimum
134
+ return x - n + i + 1 if x - n + i + 1 > min
135
+ end
136
+ x
137
+ end
138
+
139
+ def unpack_rule
140
+ $KCODE =~ /^U/i ? 'U*' : 'C*'
141
+ end
142
+ end
data/test/handset_test.rb CHANGED
@@ -6,6 +6,9 @@ class TestHandset < Test::Unit::TestCase
6
6
  def setup
7
7
  @f = Wurfl::Handset.new("f", "f", nil)
8
8
  @h = Wurfl::Handset.new("h", "h", @f)
9
+
10
+ @f2 = Wurfl::Handset.new("f2", "f2_ua", nil)
11
+ @h2 = Wurfl::Handset.new("h2","h2_ua", @f2)
9
12
  end
10
13
 
11
14
  def test_f
@@ -51,20 +54,34 @@ class TestHandset < Test::Unit::TestCase
51
54
  assert @h == h2
52
55
  end
53
56
 
54
- def test_compare
55
- f2 = Wurfl::Handset.new("f2", "f2", nil)
56
- h2 = Wurfl::Handset.new("h2","h2", f2)
57
- assert @h.compare(@f).empty?
57
+ def test_differences_handset_with_unmodified_fallback
58
+ assert @h.differences(@f).empty?
59
+ end
60
+
61
+ def test_differences_handset_with_identical_handset
62
+ assert @h.differences(@h2).empty?
63
+ end
64
+
65
+ def test_differences_handset_that_has_extra_key
58
66
  @h["k"] = "v"
67
+ assert_equal ["k"], @h.differences(@h2)
68
+ end
59
69
 
60
- assert_equal [["k", nil, nil]], @h.compare(h2)
70
+ def test_differences_other_handset_that_has_extra_key
71
+ @h2["k"] = "v"
72
+ assert_equal ["k"], @h.differences(@h2)
73
+ end
61
74
 
62
- h2["k"] = "v2"
63
- assert_equal [["k", "v2", "h2"]], @h.compare(h2)
75
+ def test_differences_handset_that_has_differing_key_value
76
+ @h["k"] = "v"
77
+ @h2["k"] = "v2"
78
+ assert_equal ["k"], @h.differences(@h2)
79
+ end
64
80
 
81
+ def test_differences_handsets_with_differening_fallback_key_value
65
82
  @f["j"] = "1"
66
- f2["j"] = "2"
67
- assert_equal [["k", "v2", "h2"], ["j", "2", "f2"]], @h.compare(h2)
83
+ @f2["j"] = "2"
84
+ assert_equal ["j"], @h.differences(@h2)
68
85
  end
69
86
 
70
87
  end
@@ -0,0 +1,34 @@
1
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
2
+ require 'wurfl/user_agent_matcher'
3
+ require 'wurfl/loader'
4
+ require 'test/unit'
5
+
6
+ class UserAgentMatcherTest < Test::Unit::TestCase
7
+ def setup
8
+ loader = Wurfl::Loader.new
9
+ handsets, fallbacks = loader.load_wurfl(File.join(File.dirname(__FILE__), "data", "wurfl.simple.xml"))
10
+ @matcher = Wurfl::UserAgentMatcher.new(handsets)
11
+ end
12
+
13
+ def test_empty_user_agent
14
+ a, shortest_distance = @matcher.match_handsets("")
15
+ assert_equal 1, a.size
16
+ assert_equal "generic_xhtml", a.first.wurfl_id
17
+ assert_equal 4, shortest_distance
18
+ end
19
+
20
+ def test_matching_user_agent
21
+ a, shortest_distance = @matcher.match_handsets("generic")
22
+ assert_equal 1, a.size
23
+ assert_equal "generic", a.first.wurfl_id
24
+ assert_equal 0, shortest_distance
25
+ end
26
+
27
+ def test_iphone
28
+ s = "Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en)"
29
+ a, shortest_distance = @matcher.match_handsets(s)
30
+ assert_equal 1, a.size
31
+ assert_equal "apple_generic", a.first.wurfl_id
32
+ assert_equal 26, shortest_distance
33
+ end
34
+ end
data/wurfl.gemspec CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{wurfl}
5
- s.version = "1.1.2"
5
+ s.version = "1.2.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Paul McMahon", "Zev Blut"]
9
- s.date = %q{2009-07-13}
9
+ s.date = %q{2009-07-22}
10
10
  s.default_executable = %q{wurfltools.rb}
11
11
  s.description = %q{TODO}
12
12
  s.email = %q{info@mobalean.com}
@@ -27,15 +27,17 @@ Gem::Specification.new do |s|
27
27
  "lib/wurfl/command/inspector.rb",
28
28
  "lib/wurfl/command/loader.rb",
29
29
  "lib/wurfl/command/sanitycheck.rb",
30
+ "lib/wurfl/command/uamatch.rb",
30
31
  "lib/wurfl/command/uaproftowurfl.rb",
31
- "lib/wurfl/command/uaprofwurflcomparator.rb",
32
32
  "lib/wurfl/handset.rb",
33
33
  "lib/wurfl/loader.rb",
34
34
  "lib/wurfl/uaproftowurfl.rb",
35
+ "lib/wurfl/user_agent_matcher.rb",
35
36
  "lib/wurfl/utils.rb",
36
37
  "test/data/wurfl.simple.xml",
37
38
  "test/handset_test.rb",
38
39
  "test/loader_test.rb",
40
+ "test/user_agent_matcher_test.rb",
39
41
  "test/utils_test.rb",
40
42
  "wurfl.gemspec"
41
43
  ]
@@ -49,7 +51,8 @@ Gem::Specification.new do |s|
49
51
  s.test_files = [
50
52
  "test/loader_test.rb",
51
53
  "test/handset_test.rb",
52
- "test/utils_test.rb"
54
+ "test/utils_test.rb",
55
+ "test/user_agent_matcher_test.rb"
53
56
  ]
54
57
 
55
58
  if s.respond_to? :specification_version then
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wurfl
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul McMahon
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-07-13 00:00:00 +09:00
13
+ date: 2009-07-22 00:00:00 +09:00
14
14
  default_executable: wurfltools.rb
15
15
  dependencies: []
16
16
 
@@ -35,15 +35,17 @@ files:
35
35
  - lib/wurfl/command/inspector.rb
36
36
  - lib/wurfl/command/loader.rb
37
37
  - lib/wurfl/command/sanitycheck.rb
38
+ - lib/wurfl/command/uamatch.rb
38
39
  - lib/wurfl/command/uaproftowurfl.rb
39
- - lib/wurfl/command/uaprofwurflcomparator.rb
40
40
  - lib/wurfl/handset.rb
41
41
  - lib/wurfl/loader.rb
42
42
  - lib/wurfl/uaproftowurfl.rb
43
+ - lib/wurfl/user_agent_matcher.rb
43
44
  - lib/wurfl/utils.rb
44
45
  - test/data/wurfl.simple.xml
45
46
  - test/handset_test.rb
46
47
  - test/loader_test.rb
48
+ - test/user_agent_matcher_test.rb
47
49
  - test/utils_test.rb
48
50
  - wurfl.gemspec
49
51
  has_rdoc: true
@@ -76,3 +78,4 @@ test_files:
76
78
  - test/loader_test.rb
77
79
  - test/handset_test.rb
78
80
  - test/utils_test.rb
81
+ - test/user_agent_matcher_test.rb
@@ -1,224 +0,0 @@
1
- require "wurfl/command"
2
- require "getoptlong"
3
- require "net/http"
4
-
5
- require "wurfl/uaproftowurfl"
6
- require "wurfl/handset"
7
- require "wurfl/utils"
8
- include Wurfl::Utils
9
-
10
- # An addition to the UAProf to Wurfl to generate a WurflHandset from the UAProf.
11
- class Wurfl::UAProfToWURLF
12
- def make_wurfl_handset
13
- hand = Wurfl::Handset.new("UAProf",@wurfl["user_agent"])
14
- @wurflgroups.each do |group|
15
- @wurfl[group].sort.each do |key,value|
16
- hand[key] = value
17
- end
18
- end
19
- return hand
20
- end
21
- end
22
-
23
-
24
- class Wurfl::Command::Uaprofwurflcomparator
25
- def parse_mapping_file(file)
26
- if !File.exist?(file)
27
- $stderr.puts "Mapping File does not exist. File passed was #{file}."
28
- return Array.new
29
- end
30
- mappings = Array.new
31
- f = File.new(file)
32
- f.each do |line|
33
- if m = /^"(.*)" "(.*)"$/.match(line.strip)
34
- uaprof = m[1]
35
- useragent = m[2]
36
- mappings<< [uaprof,useragent]
37
- else
38
- $stderr.puts "Irregular format for line: #{line}" if line.strip != ""
39
- end
40
- end
41
- f.close
42
-
43
- return mappings
44
- end
45
-
46
- def get_uaprofile(uaprof,profiledir,check=false)
47
- file = strip_uaprof(uaprof)
48
- if File.exists?("#{profiledir}/#{file}") && check
49
- return file
50
- end
51
-
52
- get_and_save_uaprof_file(uaprof,profiledir)
53
- return file
54
- end
55
-
56
- def strip_uaprof(uaprof)
57
- uaprof_file = nil
58
- if m = /([^\/]*)$/.match(uaprof)
59
- uaprof_file = m[1]
60
- else
61
- $stderr.puts "Cannot find the base UAProf file in URI: #{uaprof}"
62
- end
63
- return uaprof_file
64
- end
65
-
66
- def load_pstore(pstorefile)
67
- hands = Hash.new
68
- begin
69
- handsid, = load_wurfl_pstore(pstorefile)
70
- handsid.each { |id,val| hands[val.user_agent] = val }
71
- rescue => err
72
- $stderr.puts "Error: Cannot load PStore file. #{pstorefile}"
73
- $stderr.puts err.message
74
- exit 1
75
- end
76
- return hands
77
- end
78
-
79
- def get_and_save_uaprof_file(uaprof_url,savedirectory,limit=0)
80
- base,path,port = parse_url(uaprof_url)
81
-
82
- raise "Too many redirects from original url" if limit > 3
83
- raise "Unparseable URL: #{url}" if base.nil?
84
-
85
- port = 80 if port.nil?
86
- http = Net::HTTP.new(base,port)
87
- begin
88
- resp, data = http.get(path)
89
- if resp.code == "301"
90
- # get location and call self again
91
- http.finish
92
- limit += 1
93
- get_and_save_uaprof_file(resp['location'],savedirectory,limit)
94
- return
95
- elsif resp.code != "200"
96
- raise "Unexpected HTTP Response code:#{resp.code} for #{uaprof_url}"
97
- end
98
- rescue => err
99
- raise
100
- end
101
-
102
- f = File.new("#{savedirectory}/#{strip_uaprof(path)}","w")
103
- f.write(data)
104
- f.close
105
-
106
- end
107
-
108
- def parse_url(url)
109
- m = /(http:\/\/)?(.*?)(:(\d*))?\/(.*)/i.match(url.strip)
110
-
111
- return nil if m.nil?
112
- return m[2],"/#{m[5]}",m[4]
113
- end
114
-
115
- def usage
116
- puts "Usage: wurfltools.rb uaprofwurflcomparator -d profiledirectory -f mappingfile [-w wurfldb] [-c] [-h | --help]"
117
- puts "Examples:"
118
- puts "wurfltools.rb uaprofwurflcomparator -d ./profiles -f all-profile.2003-08.log -c -w wurfl.db"
119
- exit 1
120
- end
121
-
122
- def help
123
- puts "-d --directory : The directory to store the UA Profiles found in the log file."
124
- puts "-f --file : The log file that has a UAProfile to User-Agent mapping per line."
125
- puts "-c --check : A flag that will make sure to check if the profile is already in the directory or not. If it is not then it will download it."
126
- puts "-w --wurfldb : A Ruby PStore Database of the WURFL, that is used to compare against the UAProfiles."
127
- puts "-h --help : This message."
128
- exit 1
129
- end
130
-
131
- def execute
132
- profiledirectory = mappingfile = pstorefile = nil
133
- existancecheck = false
134
- begin
135
- opt = GetoptLong.new(
136
- ["-d","--directory", GetoptLong::REQUIRED_ARGUMENT],
137
- ["-f","--file", GetoptLong::REQUIRED_ARGUMENT],
138
- ["-c","--check", GetoptLong::NO_ARGUMENT],
139
- ["-h","--help", GetoptLong::NO_ARGUMENT],
140
- ["-w","--wurfldb", GetoptLong::REQUIRED_ARGUMENT]
141
- )
142
-
143
- opt.each { |arg,val|
144
- case arg
145
- when "-d"
146
- profiledirectory = val.strip
147
- when "-f"
148
- mappingfile = val.strip
149
- when "-c"
150
- existancecheck = true
151
- when "-h"
152
- help
153
- when "-w"
154
- pstorefile = val.strip
155
- else
156
- usage
157
- end
158
- }
159
- usage if mappingfile.nil? || profiledirectory.nil?
160
- rescue => err
161
- usage
162
- end
163
-
164
- profiles = Hash.new
165
- duplicates = Hash.new
166
- mappings = parse_mapping_file(mappingfile)
167
- mappings.each do |uaprof,useragent|
168
- begin
169
- prof_file = get_uaprofile(uaprof,profiledirectory,existancecheck)
170
- uaprof_mapper = UAProfToWURLF.new
171
- if profiles.key?(useragent)
172
- duplicates[useragent] = Array.new if !duplicates.key?(useragent)
173
- duplicates[useragent]<<uaprof
174
- next
175
- end
176
- uaprof_mapper.parse_UAProf("#{profiledirectory}/#{prof_file}")
177
- profiles[useragent] = uaprof_mapper
178
- rescue => err
179
- $stderr.puts "Error: File #{prof_file}; User-Agent:#{useragent}"
180
- $stderr.puts "Error:#{err.message}"
181
- end
182
- end
183
-
184
- duplicates.each do |key,profs|
185
- $stderr.puts "Duplicates exist for #{key}"
186
- profs.each {|prof| $stderr.puts "-- #{prof}" }
187
- end
188
-
189
- exit 0 if !pstorefile
190
-
191
- wurflhandsets = load_pstore(pstorefile)
192
-
193
- puts "Comparing WURFL Handsets"
194
- profiles.each do |key,val|
195
- puts "",""
196
-
197
- if !wurflhandsets.key?(key)
198
- puts "UAProf has a new Handset: #{key}"
199
- puts "--------------------------------"
200
- val.output_WURFL
201
- puts "--------------------------------"
202
- else
203
- uahand = val.make_wurfl_handset
204
- res = uahand.compare(wurflhandsets[key])
205
- if res.size > 0
206
- puts "#{key} : For UAProf and WURFL differ"
207
- res.each do |dkey,dval,did|
208
- next if did == "generic"
209
- #Key UAPROF Value WURFL Value WURFL source id
210
- puts " Key:#{dkey}; UVAL:#{uahand[dkey]}; WVAL:#{dval}; WSRCID:#{did}"
211
- end
212
- #val["user_agent"] = key
213
- puts ""
214
- puts "WURFL Changes are:"
215
- puts ""
216
- val.output_WURFL(res.map {|entry| entry[0]})
217
- else
218
- puts "#{key} : For UAProf and WURFL match"
219
- end
220
- end
221
- end
222
- end
223
- end
224
-