macros-garb 0.2.6

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.md ADDED
@@ -0,0 +1,202 @@
1
+ garb
2
+ ====
3
+
4
+ by Tony Pitale with much help from Justin Marney, Patrick Reagan and others at Viget Labs
5
+
6
+ http://github.com/vigetlabs/garb
7
+
8
+ Important Changes
9
+ =================
10
+
11
+ Version 0.2.4 requires happymapper from rubygems, version 0.2.5. Be sure to update.
12
+
13
+ Version 0.2.0 makes major changes (compared to 0.1.0) to the way garb is used to build reports.
14
+ There is now both a module that gets included for generating defined classes,
15
+ as well as, slight changes to the way that the Report class can be used.
16
+
17
+ Description
18
+ -----------
19
+
20
+ Provides a Ruby API to the Google Analytics API.
21
+
22
+ http://code.google.com/apis/analytics/docs/gdata/gdataDeveloperGuide.html
23
+
24
+ Basic Usage
25
+ ===========
26
+
27
+ Login
28
+ -----
29
+
30
+ > Garb::Session.login(username, password)
31
+
32
+ Accounts
33
+ --------
34
+ > Garb::Account.all
35
+
36
+ Profiles
37
+ --------
38
+
39
+ > Garb::Account.first.profiles
40
+
41
+ > Garb::Profile.all
42
+ > profile = Garb::Profile.all.first
43
+
44
+ Define a Report Class and Get Results
45
+ -------------------------------------
46
+
47
+ class Exits
48
+ include Garb::Resource
49
+
50
+ metrics :exits, :pageviews, :exit_rate
51
+ dimensions :request_uri
52
+ end
53
+
54
+ Parameters
55
+ ----------
56
+
57
+ * start_date: The date of the period you would like this report to start
58
+ * end_date: The date to end, inclusive
59
+ * limit: The maximum number of results to be returned
60
+ * offset: The starting index
61
+
62
+ Metrics & Dimensions
63
+ --------------------
64
+
65
+ Metrics and Dimensions are very complex because of the ways in which the can and cannot be combined.
66
+
67
+ I suggest reading the google documentation to familiarize yourself with this.
68
+
69
+ http://code.google.com/apis/analytics/docs/gdata/gdataReferenceDimensionsMetrics.html#bounceRate
70
+
71
+ When you've returned, you can pass the appropriate combinations (up to 50 metrics and 2 dimenstions)
72
+ to garb, as an array, of symbols. Or you can simply push a symbol into the array.
73
+
74
+ Sorting
75
+ -------
76
+
77
+ Sorting can be done on any metric or dimension defined in the request, with .desc reversing the sort.
78
+
79
+ Building a Report
80
+ -----------------
81
+
82
+ Given the class, session, and profile from above we can do:
83
+
84
+ Exits.results(profile, :limit => 10, :offset => 19)
85
+
86
+ Or, with sorting and filters:
87
+
88
+ Exits.results(profile, :limit => 10, :offset => 19) do
89
+ filter :request_uri.contains => 'season', :exits.gt => 100
90
+ sort :exits
91
+ end
92
+
93
+ reports will be an array of OpenStructs with methods for the metrics and dimensions returned.
94
+
95
+ Build a One-Off Report
96
+ ----------------------
97
+
98
+ report = Garb::Report.new(profile)
99
+ report.metrics :pageviews
100
+ report.dimensions :request_uri
101
+
102
+ report.filter :request_uri.contains => 'season', :exits.gte => 10
103
+ report.sort :exits
104
+
105
+ report.results
106
+
107
+ Filtering
108
+ ---------
109
+
110
+ Google Analytics supports a significant number of filtering options.
111
+
112
+ http://code.google.com/apis/analytics/docs/gdata/gdataReference.html#filtering
113
+
114
+ We handle filtering as an array of hashes that you can push into,
115
+ which will be joined together (AND'd)
116
+
117
+ Here is what we can do currently:
118
+ (the operator is a method on a symbol metric or dimension)
119
+
120
+ Operators on metrics:
121
+
122
+ :eql => '==',
123
+ :not_eql => '!=',
124
+ :gt => '>',
125
+ :gte => '>=',
126
+ :lt => '<',
127
+ :lte => '<='
128
+
129
+ Operators on dimensions:
130
+
131
+ :matches => '==',
132
+ :does_not_match => '!=',
133
+ :contains => '=~',
134
+ :does_not_contain => '!~',
135
+ :substring => '=@',
136
+ :not_substring => '!@'
137
+
138
+ Given the previous example one-off report, we can add a line for filter:
139
+
140
+ report.filters << {:request_uri.eql => '/extend/effectively-using-git-with-subversion/'}
141
+
142
+ SSL
143
+ ---
144
+
145
+ Version 0.2.3 includes support for real ssl encryption for authentication. First do:
146
+
147
+ Garb::Session.login(username, password, :secure => true)
148
+
149
+ Next, be sure to download http://curl.haxx.se/ca/cacert.pem into your application somewhere.
150
+ Then, define a constant CA_CERT_FILE and point to that file.
151
+
152
+ For whatever reason, simply creating a new certificate store and setting the defaults would
153
+ not validate the google ssl certificate as authentic.
154
+
155
+ TODOS
156
+ -----
157
+
158
+ * Sessions are currently global, which isn't awesome
159
+ * Single user login is the only supported method currently.
160
+ Intend to add hooks for using OAuth
161
+ * Read opensearch header in results
162
+ * OR joining filter parameters
163
+
164
+ Requirements
165
+ ------------
166
+
167
+ happymapper >= 0.2.5 (should also install libxml)
168
+
169
+ Install
170
+ -------
171
+
172
+ sudo gem install garb
173
+
174
+ OR
175
+
176
+ sudo gem install vigetlabs-garb -s http://gems.github.com
177
+
178
+ License
179
+ -------
180
+
181
+ (The MIT License)
182
+
183
+ Copyright (c) 2008 Viget Labs
184
+
185
+ Permission is hereby granted, free of charge, to any person obtaining
186
+ a copy of this software and associated documentation files (the
187
+ 'Software'), to deal in the Software without restriction, including
188
+ without limitation the rights to use, copy, modify, merge, publish,
189
+ distribute, sublicense, and/or sell copies of the Software, and to
190
+ permit persons to whom the Software is furnished to do so, subject to
191
+ the following conditions:
192
+
193
+ The above copyright notice and this permission notice shall be
194
+ included in all copies or substantial portions of the Software.
195
+
196
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
197
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
198
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
199
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
200
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
201
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
202
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rake/testtask'
4
+
5
+ require 'lib/garb/version'
6
+
7
+ task :default => :test
8
+
9
+ spec = Gem::Specification.new do |s|
10
+ s.name = 'garb'
11
+ s.version = Garb::Version.to_s
12
+ s.has_rdoc = false
13
+ s.summary = "Google Analytics API Ruby Wrapper"
14
+ s.authors = ['Tony Pitale','Justin Marney', 'Patrick Reagan']
15
+ s.email = 'tony.pitale@viget.com'
16
+ s.homepage = 'http://github.com/vigetlabs/garb'
17
+ s.files = %w(README.md Rakefile) + Dir.glob("lib/**/*")
18
+ s.test_files = Dir.glob("test/**/*")
19
+
20
+ s.add_dependency("happymapper", [">= 0.2.5"])
21
+ end
22
+
23
+ Rake::GemPackageTask.new(spec) do |pkg|
24
+ pkg.gem_spec = spec
25
+ end
26
+
27
+ Rake::TestTask.new do |t|
28
+ t.libs << 'test'
29
+ t.test_files = FileList["test/**/*_test.rb"]
30
+ t.verbose = true
31
+ end
32
+
33
+ desc 'Generate the gemspec to serve this Gem from Github'
34
+ task :github do
35
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
36
+ File.open(file, 'w') {|f| f << spec.to_ruby }
37
+ puts "Created gemspec: #{file}"
38
+ end
39
+
40
+ begin
41
+ require 'rcov/rcovtask'
42
+
43
+ desc "Generate RCov coverage report"
44
+ Rcov::RcovTask.new(:rcov) do |t|
45
+ t.test_files = FileList['test/**/*_test.rb']
46
+ t.rcov_opts << "-x lib/garb.rb -x lib/garb/version.rb"
47
+ end
48
+ rescue LoadError
49
+ nil
50
+ end
51
+
52
+ task :default => 'test'
53
+
54
+ # EOF
@@ -0,0 +1,16 @@
1
+ class Array
2
+ def group_to_array
3
+ h = Hash.new
4
+
5
+ each do |element|
6
+ key = yield(element)
7
+ if h.has_key?(key)
8
+ h[key] << element
9
+ else
10
+ h[key] = [element]
11
+ end
12
+ end
13
+
14
+ h.map{|k,v| v}
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ # Concept from dm-core
2
+ class Operator
3
+ attr_reader :target, :operator, :prefix
4
+
5
+ def initialize(target, operator, prefix=false)
6
+ @target = target.to_ga
7
+ @operator = operator
8
+ @prefix = prefix
9
+ end
10
+
11
+ def to_ga
12
+ @prefix ? "#{operator}#{target}" : "#{target}#{operator}"
13
+ end
14
+
15
+ def ==(rhs)
16
+ target == rhs.target &&
17
+ operator == rhs.operator &&
18
+ prefix == rhs.prefix
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ class String
2
+ def underscored
3
+ self.gsub(/([A-Z])/, '_\1').downcase
4
+ end
5
+
6
+ def lower_camelized
7
+ self.gsub(/(_)(.)/) { $2.upcase }
8
+ end
9
+
10
+ def to_ga
11
+ "ga:#{self}"
12
+ end
13
+
14
+ def from_ga
15
+ self.gsub(/^ga\:/, '')
16
+ end
17
+ end
@@ -0,0 +1,36 @@
1
+ class Symbol
2
+ # OPERATORS
3
+
4
+ def self.operator(operators)
5
+ operators.each do |method, operator|
6
+ class_eval <<-CODE
7
+ def #{method}
8
+ Operator.new(self, '#{operator}')
9
+ end
10
+ CODE
11
+ end
12
+ end
13
+
14
+ # Sorting
15
+ def desc
16
+ Operator.new(self, '-', true)
17
+ end
18
+
19
+ operator :eql => '==',
20
+ :not_eql => '!=',
21
+ :gt => '>',
22
+ :gte => '>=',
23
+ :lt => '<',
24
+ :lte => '<=',
25
+ :matches => '==',
26
+ :does_not_match => '!=',
27
+ :contains => '=~',
28
+ :does_not_contain => '!~',
29
+ :substring => '=@',
30
+ :not_substring => '!@'
31
+
32
+ # Metric filters
33
+ def to_ga
34
+ "ga:#{self.to_s.lower_camelized}"
35
+ end
36
+ end
data/lib/garb.rb ADDED
@@ -0,0 +1,71 @@
1
+ $:.unshift File.expand_path(File.dirname(__FILE__))
2
+
3
+ require 'net/http'
4
+ require 'net/https'
5
+ require 'rubygems'
6
+ require 'cgi'
7
+ require 'ostruct'
8
+ require 'happymapper'
9
+
10
+ require 'garb/version'
11
+ require 'garb/authentication_request'
12
+ require 'garb/data_request'
13
+ require 'garb/session'
14
+ require 'garb/profile'
15
+ require 'garb/account'
16
+ require 'garb/report_parameter'
17
+ require 'garb/report_response'
18
+ require 'garb/resource'
19
+ require 'garb/report'
20
+
21
+ require 'extensions/string'
22
+ require 'extensions/operator'
23
+ require 'extensions/symbol'
24
+ require 'extensions/array'
25
+
26
+ module Garb
27
+ # :stopdoc:
28
+ GA = "http://schemas.google.com/analytics/2008"
29
+
30
+ VERSION = '0.1.2'
31
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
32
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
33
+ # :startdoc:
34
+
35
+ # Returns the version string for the library.
36
+ #
37
+ def self.version
38
+ VERSION
39
+ end
40
+
41
+ # Returns the library path for the module. If any arguments are given,
42
+ # they will be joined to the end of the libray path using
43
+ # <tt>File.join</tt>.
44
+ #
45
+ def self.libpath( *args )
46
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
47
+ end
48
+
49
+ # Returns the lpath for the module. If any arguments are given,
50
+ # they will be joined to the end of the path using
51
+ # <tt>File.join</tt>.
52
+ #
53
+ def self.path( *args )
54
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
55
+ end
56
+
57
+ # Utility method used to rquire all files ending in .rb that lie in the
58
+ # directory below this file that has the same name as the filename passed
59
+ # in. Optionally, a specific _directory_ name can be passed in such that
60
+ # the _filename_ does not have to be equivalent to the directory.
61
+ #
62
+ def self.require_all_libs_relative_to( fname, dir = nil )
63
+ dir ||= ::File.basename(fname, '.*')
64
+ search_me = ::File.expand_path(
65
+ ::File.join(::File.dirname(fname), dir, '*', '*.rb'))
66
+
67
+ Dir.glob(search_me).sort.each {|rb| require rb}
68
+ end
69
+ end # module Garb
70
+
71
+ # EOF
@@ -0,0 +1,15 @@
1
+ module Garb
2
+ class Account
3
+ attr_reader :id, :name, :profiles
4
+
5
+ def initialize(profiles)
6
+ @id = profiles.first.account_id
7
+ @name = profiles.first.account_name
8
+ @profiles = profiles
9
+ end
10
+
11
+ def self.all
12
+ Profile.all.group_to_array{|p| p.account_id}.map{|profiles| new(profiles)}
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,52 @@
1
+ module Garb
2
+ class AuthenticationRequest
3
+ class AuthError < StandardError;end
4
+
5
+ URL = 'https://www.google.com/accounts/ClientLogin'
6
+
7
+ def initialize(email, password)
8
+ @email = email
9
+ @password = password
10
+ end
11
+
12
+ def parameters
13
+ {
14
+ 'Email' => @email,
15
+ 'Passwd' => @password,
16
+ 'accountType' => 'HOSTED_OR_GOOGLE',
17
+ 'service' => 'analytics',
18
+ 'source' => 'vigetLabs-garb-001'
19
+ }
20
+ end
21
+
22
+ def uri
23
+ URI.parse(URL)
24
+ end
25
+
26
+ def send_request(ssl_mode)
27
+ http = Net::HTTP.new(uri.host, uri.port)
28
+ http.use_ssl = true
29
+ http.verify_mode = ssl_mode
30
+
31
+ if ssl_mode == OpenSSL::SSL::VERIFY_PEER
32
+ http.ca_file = CA_CERT_FILE
33
+ end
34
+
35
+ http.request(build_request) do |response|
36
+ raise AuthError unless response.is_a?(Net::HTTPOK)
37
+ end
38
+ end
39
+
40
+ def build_request
41
+ post = Net::HTTP::Post.new(uri.path)
42
+ post.set_form_data(parameters)
43
+ post
44
+ end
45
+
46
+ def auth_token(opts={})
47
+ ssl_mode = opts[:secure] ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
48
+ send_request(ssl_mode).body.match(/^Auth=(.*)$/)[1]
49
+ end
50
+
51
+ end
52
+ end