jonuts-garb 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +191 -0
- data/Rakefile +54 -0
- data/lib/extensions/happymapper.rb +67 -0
- data/lib/extensions/operator.rb +20 -0
- data/lib/extensions/string.rb +13 -0
- data/lib/extensions/symbol.rb +36 -0
- data/lib/garb/authentication_request.rb +52 -0
- data/lib/garb/data_request.rb +28 -0
- data/lib/garb/oauth_session.rb +21 -0
- data/lib/garb/profile.rb +47 -0
- data/lib/garb/report.rb +31 -0
- data/lib/garb/report_parameter.rb +36 -0
- data/lib/garb/report_response.rb +62 -0
- data/lib/garb/resource.rb +89 -0
- data/lib/garb/session.rb +51 -0
- data/lib/garb/version.rb +13 -0
- data/lib/garb.rb +70 -0
- data/test/fixtures/cacert.pem +67 -0
- data/test/fixtures/profile_feed.xml +33 -0
- data/test/fixtures/report_feed.xml +46 -0
- data/test/test_helper.rb +16 -0
- data/test/unit/authentication_request_test.rb +121 -0
- data/test/unit/data_request_test.rb +52 -0
- data/test/unit/garb_test.rb +9 -0
- data/test/unit/oauth_session_test.rb +11 -0
- data/test/unit/operator_test.rb +37 -0
- data/test/unit/profile_test.rb +59 -0
- data/test/unit/report_parameter_test.rb +62 -0
- data/test/unit/report_response_test.rb +29 -0
- data/test/unit/report_test.rb +71 -0
- data/test/unit/resource_test.rb +19 -0
- data/test/unit/session_test.rb +34 -0
- data/test/unit/string_test.rb +9 -0
- data/test/unit/symbol_test.rb +44 -0
- metadata +119 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
module Garb
|
2
|
+
class OAuthSession
|
3
|
+
attr_accessor :access_token
|
4
|
+
|
5
|
+
OAuthGetRequestToken = "https://www.google.com/accounts/OAuthGetRequestToken"
|
6
|
+
OAuthAuthorizeToken = "https://www.google.com/accounts/OAuthAuthorizeToken"
|
7
|
+
OAuthGetAccessToken = "https://www.google.com/accounts/OAuthGetAccessToken"
|
8
|
+
|
9
|
+
def get_request_token
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
def authorization_request
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_access_token
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/garb/profile.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
module Garb
|
2
|
+
class Profile
|
3
|
+
|
4
|
+
attr_reader :table_id, :title, :account_name, :email
|
5
|
+
|
6
|
+
class Property
|
7
|
+
include HappyMapper
|
8
|
+
|
9
|
+
tag 'property'
|
10
|
+
namespace 'dxp'
|
11
|
+
|
12
|
+
attribute :name, String
|
13
|
+
attribute :value, String
|
14
|
+
end
|
15
|
+
|
16
|
+
class Entry
|
17
|
+
include HappyMapper
|
18
|
+
|
19
|
+
tag 'entry'
|
20
|
+
|
21
|
+
element :id, Integer
|
22
|
+
element :title, String
|
23
|
+
element :tableId, String, :namespace => 'dxp'
|
24
|
+
|
25
|
+
# has_one :table_id, TableId
|
26
|
+
has_many :properties, Property
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(entry, email=nil)
|
30
|
+
@email = Session.email(email)
|
31
|
+
@title = entry.title
|
32
|
+
@table_id = entry.tableId
|
33
|
+
@account_name = entry.properties.detect{|p| p.name == 'ga:accountName'}.value
|
34
|
+
end
|
35
|
+
|
36
|
+
def id
|
37
|
+
@table_id.sub(/^ga:/, '')
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.all(email=nil)
|
41
|
+
email = Session.email(email)
|
42
|
+
url = "https://www.google.com/analytics/feeds/accounts/#{email}"
|
43
|
+
response = DataRequest.new(url).send_request(email)
|
44
|
+
Entry.parse(response.body).map {|e| Garb::Profile.new(e, email)}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/garb/report.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Garb
|
2
|
+
class Report
|
3
|
+
include Resource::ResourceMethods
|
4
|
+
|
5
|
+
MONTH = 2592000
|
6
|
+
URL = "https://www.google.com/analytics/feeds/data"
|
7
|
+
|
8
|
+
def initialize(profile, opts={})
|
9
|
+
@profile = profile
|
10
|
+
|
11
|
+
@start_date = opts.fetch(:start_date, Time.now - MONTH)
|
12
|
+
@end_date = opts.fetch(:end_date, Time.now)
|
13
|
+
@limit = opts.fetch(:limit, nil)
|
14
|
+
@offset = opts.fetch(:offset, nil)
|
15
|
+
|
16
|
+
# clear filters and sort
|
17
|
+
@filters = ReportParameter.new(:filters)
|
18
|
+
@sorts = ReportParameter.new(:sort)
|
19
|
+
|
20
|
+
metrics opts.fetch(:metrics, [])
|
21
|
+
dimensions opts.fetch(:dimensions, [])
|
22
|
+
filter opts.fetch(:filter, [])
|
23
|
+
sort opts.fetch(:sort, [])
|
24
|
+
end
|
25
|
+
|
26
|
+
def results
|
27
|
+
ReportResponse.new(send_request_for_body(@profile.email)).results
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Garb
|
2
|
+
class ReportParameter
|
3
|
+
|
4
|
+
attr_reader :elements
|
5
|
+
|
6
|
+
def initialize(name)
|
7
|
+
@name = name
|
8
|
+
@elements = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def name
|
12
|
+
@name.to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
def <<(element)
|
16
|
+
(@elements += [element].flatten).compact!
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_params
|
21
|
+
params = self.elements.map do |elem|
|
22
|
+
case elem
|
23
|
+
when Hash
|
24
|
+
elem.collect do |k,v|
|
25
|
+
next unless k.is_a?(Operator)
|
26
|
+
"#{k.target}#{URI.encode(k.operator.to_s, /[=<>]/)}#{CGI::escape(v.to_s)}"
|
27
|
+
end.join(';')
|
28
|
+
else
|
29
|
+
elem.to_ga
|
30
|
+
end
|
31
|
+
end.join(',')
|
32
|
+
|
33
|
+
params.empty? ? {} : {self.name => params}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Garb
|
2
|
+
class ReportResponse
|
3
|
+
# include Enumerable
|
4
|
+
|
5
|
+
def initialize(response_body)
|
6
|
+
@xml = response_body
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse
|
10
|
+
entries = Entry.parse(@xml)
|
11
|
+
|
12
|
+
@results = entries.collect do |entry|
|
13
|
+
hash = {}
|
14
|
+
|
15
|
+
entry.metrics.each do |m|
|
16
|
+
name = m.name.sub(/^ga\:/,'').underscored
|
17
|
+
hash.merge!({name => m.value})
|
18
|
+
end
|
19
|
+
|
20
|
+
entry.dimensions.each do |d|
|
21
|
+
name = d.name.sub(/^ga\:/,'').underscored
|
22
|
+
hash.merge!({name => d.value})
|
23
|
+
end
|
24
|
+
|
25
|
+
OpenStruct.new(hash)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def results
|
30
|
+
@results || parse
|
31
|
+
end
|
32
|
+
|
33
|
+
class Metric
|
34
|
+
include HappyMapper
|
35
|
+
|
36
|
+
tag 'metric'
|
37
|
+
namespace 'dxp'
|
38
|
+
|
39
|
+
attribute :name, String
|
40
|
+
attribute :value, String
|
41
|
+
end
|
42
|
+
|
43
|
+
class Dimension
|
44
|
+
include HappyMapper
|
45
|
+
|
46
|
+
tag 'dimension'
|
47
|
+
namespace 'dxp'
|
48
|
+
|
49
|
+
attribute :name, String
|
50
|
+
attribute :value, String
|
51
|
+
end
|
52
|
+
|
53
|
+
class Entry
|
54
|
+
include HappyMapper
|
55
|
+
|
56
|
+
tag 'entry'
|
57
|
+
|
58
|
+
has_many :metrics, Metric
|
59
|
+
has_many :dimensions, Dimension
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Garb
|
2
|
+
module Resource
|
3
|
+
MONTH = 2592000
|
4
|
+
URL = "https://www.google.com/analytics/feeds/data"
|
5
|
+
|
6
|
+
def self.included(report)
|
7
|
+
report.extend(ResourceMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ResourceMethods
|
11
|
+
|
12
|
+
def metrics(*fields)
|
13
|
+
@metrics ||= ReportParameter.new(:metrics)
|
14
|
+
@metrics << fields
|
15
|
+
end
|
16
|
+
|
17
|
+
def dimensions(*fields)
|
18
|
+
@dimensions ||= ReportParameter.new(:dimensions)
|
19
|
+
@dimensions << fields
|
20
|
+
end
|
21
|
+
|
22
|
+
def filter(*hash)
|
23
|
+
@filters << hash
|
24
|
+
end
|
25
|
+
|
26
|
+
def filters
|
27
|
+
@filters ||= ReportParameter.new(:filters)
|
28
|
+
end
|
29
|
+
|
30
|
+
def sort(*fields)
|
31
|
+
@sorts << fields
|
32
|
+
end
|
33
|
+
|
34
|
+
def sorts
|
35
|
+
@sorts ||= ReportParameter.new(:sort)
|
36
|
+
end
|
37
|
+
|
38
|
+
def results(profile, opts = {}, &block)
|
39
|
+
@profile = profile
|
40
|
+
|
41
|
+
# clear filters and sort
|
42
|
+
@filters = ReportParameter.new(:filters)
|
43
|
+
@sorts = ReportParameter.new(:sort)
|
44
|
+
|
45
|
+
@start_date = opts.fetch(:start_date, Time.now - MONTH)
|
46
|
+
@end_date = opts.fetch(:end_date, Time.now)
|
47
|
+
@limit = opts.fetch(:limit, nil)
|
48
|
+
@offset = opts.fetch(:offset, nil)
|
49
|
+
|
50
|
+
instance_eval(&block) if block_given?
|
51
|
+
|
52
|
+
ReportResponse.new(send_request_for_body(profile.email)).results
|
53
|
+
end
|
54
|
+
|
55
|
+
def page_params
|
56
|
+
{'max-results' => @limit, 'start-index' => @offset}.reject{|k,v| v.nil?}
|
57
|
+
end
|
58
|
+
|
59
|
+
def default_params
|
60
|
+
{'ids' => @profile.table_id,
|
61
|
+
'start-date' => format_time(@start_date),
|
62
|
+
'end-date' => format_time(@end_date)}
|
63
|
+
end
|
64
|
+
|
65
|
+
def params
|
66
|
+
[
|
67
|
+
metrics.to_params,
|
68
|
+
dimensions.to_params,
|
69
|
+
sorts.to_params,
|
70
|
+
filters.to_params,
|
71
|
+
page_params
|
72
|
+
].inject(default_params) do |p, i|
|
73
|
+
p.merge(i)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def format_time(t)
|
78
|
+
t.strftime('%Y-%m-%d')
|
79
|
+
end
|
80
|
+
|
81
|
+
def send_request_for_body(email)
|
82
|
+
request = DataRequest.new(URL, params)
|
83
|
+
response = request.send_request(email)
|
84
|
+
response.body
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/garb/session.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
module Garb
|
2
|
+
class Session
|
3
|
+
class << self; attr_reader :sessions end
|
4
|
+
|
5
|
+
@sessions ||= []
|
6
|
+
|
7
|
+
def self.login(email, password, opts={})
|
8
|
+
auth_request = AuthenticationRequest.new(email, password)
|
9
|
+
creds = { :email => email, :auth_token => auth_request.auth_token(opts) }
|
10
|
+
update_or_create(creds)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.auth_token(email=nil)
|
14
|
+
email ? find_session(email).auth_token : default.auth_token
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.email(email=nil)
|
18
|
+
email || default.email
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.find_session(email)
|
22
|
+
@sessions.find {|session| session.email == email}
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.default
|
26
|
+
@sessions.first
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.update_or_create(creds)
|
30
|
+
if session = find_session(creds[:email])
|
31
|
+
session.auth_token = creds[:auth_token]
|
32
|
+
else
|
33
|
+
@sessions << session = new(creds)
|
34
|
+
end
|
35
|
+
session
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(creds={})
|
39
|
+
@email = creds[:email]
|
40
|
+
@auth_token = creds[:auth_token]
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_accessor :auth_token
|
44
|
+
attr_reader :email
|
45
|
+
|
46
|
+
def profiles
|
47
|
+
Profile.all(@email)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
data/lib/garb/version.rb
ADDED
data/lib/garb.rb
ADDED
@@ -0,0 +1,70 @@
|
|
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/report_parameter'
|
16
|
+
require 'garb/report_response'
|
17
|
+
require 'garb/resource'
|
18
|
+
require 'garb/report'
|
19
|
+
|
20
|
+
require 'extensions/string'
|
21
|
+
require 'extensions/operator'
|
22
|
+
require 'extensions/symbol'
|
23
|
+
require 'extensions/happymapper'
|
24
|
+
|
25
|
+
module Garb
|
26
|
+
# :stopdoc:
|
27
|
+
GA = "http://schemas.google.com/analytics/2008"
|
28
|
+
|
29
|
+
VERSION = '0.1.2'
|
30
|
+
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
31
|
+
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
32
|
+
# :startdoc:
|
33
|
+
|
34
|
+
# Returns the version string for the library.
|
35
|
+
#
|
36
|
+
def self.version
|
37
|
+
VERSION
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the library path for the module. If any arguments are given,
|
41
|
+
# they will be joined to the end of the libray path using
|
42
|
+
# <tt>File.join</tt>.
|
43
|
+
#
|
44
|
+
def self.libpath( *args )
|
45
|
+
args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the lpath for the module. If any arguments are given,
|
49
|
+
# they will be joined to the end of the path using
|
50
|
+
# <tt>File.join</tt>.
|
51
|
+
#
|
52
|
+
def self.path( *args )
|
53
|
+
args.empty? ? PATH : ::File.join(PATH, args.flatten)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Utility method used to rquire all files ending in .rb that lie in the
|
57
|
+
# directory below this file that has the same name as the filename passed
|
58
|
+
# in. Optionally, a specific _directory_ name can be passed in such that
|
59
|
+
# the _filename_ does not have to be equivalent to the directory.
|
60
|
+
#
|
61
|
+
def self.require_all_libs_relative_to( fname, dir = nil )
|
62
|
+
dir ||= ::File.basename(fname, '.*')
|
63
|
+
search_me = ::File.expand_path(
|
64
|
+
::File.join(::File.dirname(fname), dir, '*', '*.rb'))
|
65
|
+
|
66
|
+
Dir.glob(search_me).sort.each {|rb| require rb}
|
67
|
+
end
|
68
|
+
end # module Garb
|
69
|
+
|
70
|
+
# EOF
|
@@ -0,0 +1,67 @@
|
|
1
|
+
##
|
2
|
+
## cacert.pem-foo -- Bundle of CA Root Certificates
|
3
|
+
##
|
4
|
+
## Converted at: Thu Mar 26 21:23:06 2009 UTC
|
5
|
+
##
|
6
|
+
## This is a bundle of X.509 certificates of public Certificate Authorities
|
7
|
+
## (CA). These were automatically extracted from Mozilla's root certificates
|
8
|
+
## file (certdata.txt). This file can be found in the mozilla source tree:
|
9
|
+
## '/mozilla/security/nss/lib/ckfw/builtins/certdata.txt'
|
10
|
+
##
|
11
|
+
## It contains the certificates in PEM format and therefore
|
12
|
+
## can be directly used with curl / libcurl / php_curl, or with
|
13
|
+
## an Apache+mod_ssl webserver for SSL client authentication.
|
14
|
+
## Just configure this file as the SSLCACertificateFile.
|
15
|
+
##
|
16
|
+
|
17
|
+
# ***** BEGIN LICENSE BLOCK *****
|
18
|
+
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
19
|
+
#
|
20
|
+
# The contents of this file are subject to the Mozilla Public License Version
|
21
|
+
# 1.1 (the "License"); you may not use this file except in compliance with
|
22
|
+
# the License. You may obtain a copy of the License at
|
23
|
+
# http://www.mozilla.org/MPL/
|
24
|
+
#
|
25
|
+
# Software distributed under the License is distributed on an "AS IS" basis,
|
26
|
+
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
27
|
+
# for the specific language governing rights and limitations under the
|
28
|
+
# License.
|
29
|
+
#
|
30
|
+
# The Original Code is the Netscape security libraries.
|
31
|
+
#
|
32
|
+
# The Initial Developer of the Original Code is
|
33
|
+
# Netscape Communications Corporation.
|
34
|
+
# Portions created by the Initial Developer are Copyright (C) 1994-2000
|
35
|
+
# the Initial Developer. All Rights Reserved.
|
36
|
+
#
|
37
|
+
# Contributor(s):
|
38
|
+
#
|
39
|
+
# Alternatively, the contents of this file may be used under the terms of
|
40
|
+
# either the GNU General Public License Version 2 or later (the "GPL"), or
|
41
|
+
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
42
|
+
# in which case the provisions of the GPL or the LGPL are applicable instead
|
43
|
+
# of those above. If you wish to allow use of your version of this file only
|
44
|
+
# under the terms of either the GPL or the LGPL, and not to allow others to
|
45
|
+
# use your version of this file under the terms of the MPL, indicate your
|
46
|
+
# decision by deleting the provisions above and replace them with the notice
|
47
|
+
# and other provisions required by the GPL or the LGPL. If you do not delete
|
48
|
+
# the provisions above, a recipient may use your version of this file under
|
49
|
+
# the terms of any one of the MPL, the GPL or the LGPL.
|
50
|
+
#
|
51
|
+
# ***** END LICENSE BLOCK *****
|
52
|
+
# @(#) $RCSfile: certdata.txt,v $ $Revision: 1.51 $ $Date: 2009/01/15 22:35:15 $
|
53
|
+
|
54
|
+
Verisign/RSA Secure Server CA
|
55
|
+
=============================
|
56
|
+
-----BEGIN CERTIFICATE-----
|
57
|
+
MIICNDCCAaECEAKtZn5ORf5eV288mBle3cAwDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMx
|
58
|
+
IDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2VydmVy
|
59
|
+
IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk0MTEwOTAwMDAwMFoXDTEwMDEwNzIzNTk1OVow
|
60
|
+
XzELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQL
|
61
|
+
EyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGbMA0GCSqGSIb3DQEBAQUA
|
62
|
+
A4GJADCBhQJ+AJLOesGugz5aqomDV6wlAXYMra6OLDfO6zV4ZFQD5YRAUcm/jwjiioII0haGN1Xp
|
63
|
+
sSECrXZogZoFokvJSyVmIlZsiAeP94FZbYQHZXATcXY+m3dM41CJVphIuR2nKRoTLkoRWZweFdVJ
|
64
|
+
VCxzOmmCsZc5nG1wZ0jl3S3WyB57AgMBAAEwDQYJKoZIhvcNAQECBQADfgBl3X7hsuyw4jrg7HFG
|
65
|
+
mhkRuNPHoLQDQCYCPgmc4RKz0Vr2N6W3YQO2WxZpO8ZECAyIUwxrl0nHPjXcbLm7qt9cuzovk2C2
|
66
|
+
qUtN8iD3zV9/ZHuO3ABc1/p3yjkWWW8O6tO1g39NTUJWdrTJXwT4OPjr0l91X817/OWOgHz8UA==
|
67
|
+
-----END CERTIFICATE-----
|