mini_strava 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3fe815926d04faf11099a80f03e0b8a600aeec83
4
+ data.tar.gz: 4ea64770e09e52df2c0992f501fd1d6f62f46445
5
+ SHA512:
6
+ metadata.gz: e1ab20bf97b537d815007926b2844e9a9ce0d258d67166c77bed2878a120ac6adbac6a5cca393e2e56dce6261b7dbf12a62e0e4e49bfe00e083855fece1bf36c
7
+ data.tar.gz: 6f53c3fca48c184efd6e3d6afc0f087ef5dce56b085f7a1faaa85d15c37ad66674d8d2faa8f7a46d675776cfd44904b50442f355b67f419bfa222d5272e206f1
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mini_strava.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Andrea Franz
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,63 @@
1
+ # MiniStrava
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'mini_strava'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install mini_strava
18
+
19
+ ## Usage
20
+
21
+ > client = MiniStrava::Client.new 'XYZ'
22
+ => #<MiniStrava::Client:0x007fb42924a380 @access_token="XYZ">
23
+
24
+ > a = client.athlete
25
+ > a.firstname
26
+ => "Andrea"
27
+
28
+ > a.bikes.first.name
29
+ => 'Giant TCR'
30
+
31
+ > activities = client.activities
32
+ > activities.first.average_speed
33
+ => 6.347
34
+
35
+ > act = client.activity activities[0].id
36
+ > act.name
37
+ => 'Evening ride'
38
+ > act.distance
39
+ => 24727.3
40
+
41
+ Check the Strava API documentation to know more about the available fields for each resource.
42
+
43
+ ### CLI
44
+
45
+ An executable called `mini-strava` will be installed with the gem. To use it, set the `STRAVA_ACCESS_TOKEN` env variable:
46
+
47
+ export STRAVA_ACCESS_TOKEN="xyz"
48
+
49
+ and then use the CLI:
50
+
51
+ $ mini-strava
52
+ MiniStrava CLI > athlete.firstname
53
+ => "Andrea"
54
+ MiniStrava CLI > activities.first.name
55
+ => "Evening Ride"
56
+
57
+ ## Contributing
58
+
59
+ 1. Fork it ( http://github.com/<my-github-username>/mini_strava/fork )
60
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
61
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
62
+ 4. Push to the branch (`git push origin my-new-feature`)
63
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/mini-strava ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
4
+ require 'mini_strava'
5
+ require 'ripl'
6
+ require 'delegate'
7
+
8
+ class CLI < SimpleDelegator
9
+ attr_reader :client
10
+
11
+ def initialize
12
+ @client = MiniStrava::Client.new ENV['STRAVA_ACCESS_TOKEN']
13
+ super @client
14
+ end
15
+
16
+ def loop
17
+ Ripl.start binding: binding, prompt: 'MiniStrava CLI > '
18
+ end
19
+ end
20
+
21
+ begin
22
+ CLI.new.loop
23
+ rescue MiniStrava::Client::BlankAccessTokenError
24
+ puts 'you must set the STRAVA_ACCESS_TOKEN env variable'
25
+ end
@@ -0,0 +1,70 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'json'
4
+
5
+ module MiniStrava
6
+ class Client
7
+ class AuthorizationError < Exception; end
8
+ class BlankAccessTokenError < Exception; end
9
+
10
+ BaseUrl = 'https://www.strava.com/api/v3'
11
+
12
+ def initialize access_token
13
+ raise BlankAccessTokenError.new "access_token can't be blank" if access_token.to_s.strip == ''
14
+ @access_token = access_token
15
+ end
16
+
17
+ def athlete
18
+ response = get '/athlete'
19
+ parse_response response, Models::Athlete
20
+ end
21
+
22
+ # valid options: before, after, page, per_page
23
+ def activities options={}
24
+ response = get '/activities', options
25
+ parse_response response, Models::Activity
26
+ end
27
+
28
+ def activity id, options={}
29
+ response = get "/activities/#{id}"
30
+ parse_response response, Models::Activity
31
+ end
32
+
33
+ private
34
+
35
+ def get path, params={}
36
+ uri = build_uri path, params
37
+ request = Net::HTTP::Get.new uri
38
+ perform_request request, uri
39
+ end
40
+
41
+ def perform_request request, uri
42
+ response = Net::HTTP.start uri.hostname, uri.port, use_ssl: true do |http|
43
+ http.request request
44
+ end
45
+
46
+ case response.code
47
+ when 401
48
+ raise AuthorizationError.new
49
+ else
50
+ response
51
+ end
52
+ end
53
+
54
+ def parse_response response, type
55
+ attributes = JSON.parse response.body
56
+ if attributes.is_a? Array
57
+ attributes.collect { |a| type.build a }
58
+ else
59
+ type.build attributes
60
+ end
61
+ end
62
+
63
+ def build_uri path, params={}
64
+ params = params.merge access_token: @access_token
65
+ uri = URI(File.join(BaseUrl, path))
66
+ uri.query = URI.encode_www_form params
67
+ uri
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,64 @@
1
+ module MiniStrava
2
+ module Model
3
+ DefaultAttrType = :basic
4
+
5
+ def self.included base
6
+ base.send :include, InstanceMethods
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ module InstanceMethods
11
+ def attributes
12
+ @attributes ||= {}
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+ def build attributes
18
+ object = self.new
19
+ model_attributes.each do |attr|
20
+ object.send "#{attr[:name]}=", attributes[attr[:name]]
21
+ end
22
+ object
23
+ end
24
+
25
+ def model_attributes
26
+ @model_attributes ||= []
27
+ end
28
+
29
+ def model_attr name, type: DefaultAttrType, default: nil
30
+ model_attributes << {
31
+ name: name.to_s,
32
+ type: type,
33
+ default: default
34
+ }
35
+ define_model_att_reader name
36
+ define_model_att_writer name, type, default
37
+ end
38
+
39
+ def define_model_att_reader name
40
+ define_method name do
41
+ attributes[name]
42
+ end
43
+ end
44
+
45
+ def define_model_att_writer name, type, default
46
+ define_method "#{name}=" do |value|
47
+ value = default if value.nil?
48
+ if type != DefaultAttrType
49
+ klass = Models.const_get type.to_s
50
+ value = if value.is_a? Array
51
+ value.collect do |v|
52
+ klass.build v || {}
53
+ end
54
+ else
55
+ klass.build value || {}
56
+ end
57
+ end
58
+
59
+ attributes[name] = value
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,31 @@
1
+ module MiniStrava
2
+ module Models
3
+ class Activity
4
+ include Model
5
+
6
+ model_attr :id
7
+ model_attr :resource_state
8
+ model_attr :external_id
9
+ model_attr :athlete, type: :Athlete
10
+ model_attr :name
11
+ model_attr :distance
12
+ model_attr :moving_time
13
+ model_attr :elapsed_time
14
+ model_attr :total_elevation_gain
15
+ model_attr :type
16
+ model_attr :start_date
17
+ model_attr :start_date_local
18
+ model_attr :timezone
19
+ model_attr :start_latlng
20
+ model_attr :end_latlng
21
+ model_attr :location_city
22
+ model_attr :location_state
23
+ model_attr :location_country
24
+ model_attr :start_latitude
25
+ model_attr :start_longitude
26
+ model_attr :average_speed
27
+ model_attr :max_speed
28
+ model_attr :segment_efforts, type: :SegmentEffort, default: []
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ module MiniStrava
2
+ module Models
3
+ class Athlete
4
+ include Model
5
+
6
+ model_attr :id
7
+ model_attr :firstname
8
+ model_attr :lastname
9
+ model_attr :resource_state
10
+ model_attr :profile_medium
11
+ model_attr :profile
12
+ model_attr :city
13
+ model_attr :state
14
+ model_attr :country
15
+ model_attr :sex
16
+ model_attr :date_preference
17
+ model_attr :measurement_preference
18
+ model_attr :email
19
+ model_attr :bikes, type: :Gear, default: []
20
+ model_attr :shoes, type: :Gear, default: []
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ module MiniStrava
2
+ module Models
3
+ class Gear
4
+ include Model
5
+
6
+ model_attr :id
7
+ model_attr :primary
8
+ model_attr :name
9
+ model_attr :resource_state
10
+ model_attr :distance
11
+ model_attr :brand_name
12
+ model_attr :model_name
13
+ model_attr :frame_type
14
+ model_attr :description
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ module MiniStrava
2
+ module Models
3
+ class Segment
4
+ include Model
5
+
6
+ model_attr :id
7
+ model_attr :resource_state
8
+ model_attr :name
9
+ model_attr :activity_type
10
+ model_attr :distance
11
+ model_attr :average_grade
12
+ model_attr :maximum_grade
13
+ model_attr :elevation_high
14
+ model_attr :elevation_low
15
+ model_attr :city
16
+ model_attr :state
17
+ model_attr :country
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module MiniStrava
2
+ module Models
3
+ class SegmentEffort
4
+ include Model
5
+
6
+ model_attr :id
7
+ model_attr :resource_state
8
+ model_attr :name
9
+ model_attr :activity, type: :Activity
10
+ model_attr :athlete, type: :Athlete
11
+ model_attr :elapsed_time
12
+ model_attr :moving_time
13
+ model_attr :start_date
14
+ model_attr :start_date_local
15
+ model_attr :distance
16
+ model_attr :segment, type: :Segment
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,3 @@
1
+ module MiniStrava
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,11 @@
1
+ require 'mini_strava/version'
2
+ require 'mini_strava/client'
3
+ require 'mini_strava/model'
4
+ require 'mini_strava/models/activity'
5
+ require 'mini_strava/models/athlete'
6
+ require 'mini_strava/models/gear'
7
+ require 'mini_strava/models/segment'
8
+ require 'mini_strava/models/segment_effort'
9
+
10
+ module MiniStrava
11
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mini_strava/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "mini_strava"
8
+ spec.version = MiniStrava::VERSION
9
+ spec.authors = ["Andrea Franz"]
10
+ spec.email = ["andrea@gravityblast.com"]
11
+ spec.summary = %q{A simple client for the strava API}
12
+ spec.description = %q{A simple client for the strava API}
13
+ spec.homepage = "https://github.com/pilu/mini-strava"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'ripl'
22
+
23
+ spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "rspec"
25
+ spec.add_development_dependency "pry"
26
+ end
@@ -0,0 +1,85 @@
1
+ require 'spec_helper'
2
+
3
+ module MiniStrava
4
+ describe Client do
5
+ subject { Client.new 'test_token' }
6
+
7
+ context '#build_uri' do
8
+ it 'should build uri with params' do
9
+ require 'pry'; binding.pry;
10
+ params = { foo: :bar, baz: :qux }
11
+ uri = subject.send(:build_uri, '/test', params)
12
+
13
+ expected = Client::BaseUrl + '/test?foo=bar&baz=qux&access_token=test_token'
14
+ expect(uri.to_s).to eq(expected)
15
+ end
16
+ end
17
+
18
+ context 'parse_response' do
19
+ it 'should parse response and return an athlete model' do
20
+ body = double('body')
21
+ parsed_body = double('parsed_body')
22
+ model_class = double('model_class')
23
+ response = double('response', body: body)
24
+
25
+ expect(JSON).to receive(:parse).with(body).and_return(parsed_body)
26
+ expect(model_class).to receive(:build).with(parsed_body)
27
+ subject.send :parse_response, response, model_class
28
+ end
29
+ end
30
+
31
+ describe '#athlete' do
32
+ it 'should retrieve current athlete' do
33
+ body = fixture 'athlete'
34
+ response = double('response', body: body)
35
+ expect(subject).to receive(:get).with('/athlete').and_return(response)
36
+
37
+ athlete = subject.athlete
38
+ expect(athlete.class).to eq(Models::Athlete)
39
+ expect(athlete.firstname).to eq('Foo')
40
+ end
41
+ end
42
+
43
+ context '#activity' do
44
+ it 'should retrieve activity' do
45
+ body = fixture 'activity'
46
+ response = double('response', body: body)
47
+ expect(subject).to receive(:get).with('/activities/10').and_return(response)
48
+
49
+ activity = subject.activity 10
50
+ expect(activity.class).to eq(Models::Activity)
51
+ expect(activity.name).to eq('Morning Commute')
52
+ end
53
+ end
54
+
55
+ context '#activities' do
56
+ it 'should retrieve activities' do
57
+ body = fixture 'activities'
58
+ response = double('response', body: body)
59
+ expect(subject).to receive(:get).with('/activities', {}).and_return(response)
60
+
61
+ activities = subject.activities
62
+ expect(activities.class).to eq(Array)
63
+ expect(activities.size).to eq(2)
64
+
65
+ expect(activities[0].class).to eq(Models::Activity)
66
+ expect(activities[0].id).to eq(1)
67
+ expect(activities[0].name).to eq('Morning Commute')
68
+
69
+ expect(activities[1].class).to eq(Models::Activity)
70
+ expect(activities[1].id).to eq(2)
71
+ expect(activities[1].name).to eq('Morning Commute 2')
72
+ end
73
+ end
74
+
75
+ context '401 Not Authorized' do
76
+ it 'should raise a AuthorizationError exception' do
77
+ response = double('response', code: 401)
78
+ expect(Net::HTTP).to receive(:start).and_return(response)
79
+ expect do
80
+ subject.athlete
81
+ end.to raise_error(Client::AuthorizationError)
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,116 @@
1
+ [
2
+ {
3
+ "id": 1,
4
+ "resource_state": 3,
5
+ "external_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
6
+ "athlete": {
7
+ "id": 101,
8
+ "resource_state": 1
9
+ },
10
+ "name": "Morning Commute",
11
+ "distance": 1850.6,
12
+ "moving_time": 443,
13
+ "elapsed_time": 443,
14
+ "total_elevation_gain": 12.6,
15
+ "type": "Ride",
16
+ "start_date": "2014-08-08T09:14:08Z",
17
+ "start_date_local": "2014-08-08T10:14:08Z",
18
+ "timezone": "(GMT+00:00) Europe/London",
19
+ "start_latlng": [50.0, -1],
20
+ "end_latlng": [52.0, -2],
21
+ "location_city": "London",
22
+ "location_state": "England",
23
+ "location_country": "United Kingdom",
24
+ "start_latitude": 50.0,
25
+ "start_longitude": -1,
26
+ "achievement_count": 0,
27
+ "comment_count": 0,
28
+ "athlete_count": 1,
29
+ "photo_count": 0,
30
+ "map": {
31
+ "id": "a177190357",
32
+ "polyline": "test-test-test",
33
+ "resource_state": 3,
34
+ "summary_polyline": "test"
35
+ },
36
+ "trainer": false,
37
+ "commute": false,
38
+ "manual": false,
39
+ "private": false,
40
+ "flagged": false,
41
+ "gear_id": "12345",
42
+ "average_speed": 4.177,
43
+ "max_speed": 13.6,
44
+ "average_watts": 70.7,
45
+ "kilojoules": 31.3,
46
+ "truncated": 183,
47
+ "has_kudoed": false,
48
+ "description": "",
49
+ "calories": 34.9,
50
+ "segment_efforts": [],
51
+ "gear": {
52
+ "id": "12345",
53
+ "primary": false,
54
+ "name": "Bike 1",
55
+ "resource_state": 2,
56
+ "distance": 270403.0
57
+ }
58
+ },
59
+ {
60
+ "id": 2,
61
+ "resource_state": 3,
62
+ "external_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
63
+ "athlete": {
64
+ "id": 101,
65
+ "resource_state": 1
66
+ },
67
+ "name": "Morning Commute 2",
68
+ "distance": 1850.6,
69
+ "moving_time": 443,
70
+ "elapsed_time": 443,
71
+ "total_elevation_gain": 12.6,
72
+ "type": "Ride",
73
+ "start_date": "2014-08-08T09:14:08Z",
74
+ "start_date_local": "2014-08-08T10:14:08Z",
75
+ "timezone": "(GMT+00:00) Europe/London",
76
+ "start_latlng": [50.0, -1],
77
+ "end_latlng": [52.0, -2],
78
+ "location_city": "London",
79
+ "location_state": "England",
80
+ "location_country": "United Kingdom",
81
+ "start_latitude": 50.0,
82
+ "start_longitude": -1,
83
+ "achievement_count": 0,
84
+ "comment_count": 0,
85
+ "athlete_count": 1,
86
+ "photo_count": 0,
87
+ "map": {
88
+ "id": "a177190357",
89
+ "polyline": "test-test-test",
90
+ "resource_state": 3,
91
+ "summary_polyline": "test"
92
+ },
93
+ "trainer": false,
94
+ "commute": false,
95
+ "manual": false,
96
+ "private": false,
97
+ "flagged": false,
98
+ "gear_id": "12345",
99
+ "average_speed": 4.177,
100
+ "max_speed": 13.6,
101
+ "average_watts": 70.7,
102
+ "kilojoules": 31.3,
103
+ "truncated": 183,
104
+ "has_kudoed": false,
105
+ "description": "",
106
+ "calories": 34.9,
107
+ "segment_efforts": [],
108
+ "gear": {
109
+ "id": "12345",
110
+ "primary": false,
111
+ "name": "Bike 1",
112
+ "resource_state": 2,
113
+ "distance": 270403.0
114
+ }
115
+ }
116
+ ]