mini_strava 0.1.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.
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
+ ]