nike 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +136 -0
- data/Rakefile +1 -0
- data/lib/nike/client.rb +135 -0
- data/lib/nike/core_ext/hash.rb +34 -0
- data/lib/nike/mash.rb +99 -0
- data/lib/nike/version.rb +3 -0
- data/lib/nike.rb +5 -0
- data/nike.gemspec +21 -0
- metadata +89 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Alex Skryl
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
# Nike
|
2
|
+
|
3
|
+
A Ruby client for the Nike+ API
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
* Run Stats
|
8
|
+
* Heart Rate Stats
|
9
|
+
* GPS Data
|
10
|
+
* Automatic Unit Conversion
|
11
|
+
* Data Set Caching
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
gem 'nike'
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install nike
|
26
|
+
|
27
|
+
## Basic Usage
|
28
|
+
|
29
|
+
Initialize the client
|
30
|
+
|
31
|
+
$ c = Nike::Client.new('your_email', 'your_password')
|
32
|
+
|
33
|
+
A summary of all activities by type (type is :run by default)
|
34
|
+
|
35
|
+
$ c.activities # get all runs
|
36
|
+
|
37
|
+
$ c.activities(type: :hr) # get all heart rate activities
|
38
|
+
|
39
|
+
Full activity data (Slow if you have alot of data, use c.activity to fetch a detailed data set for a single activity)
|
40
|
+
|
41
|
+
$ c.detailed_activities # get detailed data for all runs
|
42
|
+
|
43
|
+
$ c.detailed_activities(type: :hr) # get detailed data for all hr activity
|
44
|
+
|
45
|
+
Detailed data for a single activity (The id can be found in using the summary calls above)
|
46
|
+
|
47
|
+
$ c.activity(id)
|
48
|
+
|
49
|
+
Lifetime stats
|
50
|
+
|
51
|
+
$ c.lifetime_totals # lifetime running totals
|
52
|
+
|
53
|
+
$ c.lifetime_totals(type: :hr) # lifetime hr totals
|
54
|
+
|
55
|
+
More metrics
|
56
|
+
|
57
|
+
$ c.time_span_metrics # run metrics
|
58
|
+
|
59
|
+
$ c.time_span_metrics # hr metrics
|
60
|
+
|
61
|
+
Distance by time of day
|
62
|
+
|
63
|
+
$ c.time_of_day_metrics
|
64
|
+
|
65
|
+
Distance by terrain
|
66
|
+
|
67
|
+
$ c.terrains
|
68
|
+
|
69
|
+
Pace data
|
70
|
+
|
71
|
+
$ c.paces
|
72
|
+
|
73
|
+
Basic stats that appear on the Nike+ homepage
|
74
|
+
|
75
|
+
$ c.homepage_stats
|
76
|
+
|
77
|
+
## GPS Data
|
78
|
+
|
79
|
+
Cheking if an activity includes GPS data
|
80
|
+
|
81
|
+
$ a = @c.activity(#######)
|
82
|
+
|
83
|
+
$ a.gps # => true
|
84
|
+
|
85
|
+
Getting to the GPS data
|
86
|
+
|
87
|
+
$ a.geo.waypoints # list all GPS waypoints
|
88
|
+
|
89
|
+
$ a.geo.waypoints.first # => {"lat"=>42.115833, "lon"=>-87.776344, "ele"=>181.78954}
|
90
|
+
$ a.geo.waypoints.first.lat # => 42.115833
|
91
|
+
|
92
|
+
## Automatic Unit Conversion
|
93
|
+
|
94
|
+
Check to see which conversion helpers are available for a specific data set
|
95
|
+
|
96
|
+
$ a = @c.activity(#######)
|
97
|
+
|
98
|
+
$ a.conversion_helpers # => [:distance_in_kilometers, :distance_in_miles, :duration_inseconds,
|
99
|
+
:duration_in_minutes, :duration_in_hours, :duration_in_hms,
|
100
|
+
:speed_in_mph, :speed_in_kph]
|
101
|
+
|
102
|
+
Examples
|
103
|
+
|
104
|
+
$ a.duration # => 6402672
|
105
|
+
$ a.duration_in_seconds # => 6402.672
|
106
|
+
$ a.duration_in_minutes # => 106.7112
|
107
|
+
$ a.duration_in_hours # => 1.77852
|
108
|
+
$ a.duration_in_hms # => "01:46:42"
|
109
|
+
|
110
|
+
All time fields are automatically converted to Ruby Time objects
|
111
|
+
|
112
|
+
$ a.start_time_utc.class # => Time
|
113
|
+
|
114
|
+
## Caching
|
115
|
+
|
116
|
+
Toggle caching during client initialization
|
117
|
+
|
118
|
+
$ c = Nike::Client.new('your_email', 'your_password', caching: true)
|
119
|
+
|
120
|
+
Toggle caching after client initialization
|
121
|
+
|
122
|
+
$ c.caching = false
|
123
|
+
|
124
|
+
Perform the HTTP request for a particular call even if caching is enabled. This
|
125
|
+
will have the side-effect of refreshing the cache.
|
126
|
+
|
127
|
+
$ c.activities!
|
128
|
+
|
129
|
+
|
130
|
+
## Contributing
|
131
|
+
|
132
|
+
1. Fork it
|
133
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
134
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
135
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
136
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/nike/client.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
require_relative 'core_ext/hash.rb'
|
2
|
+
|
3
|
+
require 'httparty'
|
4
|
+
require 'hashie'
|
5
|
+
require 'active_support/core_ext/string/inflections'
|
6
|
+
|
7
|
+
class Nike::Client
|
8
|
+
include HTTParty
|
9
|
+
# debug_output $stdout
|
10
|
+
|
11
|
+
LOGIN_URL = 'https://secure-nikeplus.nike.com/nsl/services/user/login'
|
12
|
+
BASE_URL = 'http://nikeplus.nike.com/plus'
|
13
|
+
APP_KEY = 'b31990e7-8583-4251-808f-9dc67b40f5d2'
|
14
|
+
FORMAT = :json
|
15
|
+
|
16
|
+
# service urls
|
17
|
+
#
|
18
|
+
RUN_ACTIVITIES_URL = '/activity/running/[user_id]/lifetime/activities'
|
19
|
+
HR_ACTIVITIES_URL = '/activity/running/[user_id]/heartrate/lifetime/activities'
|
20
|
+
ACTIVITY_URL = '/running/ajax'
|
21
|
+
|
22
|
+
ACTIVITIES_URLS = {
|
23
|
+
run: RUN_ACTIVITIES_URL,
|
24
|
+
hr: HR_ACTIVITIES_URL
|
25
|
+
}
|
26
|
+
|
27
|
+
format FORMAT
|
28
|
+
base_uri BASE_URL
|
29
|
+
default_params format: FORMAT, app: APP_KEY
|
30
|
+
|
31
|
+
attr_accessor :caching
|
32
|
+
|
33
|
+
def initialize(email, password, opts = {})
|
34
|
+
@email, @password, @user_id = email, password, nil
|
35
|
+
@caching, @cache = opts[:caching] || true, {}
|
36
|
+
end
|
37
|
+
|
38
|
+
def activity(id)
|
39
|
+
fetch_activity_data(id.to_s).activity
|
40
|
+
end
|
41
|
+
|
42
|
+
def activities(opts = {})
|
43
|
+
fetch_user_data(opts).activities.map { |a| a.activity }
|
44
|
+
end
|
45
|
+
|
46
|
+
def detailed_activities(opts = {})
|
47
|
+
activities(opts).map { |a| activity(a.activity_id) }
|
48
|
+
end
|
49
|
+
|
50
|
+
[:lifetime_totals, :time_span_metrics, :time_of_day_metrics, :terrains, :paces, :homepage_stats].each do |m|
|
51
|
+
eval %(
|
52
|
+
def #{m}(opts = {})
|
53
|
+
fetch_user_data(opts).send(:#{m})
|
54
|
+
end
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
def method_missing(method_name, *args, &block)
|
59
|
+
if /(.*)!$/ === method_name && self.respond_to?(method_name.to_s.chop)
|
60
|
+
no_cache { self.send($1, *args, &block) }
|
61
|
+
else super
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def respond_to_missing?(method_name, include_private = false)
|
66
|
+
method_name.to_s.end_with?('!') && self.respond_to?(method_name.to_s.chop)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# data
|
72
|
+
|
73
|
+
def fetch_activity_data(id)
|
74
|
+
cache(id) do
|
75
|
+
wrap get_authorized(ACTIVITY_URL + "/#{id}")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def fetch_user_data(opts)
|
80
|
+
type = opts[:type].to_sym || :run
|
81
|
+
cache(type) do
|
82
|
+
wrap get_authorized(ACTIVITIES_URLS[type], query: { indexStart: 0, indexEnd: 999999})
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# auth
|
87
|
+
|
88
|
+
def get_authorized(url, opts = {})
|
89
|
+
login_if_unauthenticated
|
90
|
+
raise "Authentication failed!" unless logged_in?
|
91
|
+
self.class.get(personify_url(url), opts).to_hash
|
92
|
+
end
|
93
|
+
|
94
|
+
def login_if_unauthenticated
|
95
|
+
return if logged_in?
|
96
|
+
response = self.class.login(@email, @password)
|
97
|
+
@user_id = response['serviceResponse']['body']['User']['screenName']
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.login(email, password)
|
101
|
+
response = post(LOGIN_URL, query: { email: email, password: password })
|
102
|
+
self.default_cookies.add_cookies(response.headers['set-cookie'])
|
103
|
+
response
|
104
|
+
end
|
105
|
+
|
106
|
+
def logged_in?
|
107
|
+
!@user_id.nil? && !self.class.cookies[:slCheck].nil?
|
108
|
+
end
|
109
|
+
|
110
|
+
# caching
|
111
|
+
|
112
|
+
def cache(key)
|
113
|
+
@caching ? @cache[key] ||= yield : yield
|
114
|
+
end
|
115
|
+
|
116
|
+
def no_cache
|
117
|
+
caching = @caching
|
118
|
+
@caching = false
|
119
|
+
yield
|
120
|
+
ensure
|
121
|
+
@caching = caching
|
122
|
+
end
|
123
|
+
|
124
|
+
# helpers
|
125
|
+
|
126
|
+
def personify_url(url)
|
127
|
+
vars = url.scan(/\[[^\]]*\]/)
|
128
|
+
vars.inject(url){ |u, v| u.gsub(v, self.instance_variable_get("@#{v[1..-2]}").to_s) }
|
129
|
+
end
|
130
|
+
|
131
|
+
def wrap(response)
|
132
|
+
Nike::Mash.new(response.underscore_keys)
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Hash
|
2
|
+
|
3
|
+
def deep_merge(other_hash)
|
4
|
+
dup.deep_merge!(other_hash)
|
5
|
+
end
|
6
|
+
|
7
|
+
def deep_merge!(other_hash)
|
8
|
+
other_hash.each_pair do |k,v|
|
9
|
+
tv = self[k]
|
10
|
+
self[k] = \
|
11
|
+
if tv.is_a?(Hash) && v.is_a?(Hash)
|
12
|
+
tv.deep_merge(v)
|
13
|
+
elsif tv.is_a?(Array) && v.is_a?(Array)
|
14
|
+
tv + v
|
15
|
+
else v
|
16
|
+
end
|
17
|
+
end
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def underscore_keys
|
22
|
+
self.inject({}) do |h, (k,v)|
|
23
|
+
h[k.to_s.underscore] = \
|
24
|
+
case v
|
25
|
+
when Hash
|
26
|
+
v.underscore_keys
|
27
|
+
when Array
|
28
|
+
v.map { |i| i.is_a?(Hash) ? i.underscore_keys : i }
|
29
|
+
else v
|
30
|
+
end; h
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
data/lib/nike/mash.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'hashie'
|
2
|
+
|
3
|
+
class Nike::Mash < Hashie::Mash
|
4
|
+
KM_TO_MILE = 0.621371192
|
5
|
+
|
6
|
+
CONVERTER_AVAILABILITY = {
|
7
|
+
distance: [:distance_in_kilometers, :distance_in_miles, :speed_in_mph, :speed_in_kph],
|
8
|
+
duration: [:duration_in_seconds, :duration_in_minutes, :duration_in_hours, :duration_in_hms],
|
9
|
+
pace: [:pace_in_mpk, :pace_in_mpm]
|
10
|
+
}
|
11
|
+
|
12
|
+
CONVERTER_LOOKUP = {
|
13
|
+
distance_in_kilometers: :distance_to_kilometers,
|
14
|
+
distance_in_miles: :distance_to_miles,
|
15
|
+
duration_in_seconds: :duration_to_seconds,
|
16
|
+
duration_in_minutes: :duration_to_minutes,
|
17
|
+
duration_in_hours: :duration_to_hours,
|
18
|
+
duration_in_hms: :duration_to_hms,
|
19
|
+
speed_in_mph: :speed_to_mph,
|
20
|
+
speed_in_kph: :speed_to_kph,
|
21
|
+
pace_in_mpk: :pace_to_mpk,
|
22
|
+
pace_in_mpm: :pace_to_mpm,
|
23
|
+
}
|
24
|
+
|
25
|
+
def method_missing(method, *args, &block)
|
26
|
+
case method.to_s
|
27
|
+
when /_time$|_time_utc$/
|
28
|
+
Time.parse(super)
|
29
|
+
else
|
30
|
+
converter = CONVERTER_LOOKUP[method]
|
31
|
+
converter ? self.send(converter) : super
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def respond_to_missing?(method, include_private = false)
|
36
|
+
conversion_helpers.include?(method)
|
37
|
+
end
|
38
|
+
|
39
|
+
def conversion_helpers
|
40
|
+
CONVERTER_AVAILABILITY.inject([]) do |a, (m, converters)|
|
41
|
+
self.include?(m.to_s) ? a += converters : a
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# distance
|
48
|
+
|
49
|
+
def distance_to_kilometers
|
50
|
+
distance
|
51
|
+
end
|
52
|
+
|
53
|
+
def distance_to_miles
|
54
|
+
distance * KM_TO_MILE
|
55
|
+
end
|
56
|
+
|
57
|
+
# duration
|
58
|
+
|
59
|
+
def duration_to_seconds
|
60
|
+
duration.to_f / 1000
|
61
|
+
end
|
62
|
+
|
63
|
+
def duration_to_minutes
|
64
|
+
duration.to_f / 60000
|
65
|
+
end
|
66
|
+
|
67
|
+
def duration_to_hours
|
68
|
+
duration.to_f / 3600000
|
69
|
+
end
|
70
|
+
|
71
|
+
def duration_to_hms
|
72
|
+
Time.at(duration_to_seconds).gmtime.strftime('%R:%S')
|
73
|
+
end
|
74
|
+
|
75
|
+
# speed
|
76
|
+
|
77
|
+
def speed_to_mph
|
78
|
+
distance_in_miles && duration_in_hours &&
|
79
|
+
distance_in_miles / duration_in_hours
|
80
|
+
end
|
81
|
+
|
82
|
+
def speed_to_kph
|
83
|
+
distance_in_kilometers && duration_in_hours &&
|
84
|
+
distance_in_kilometers / duration_in_hours
|
85
|
+
end
|
86
|
+
|
87
|
+
# speed
|
88
|
+
|
89
|
+
def pace_to_mpk
|
90
|
+
distnace_in_kilometers && duration_in_minutes &&
|
91
|
+
duration_in_minutes / distance_in_kilometers
|
92
|
+
end
|
93
|
+
|
94
|
+
def pace_to_mpm
|
95
|
+
distnace_in_miles && duration_in_minutes &&
|
96
|
+
duration_in_minutes / distance_in_miles
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
data/lib/nike/version.rb
ADDED
data/lib/nike.rb
ADDED
data/nike.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'nike/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "nike"
|
8
|
+
gem.version = Nike::VERSION
|
9
|
+
gem.authors = ["Alex Skryl"]
|
10
|
+
gem.email = ["rut216@gmail.com"]
|
11
|
+
gem.summary = %q{A Ruby client for the Nike+ API}
|
12
|
+
gem.description = %q{A Ruby client for the Nike+ API with support for Run/GPS/HR Data}
|
13
|
+
|
14
|
+
gem.files = `git ls-files`.split($/)
|
15
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.require_paths = ["lib"]
|
18
|
+
|
19
|
+
gem.add_dependency(%q<hashie>, [">= 1.2.0"])
|
20
|
+
gem.add_dependency(%q<activesupport>, [">= 3.2.0"])
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nike
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Alex Skryl
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-13 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: hashie
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.2.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.2.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: activesupport
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 3.2.0
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 3.2.0
|
46
|
+
description: A Ruby client for the Nike+ API with support for Run/GPS/HR Data
|
47
|
+
email:
|
48
|
+
- rut216@gmail.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- Gemfile
|
55
|
+
- LICENSE.txt
|
56
|
+
- README.md
|
57
|
+
- Rakefile
|
58
|
+
- lib/nike.rb
|
59
|
+
- lib/nike/client.rb
|
60
|
+
- lib/nike/core_ext/hash.rb
|
61
|
+
- lib/nike/mash.rb
|
62
|
+
- lib/nike/version.rb
|
63
|
+
- nike.gemspec
|
64
|
+
homepage:
|
65
|
+
licenses: []
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ! '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
requirements: []
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 1.8.23
|
85
|
+
signing_key:
|
86
|
+
specification_version: 3
|
87
|
+
summary: A Ruby client for the Nike+ API
|
88
|
+
test_files: []
|
89
|
+
has_rdoc:
|