wurfl 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
-